From e21a3be55ba209d64a076030f833b94793163dde Mon Sep 17 00:00:00 2001 From: GMatrixGames Date: Sat, 11 Jun 2022 20:07:59 -0400 Subject: [PATCH] Update/net7 (#290) * file-scoped namespace & net7.0 * Workflow --- .github/workflows/main.yml | 9 +- FModel/App.xaml.cs | 258 +-- FModel/Constants.cs | 37 +- FModel/Creator/Bases/BB/BaseBreakersIcon.cs | 67 +- FModel/Creator/Bases/FN/BaseBundle.cs | 219 ++- FModel/Creator/Bases/FN/BaseCommunity.cs | 425 +++-- FModel/Creator/Bases/FN/BaseIcon.cs | 467 +++-- FModel/Creator/Bases/FN/BaseIconStats.cs | 515 +++--- .../Creator/Bases/FN/BaseItemAccessToken.cs | 179 +- .../Creator/Bases/FN/BaseMaterialInstance.cs | 129 +- FModel/Creator/Bases/FN/BaseMtxOffer.cs | 123 +- .../Creator/Bases/FN/BaseOfferDisplayData.cs | 59 +- FModel/Creator/Bases/FN/BasePlaylist.cs | 133 +- FModel/Creator/Bases/FN/BaseQuest.cs | 495 +++-- FModel/Creator/Bases/FN/BaseSeason.cs | 242 ++- FModel/Creator/Bases/FN/BaseSeries.cs | 41 +- FModel/Creator/Bases/FN/BaseTandem.cs | 429 +++-- FModel/Creator/Bases/FN/BaseUserControl.cs | 337 ++-- FModel/Creator/Bases/FN/Reward.cs | 255 ++- FModel/Creator/Bases/SB/BaseDivision.cs | 77 +- FModel/Creator/Bases/SB/BaseGameModeInfo.cs | 81 +- FModel/Creator/Bases/SB/BaseLeague.cs | 93 +- FModel/Creator/Bases/SB/BaseSpellIcon.cs | 155 +- FModel/Creator/Bases/UCreator.cs | 401 +++-- FModel/Creator/CreatorPackage.cs | 475 +++-- FModel/Creator/Typefaces.cs | 505 +++--- FModel/Creator/Utils.cs | 718 ++++---- FModel/Enums.cs | 275 ++- FModel/Extensions/AvalonExtensions.cs | 73 +- FModel/Extensions/ClipboardExtensions.cs | 338 ++-- FModel/Extensions/CollectionExtensions.cs | 51 +- FModel/Extensions/EnumExtensions.cs | 116 +- FModel/Extensions/StreamExtensions.cs | 41 +- FModel/Extensions/StringExtensions.cs | 233 ++- FModel/FModel.csproj | 2 +- FModel/Framework/AsyncQueue.cs | 43 +- FModel/Framework/Command.cs | 23 +- FModel/Framework/CustomSKShaper.cs | 150 +- FModel/Framework/FullyObservableCollection.cs | 181 +- FModel/Framework/Hotkey.cs | 73 +- FModel/Framework/JsonNetSerializer.cs | 63 +- FModel/Framework/NavigationList.cs | 52 +- FModel/Framework/RangeObservableCollection.cs | 53 +- FModel/Framework/ViewModel.cs | 119 +- FModel/Framework/ViewModelCommand.cs | 73 +- FModel/Helper.cs | 151 +- FModel/MainWindow.xaml.cs | 250 +-- FModel/ViewModels/AesManagerViewModel.cs | 249 ++- FModel/ViewModels/ApiEndpointViewModel.cs | 39 +- .../ApiEndpoints/AbstractApiProvider.cs | 17 +- .../ApiEndpoints/BenbotApiEndpoint.cs | 109 +- .../ApiEndpoints/EpicApiEndpoint.cs | 79 +- FModel/ViewModels/ApiEndpoints/FModelApi.cs | 293 ++- .../ApiEndpoints/FortniteApiEndpoint.cs | 41 +- .../ApiEndpoints/Models/AesResponse.cs | 31 +- .../ApiEndpoints/Models/EpicResponse.cs | 13 +- .../ApiEndpoints/Models/FModelResponse.cs | 334 ++-- .../ApiEndpoints/Models/MappingsResponse.cs | 35 +- .../ApiEndpoints/Models/PlaylistResponse.cs | 47 +- .../ApiEndpoints/ValorantApiEndpoint.cs | 497 ++--- FModel/ViewModels/ApplicationViewModel.cs | 362 ++-- FModel/ViewModels/AssetsFolderViewModel.cs | 257 ++- FModel/ViewModels/AssetsListViewModel.cs | 129 +- FModel/ViewModels/AudioPlayerViewModel.cs | 1102 ++++++------ FModel/ViewModels/BackupManagerViewModel.cs | 169 +- FModel/ViewModels/CUE4ParseViewModel.cs | 1405 ++++++++------- .../Commands/AddEditDirectoryCommand.cs | 47 +- FModel/ViewModels/Commands/AddTabCommand.cs | 21 +- FModel/ViewModels/Commands/AudioCommand.cs | 71 +- FModel/ViewModels/Commands/CopyCommand.cs | 65 +- .../Commands/DeleteDirectoryCommand.cs | 23 +- FModel/ViewModels/Commands/GoToCommand.cs | 81 +- FModel/ViewModels/Commands/ImageCommand.cs | 62 +- FModel/ViewModels/Commands/LoadCommand.cs | 361 ++-- FModel/ViewModels/Commands/MenuCommand.cs | 187 +- .../Commands/RightClickMenuCommand.cs | 111 +- FModel/ViewModels/Commands/TabCommand.cs | 69 +- .../ViewModels/CustomDirectoriesViewModel.cs | 305 ++-- FModel/ViewModels/GameDirectoryViewModel.cs | 171 +- FModel/ViewModels/GameSelectorViewModel.cs | 575 +++--- FModel/ViewModels/LoadingModesViewModel.cs | 27 +- FModel/ViewModels/MapViewerViewModel.cs | 1598 +++++++++-------- FModel/ViewModels/ModelViewerViewModel.cs | 1324 +++++++------- FModel/ViewModels/SearchViewModel.cs | 90 +- FModel/ViewModels/SettingsViewModel.cs | 635 +++---- FModel/ViewModels/TabControlViewModel.cs | 797 ++++---- FModel/ViewModels/ThreadWorkerViewModel.cs | 209 ++- FModel/Views/About.xaml.cs | 11 +- FModel/Views/AesManager.xaml.cs | 45 +- FModel/Views/AudioPlayer.xaml.cs | 161 +- FModel/Views/BackupManager.xaml.cs | 41 +- FModel/Views/CustomDir.xaml.cs | 35 +- FModel/Views/DirectorySelector.xaml.cs | 111 +- FModel/Views/ImageMerger.xaml.cs | 527 +++--- FModel/Views/MapViewer.xaml.cs | 117 +- FModel/Views/ModelViewer.xaml.cs | 187 +- .../Controls/Aed/BraceFoldingStrategy.cs | 237 +-- .../Controls/Aed/GamePathElementGenerator.cs | 53 +- .../Controls/Aed/GamePathVisualLineText.cs | 134 +- .../Controls/Aed/HexColorElementGenerator.cs | 54 +- .../Controls/Aed/HexColorVisualLineText.cs | 37 +- .../Controls/Aup/CustomCodecFactory.cs | 119 +- .../Views/Resources/Controls/Aup/ISource.cs | 29 +- .../Resources/Controls/Aup/NVorbisSource.cs | 91 +- .../Resources/Controls/Aup/SourceEventArgs.cs | 23 +- .../Aup/SourcePropertyChangedEventArgs.cs | 37 +- .../Controls/Aup/SpectrumAnalyzer.cs | 837 ++++----- .../Controls/Aup/SpectrumProvider.cs | 69 +- .../Views/Resources/Controls/Aup/Timeclock.cs | 622 +++---- .../Views/Resources/Controls/Aup/Timeline.cs | 650 +++---- .../Resources/Controls/AvalonEditor.xaml.cs | 456 ++--- .../Resources/Controls/Breadcrumb.xaml.cs | 96 +- .../Controls/DictionaryEditor.xaml.cs | 121 +- .../Views/Resources/Controls/HotkeyTextBox.cs | 138 +- .../Resources/Controls/ImagePopout.xaml.cs | 11 +- .../Resources/Controls/Mgn/EFrameType.cs | 11 +- .../Views/Resources/Controls/Mgn/Magnifier.cs | 325 ++-- .../Controls/Mgn/MagnifierAdorner.cs | 123 +- .../Controls/Mgn/MagnifierManager.cs | 171 +- .../Controls/OnTagDataTemplateSelector.cs | 13 +- .../Controls/PropertiesPopout.xaml.cs | 155 +- .../Controls/Rtb/CustomRichTextBox.cs | 289 ++- .../Controls/Rtb/CustomScrollViewer.cs | 89 +- .../Controls/TreeViewItemBehavior.cs | 57 +- .../Converters/BoolToFillModeConverter.cs | 41 +- .../Converters/BoolToRenderModeConverter.cs | 32 +- .../Converters/BoolToToggleConverter.cs | 33 +- ...rderThicknessToStrokeThicknessConverter.cs | 25 +- .../CaseInsensitiveStringEqualsConverter.cs | 23 +- .../Converters/DateTimeToStringConverter.cs | 23 +- .../Converters/EnumToStringConverter.cs | 40 +- .../FileExtensionEqualsConverter.cs | 23 +- .../FolderToSeparatorTagConverter.cs | 27 +- .../Converters/FullPathToFileConverter.cs | 23 +- .../IsNullToBoolReversedConverter.cs | 23 +- .../Converters/MultiParameterConverter.cs | 23 +- .../Resources/Converters/RatioConverter.cs | 39 +- .../Converters/SizeToStringConverter.cs | 23 +- .../Converters/StringToGameConverter.cs | 65 +- .../Resources/Converters/TabSizeConverter.cs | 31 +- .../Converters/TagToColorConverter.cs | 35 +- .../Converters/TrimRightToLeftConverter.cs | 41 +- FModel/Views/SearchView.xaml.cs | 131 +- FModel/Views/SettingsView.xaml.cs | 303 ++-- 144 files changed, 14135 insertions(+), 14472 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6c02694b..bc2d0c20 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,16 +21,17 @@ jobs: - name: Fetch Submodules Recursively run: git submodule update --init --recursive - - name: .NET 6 Setup - uses: actions/setup-dotnet@v1 + - name: .NET 7 Setup + uses: actions/setup-dotnet@v2 with: - dotnet-version: 6.0.x + dotnet-version: '7.0.x' + include-prerelease: true - name: .NET Restore run: dotnet restore FModel - name: .NET Publish - run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net6.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }} + run: dotnet publish FModel -c Release --no-self-contained -r win-x64 -f net7.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=false -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:AssemblyVersion=${{ github.event.inputs.appVersion }} -p:FileVersion=${{ github.event.inputs.appVersion }} - name: ZIP File uses: papeloto/action-zip@v1 diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index 07845592..e991c0ad 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -16,146 +16,150 @@ using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; using MessageBoxResult = AdonisUI.Controls.MessageBoxResult; -namespace FModel +namespace FModel; + +/// +/// Interaction logic for App.xaml +/// +public partial class App { - /// - /// Interaction logic for App.xaml - /// - public partial class App + [DllImport("kernel32.dll")] + private static extern bool AttachConsole(int dwProcessId); + + [DllImport("winbrand.dll", CharSet = CharSet.Unicode)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + static extern string BrandingFormatString(string format); + + protected override void OnStartup(StartupEventArgs e) { - [DllImport("kernel32.dll")] - private static extern bool AttachConsole(int dwProcessId); - - [DllImport("winbrand.dll", CharSet = CharSet.Unicode)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - static extern string BrandingFormatString(string format); - - protected override void OnStartup(StartupEventArgs e) - { #if DEBUG - AttachConsole(-1); + AttachConsole(-1); #endif - base.OnStartup(e); + base.OnStartup(e); - try - { - UserSettings.Default = JsonConvert.DeserializeObject( - File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); - } - catch - { - UserSettings.Default = new UserSettings(); - } - - var createMe = false; - if (!Directory.Exists(UserSettings.Default.OutputDirectory)) - { - UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output"); - } - if (!Directory.Exists(UserSettings.Default.RawDataDirectory)) - { - createMe = true; - UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); - } - if (!Directory.Exists(UserSettings.Default.PropertiesDirectory)) - { - createMe = true; - UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); - } - if (!Directory.Exists(UserSettings.Default.TextureDirectory)) - { - createMe = true; - UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); - } - if (!Directory.Exists(UserSettings.Default.AudioDirectory)) - { - createMe = true; - UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); - } - if (!Directory.Exists(UserSettings.Default.ModelDirectory)) - { - createMe = true; - UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); - } - - Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel")); - Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups")); - if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports")); - Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs")); - Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); - - Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File( - Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"), - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger(); - - Log.Information("Version {Version}", Constants.APP_VERSION); - Log.Information("{OS}", GetOperatingSystemProductName()); - Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription); - Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture); - } - - private void AppExit(object sender, ExitEventArgs e) + try { - Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––"); - Log.CloseAndFlush(); - UserSettings.Save(); - Environment.Exit(0); + UserSettings.Default = JsonConvert.DeserializeObject( + File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); } - - private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + catch { - Log.Error("{Exception}", e.Exception); - - var messageBox = new MessageBoxModel - { - Text = $"An unhandled exception occurred: {e.Exception.Message}", - Caption = "Fatal Error", - Icon = MessageBoxImage.Error, - Buttons = new[] - { - MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings), - MessageBoxButtons.Custom("Restart", EErrorKind.Restart), - MessageBoxButtons.Custom("OK", EErrorKind.Ignore) - }, - IsSoundEnabled = false - }; - - MessageBox.Show(messageBox); - if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore) - { - if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings) - UserSettings.Default = new UserSettings(); - - ApplicationService.ApplicationView.Restart(); - } - - e.Handled = true; + UserSettings.Default = new UserSettings(); } - private string GetOperatingSystemProductName() + var createMe = false; + if (!Directory.Exists(UserSettings.Default.OutputDirectory)) { - var productName = string.Empty; - try - { - productName = BrandingFormatString("%WINDOWS_LONG%"); - } - catch - { - // ignored - } - - if (string.IsNullOrEmpty(productName)) - productName = Environment.OSVersion.VersionString; - - return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)"; + UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output"); } - public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser) + if (!Directory.Exists(UserSettings.Default.RawDataDirectory)) { - using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path); - if (rk != null) - return rk.GetValue(name, null) as string; - return string.Empty; + createMe = true; + UserSettings.Default.RawDataDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); } + + if (!Directory.Exists(UserSettings.Default.PropertiesDirectory)) + { + createMe = true; + UserSettings.Default.PropertiesDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); + } + + if (!Directory.Exists(UserSettings.Default.TextureDirectory)) + { + createMe = true; + UserSettings.Default.TextureDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); + } + + if (!Directory.Exists(UserSettings.Default.AudioDirectory)) + { + createMe = true; + UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); + } + + if (!Directory.Exists(UserSettings.Default.ModelDirectory)) + { + createMe = true; + UserSettings.Default.ModelDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports"); + } + + Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups")); + if (createMe) Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); + + Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File( + Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"), + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger(); + + Log.Information("Version {Version}", Constants.APP_VERSION); + Log.Information("{OS}", GetOperatingSystemProductName()); + Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription); + Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture); } -} + + private void AppExit(object sender, ExitEventArgs e) + { + Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––"); + Log.CloseAndFlush(); + UserSettings.Save(); + Environment.Exit(0); + } + + private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Log.Error("{Exception}", e.Exception); + + var messageBox = new MessageBoxModel + { + Text = $"An unhandled exception occurred: {e.Exception.Message}", + Caption = "Fatal Error", + Icon = MessageBoxImage.Error, + Buttons = new[] + { + MessageBoxButtons.Custom("Reset Settings", EErrorKind.ResetSettings), + MessageBoxButtons.Custom("Restart", EErrorKind.Restart), + MessageBoxButtons.Custom("OK", EErrorKind.Ignore) + }, + IsSoundEnabled = false + }; + + MessageBox.Show(messageBox); + if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore) + { + if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings) + UserSettings.Default = new UserSettings(); + + ApplicationService.ApplicationView.Restart(); + } + + e.Handled = true; + } + + private string GetOperatingSystemProductName() + { + var productName = string.Empty; + try + { + productName = BrandingFormatString("%WINDOWS_LONG%"); + } + catch + { + // ignored + } + + if (string.IsNullOrEmpty(productName)) + productName = Environment.OSVersion.VersionString; + + return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)"; + } + + public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser) + { + using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path); + if (rk != null) + return rk.GetValue(name, null) as string; + return string.Empty; + } +} \ No newline at end of file diff --git a/FModel/Constants.cs b/FModel/Constants.cs index 2c00ab02..6cb078ac 100644 --- a/FModel/Constants.cs +++ b/FModel/Constants.cs @@ -1,27 +1,26 @@ using System.Reflection; using CUE4Parse.UE4.Objects.Core.Misc; -namespace FModel +namespace FModel; + +public static class Constants { - public static class Constants - { - public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); - public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000"; - public static readonly FGuid ZERO_GUID = new(0U); + public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); + public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000"; + public static readonly FGuid ZERO_GUID = new(0U); - public const string WHITE = "#DAE5F2"; - public const string RED = "#E06C75"; - public const string GREEN = "#98C379"; - public const string YELLOW = "#E5C07B"; - public const string BLUE = "#528BCC"; - - public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose"; - public const string DONATE_LINK = "https://fmodel.app/donate"; - public const string DISCORD_LINK = "https://fmodel.app/discord"; + public const string WHITE = "#DAE5F2"; + public const string RED = "#E06C75"; + public const string GREEN = "#98C379"; + public const string YELLOW = "#E5C07B"; + public const string BLUE = "#528BCC"; - public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest"; - public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest"; + public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose"; + public const string DONATE_LINK = "https://fmodel.app/donate"; + public const string DISCORD_LINK = "https://fmodel.app/discord"; - public const string _NO_PRESET_TRIGGER = "Hand Made"; - } + public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest"; + public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest"; + + public const string _NO_PRESET_TRIGGER = "Hand Made"; } \ No newline at end of file diff --git a/FModel/Creator/Bases/BB/BaseBreakersIcon.cs b/FModel/Creator/Bases/BB/BaseBreakersIcon.cs index 21f6bc8a..448e52c9 100644 --- a/FModel/Creator/Bases/BB/BaseBreakersIcon.cs +++ b/FModel/Creator/Bases/BB/BaseBreakersIcon.cs @@ -4,40 +4,39 @@ using CUE4Parse.UE4.Objects.UObject; using FModel.Creator.Bases.FN; using SkiaSharp; -namespace FModel.Creator.Bases.BB +namespace FModel.Creator.Bases.BB; + +public class BaseBreakersIcon : BaseIcon { - public class BaseBreakersIcon : BaseIcon + public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style) { - public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style) - { - SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient"); - Background = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("636363")}; - Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")}; - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage")) - Preview = Utils.GetBitmap(iconTextureAssetData); - - if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription")) - Description = description.Text; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - - return new []{ret}; - } + SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient"); + Background = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") }; + Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") }; } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData", "UnlockPortraitGuideImage")) + Preview = Utils.GetBitmap(iconTextureAssetData); + + if (Object.TryGetValue(out FText displayName, "DisplayName", "RegionDisplayName", "ZoneName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description", "RegionShortName", "ZoneDescription")) + Description = description.Text; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + + return new[] { ret }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseBundle.cs b/FModel/Creator/Bases/FN/BaseBundle.cs index 00ac3815..a8b4f782 100644 --- a/FModel/Creator/Bases/FN/BaseBundle.cs +++ b/FModel/Creator/Bases/FN/BaseBundle.cs @@ -8,134 +8,133 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN -{ - public class BaseBundle : UCreator - { - private IList _quests; - private const int _headerHeight = 100; +namespace FModel.Creator.Bases.FN; - public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style) +public class BaseBundle : UCreator +{ + private IList _quests; + private const int _headerHeight = 100; + + public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 1024; + Height = _headerHeight; + Margin = 0; + } + + public override void ParseForInfo() + { + _quests = new List(); + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text.ToUpperInvariant(); + + if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :) { - Width = 1024; - Height = _headerHeight; - Margin = 0; + foreach (var quest in quests) + { + if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue; + + BaseQuest q; + var path = questDefinition.AssetPathName.Text; + do + { + if (!Utils.TryLoadObject(path, out UObject uObject)) break; + + q = new BaseQuest(uObject, Style); + q.ParseForInfo(); + _quests.Add(q); + path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName; + } while (!string.IsNullOrEmpty(q.NextQuestName)); + } } - public override void ParseForInfo() + if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards")) { - _quests = new List(); - - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text.ToUpperInvariant(); - - if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :) + foreach (var completionReward in completionRewards) { - foreach (var quest in quests) + if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") || + !completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue; + + foreach (var reward in rewards) { - if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue; + if (!reward.TryGetValue(out int quantity, "Quantity") || + !reward.TryGetValue(out string templateId, "TemplateId") || + !reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue; - BaseQuest q; - var path = questDefinition.AssetPathName.Text; - do + if (!itemDefinition.AssetPathName.IsNone && + !itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") && + !itemDefinition.AssetPathName.Text.Contains("/Items/Quests")) { - if (!Utils.TryLoadObject(path, out UObject uObject)) break; - - q = new BaseQuest(uObject, Style); - q.ParseForInfo(); - _quests.Add(q); - path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName; - } while (!string.IsNullOrEmpty(q.NextQuestName)); - } - } - - if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards")) - { - foreach (var completionReward in completionRewards) - { - if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") || - !completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue; - - foreach (var reward in rewards) + _quests.Add(new BaseQuest(completionCount, itemDefinition, Style)); + } + else if (!string.IsNullOrWhiteSpace(templateId)) { - if (!reward.TryGetValue(out int quantity, "Quantity") || - !reward.TryGetValue(out string templateId, "TemplateId") || - !reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue; - - if (!itemDefinition.AssetPathName.IsNone && - !itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") && - !itemDefinition.AssetPathName.Text.Contains("/Items/Quests")) - { - _quests.Add(new BaseQuest(completionCount, itemDefinition, Style)); - } - else if (!string.IsNullOrWhiteSpace(templateId)) - { - _quests.Add(new BaseQuest(completionCount, quantity, templateId, Style)); - } + _quests.Add(new BaseQuest(completionCount, quantity, templateId, Style)); } } } - - Height += 256 * _quests.Count; } - public override SKBitmap[] Draw() + Height += 256 * _quests.Count; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + DrawDisplayName(c); + DrawQuests(c); + + return new[] { ret }; + } + + private readonly SKPaint _headerPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Bundle, TextSize = 50, + TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") + }; + + private void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); + + var background = _quests.Count > 0 ? _quests[0].Background : Background; + _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] { background[0].WithAlpha(50), background[1].WithAlpha(50) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); + + _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] { SKColors.Black.WithAlpha(25), background[1].WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); + } + + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + + _headerPaint.Shader = null; + _headerPaint.Color = SKColors.White; + while (_headerPaint.MeasureText(DisplayName) > Width) { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawHeader(c); - DrawDisplayName(c); - DrawQuests(c); - - return new []{ret}; + _headerPaint.TextSize -= 1; } - private readonly SKPaint _headerPaint = new() + var shaper = new CustomSKShaper(_headerPaint.Typeface); + var shapedText = shaper.Shape(DisplayName, _headerPaint); + c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); + } + + private void DrawQuests(SKCanvas c) + { + var y = _headerHeight; + foreach (var quest in _quests) { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.Bundle, TextSize = 50, - TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") - }; - - private void DrawHeader(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); - - var background = _quests.Count > 0 ? _quests[0].Background : Background; - _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, - new[] {background[0].WithAlpha(50), background[1].WithAlpha(50)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); - - _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), - new[] {SKColors.Black.WithAlpha(25), background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); - } - - private new void DrawDisplayName(SKCanvas c) - { - if (string.IsNullOrEmpty(DisplayName)) return; - - _headerPaint.Shader = null; - _headerPaint.Color = SKColors.White; - while (_headerPaint.MeasureText(DisplayName) > Width) - { - _headerPaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(_headerPaint.Typeface); - var shapedText = shaper.Shape(DisplayName, _headerPaint); - c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); - } - - private void DrawQuests(SKCanvas c) - { - var y = _headerHeight; - foreach (var quest in _quests) - { - quest.DrawQuest(c, y); - y += quest.Height; - } + quest.DrawQuest(c, y); + y += quest.Height; } } -} +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseCommunity.cs b/FModel/Creator/Bases/FN/BaseCommunity.cs index 280dc298..8daf66a4 100644 --- a/FModel/Creator/Bases/FN/BaseCommunity.cs +++ b/FModel/Creator/Bases/FN/BaseCommunity.cs @@ -11,231 +11,145 @@ using FModel.ViewModels.ApiEndpoints.Models; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseCommunity : BaseIcon { - public class BaseCommunity : BaseIcon + private readonly CommunityDesign _design; + private string _rarityName; + private string _source; + private string _season; + private bool _lowerDrawn; + + public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style) { - private readonly CommunityDesign _design; - private string _rarityName; - private string _source; - private string _season; - private bool _lowerDrawn; + Margin = 0; + _lowerDrawn = false; + _design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName); + } - public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style) + public override void ParseForInfo() + { + ParseForReward(UserSettings.Default.CosmeticDisplayAsset); + + if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export)) + _rarityName = export.Name; + else + _rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription(); + + if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) + CheckGameplayTags(gameplayTags); + if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) + CosmeticSource = cosmeticItem.Name.ToUpper(); + + DisplayName = DisplayName.ToUpper(); + Description = Description.ToUpper(); + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + if (_design == null) { - Margin = 0; - _lowerDrawn = false; - _design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName); + base.Draw(c); + } + else + { + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font)) + DrawToBottom(c, font, _season); + if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font)) + DrawToBottom(c, font, _source); + DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly); } - public override void ParseForInfo() + return new[] { ret }; + } + + private void CheckGameplayTags(FGameplayTagContainer gameplayTags) + { + if (_design == null) return; + if (_design.DrawSource) { - ParseForReward(UserSettings.Default.CosmeticDisplayAsset); - - if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export)) - _rarityName = export.Name; - else - _rarityName = Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription(); - - if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) - CheckGameplayTags(gameplayTags); - if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) - CosmeticSource = cosmeticItem.Name.ToUpper(); - - DisplayName = DisplayName.ToUpper(); - Description = Description.ToUpper(); + if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) + _source = source.Text["Cosmetics.Source.".Length..].ToUpper(); + else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) + _source = action.Text["Athena.ItemAction.".Length..].ToUpper(); } - public override SKBitmap[] Draw() + if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) + Description += GetCosmeticSet(set.Text, _design.DrawSetShort); + if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) + _season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort); + + var triggers = _design.GameplayTags.DrawCustomOnly ? new[] { "Cosmetics.UserFacingFlags." } : new[] { "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender." }; + GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers)); + } + + private string GetCosmeticSet(string setName, bool bShort) + { + return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName); + } + + private string GetCosmeticSeason(string seasonNumber, bool bShort) + { + if (!bShort) return base.GetCosmeticSeason(seasonNumber); + var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; + var number = int.Parse(s); + + switch (number) { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - if (_design == null) - { - base.Draw(c); - } - else - { - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font)) - DrawToBottom(c, font, _season); - if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font)) - DrawToBottom(c, font, _source); - DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly); - } - - return new []{ret}; + case 10: + s = "X"; + break; + case > 18: + number += 2; + s = number.ToString(); + break; } - private void CheckGameplayTags(FGameplayTagContainer gameplayTags) + return $"C{number / 10 + 1} S{s[^1..]}"; + } + + private new void DrawBackground(SKCanvas c) + { + if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) { - if (_design == null) return; - if (_design.DrawSource) - { - if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) - _source = source.Text["Cosmetics.Source.".Length..].ToUpper(); - else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) - _source = action.Text["Athena.ItemAction.".Length..].ToUpper(); - } - - if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) - Description += GetCosmeticSet(set.Text, _design.DrawSetShort); - if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) - _season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort); - - var triggers = _design.GameplayTags.DrawCustomOnly ? new[] {"Cosmetics.UserFacingFlags."} : new[] {"Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."}; - GetUserFacingFlags(gameplayTags.GetAllGameplayTags(triggers)); + c.DrawBitmap(rarity.Background, 0, 0, ImagePaint); + c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint); } - - private string GetCosmeticSet(string setName, bool bShort) + else { - return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName); + base.DrawBackground(c); } + } - private string GetCosmeticSeason(string seasonNumber, bool bShort) + private new void DrawTextBackground(SKCanvas c) + { + if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + + _lowerDrawn = true; + if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) { - if (!bShort) return base.GetCosmeticSeason(seasonNumber); - var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; - var number = int.Parse(s); - - switch (number) - { - case 10: - s = "X"; - break; - case > 18: - number += 2; - s = number.ToString(); - break; - } - - return $"C{number / 10 + 1} S{s[^1..]}"; + c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint); } - - private new void DrawBackground(SKCanvas c) + else { - if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) - { - c.DrawBitmap(rarity.Background, 0, 0, ImagePaint); - c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint); - } - else - { - base.DrawBackground(c); - } + base.DrawTextBackground(c); } + } - private new void DrawTextBackground(SKCanvas c) + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font)) { - if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; - - _lowerDrawn = true; - if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) - { - c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint); - } - else - { - base.DrawTextBackground(c); - } - } - - private new void DrawDisplayName(SKCanvas c) - { - if (string.IsNullOrEmpty(DisplayName)) return; - if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font)) - { - DisplayNamePaint.TextSize = font.FontSize; - DisplayNamePaint.TextScaleX = font.FontScale; - DisplayNamePaint.Color = font.FontColor; - DisplayNamePaint.TextSkewX = font.SkewValue; - DisplayNamePaint.TextAlign = font.Alignment; - if (font.ShadowValue > 0) - DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); - if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || - font.Typeface.TryGetValue(ELanguage.English, out path)) - DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path); - - while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) - { - DisplayNamePaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); - var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); - var x = font.Alignment switch - { - SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, - _ => font.X - }; - - c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint); - } - else - { - base.DrawDisplayName(c); - } - } - - private new void DrawDescription(SKCanvas c) - { - if (string.IsNullOrEmpty(Description)) return; - if (_design.Fonts.TryGetValue(nameof(Description), out var font)) - { - DescriptionPaint.TextSize = font.FontSize; - DescriptionPaint.TextScaleX = font.FontScale; - DescriptionPaint.Color = font.FontColor; - DescriptionPaint.TextSkewX = font.SkewValue; - DescriptionPaint.TextAlign = font.Alignment; - if (font.ShadowValue > 0) - DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); - if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || - font.Typeface.TryGetValue(ELanguage.English, out path)) - DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path); - - while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2) - { - DescriptionPaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(DescriptionPaint.Typeface); - var shapedText = shaper.Shape(Description, DescriptionPaint); - var x = font.Alignment switch - { - SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, - _ => font.X - }; - - if (font.MaxLineCount < 2) - { - c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint); - } - else - { - Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign, - new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _); - } - } - else - { - base.DrawDescription(c); - } - } - - private void DrawToBottom(SKCanvas c, FontDesign font, string text) - { - if (string.IsNullOrEmpty(text)) return; - if (!_lowerDrawn) - { - _lowerDrawn = true; - DrawTextBackground(c); - } - DisplayNamePaint.TextSize = font.FontSize; DisplayNamePaint.TextScaleX = font.FontScale; DisplayNamePaint.Color = font.FontColor; @@ -247,30 +161,115 @@ namespace FModel.Creator.Bases.FN font.Typeface.TryGetValue(ELanguage.English, out path)) DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path); + while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + DisplayNamePaint.TextSize -= 1; + } + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); - var shapedText = shaper.Shape(text, DisplayNamePaint); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); var x = font.Alignment switch { SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, - SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text), _ => font.X }; - c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint); + c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint); } - - private void DrawUserFacingFlags(SKCanvas c, bool customOnly) + else { - if (UserFacingFlags == null || UserFacingFlags.Count < 1) return; - if (customOnly) + base.DrawDisplayName(c); + } + } + + private new void DrawDescription(SKCanvas c) + { + if (string.IsNullOrEmpty(Description)) return; + if (_design.Fonts.TryGetValue(nameof(Description), out var font)) + { + DescriptionPaint.TextSize = font.FontSize; + DescriptionPaint.TextScaleX = font.FontScale; + DescriptionPaint.Color = font.FontColor; + DescriptionPaint.TextSkewX = font.SkewValue; + DescriptionPaint.TextAlign = font.Alignment; + if (font.ShadowValue > 0) + DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); + if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || + font.Typeface.TryGetValue(ELanguage.English, out path)) + DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path); + + while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2) { - c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint); + DescriptionPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(DescriptionPaint.Typeface); + var shapedText = shaper.Shape(Description, DescriptionPaint); + var x = font.Alignment switch + { + SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, + _ => font.X + }; + + if (font.MaxLineCount < 2) + { + c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint); } else { - // add size to api - // draw + Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign, + new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _); } } + else + { + base.DrawDescription(c); + } } -} + + private void DrawToBottom(SKCanvas c, FontDesign font, string text) + { + if (string.IsNullOrEmpty(text)) return; + if (!_lowerDrawn) + { + _lowerDrawn = true; + DrawTextBackground(c); + } + + DisplayNamePaint.TextSize = font.FontSize; + DisplayNamePaint.TextScaleX = font.FontScale; + DisplayNamePaint.Color = font.FontColor; + DisplayNamePaint.TextSkewX = font.SkewValue; + DisplayNamePaint.TextAlign = font.Alignment; + if (font.ShadowValue > 0) + DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); + if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || + font.Typeface.TryGetValue(ELanguage.English, out path)) + DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path); + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(text, DisplayNamePaint); + var x = font.Alignment switch + { + SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, + SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text), + _ => font.X + }; + + c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint); + } + + private void DrawUserFacingFlags(SKCanvas c, bool customOnly) + { + if (UserFacingFlags == null || UserFacingFlags.Count < 1) return; + if (customOnly) + { + c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint); + } + else + { + // add size to api + // draw + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseIcon.cs b/FModel/Creator/Bases/FN/BaseIcon.cs index 931b7ee3..dd00cb57 100644 --- a/FModel/Creator/Bases/FN/BaseIcon.cs +++ b/FModel/Creator/Bases/FN/BaseIcon.cs @@ -16,272 +16,269 @@ using CUE4Parse_Fortnite.Enums; using FModel.Settings; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseIcon : UCreator { - public class BaseIcon : UCreator + public SKBitmap SeriesBackground { get; protected set; } + protected string ShortDescription { get; set; } + protected string CosmeticSource { get; set; } + protected Dictionary UserFacingFlags { get; set; } + + public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { - public SKBitmap SeriesBackground { get; protected set; } - protected string ShortDescription { get; set; } - protected string CosmeticSource { get; set; } - protected Dictionary UserFacingFlags { get; set; } + } - public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) + public void ParseForReward(bool isUsingDisplayAsset) + { + // rarity + if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series); + else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon + + // preview + if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview)) + Preview = preview; + else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition")) + Preview = Utils.GetBitmap(itemDefinition); + else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon")) + Preview = Utils.GetBitmap(largePreview); + else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s)) + Preview = Utils.GetBitmap(s); + else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item")) + Preview = Utils.GetBitmap(otherPreview); + else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage")) + Preview = Utils.GetBitmap(materialInstancePreview); + else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject")) + Preview = Utils.GetBitmap(res); + + // text + if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription")) + Description = description.Text; + else if (Object.TryGetValue(out FText[] descriptions, "Description")) + Description = string.Join('\n', descriptions.Select(x => x.Text)); + if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName")) + ShortDescription = shortDescription.Text; + else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase)) + ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap"); + + // Only works on non-cataba designs + if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") && + eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") && + Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") && + eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor")) { + Background = new[] { SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex) }; + Border = new[] { SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex) }; } - public void ParseForReward(bool isUsingDisplayAsset) + Description = Utils.RemoveHtmlTags(Description); + } + + public override void ParseForInfo() + { + ParseForReward(UserSettings.Default.CosmeticDisplayAsset); + + if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) + CheckGameplayTags(gameplayTags); + if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) + CosmeticSource = cosmeticItem.Name; + } + + protected void Draw(SKCanvas c) + { + switch (Style) { - // rarity - if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series); - else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawBackground(c); + DrawPreview(c); + DrawUserFacingFlags(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + DrawToBottom(c, SKTextAlign.Right, CosmeticSource); + if (Description != ShortDescription) + DrawToBottom(c, SKTextAlign.Left, ShortDescription); + DrawUserFacingFlags(c); + break; + } + } - // preview - if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview)) - Preview = preview; - else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition")) - Preview = Utils.GetBitmap(itemDefinition); - else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon")) - Preview = Utils.GetBitmap(largePreview); - else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s)) - Preview = Utils.GetBitmap(s); - else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item")) - Preview = Utils.GetBitmap(otherPreview); - else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage")) - Preview = Utils.GetBitmap(materialInstancePreview); - else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject")) - Preview = Utils.GetBitmap(res); + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); - // text - if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription")) - Description = description.Text; - else if (Object.TryGetValue(out FText[] descriptions, "Description")) - Description = string.Join('\n', descriptions.Select(x => x.Text)); - if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName")) - ShortDescription = shortDescription.Text; - else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase)) - ShortDescription = Utils.GetLocalizedResource("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap"); + Draw(c); - // Only works on non-cataba designs - if (Object.TryGetValue(out FStructFallback eventArrowColor, "EventArrowColor") && - eventArrowColor.TryGetValue(out FLinearColor specifiedArrowColor, "SpecifiedColor") && - Object.TryGetValue(out FStructFallback eventArrowShadowColor, "EventArrowShadowColor") && - eventArrowShadowColor.TryGetValue(out FLinearColor specifiedShadowColor, "SpecifiedColor")) + return new[] { ret }; + } + + private void GetSeries(FPackageIndex s) + { + if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return; + + GetSeries(export); + } + + protected void GetSeries(UObject uObject) + { + if (uObject is UTexture2D texture2D) + { + SeriesBackground = texture2D.Decode(); + return; + } + + if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture")) + { + SeriesBackground = Utils.GetBitmap(backgroundTexture); + } + + if (uObject.TryGetValue(out FStructFallback colors, "Colors") && + colors.TryGetValue(out FLinearColor color1, "Color1") && + colors.TryGetValue(out FLinearColor color2, "Color2") && + colors.TryGetValue(out FLinearColor color3, "Color3")) + { + Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; + Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; + } + + if (uObject.Name.Equals("PlatformSeries") && + uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") && + Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material)) + { + foreach (var vectorParameter in material.VectorParameterValues) { - Background = new[] {SKColor.Parse(specifiedArrowColor.Hex), SKColor.Parse(specifiedShadowColor.Hex)}; - Border = new[] {SKColor.Parse(specifiedShadowColor.Hex), SKColor.Parse(specifiedArrowColor.Hex)}; - } + if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground")) + continue; - Description = Utils.RemoveHtmlTags(Description); - } - - public override void ParseForInfo() - { - ParseForReward(UserSettings.Default.CosmeticDisplayAsset); - - if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) - CheckGameplayTags(gameplayTags); - if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) - CosmeticSource = cosmeticItem.Name; - } - - protected void Draw(SKCanvas c) - { - switch (Style) - { - case EIconStyle.NoBackground: - DrawPreview(c); - break; - case EIconStyle.NoText: - DrawBackground(c); - DrawPreview(c); - DrawUserFacingFlags(c); - break; - default: - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - DrawToBottom(c, SKTextAlign.Right, CosmeticSource); - if (Description != ShortDescription) - DrawToBottom(c, SKTextAlign.Left, ShortDescription); - DrawUserFacingFlags(c); - break; + Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); } } + } - public override SKBitmap[] Draw() + private void GetRarity(EFortRarity r) + { + if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return; + + if (export.GetByIndex((int) r) is { } data && + data.TryGetValue(out FLinearColor color1, "Color1") && + data.TryGetValue(out FLinearColor color2, "Color2") && + data.TryGetValue(out FLinearColor color3, "Color3")) { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); + Background = new[] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; + Border = new[] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; + } + } - Draw(c); + protected string GetCosmeticSet(string setName) + { + if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets)) + return string.Empty; - return new []{ret}; + if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject)) + return string.Empty; + + var name = string.Empty; + if (uObject.TryGetValue(out FText displayName, "DisplayName")) + name = displayName.Text; + + var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); + return string.Format(format, name); + } + + protected string GetCosmeticSeason(string seasonNumber) + { + var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; + var number = int.Parse(s); + + switch (number) + { + case 10: + s = "X"; + break; + case > 18: + number += 2; + s = number.ToString(); + break; } - private void GetSeries(FPackageIndex s) + var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); + var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}."); + if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s))); + + var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); + var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); + var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..])); + return Utils.RemoveHtmlTags(string.Format(introduced, d)); + } + + private void CheckGameplayTags(FGameplayTagContainer gameplayTags) + { + if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) + CosmeticSource = source.Text["Cosmetics.Source.".Length..]; + else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) + CosmeticSource = action.Text["Athena.ItemAction.".Length..]; + + if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) + Description += GetCosmeticSet(set.Text); + if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) + Description += GetCosmeticSeason(season.Text); + + GetUserFacingFlags(gameplayTags.GetAllGameplayTags( + "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender.")); + } + + protected void GetUserFacingFlags(IList userFacingFlags) + { + if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories)) + return; + + if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories")) + return; + + UserFacingFlags = new Dictionary(userFacingFlags.Count); + foreach (var flag in userFacingFlags) { - if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return; - - GetSeries(export); - } - - protected void GetSeries(UObject uObject) - { - if (uObject is UTexture2D texture2D) + if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase)) { - SeriesBackground = texture2D.Decode(); - return; + if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase)) + UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream); + else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream); } - - if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture")) + else { - SeriesBackground = Utils.GetBitmap(backgroundTexture); - } - - if (uObject.TryGetValue(out FStructFallback colors, "Colors") && - colors.TryGetValue(out FLinearColor color1, "Color1") && - colors.TryGetValue(out FLinearColor color2, "Color2") && - colors.TryGetValue(out FLinearColor color3, "Color3")) - { - Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)}; - Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)}; - } - - if (uObject.Name.Equals("PlatformSeries") && - uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") && - Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material)) - { - foreach (var vectorParameter in material.VectorParameterValues) + foreach (var category in tertiaryCategories) { - if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground")) - continue; - - Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); - } - } - } - - private void GetRarity(EFortRarity r) - { - if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return; - - if (export.GetByIndex((int) r) is { } data && - data.TryGetValue(out FLinearColor color1, "Color1") && - data.TryGetValue(out FLinearColor color2, "Color2") && - data.TryGetValue(out FLinearColor color3, "Color3")) - { - Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)}; - Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)}; - } - } - - protected string GetCosmeticSet(string setName) - { - if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets)) - return string.Empty; - - if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject)) - return string.Empty; - - var name = string.Empty; - if (uObject.TryGetValue(out FText displayName, "DisplayName")) - name = displayName.Text; - - var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); - return string.Format(format, name); - } - - protected string GetCosmeticSeason(string seasonNumber) - { - var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; - var number = int.Parse(s); - - switch (number) - { - case 10: - s = "X"; - break; - case > 18: - number += 2; - s = number.ToString(); - break; - } - - var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); - var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}."); - if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s))); - - var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); - var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); - var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..])); - return Utils.RemoveHtmlTags(string.Format(introduced, d)); - } - - private void CheckGameplayTags(FGameplayTagContainer gameplayTags) - { - if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) - CosmeticSource = source.Text["Cosmetics.Source.".Length..]; - else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) - CosmeticSource = action.Text["Athena.ItemAction.".Length..]; - - if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) - Description += GetCosmeticSet(set.Text); - if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) - Description += GetCosmeticSeason(season.Text); - - GetUserFacingFlags(gameplayTags.GetAllGameplayTags( - "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender.")); - } - - protected void GetUserFacingFlags(IList userFacingFlags) - { - if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories)) - return; - - if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories")) - return; - - UserFacingFlags = new Dictionary(userFacingFlags.Count); - foreach (var flag in userFacingFlags) - { - if (flag.Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase)) - { - if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase)) - UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream); - else UserFacingFlags[flag] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream); - } - else - { - foreach (var category in tertiaryCategories) + if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) && + category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") && + brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture)) { - if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(flag, out _) && - category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") && - brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture)) - { - UserFacingFlags[flag] = Utils.GetBitmap(texture); - } + UserFacingFlags[flag] = Utils.GetBitmap(texture); } } } } + } - private void DrawUserFacingFlags(SKCanvas c) + private void DrawUserFacingFlags(SKCanvas c) + { + if (UserFacingFlags == null) return; + + const int size = 25; + var x = Margin * (int) 2.5; + foreach (var flag in UserFacingFlags.Values.Where(flag => flag != null)) { - if (UserFacingFlags == null) return; - - const int size = 25; - var x = Margin * (int) 2.5; - foreach (var flag in UserFacingFlags.Values) - { - if (flag == null) continue; - - c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint); - x += size; - } + c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint); + x += size; } } -} +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs index c68e8dc3..a23e02fc 100644 --- a/FModel/Creator/Bases/FN/BaseIconStats.cs +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -13,280 +13,279 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseIconStats : BaseIcon { - public class BaseIconStats : BaseIcon + private readonly IList _statistics; + private const int _headerHeight = 128; + private bool _screenLayer; + + public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style) { - private readonly IList _statistics; - private const int _headerHeight = 128; - private bool _screenLayer; - - public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style) - { - Width = 1024; - Height = _headerHeight; - Margin = 0; - _statistics = new List(); - _screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase); - DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); - } - - public override void ParseForInfo() - { - base.ParseForInfo(); - DisplayName = DisplayName.ToUpperInvariant(); - - if (Object.TryGetValue(out FName accoladeType, "AccoladeType") && - accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase)) - { - _screenLayer = false; - } - - if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") && - Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) && - uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) - { - foreach (var location in poiLocations) - { - var locationName = "Unknown"; - foreach (var poi in challengeMapPoiData) - { - if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") || - !tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue; - - locationName = text.Text; - break; - } - - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper())); - } - } - - if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize")) - { - if (maxStackSize.TryGetValue(out float v, "Value") && v > 0) - { - _statistics.Add(new IconStat("Max Stack", v , 15)); - } - else if (TryGetCurveTableStat(maxStackSize, out var s)) - { - _statistics.Add(new IconStat("Max Stack", s , 15)); - } - } - - if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x)) - { - _statistics.Add(new IconStat("XP Amount", x)); - } - - if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") && - weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") && - weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") && - dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue)) - { - if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge")) - { - var multiplier = bpc != 0f ? bpc : 1; - if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200)); - } - - if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical")) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200)); - } - } - - if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50)); - } - - if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15)); - } - - if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125)); - } - - if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15)); - } - - if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) || - Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) && - weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") && - weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") && - durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) && - durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription())) - { - _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20)); - } - } - - if (!string.IsNullOrEmpty(Description)) - Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count; - Height += 50 * _statistics.Count; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawHeader(c); - DrawDisplayName(c); - DrawStatistics(c); - - return new []{ret}; - } - - private bool TryGetCurveTableStat(FStructFallback property, out float statValue) - { - if (property.TryGetValue(out FStructFallback curve, "Curve") && - curve.TryGetValue(out FName rowName, "RowName") && - curve.TryGetValue(out UCurveTable curveTable, "CurveTable") && - curveTable.TryFindCurve(rowName, out var rowValue) && - rowValue is FSimpleCurve s && s.Keys.Length > 0) - { - statValue = s.Keys[0].Value; - return true; - } - - statValue = 0F; - return false; - } - - private readonly SKPaint _informationPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Color = SKColor.Parse("#262630"), TextSize = 16, - Typeface = Utils.Typefaces.Description - }; - - private void DrawHeader(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, - new[] {Background[0].WithAlpha(180), Background[1].WithAlpha(220)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint); - - _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, - new[] {SKColor.Parse("#262630"), SKColor.Parse("#1f1f26")}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), - new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint); - - _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); - using var rect = new SKPath {FillType = SKPathFillType.EvenOdd}; - rect.MoveTo(0, 0); - rect.LineTo(_headerHeight + _headerHeight / 3, 0); - rect.LineTo(_headerHeight, _headerHeight); - rect.LineTo(0, _headerHeight); - rect.Close(); - c.DrawPath(rect, _informationPaint); - - _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2), - new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawPath(rect, _informationPaint); - - _informationPaint.Shader = null; - - ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; - c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint); - } - - private new void DrawDisplayName(SKCanvas c) - { - if (string.IsNullOrEmpty(DisplayName)) return; - - _informationPaint.TextSize = 50; - _informationPaint.Color = SKColors.White; - _informationPaint.Typeface = Utils.Typefaces.Bundle; - while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2) - { - _informationPaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(_informationPaint.Typeface); - shaper.Shape(DisplayName, _informationPaint); - c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint); - } - - private void DrawStatistics(SKCanvas c) - { - var outY = _headerHeight + 25f; - if (!string.IsNullOrEmpty(Description)) - { - _informationPaint.TextSize = 16; - _informationPaint.Color = SKColors.White.WithAlpha(175); - _informationPaint.Typeface = Utils.Typefaces.Description; - Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center, - new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY); - outY += 25; - } - - foreach (var stat in _statistics) - { - stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY); - outY += 50; - } - } + Width = 1024; + Height = _headerHeight; + Margin = 0; + _statistics = new List(); + _screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase); + DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); } - public class IconStat + public override void ParseForInfo() { - private readonly string _statName; - private readonly object _value; - private readonly float _maxValue; + base.ParseForInfo(); + DisplayName = DisplayName.ToUpperInvariant(); - public IconStat(string statName, object value, float maxValue = 0) + if (Object.TryGetValue(out FName accoladeType, "AccoladeType") && + accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase)) { - _statName = statName.ToUpperInvariant(); - _value = value; - _maxValue = maxValue; + _screenLayer = false; } - private readonly SKPaint _statPaint = new() + if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") && + Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) && + uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - TextSize = 25, Typeface = Utils.Typefaces.DisplayName, - Color = SKColors.White - }; - - public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y) - { - while (_statPaint.MeasureText(_statName) > height * 2 - 40) + foreach (var location in poiLocations) { - _statPaint.TextSize -= 1; + var locationName = "Unknown"; + foreach (var poi in challengeMapPoiData) + { + if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") || + !tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue; + + locationName = text.Text; + break; + } + + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper())); + } + } + + if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize")) + { + if (maxStackSize.TryGetValue(out float v, "Value") && v > 0) + { + _statistics.Add(new IconStat("Max Stack", v, 15)); + } + else if (TryGetCurveTableStat(maxStackSize, out var s)) + { + _statistics.Add(new IconStat("Max Stack", s, 15)); + } + } + + if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x)) + { + _statistics.Add(new IconStat("XP Amount", x)); + } + + if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") && + weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") && + weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") && + dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue)) + { + if (weaponRowValue.TryGetValue(out int bpc, "BulletsPerCartridge")) + { + var multiplier = bpc != 0f ? bpc : 1; + if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200)); + } + + if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical")) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200)); + } } - var shaper = new CustomSKShaper(_statPaint.Typeface); - shaper.Shape(_statName, _statPaint); - c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint); + if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50)); + } - _statPaint.TextAlign = SKTextAlign.Right; - _statPaint.Typeface = Utils.Typefaces.BundleNumber; - _statPaint.Color = sliderColor; - var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString()); - c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint); + if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15)); + } - _statPaint.Color = SKColors.White; - c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint); + if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125)); + } - if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return; - if (floatValue < 0) - floatValue = 0; - var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue); - c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint); + if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15)); + } + + if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) || + Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) && + weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") && + weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") && + durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) && + durability.TryGetValue(out int duraByRarity, Object.GetOrDefault("Rarity", EFortRarity.Uncommon).GetDescription())) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20)); + } + } + + if (!string.IsNullOrEmpty(Description)) + Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count; + Height += 50 * _statistics.Count; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + DrawDisplayName(c); + DrawStatistics(c); + + return new[] { ret }; + } + + private bool TryGetCurveTableStat(FStructFallback property, out float statValue) + { + if (property.TryGetValue(out FStructFallback curve, "Curve") && + curve.TryGetValue(out FName rowName, "RowName") && + curve.TryGetValue(out UCurveTable curveTable, "CurveTable") && + curveTable.TryFindCurve(rowName, out var rowValue) && + rowValue is FSimpleCurve s && s.Keys.Length > 0) + { + statValue = s.Keys[0].Value; + return true; + } + + statValue = 0F; + return false; + } + + private readonly SKPaint _informationPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColor.Parse("#262630"), TextSize = 16, + Typeface = Utils.Typefaces.Description + }; + + private void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] { Background[0].WithAlpha(180), Background[1].WithAlpha(220) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] { SKColor.Parse("#262630"), SKColor.Parse("#1f1f26") }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); + using var rect = new SKPath { FillType = SKPathFillType.EvenOdd }; + rect.MoveTo(0, 0); + rect.LineTo(_headerHeight + _headerHeight / 3, 0); + rect.LineTo(_headerHeight, _headerHeight); + rect.LineTo(0, _headerHeight); + rect.Close(); + c.DrawPath(rect, _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2), + new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawPath(rect, _informationPaint); + + _informationPaint.Shader = null; + + ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; + c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint); + } + + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + + _informationPaint.TextSize = 50; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.Bundle; + while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2) + { + _informationPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_informationPaint.Typeface); + shaper.Shape(DisplayName, _informationPaint); + c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint); + } + + private void DrawStatistics(SKCanvas c) + { + var outY = _headerHeight + 25f; + if (!string.IsNullOrEmpty(Description)) + { + _informationPaint.TextSize = 16; + _informationPaint.Color = SKColors.White.WithAlpha(175); + _informationPaint.Typeface = Utils.Typefaces.Description; + Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center, + new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY); + outY += 25; + } + + foreach (var stat in _statistics) + { + stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY); + outY += 50; } } } + +public class IconStat +{ + private readonly string _statName; + private readonly object _value; + private readonly float _maxValue; + + public IconStat(string statName, object value, float maxValue = 0) + { + _statName = statName.ToUpperInvariant(); + _value = value; + _maxValue = maxValue; + } + + private readonly SKPaint _statPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + TextSize = 25, Typeface = Utils.Typefaces.DisplayName, + Color = SKColors.White + }; + + public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y) + { + while (_statPaint.MeasureText(_statName) > height * 2 - 40) + { + _statPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_statPaint.Typeface); + shaper.Shape(_statName, _statPaint); + c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint); + + _statPaint.TextAlign = SKTextAlign.Right; + _statPaint.Typeface = Utils.Typefaces.BundleNumber; + _statPaint.Color = sliderColor; + var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString()); + c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint); + + _statPaint.Color = SKColors.White; + c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint); + + if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return; + if (floatValue < 0) + floatValue = 0; + var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue); + c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseItemAccessToken.cs b/FModel/Creator/Bases/FN/BaseItemAccessToken.cs index 49ac8119..fb621d6f 100644 --- a/FModel/Creator/Bases/FN/BaseItemAccessToken.cs +++ b/FModel/Creator/Bases/FN/BaseItemAccessToken.cs @@ -5,96 +5,95 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseItemAccessToken : UCreator { - public class BaseItemAccessToken : UCreator + private readonly SKBitmap _locked, _unlocked; + private string _unlockedDescription, _exportName; + private BaseIcon _icon; + + public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style) { - private readonly SKBitmap _locked, _unlocked; - private string _unlockedDescription, _exportName; - private BaseIcon _icon; - - public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style) - { - _unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24); - _locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24); - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") && - Utils.TryGetPackageIndexExport(accessItem, out UObject uObject)) - { - _exportName = uObject.Name; - _icon = new BaseIcon(uObject, EIconStyle.Default); - _icon.ParseForReward(false); - } - - if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD") - DisplayName = displayName.Text; - else - DisplayName = _icon?.DisplayName; - - Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description; - if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - switch (Style) - { - case EIconStyle.NoBackground: - Preview = _icon.Preview; - DrawPreview(c); - break; - case EIconStyle.NoText: - Preview = _icon.Preview; - _icon.DrawBackground(c); - DrawPreview(c); - break; - default: - _icon.DrawBackground(c); - DrawInformation(c); - DrawToBottom(c, SKTextAlign.Right, _exportName); - break; - } - - return new []{ret}; - } - - private void DrawInformation(SKCanvas c) - { - var size = 45; - var left = Width / 2; - - while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2) - { - DisplayNamePaint.TextSize = size -= 2; - } - - var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); - var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); - c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint); - - float topBase = _icon.Margin + size * 2; - if (!string.IsNullOrEmpty(_unlockedDescription)) - { - c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint); - Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left, - new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); - } - - if (!string.IsNullOrEmpty(Description)) - { - c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint); - Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left, - new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); - } - - var h = Width - _icon.Margin - topBase; - c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint); - } + _unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24); + _locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24); } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") && + Utils.TryGetPackageIndexExport(accessItem, out UObject uObject)) + { + _exportName = uObject.Name; + _icon = new BaseIcon(uObject, EIconStyle.Default); + _icon.ParseForReward(false); + } + + if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD") + DisplayName = displayName.Text; + else + DisplayName = _icon?.DisplayName; + + Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description; + if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + Preview = _icon.Preview; + DrawPreview(c); + break; + case EIconStyle.NoText: + Preview = _icon.Preview; + _icon.DrawBackground(c); + DrawPreview(c); + break; + default: + _icon.DrawBackground(c); + DrawInformation(c); + DrawToBottom(c, SKTextAlign.Right, _exportName); + break; + } + + return new[] { ret }; + } + + private void DrawInformation(SKCanvas c) + { + var size = 45; + var left = Width / 2; + + while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2) + { + DisplayNamePaint.TextSize = size -= 2; + } + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); + c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint); + + float topBase = _icon.Margin + size * 2; + if (!string.IsNullOrEmpty(_unlockedDescription)) + { + c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint); + Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left, + new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); + } + + if (!string.IsNullOrEmpty(Description)) + { + c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint); + Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left, + new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); + } + + var h = Width - _icon.Margin - topBase; + c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseMaterialInstance.cs b/FModel/Creator/Bases/FN/BaseMaterialInstance.cs index f8293614..55e06923 100644 --- a/FModel/Creator/Bases/FN/BaseMaterialInstance.cs +++ b/FModel/Creator/Bases/FN/BaseMaterialInstance.cs @@ -5,83 +5,82 @@ using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.UObject; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseMaterialInstance : BaseIcon { - public class BaseMaterialInstance : BaseIcon + public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style) { - public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style) + Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") }; + Border = new[] { SKColor.Parse("9092AB") }; + } + + public override void ParseForInfo() + { + if (Object is not UMaterialInstanceConstant material) return; + + texture_finding: + foreach (var textureParameter in material.TextureParameterValues) // get texture from base material { - Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")}; - Border = new[] {SKColor.Parse("9092AB")}; - } - - public override void ParseForInfo() - { - if (Object is not UMaterialInstanceConstant material) return; - - texture_finding: - foreach (var textureParameter in material.TextureParameterValues) // get texture from base material + if (!textureParameter.ParameterValue.TryLoad(out var texture) || Preview != null) continue; + switch (textureParameter.ParameterInfo.Name.Text) { - if (!textureParameter.ParameterValue.TryLoad(out var texture) || Preview != null) continue; - switch (textureParameter.ParameterInfo.Name.Text) - { - case "SeriesTexture": - GetSeries(texture); - break; - case "TextureA": - case "TextureB": - case "OfferImage": - Preview = Utils.GetBitmap(texture); - break; - } - } - - while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here - material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color - { - if (material.TryGetValue(out FPackageIndex parent, "Parent")) - Utils.TryGetPackageIndexExport(parent, out material); - else return; - - if (material == null) return; - } - - if (Preview == null) - goto texture_finding; - - foreach (var vectorParameter in material.VectorParameterValues) - { - if (vectorParameter.ParameterValue == null) continue; - switch (vectorParameter.ParameterInfo.Name.Text) - { - case "Background_Color_A": - Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); - Border[0] = Background[0]; - break; - case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base - Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); - break; - } + case "SeriesTexture": + GetSeries(texture); + break; + case "TextureA": + case "TextureB": + case "OfferImage": + Preview = Utils.GetBitmap(texture); + break; } } - public override SKBitmap[] Draw() + while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here + material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); + if (material.TryGetValue(out FPackageIndex parent, "Parent")) + Utils.TryGetPackageIndexExport(parent, out material); + else return; - switch (Style) + if (material == null) return; + } + + if (Preview == null) + goto texture_finding; + + foreach (var vectorParameter in material.VectorParameterValues) + { + if (vectorParameter.ParameterValue == null) continue; + switch (vectorParameter.ParameterInfo.Name.Text) { - case EIconStyle.NoBackground: - DrawPreview(c); + case "Background_Color_A": + Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); + Border[0] = Background[0]; break; - default: - DrawBackground(c); - DrawPreview(c); + case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base + Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); break; } - - return new []{ret}; } } -} + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + break; + } + + return new[] { ret }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseMtxOffer.cs b/FModel/Creator/Bases/FN/BaseMtxOffer.cs index 89c4c861..68e64762 100644 --- a/FModel/Creator/Bases/FN/BaseMtxOffer.cs +++ b/FModel/Creator/Bases/FN/BaseMtxOffer.cs @@ -5,81 +5,80 @@ using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.UObject; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseMtxOffer : UCreator { - public class BaseMtxOffer : UCreator + public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style) { - public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style) + Background = new[] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") }; + Border = new[] { SKColor.Parse("9092AB") }; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") && + typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject")) { - Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")}; - Border = new[] {SKColor.Parse("9092AB")}; + Preview = Utils.GetBitmap(resource); } - public override void ParseForInfo() + if (Object.TryGetValue(out FStructFallback gradient, "Gradient") && + gradient.TryGetValue(out FLinearColor start, "Start") && + gradient.TryGetValue(out FLinearColor stop, "Stop")) { - if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") && - typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject")) - { - Preview = Utils.GetBitmap(resource); - } + Background = new[] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) }; + } - if (Object.TryGetValue(out FStructFallback gradient, "Gradient") && - gradient.TryGetValue(out FLinearColor start, "Start") && - gradient.TryGetValue(out FLinearColor stop, "Stop")) - { - Background = new[] {SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex)}; - } + if (Object.TryGetValue(out FLinearColor background, "Background")) + Border = new[] { SKColor.Parse(background.Hex) }; + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText shortDescription, "ShortDescription")) + Description = shortDescription.Text; - if (Object.TryGetValue(out FLinearColor background, "Background")) - Border = new[] {SKColor.Parse(background.Hex)}; - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText shortDescription, "ShortDescription")) - Description = shortDescription.Text; - - if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes")) + if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes")) + { + foreach (var detail in details) { - foreach (var detail in details) + if (detail.TryGetValue(out FText detailName, "Name")) { - if (detail.TryGetValue(out FText detailName, "Name")) - { - Description += $"\n- {detailName.Text.TrimEnd()}"; - } + Description += $"\n- {detailName.Text.TrimEnd()}"; + } - if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text)) - { - Description += $" ({detailValue.Text})"; - } + if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text)) + { + Description += $" ({detailValue.Text})"; } } - - Description = Utils.RemoveHtmlTags(Description); } - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - switch (Style) - { - case EIconStyle.NoBackground: - DrawPreview(c); - break; - case EIconStyle.NoText: - DrawBackground(c); - DrawPreview(c); - break; - default: - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - break; - } - - return new []{ret}; - } + Description = Utils.RemoveHtmlTags(Description); } -} + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawBackground(c); + DrawPreview(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + break; + } + + return new[] { ret }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseOfferDisplayData.cs b/FModel/Creator/Bases/FN/BaseOfferDisplayData.cs index bf4458b1..b1a1808e 100644 --- a/FModel/Creator/Bases/FN/BaseOfferDisplayData.cs +++ b/FModel/Creator/Bases/FN/BaseOfferDisplayData.cs @@ -2,39 +2,38 @@ using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseOfferDisplayData : UCreator { - public class BaseOfferDisplayData : UCreator + private BaseMaterialInstance[] _offerImages; + + public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style) { - private BaseMaterialInstance[] _offerImages; + } - public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style) + public override void ParseForInfo() + { + if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations")) + return; + + _offerImages = new BaseMaterialInstance[presentations.Length]; + for (var i = 0; i < _offerImages.Length; i++) { - } - - public override void ParseForInfo() - { - if (!Object.TryGetValue(out UMaterialInterface[] presentations, "Presentations")) - return; - - _offerImages = new BaseMaterialInstance[presentations.Length]; - for (int i = 0; i < _offerImages.Length; i++) - { - var offerImage = new BaseMaterialInstance(presentations[i], Style); - offerImage.ParseForInfo(); - _offerImages[i] = offerImage; - } - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap[_offerImages.Length]; - for (int i = 0; i < ret.Length; i++) - { - ret[i] = _offerImages[i].Draw()[0]; - } - - return ret; + var offerImage = new BaseMaterialInstance(presentations[i], Style); + offerImage.ParseForInfo(); + _offerImages[i] = offerImage; } } -} + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap[_offerImages.Length]; + for (var i = 0; i < ret.Length; i++) + { + ret[i] = _offerImages[i].Draw()[0]; + } + + return ret; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BasePlaylist.cs b/FModel/Creator/Bases/FN/BasePlaylist.cs index 9637d453..fe7fd7c9 100644 --- a/FModel/Creator/Bases/FN/BasePlaylist.cs +++ b/FModel/Creator/Bases/FN/BasePlaylist.cs @@ -6,73 +6,72 @@ using FModel.Services; using FModel.ViewModels; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BasePlaylist : UCreator { - public class BasePlaylist : UCreator + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private SKBitmap _missionIcon; + + public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style) { - private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; - private SKBitmap _missionIcon; - - public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style) - { - Margin = 0; - Width = 1024; - Height = 512; - Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default"); - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText description, "UIDescription", "Description")) - Description = description.Text; - if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon")) - _missionIcon = Utils.GetBitmap(missionIcon).Resize(25); - - if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text)) - return; - - var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text); - if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase || - !_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image)) - return; - - Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512); - Width = Preview.Width; - Height = Preview.Height; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - switch (Style) - { - case EIconStyle.NoBackground: - DrawPreview(c); - break; - case EIconStyle.NoText: - DrawPreview(c); - DrawMissionIcon(c); - break; - default: - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - DrawMissionIcon(c); - break; - } - - return new []{ret}; - } - - private void DrawMissionIcon(SKCanvas c) - { - if (_missionIcon == null) return; - c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint); - } + Margin = 0; + Width = 1024; + Height = 512; + Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default"); } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "UIDescription", "Description")) + Description = description.Text; + if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon")) + _missionIcon = Utils.GetBitmap(missionIcon).Resize(25); + + if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text)) + return; + + var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text); + if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase || + !_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image)) + return; + + Preview = Utils.GetBitmap(image).ResizeWithRatio(1024, 512); + Width = Preview.Width; + Height = Preview.Height; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawPreview(c); + DrawMissionIcon(c); + break; + default: + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + DrawMissionIcon(c); + break; + } + + return new[] { ret }; + } + + private void DrawMissionIcon(SKCanvas c) + { + if (_missionIcon == null) return; + c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs index 23c00422..0a35dc21 100644 --- a/FModel/Creator/Bases/FN/BaseQuest.cs +++ b/FModel/Creator/Bases/FN/BaseQuest.cs @@ -12,256 +12,255 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseQuest : BaseIcon { - public class BaseQuest : BaseIcon + private int _count; + private Reward _reward; + private readonly bool _screenLayer; + private readonly string[] _unauthorizedReward = { "Token", "ChallengeBundle", "GiftBox" }; + + public string NextQuestName { get; private set; } + + public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style) { - private int _count; - private Reward _reward; - private readonly bool _screenLayer; - private readonly string[] _unauthorizedReward = {"Token", "ChallengeBundle", "GiftBox"}; - - public string NextQuestName { get; private set; } - - public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style) + Margin = 0; + Width = 1024; + Height = 256; + DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); + if (uObject != null) { - Margin = 0; - Width = 1024; - Height = 256; - DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); - if (uObject != null) - { - _screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase); - } - } - - private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion - { - var description = completionCount < 0 ? - Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges to earn the reward item") : - Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges to earn the reward item"); - - DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0); - } - - public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion - { - _reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward(); - } - - public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion - { - _reward = new Reward(quantity, reward); - } - - public override void ParseForInfo() - { - ParseForReward(false); - - if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData")) - { - if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle")) - DisplayName = eventTitle.Text; - if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription")) - Description = eventDescription.Text; - if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage")) - Preview = Utils.GetBitmap(alertIcon); - } - else - { - Description = ShortDescription; - if (Object.TryGetValue(out FText completionText, "CompletionText")) - Description += "\n" + completionText.Text; - if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") && - Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) && - uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") && - Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject)) - { - Preview = iconObject switch - { - UTexture2D text => Utils.GetBitmap(text), - UMaterialInstanceConstant mat => Utils.GetBitmap(mat), - _ => Preview - }; - } - } - - if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount")) - _count = objectiveCompletionCount; - - if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0) - { - // actual description doesn't exist - if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description")) - Description = description.Text; - - // ObjectiveCompletionCount doesn't exist - if (_count == 0) - { - if (objectives[0].TryGetValue(out int count, "Count") && count > 1) - _count = count; - else - _count = objectives.Length; - } - } - - if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards")) - { - foreach (var reward in rewards) - { - if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") || - !reward.TryGetValue(out int quantity, "Quantity")) continue; - - if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") || - !itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") || - !primaryAssetType.TryGetValue(out FName name, "Name")) continue; - - if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase)) - { - NextQuestName = primaryAssetName.Text; - } - else if (!_unauthorizedReward.Contains(name.Text)) - { - _reward = new Reward(quantity, $"{name}:{primaryAssetName}"); - } - } - } - - if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable")) - { - if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row)) - { - if (row.TryGetValue(out FName templateId, "TemplateId") && - row.TryGetValue(out int quantity, "Quantity")) - { - _reward = new Reward(quantity, templateId); - } - } - } - - if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards")) - { - foreach (var hiddenReward in hiddenRewards) - { - if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") || - !hiddenReward.TryGetValue(out int quantity, "Quantity")) continue; - - _reward = new Reward(quantity, templateId); - break; - } - } - - _reward ??= new Reward(); - } - - public void DrawQuest(SKCanvas c, int y) - { - DrawBackground(c, y); - DrawPreview(c, y); - DrawTexts(c, y); - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawQuest(c, 0); - - return new []{ret}; - } - - private string ReformatString(string s, string completionCount, bool isAll) - { - s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}"); - var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase); - if (index > -1) - { - var p = s.Substring(index, s[index..].IndexOf(')') + 1); - s = s.Replace(p, string.Empty); - s = s.Insert(s.IndexOf("", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(",")); - } - - var upper = s.SubstringAfter(">").SubstringBefore(""); - return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " "); - } - - private readonly SKPaint _informationPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Color = SKColor.Parse("#262630") - }; - - private void DrawBackground(SKCanvas c, int y) - { - c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, - new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75), - new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint); - - _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2), - new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint); - } - - private void DrawPreview(SKCanvas c, int y) - { - ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; - c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint); - } - - private void DrawTexts(SKCanvas c, int y) - { - _informationPaint.Shader = null; - - if (!string.IsNullOrWhiteSpace(DisplayName)) - { - _informationPaint.TextSize = 40; - _informationPaint.Color = SKColors.White; - _informationPaint.Typeface = Utils.Typefaces.Bundle; - while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10) - { - _informationPaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(_informationPaint.Typeface); - shaper.Shape(DisplayName, _informationPaint); - c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint); - } - - var outY = y + 75f; - if (!string.IsNullOrWhiteSpace(Description)) - { - _informationPaint.TextSize = 16; - _informationPaint.Color = SKColors.White.WithAlpha(175); - _informationPaint.Typeface = Utils.Typefaces.Description; - Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left, - new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY); - } - - _informationPaint.Color = Border[0].WithAlpha(100); - c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint); - - if (_count > 0) - { - _informationPaint.TextSize = 25; - _informationPaint.Color = SKColors.White; - _informationPaint.Typeface = Utils.Typefaces.BundleNumber; - c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint); - - _informationPaint.Color = Border[0]; - c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint); - } - - _reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25)); + _screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase); } } -} + + private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion + { + var description = completionCount < 0 ? + Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges to earn the reward item") : + Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges to earn the reward item"); + + DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0); + } + + public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion + { + _reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward(); + } + + public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion + { + _reward = new Reward(quantity, reward); + } + + public override void ParseForInfo() + { + ParseForReward(false); + + if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData")) + { + if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle")) + DisplayName = eventTitle.Text; + if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription")) + Description = eventDescription.Text; + if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage")) + Preview = Utils.GetBitmap(alertIcon); + } + else + { + Description = ShortDescription; + if (Object.TryGetValue(out FText completionText, "CompletionText")) + Description += "\n" + completionText.Text; + if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") && + Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) && + uObject.TryGetValue(out FSoftObjectPath tandemIcon, "EntryListIcon", "ToastIcon") && + Utils.TryLoadObject(tandemIcon.AssetPathName.Text, out UObject iconObject)) + { + Preview = iconObject switch + { + UTexture2D text => Utils.GetBitmap(text), + UMaterialInstanceConstant mat => Utils.GetBitmap(mat), + _ => Preview + }; + } + } + + if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount")) + _count = objectiveCompletionCount; + + if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0) + { + // actual description doesn't exist + if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description")) + Description = description.Text; + + // ObjectiveCompletionCount doesn't exist + if (_count == 0) + { + if (objectives[0].TryGetValue(out int count, "Count") && count > 1) + _count = count; + else + _count = objectives.Length; + } + } + + if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards")) + { + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") || + !reward.TryGetValue(out int quantity, "Quantity")) continue; + + if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") || + !itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") || + !primaryAssetType.TryGetValue(out FName name, "Name")) continue; + + if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase)) + { + NextQuestName = primaryAssetName.Text; + } + else if (!_unauthorizedReward.Contains(name.Text)) + { + _reward = new Reward(quantity, $"{name}:{primaryAssetName}"); + } + } + } + + if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable")) + { + if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row)) + { + if (row.TryGetValue(out FName templateId, "TemplateId") && + row.TryGetValue(out int quantity, "Quantity")) + { + _reward = new Reward(quantity, templateId); + } + } + } + + if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards")) + { + foreach (var hiddenReward in hiddenRewards) + { + if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") || + !hiddenReward.TryGetValue(out int quantity, "Quantity")) continue; + + _reward = new Reward(quantity, templateId); + break; + } + } + + _reward ??= new Reward(); + } + + public void DrawQuest(SKCanvas c, int y) + { + DrawBackground(c, y); + DrawPreview(c, y); + DrawTexts(c, y); + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawQuest(c, 0); + + return new[] { ret }; + } + + private string ReformatString(string s, string completionCount, bool isAll) + { + s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}"); + var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase); + if (index > -1) + { + var p = s.Substring(index, s[index..].IndexOf(')') + 1); + s = s.Replace(p, string.Empty); + s = s.Insert(s.IndexOf("", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(",")); + } + + var upper = s.SubstringAfter(">").SubstringBefore(""); + return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " "); + } + + private readonly SKPaint _informationPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColor.Parse("#262630") + }; + + private void DrawBackground(SKCanvas c, int y) + { + c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, + new[] { Background[0].WithAlpha(50), Background[1].WithAlpha(50) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75), + new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2), + new[] { SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint); + } + + private void DrawPreview(SKCanvas c, int y) + { + ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; + c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint); + } + + private void DrawTexts(SKCanvas c, int y) + { + _informationPaint.Shader = null; + + if (!string.IsNullOrWhiteSpace(DisplayName)) + { + _informationPaint.TextSize = 40; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.Bundle; + while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10) + { + _informationPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_informationPaint.Typeface); + shaper.Shape(DisplayName, _informationPaint); + c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint); + } + + var outY = y + 75f; + if (!string.IsNullOrWhiteSpace(Description)) + { + _informationPaint.TextSize = 16; + _informationPaint.Color = SKColors.White.WithAlpha(175); + _informationPaint.Typeface = Utils.Typefaces.Description; + Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left, + new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY); + } + + _informationPaint.Color = Border[0].WithAlpha(100); + c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint); + + if (_count > 0) + { + _informationPaint.TextSize = 25; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.BundleNumber; + c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint); + + _informationPaint.Color = Border[0]; + c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint); + } + + _reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25)); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseSeason.cs b/FModel/Creator/Bases/FN/BaseSeason.cs index a063f2c9..2d06deb3 100644 --- a/FModel/Creator/Bases/FN/BaseSeason.cs +++ b/FModel/Creator/Bases/FN/BaseSeason.cs @@ -8,158 +8,152 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class Page { - public class Page + public int LevelsNeededForUnlock; + public int RewardsNeededForUnlock; + public Reward[] RewardEntryList; +} + +public class BaseSeason : UCreator +{ + private Reward _firstWinReward; + private Page[] _bookXpSchedule; + private const int _headerHeight = 150; + // keep the list because rewards are ordered by least to most important + // we only care about the most but we also have filters so we can't just take the last reward + + public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style) { - public int LevelsNeededForUnlock; - public int RewardsNeededForUnlock; - public Reward[] RewardEntryList; + Width = 1024; + Height = _headerHeight + 50; + Margin = 0; } - public class BaseSeason : UCreator + public override void ParseForInfo() { - private Reward _firstWinReward; - private Page[] _bookXpSchedule; - private const int _headerHeight = 150; - // keep the list because rewards are ordered by least to most important - // we only care about the most but we also have filters so we can't just take the last reward + _bookXpSchedule = Array.Empty(); - public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style) + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text.ToUpperInvariant(); + + if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") && + seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards")) { - Width = 1024; - Height = _headerHeight + 50; - Margin = 0; + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || + !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; + + _firstWinReward = new Reward(uObject); + break; + } } - public override void ParseForInfo() + if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData")) { - _bookXpSchedule = Array.Empty(); - - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text.ToUpperInvariant(); - - if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") && - seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards")) + foreach (var data in additionalSeasonData) { - foreach (var reward in rewards) + if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) || + !packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue; + + var i = 0; + _bookXpSchedule = new Page[pageList.Length]; + foreach (var page in pageList) { - if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || - !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; + if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") || + !page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") || + !page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList")) + continue; - _firstWinReward = new Reward(uObject); - break; - } - } - - if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData")) - { - foreach (var data in additionalSeasonData) - { - if (!Utils.TryGetPackageIndexExport(data, out UObject packageIndex) || - !packageIndex.TryGetValue(out FStructFallback[] pageList, "PageList")) continue; - - var i = 0; - _bookXpSchedule = new Page[pageList.Length]; - foreach (var page in pageList) + var p = new Page { - if (!page.TryGetValue(out int levelsNeededForUnlock, "LevelsNeededForUnlock") || - !page.TryGetValue(out int rewardsNeededForUnlock, "RewardsNeededForUnlock") || - !page.TryGetValue(out FPackageIndex[] rewardEntryList, "RewardEntryList")) - continue; + LevelsNeededForUnlock = levelsNeededForUnlock, + RewardsNeededForUnlock = rewardsNeededForUnlock, + RewardEntryList = new Reward[rewardEntryList.Length] + }; - var p = new Page - { - LevelsNeededForUnlock = levelsNeededForUnlock, - RewardsNeededForUnlock = rewardsNeededForUnlock, - RewardEntryList = new Reward[rewardEntryList.Length] - }; + for (var j = 0; j < p.RewardEntryList.Length; j++) + { + if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) || + !packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") || + !battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") || + !rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || + !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; - for (var j = 0; j < p.RewardEntryList.Length; j++) - { - if (!Utils.TryGetPackageIndexExport(rewardEntryList[j], out packageIndex) || - !packageIndex.TryGetValue(out FStructFallback battlePassOffer, "BattlePassOffer") || - !battlePassOffer.TryGetValue(out FStructFallback rewardItem, "RewardItem") || - !rewardItem.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || - !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; - - p.RewardEntryList[j] = new Reward(uObject); - } - - _bookXpSchedule[i++] = p; + p.RewardEntryList[j] = new Reward(uObject); } - break; + _bookXpSchedule[i++] = p; } + + break; } - - Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length; } - public override SKBitmap[] Draw() + Height += 100 * _bookXpSchedule.Sum(x => x.RewardEntryList.Length) / _bookXpSchedule.Length; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + _firstWinReward?.DrawSeasonWin(c, _headerHeight); + DrawBookSchedule(c); + + return new[] { ret }; + } + + private const int _DEFAULT_AREA_SIZE = 80; + private readonly SKPaint _headerPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Bundle, TextSize = 50, + TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") + }; + + public void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); + + _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] { SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); + + _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] { SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0) }, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); + + _headerPaint.Shader = null; + _headerPaint.Color = SKColors.White; + while (_headerPaint.MeasureText(DisplayName) > Width) { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawHeader(c); - _firstWinReward?.DrawSeasonWin(c, _headerHeight); - DrawBookSchedule(c); - - return new []{ret}; + _headerPaint.TextSize -= 1; } - private const int _DEFAULT_AREA_SIZE = 80; - private readonly SKPaint _headerPaint = new() + var shaper = new CustomSKShaper(_headerPaint.Typeface); + var shapedText = shaper.Shape(DisplayName, _headerPaint); + c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); + } + + private void DrawBookSchedule(SKCanvas c) + { + var x = 20; + var y = _headerHeight + 50; + foreach (var page in _bookXpSchedule) { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.Bundle, TextSize = 50, - TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") - }; - private readonly SKPaint _bookPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber, - Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15 - }; - - public void DrawHeader(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); - - _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, - new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); - - _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), - new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp); - c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); - - _headerPaint.Shader = null; - _headerPaint.Color = SKColors.White; - while (_headerPaint.MeasureText(DisplayName) > Width) + foreach (var reward in page.RewardEntryList) { - _headerPaint.TextSize -= 1; + reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE); + x += _DEFAULT_AREA_SIZE + 20; } - var shaper = new CustomSKShaper(_headerPaint.Typeface); - var shapedText = shaper.Shape(DisplayName, _headerPaint); - c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); - } - - private void DrawBookSchedule(SKCanvas c) - { - var x = 20; - var y = _headerHeight + 50; - foreach (var page in _bookXpSchedule) - { - foreach (var reward in page.RewardEntryList) - { - reward.DrawSeason(c, x, y, _DEFAULT_AREA_SIZE); - x += _DEFAULT_AREA_SIZE + 20; - } - y += _DEFAULT_AREA_SIZE + 20; - x = 20; - } + y += _DEFAULT_AREA_SIZE + 20; + x = 20; } } -} +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseSeries.cs b/FModel/Creator/Bases/FN/BaseSeries.cs index 769b443a..dcc29897 100644 --- a/FModel/Creator/Bases/FN/BaseSeries.cs +++ b/FModel/Creator/Bases/FN/BaseSeries.cs @@ -1,27 +1,26 @@ using CUE4Parse.UE4.Assets.Exports; using SkiaSharp; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseSeries : BaseIcon { - public class BaseSeries : BaseIcon + public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style) { - public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style) - { - } - - public override void ParseForInfo() - { - GetSeries(Object); - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackground(c); - - return new []{ret}; - } } -} + + public override void ParseForInfo() + { + GetSeries(Object); + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + + return new []{ret}; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseTandem.cs b/FModel/Creator/Bases/FN/BaseTandem.cs index 350394dd..e9ab250c 100644 --- a/FModel/Creator/Bases/FN/BaseTandem.cs +++ b/FModel/Creator/Bases/FN/BaseTandem.cs @@ -9,221 +9,220 @@ using FModel.Settings; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseTandem : BaseIcon { - public class BaseTandem : BaseIcon + private string _generalDescription, _additionalDescription; + + public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style) { - private string _generalDescription, _additionalDescription; - - public BaseTandem(UObject uObject, EIconStyle style) : base(uObject, style) - { - DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy"); - Margin = 0; - Width = 690; - Height = 1080; - } - - public override void ParseForInfo() - { - base.ParseForInfo(); - - string sidePanel = string.Empty, entryList = string.Empty; - - if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon")) - sidePanel = sidePanelIcon.AssetPathName.Text; - if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon")) - entryList = entryListIcon.AssetPathName.Text; - - // Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs - if (sidePanel.Contains("Clown") && entryList.Contains("Clown")) - Preview = null; - else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana")) - Preview = Utils.GetBitmap(entryList); - else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown")) - Preview = Utils.GetBitmap(sidePanel); - else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList)) - Preview = Utils.GetBitmap(entryList); - - if (Object.TryGetValue(out FText genDesc, "GeneralDescription")) - _generalDescription = genDesc.Text; - if (Object.TryGetValue(out FText addDesc, "AdditionalDescription")) - _additionalDescription = addDesc.Text; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawPreview(c); - DrawHaze(c); - - // Korean is slightly smaller than other languages, so the font size is increased slightly - DrawName(c); - DrawGeneralDescription(c); - DrawAdditionalDescription(c); - - return new []{ret}; - } - - private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") }; - - private new void DrawBackground(SKCanvas c) - { - c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) }); - - using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd }; - _panelPaint.Color = SKColor.Parse("#002A8C"); - rect1.MoveTo(29, 0); - rect1.LineTo(62, Height); - rect1.LineTo(Width, Height); - rect1.LineTo(Width, 0); - rect1.LineTo(29, 0); - rect1.Close(); - c.DrawPath(rect1, _panelPaint); - - _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height), - new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp); - c.DrawPath(rect1, _panelPaint); - - _panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp); - c.DrawPath(rect1, _panelPaint); - - using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd }; - - rect2.MoveTo(10, 0); - rect2.LineTo(30, 0); - rect2.LineTo(63, Height); - rect2.LineTo(56, Height); - rect2.LineTo(10, 0); - rect2.Close(); - c.DrawPath(rect2, _panelPaint); - - _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height), - new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp); - c.DrawPath(rect2, _panelPaint); - } - - private new void DrawPreview(SKCanvas c) - { - var previewToUse = Preview ?? DefaultPreview; - - if (Preview == null) - { - previewToUse = DefaultPreview; - ImagePaint.BlendMode = SKBlendMode.DstOut; - ImagePaint.Color = SKColor.Parse("#00175F"); - } - - var x = -125; - - switch (previewToUse.Width) - { - case 512 when previewToUse.Height == 1024: - previewToUse = previewToUse.ResizeWithRatio(500, 1000); - x = 100; - break; - case 512 when previewToUse.Height == 512: - case 128 when previewToUse.Height == 128: - previewToUse = previewToUse.Resize(512); - x = 125; - break; - default: - previewToUse = previewToUse.Resize(1000, 1000); - break; - } - - c.DrawBitmap(previewToUse, x, 30, ImagePaint); - } - - private void DrawHaze(SKCanvas c) - { - using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd }; - rect1.MoveTo(29, 0); - rect1.LineTo(62, Height); - rect1.LineTo(Width, Height); - rect1.LineTo(Width, 0); - rect1.LineTo(29, 0); - rect1.Close(); - - _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height), - new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp); - c.DrawPath(rect1, _panelPaint); - } - - private void DrawName(SKCanvas c) - { - if (string.IsNullOrWhiteSpace(DisplayName)) return; - - DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch - { - ELanguage.Korean => 56, - _ => 42 - }; - - DisplayNamePaint.TextScaleX = (float) 1.1; - DisplayNamePaint.Color = SKColors.White; - DisplayNamePaint.TextSkewX = (float) -.25; - DisplayNamePaint.TextAlign = SKTextAlign.Left; - - var typeface = Utils.Typefaces.TandemDisplayName; - if (typeface == Utils.Typefaces.Default) - { - DisplayNamePaint.TextSize = 30; - } - - DisplayNamePaint.Typeface = typeface; - var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); - c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint); - } - - private void DrawGeneralDescription(SKCanvas c) - { - if (string.IsNullOrWhiteSpace(_generalDescription)) return; - - DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch - { - ELanguage.Korean => 20, - _ => 17 - }; - - DescriptionPaint.Color = SKColor.Parse("#00FFFB"); - DescriptionPaint.TextAlign = SKTextAlign.Left; - - var typeface = Utils.Typefaces.TandemGenDescription; - if (typeface == Utils.Typefaces.Default) - { - DescriptionPaint.TextSize = 21; - } - - DescriptionPaint.Typeface = typeface; - var shaper = new CustomSKShaper(DescriptionPaint.Typeface); - c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint); - } - - private void DrawAdditionalDescription(SKCanvas c) - { - if (string.IsNullOrWhiteSpace(_additionalDescription)) return; - - DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch - { - ELanguage.Korean => 22, - _ => 18 - }; - - DescriptionPaint.Color = SKColor.Parse("#89D8FF"); - DescriptionPaint.TextAlign = SKTextAlign.Left; - - var typeface = Utils.Typefaces.TandemAddDescription; - if (typeface == Utils.Typefaces.Default) - { - DescriptionPaint.TextSize = 20; - } - - DescriptionPaint.Typeface = typeface; - Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left, - new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _); - } + DefaultPreview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy.T-AthenaSoldiers-CID-883-Athena-Commando-M-ChOneJonesy"); + Margin = 0; + Width = 690; + Height = 1080; } -} + + public override void ParseForInfo() + { + base.ParseForInfo(); + + string sidePanel = string.Empty, entryList = string.Empty; + + if (Object.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon")) + sidePanel = sidePanelIcon.AssetPathName.Text; + if (Object.TryGetValue(out FSoftObjectPath entryListIcon, "EntryListIcon")) + entryList = entryListIcon.AssetPathName.Text; + + // Overrides for generic "default" images Epic uses for Quest-only or unfinished NPCs + if (sidePanel.Contains("Clown") && entryList.Contains("Clown")) + Preview = null; + else if (sidePanel.Contains("Bane") && !Object.Name.Contains("Sorana")) + Preview = Utils.GetBitmap(entryList); + else if (!string.IsNullOrWhiteSpace(sidePanel) && !sidePanel.Contains("Clown")) + Preview = Utils.GetBitmap(sidePanel); + else if ((string.IsNullOrWhiteSpace(sidePanel) || sidePanel.Contains("Clown")) && !string.IsNullOrWhiteSpace(entryList)) + Preview = Utils.GetBitmap(entryList); + + if (Object.TryGetValue(out FText genDesc, "GeneralDescription")) + _generalDescription = genDesc.Text; + if (Object.TryGetValue(out FText addDesc, "AdditionalDescription")) + _additionalDescription = addDesc.Text; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawHaze(c); + + // Korean is slightly smaller than other languages, so the font size is increased slightly + DrawName(c); + DrawGeneralDescription(c); + DrawAdditionalDescription(c); + + return new[] { ret }; + } + + private readonly SKPaint _panelPaint = new() { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = SKColor.Parse("#0045C7") }; + + private new void DrawBackground(SKCanvas c) + { + c.DrawBitmap(SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/npcleftside.png"))?.Stream).Resize(Width, Height), 0, 0, new SKPaint { IsAntialias = false, FilterQuality = SKFilterQuality.None, ImageFilter = SKImageFilter.CreateBlur(0, 25) }); + + using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd }; + _panelPaint.Color = SKColor.Parse("#002A8C"); + rect1.MoveTo(29, 0); + rect1.LineTo(62, Height); + rect1.LineTo(Width, Height); + rect1.LineTo(Width, 0); + rect1.LineTo(29, 0); + rect1.Close(); + c.DrawPath(rect1, _panelPaint); + + _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(29, 0), new SKPoint(Width, Height), + new[] { SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp); + c.DrawPath(rect1, _panelPaint); + + _panelPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(348, 196), 300, new[] { SKColor.Parse("#0049CE"), SKColor.Parse("#002A8C") }, SKShaderTileMode.Clamp); + c.DrawPath(rect1, _panelPaint); + + using var rect2 = new SKPath { FillType = SKPathFillType.EvenOdd }; + + rect2.MoveTo(10, 0); + rect2.LineTo(30, 0); + rect2.LineTo(63, Height); + rect2.LineTo(56, Height); + rect2.LineTo(10, 0); + rect2.Close(); + c.DrawPath(rect2, _panelPaint); + + _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(10, 0), new SKPoint(62, Height), + new[] { SKColor.Parse("#0045C7") }, SKShaderTileMode.Clamp); + c.DrawPath(rect2, _panelPaint); + } + + private new void DrawPreview(SKCanvas c) + { + var previewToUse = Preview ?? DefaultPreview; + + if (Preview == null) + { + previewToUse = DefaultPreview; + ImagePaint.BlendMode = SKBlendMode.DstOut; + ImagePaint.Color = SKColor.Parse("#00175F"); + } + + var x = -125; + + switch (previewToUse.Width) + { + case 512 when previewToUse.Height == 1024: + previewToUse = previewToUse.ResizeWithRatio(500, 1000); + x = 100; + break; + case 512 when previewToUse.Height == 512: + case 128 when previewToUse.Height == 128: + previewToUse = previewToUse.Resize(512); + x = 125; + break; + default: + previewToUse = previewToUse.Resize(1000, 1000); + break; + } + + c.DrawBitmap(previewToUse, x, 30, ImagePaint); + } + + private void DrawHaze(SKCanvas c) + { + using var rect1 = new SKPath { FillType = SKPathFillType.EvenOdd }; + rect1.MoveTo(29, 0); + rect1.LineTo(62, Height); + rect1.LineTo(Width, Height); + rect1.LineTo(Width, 0); + rect1.LineTo(29, 0); + rect1.Close(); + + _panelPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(343, 0), new SKPoint(343, Height), + new[] { SKColors.Transparent, SKColor.Parse("#001E70FF"), SKColor.Parse("#001E70").WithAlpha(200), SKColor.Parse("#001E70").WithAlpha(245), SKColor.Parse("#001E70") }, new[] { 0, (float) .1, (float) .65, (float) .85, 1 }, SKShaderTileMode.Clamp); + c.DrawPath(rect1, _panelPaint); + } + + private void DrawName(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(DisplayName)) return; + + DisplayNamePaint.TextSize = UserSettings.Default.AssetLanguage switch + { + ELanguage.Korean => 56, + _ => 42 + }; + + DisplayNamePaint.TextScaleX = (float) 1.1; + DisplayNamePaint.Color = SKColors.White; + DisplayNamePaint.TextSkewX = (float) -.25; + DisplayNamePaint.TextAlign = SKTextAlign.Left; + + var typeface = Utils.Typefaces.TandemDisplayName; + if (typeface == Utils.Typefaces.Default) + { + DisplayNamePaint.TextSize = 30; + } + + DisplayNamePaint.Typeface = typeface; + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + c.DrawShapedText(shaper, DisplayName.ToUpper(), 97, 900, DisplayNamePaint); + } + + private void DrawGeneralDescription(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(_generalDescription)) return; + + DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch + { + ELanguage.Korean => 20, + _ => 17 + }; + + DescriptionPaint.Color = SKColor.Parse("#00FFFB"); + DescriptionPaint.TextAlign = SKTextAlign.Left; + + var typeface = Utils.Typefaces.TandemGenDescription; + if (typeface == Utils.Typefaces.Default) + { + DescriptionPaint.TextSize = 21; + } + + DescriptionPaint.Typeface = typeface; + var shaper = new CustomSKShaper(DescriptionPaint.Typeface); + c.DrawShapedText(shaper, _generalDescription.ToUpper(), 97, 930, DescriptionPaint); + } + + private void DrawAdditionalDescription(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(_additionalDescription)) return; + + DescriptionPaint.TextSize = UserSettings.Default.AssetLanguage switch + { + ELanguage.Korean => 22, + _ => 18 + }; + + DescriptionPaint.Color = SKColor.Parse("#89D8FF"); + DescriptionPaint.TextAlign = SKTextAlign.Left; + + var typeface = Utils.Typefaces.TandemAddDescription; + if (typeface == Utils.Typefaces.Default) + { + DescriptionPaint.TextSize = 20; + } + + DescriptionPaint.Typeface = typeface; + Utils.DrawMultilineText(c, _additionalDescription, Width, 0, SKTextAlign.Left, + new SKRect(97, 960, Width - 10, Height), DescriptionPaint, out _); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseUserControl.cs b/FModel/Creator/Bases/FN/BaseUserControl.cs index 502ad8ca..39bbae5d 100644 --- a/FModel/Creator/Bases/FN/BaseUserControl.cs +++ b/FModel/Creator/Bases/FN/BaseUserControl.cs @@ -9,185 +9,184 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class BaseUserControl : UCreator { - public class BaseUserControl : UCreator + private List _optionValues = new(); + + private readonly SKPaint _displayNamePaint = new() { - private List _optionValues = new(); + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 45, + Color = SKColors.White, TextAlign = SKTextAlign.Left + }; + private readonly SKPaint _descriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 25, + Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left + }; - private readonly SKPaint _displayNamePaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.DisplayName, TextSize = 45, - Color = SKColors.White, TextAlign = SKTextAlign.Left - }; - private readonly SKPaint _descriptionPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.DisplayName, TextSize = 25, - Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left - }; - - public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style) - { - Width = 512; - Height = 128; - Margin = 32; - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName")) - DisplayName = optionDisplayName.Text.ToUpperInvariant(); - if (Object.TryGetValue(out FText optionDescription, "OptionDescription")) - { - Description = optionDescription.Text; - - if (string.IsNullOrWhiteSpace(Description)) return; - Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count; - Height += (int) _descriptionPaint.TextSize; - } - - if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues")) - { - _optionValues = new List(); - foreach (var option in optionValues) - { - if (option.TryGetValue(out FText displayName, "DisplayName")) - { - var opt = new Options {Option = displayName.Text.ToUpperInvariant()}; - if (option.TryGetValue(out FLinearColor color, "Value")) - opt.Color = SKColor.Parse(color.Hex).WithAlpha(150); - - _optionValues.Add(opt); - } - else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName")) - { - _optionValues.Add(new Options {Option = primaryAssetName.Text}); - } - } - } - - if (Object.TryGetValue(out FText optionOnText, "OptionOnText")) - _optionValues.Add(new Options {Option = optionOnText.Text}); - if (Object.TryGetValue(out FText optionOffText, "OptionOffText")) - _optionValues.Add(new Options {Option = optionOffText.Text}); - - if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") && - Object.TryGetValue(out int iMax, "Max")) - { - var increment = iMin; - if (Object.TryGetValue(out int incrementValue, "IncrementValue")) - increment = incrementValue; - - var format = "{0}"; - if (Object.TryGetValue(out FText unitName, "UnitName")) - format = unitName.Text; - - for (var i = iMin; i <= iMax; i += increment) - { - _optionValues.Add(new Options {Option = string.Format(format, i)}); - } - } - - if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") && - Object.TryGetValue(out float fMax, "Max")) - { - var increment = fMin; - if (Object.TryGetValue(out float incrementValue, "IncrementValue")) - increment = incrementValue; - - var format = "{0}"; - if (Object.TryGetValue(out FText unitName, "UnitName")) - format = unitName.Text; - - for (var i = fMin; i <= fMax; i += increment) - { - _optionValues.Add(new Options {Option = string.Format(format, i)}); - } - } - - Height += Margin; - Height += 35 * _optionValues.Count; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawInformation(c); - - return new []{ret}; - } - - private new void DrawBackground(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, Height), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient( - new SKPoint(Width / 2, Height), - new SKPoint(Width, Height / 4), - new[] {SKColor.Parse("01369C"), SKColor.Parse("1273C8")}, - SKShaderTileMode.Clamp) - }); - } - - private void DrawInformation(SKCanvas c) - { - // display name - while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) - { - _displayNamePaint.TextSize -= 2; - } - - var shaper = new CustomSKShaper(_displayNamePaint.Typeface); - shaper.Shape(DisplayName, _displayNamePaint); - c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint); -#if DEBUG - c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint {Color = SKColors.Blue, IsStroke = true}); -#endif - - // description - float y = Margin; - if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize; - if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F; - - Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left, - new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top); - - // options - foreach (var option in _optionValues) - { - option.Draw(c, Margin, Width, ref top); - } - } + public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 512; + Height = 128; + Margin = 32; } - public class Options + public override void ParseForInfo() { - private const int _SPACE = 5; - private const int _HEIGHT = 30; - - private readonly SKPaint _optionPaint = new() + if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName")) + DisplayName = optionDisplayName.Text.ToUpperInvariant(); + if (Object.TryGetValue(out FText optionDescription, "OptionDescription")) { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.DisplayName, TextSize = 20, - Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left - }; + Description = optionDescription.Text; - public string Option; - public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150); + if (string.IsNullOrWhiteSpace(Description)) return; + Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count; + Height += (int) _descriptionPaint.TextSize; + } - public void Draw(SKCanvas c, int margin, int width, ref float top) + if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues")) { - c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint {IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color}); - c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint); - top += _HEIGHT + _SPACE; + _optionValues = new List(); + foreach (var option in optionValues) + { + if (option.TryGetValue(out FText displayName, "DisplayName")) + { + var opt = new Options { Option = displayName.Text.ToUpperInvariant() }; + if (option.TryGetValue(out FLinearColor color, "Value")) + opt.Color = SKColor.Parse(color.Hex).WithAlpha(150); + + _optionValues.Add(opt); + } + else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName")) + { + _optionValues.Add(new Options { Option = primaryAssetName.Text }); + } + } + } + + if (Object.TryGetValue(out FText optionOnText, "OptionOnText")) + _optionValues.Add(new Options { Option = optionOnText.Text }); + if (Object.TryGetValue(out FText optionOffText, "OptionOffText")) + _optionValues.Add(new Options { Option = optionOffText.Text }); + + if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") && + Object.TryGetValue(out int iMax, "Max")) + { + var increment = iMin; + if (Object.TryGetValue(out int incrementValue, "IncrementValue")) + increment = incrementValue; + + var format = "{0}"; + if (Object.TryGetValue(out FText unitName, "UnitName")) + format = unitName.Text; + + for (var i = iMin; i <= iMax; i += increment) + { + _optionValues.Add(new Options { Option = string.Format(format, i) }); + } + } + + if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") && + Object.TryGetValue(out float fMax, "Max")) + { + var increment = fMin; + if (Object.TryGetValue(out float incrementValue, "IncrementValue")) + increment = incrementValue; + + var format = "{0}"; + if (Object.TryGetValue(out FText unitName, "UnitName")) + format = unitName.Text; + + for (var i = fMin; i <= fMax; i += increment) + { + _optionValues.Add(new Options { Option = string.Format(format, i) }); + } + } + + Height += Margin; + Height += 35 * _optionValues.Count; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawInformation(c); + + return new[] { ret }; + } + + private new void DrawBackground(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(Width / 2, Height), + new SKPoint(Width, Height / 4), + new[] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") }, + SKShaderTileMode.Clamp) + }); + } + + private void DrawInformation(SKCanvas c) + { + // display name + while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + _displayNamePaint.TextSize -= 2; + } + + var shaper = new CustomSKShaper(_displayNamePaint.Typeface); + shaper.Shape(DisplayName, _displayNamePaint); + c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint); +#if DEBUG + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint { Color = SKColors.Blue, IsStroke = true }); +#endif + + // description + float y = Margin; + if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize; + if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F; + + Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left, + new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top); + + // options + foreach (var option in _optionValues) + { + option.Draw(c, Margin, Width, ref top); } } } + +public class Options +{ + private const int _SPACE = 5; + private const int _HEIGHT = 30; + + private readonly SKPaint _optionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 20, + Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left + }; + + public string Option; + public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150); + + public void Draw(SKCanvas c, int margin, int width, ref float top) + { + c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color }); + c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint); + top += _HEIGHT + _SPACE; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/Reward.cs b/FModel/Creator/Bases/FN/Reward.cs index 0b2b287d..908dd7d6 100644 --- a/FModel/Creator/Bases/FN/Reward.cs +++ b/FModel/Creator/Bases/FN/Reward.cs @@ -5,148 +5,147 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases.FN +namespace FModel.Creator.Bases.FN; + +public class Reward { - public class Reward + private string _rewardQuantity; + private BaseIcon _theReward; + + public bool HasReward() => _theReward != null; + + public Reward() { - private string _rewardQuantity; - private BaseIcon _theReward; + _rewardQuantity = "x0"; + } - public bool HasReward() => _theReward != null; + public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text) + { + } - public Reward() + public Reward(int quantity, string assetName) : this() + { + _rewardQuantity = $"x{quantity:###,###,###}".Trim(); + + if (assetName.Contains(':')) { - _rewardQuantity = "x0"; - } + var parts = assetName.Split(':'); - public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text) - { - } - - public Reward(int quantity, string assetName) : this() - { - _rewardQuantity = $"x{quantity:###,###,###}".Trim(); - - if (assetName.Contains(':')) + if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase)) { - var parts = assetName.Split(':'); + if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p)) + return; - if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase)) + _theReward = new BaseIcon(p, EIconStyle.Default); + _theReward.ParseForReward(false); + _theReward.Border[0] = SKColors.White; + _rewardQuantity = _theReward.DisplayName; + } + else GetReward(parts[1]); + } + else GetReward(assetName); + } + + public Reward(UObject uObject) + { + _theReward = new BaseIcon(uObject, EIconStyle.Default); + _theReward.ParseForReward(false); + _theReward.Border[0] = SKColors.White; + _rewardQuantity = _theReward.DisplayName; + } + + private readonly SKPaint _rewardPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High + }; + + public void DrawQuest(SKCanvas c, SKRect rect) + { + _rewardPaint.TextSize = 50; + if (HasReward()) + { + c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint); + + _rewardPaint.Color = _theReward.Border[0]; + _rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle; + _rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150)); + while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width) + { + _rewardPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_rewardPaint.Typeface); + shaper.Shape(_rewardQuantity, _rewardPaint); + c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint); + } + else + { + _rewardPaint.Color = SKColors.White; + _rewardPaint.Typeface = Utils.Typefaces.BundleNumber; + c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint); + } + } + + public void DrawSeasonWin(SKCanvas c, int size) + { + if (!HasReward()) return; + c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint); + } + + public void DrawSeason(SKCanvas c, int x, int y, int areaSize) + { + if (!HasReward()) return; + + // area + icon + _rewardPaint.Color = SKColor.Parse("#0F5CAF"); + c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint); + c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint); + + // rarity color + _rewardPaint.Color = _theReward.Background[0]; + var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd}; + pathBottom.MoveTo(x, y + areaSize); + pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f); + pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f); + pathBottom.LineTo(x + areaSize, y + areaSize); + pathBottom.Close(); + c.DrawPath(pathBottom, _rewardPaint); + } + + private void GetReward(string trigger) + { + switch (trigger.ToLower()) + { + case "athenabattlestar": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("FFDB67"); + _theReward.Background[0] = SKColor.Parse("8F4A20"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L"); + break; + case "athenaseasonalxp": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("E6FDB1"); + _theReward.Background[0] = SKColor.Parse("51830F"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L"); + break; + case "mtxgiveaway": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("DCE6FF"); + _theReward.Background[0] = SKColor.Parse("64A0AF"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX"); + break; + default: + { + var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname + if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d)) { - if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p)) - return; - - _theReward = new BaseIcon(p, EIconStyle.Default); + _theReward = new BaseIcon(d, EIconStyle.Default); _theReward.ParseForReward(false); _theReward.Border[0] = SKColors.White; _rewardQuantity = _theReward.DisplayName; } - else GetReward(parts[1]); - } - else GetReward(assetName); - } - public Reward(UObject uObject) - { - _theReward = new BaseIcon(uObject, EIconStyle.Default); - _theReward.ParseForReward(false); - _theReward.Border[0] = SKColors.White; - _rewardQuantity = _theReward.DisplayName; - } - - private readonly SKPaint _rewardPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High - }; - - public void DrawQuest(SKCanvas c, SKRect rect) - { - _rewardPaint.TextSize = 50; - if (HasReward()) - { - c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint); - - _rewardPaint.Color = _theReward.Border[0]; - _rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle; - _rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150)); - while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width) - { - _rewardPaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(_rewardPaint.Typeface); - shaper.Shape(_rewardQuantity, _rewardPaint); - c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint); - } - else - { - _rewardPaint.Color = SKColors.White; - _rewardPaint.Typeface = Utils.Typefaces.BundleNumber; - c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint); - } - } - - public void DrawSeasonWin(SKCanvas c, int size) - { - if (!HasReward()) return; - c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint); - } - - public void DrawSeason(SKCanvas c, int x, int y, int areaSize) - { - if (!HasReward()) return; - - // area + icon - _rewardPaint.Color = SKColor.Parse("#0F5CAF"); - c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint); - c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint); - - // rarity color - _rewardPaint.Color = _theReward.Background[0]; - var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd}; - pathBottom.MoveTo(x, y + areaSize); - pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f); - pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f); - pathBottom.LineTo(x + areaSize, y + areaSize); - pathBottom.Close(); - c.DrawPath(pathBottom, _rewardPaint); - } - - private void GetReward(string trigger) - { - switch (trigger.ToLower()) - { - case "athenabattlestar": - _theReward = new BaseIcon(null, EIconStyle.Default); - _theReward.Border[0] = SKColor.Parse("FFDB67"); - _theReward.Background[0] = SKColor.Parse("8F4A20"); - _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/Athena/UI/Frontend/Art/T_UI_BP_BattleStar_L.T_UI_BP_BattleStar_L"); - break; - case "athenaseasonalxp": - _theReward = new BaseIcon(null, EIconStyle.Default); - _theReward.Border[0] = SKColor.Parse("E6FDB1"); - _theReward.Background[0] = SKColor.Parse("51830F"); - _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPUncommon-L.T-FNBR-XPUncommon-L"); - break; - case "mtxgiveaway": - _theReward = new BaseIcon(null, EIconStyle.Default); - _theReward.Border[0] = SKColor.Parse("DCE6FF"); - _theReward.Background[0] = SKColor.Parse("64A0AF"); - _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX"); - break; - default: - { - var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname - if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d)) - { - _theReward = new BaseIcon(d, EIconStyle.Default); - _theReward.ParseForReward(false); - _theReward.Border[0] = SKColors.White; - _rewardQuantity = _theReward.DisplayName; - } - - break; - } + break; } } } diff --git a/FModel/Creator/Bases/SB/BaseDivision.cs b/FModel/Creator/Bases/SB/BaseDivision.cs index 183c4163..1fcea63e 100644 --- a/FModel/Creator/Bases/SB/BaseDivision.cs +++ b/FModel/Creator/Bases/SB/BaseDivision.cs @@ -4,45 +4,44 @@ using CUE4Parse.UE4.Objects.Core.Math; using CUE4Parse.UE4.Objects.UObject; using SkiaSharp; -namespace FModel.Creator.Bases.SB +namespace FModel.Creator.Bases.SB; + +public class BaseDivision : UCreator { - public class BaseDivision : UCreator + public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style) { - public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style) - { - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier")) - { - Preview = Utils.GetBitmap(icon); - } - - if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") && - Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") && - Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") && - Object.TryGetValue(out FLinearColor cardColor, "UICardColor")) - { - Background = new[] {SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex)}; - Border = new[] {SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex)}; - } - - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - - return new []{ret}; - } } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier")) + { + Preview = Utils.GetBitmap(icon); + } + + if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") && + Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") && + Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") && + Object.TryGetValue(out FLinearColor cardColor, "UICardColor")) + { + Background = new[] { SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex) }; + Border = new[] { SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex) }; + } + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + + return new[] { ret }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/SB/BaseGameModeInfo.cs b/FModel/Creator/Bases/SB/BaseGameModeInfo.cs index 39a7cea0..47709e9a 100644 --- a/FModel/Creator/Bases/SB/BaseGameModeInfo.cs +++ b/FModel/Creator/Bases/SB/BaseGameModeInfo.cs @@ -4,47 +4,46 @@ using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.i18N; using SkiaSharp; -namespace FModel.Creator.Bases.SB +namespace FModel.Creator.Bases.SB; + +public class BaseGameModeInfo : UCreator { - public class BaseGameModeInfo : UCreator + private SKBitmap _icon; + + public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style) { - private SKBitmap _icon; - - public BaseGameModeInfo(UObject uObject, EIconStyle style) : base(uObject, style) - { - Width = 738; - Height = 1024; - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText description, "Description")) - Description = description.Text; - if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait")) - Preview = Utils.GetBitmap(portrait); - if (Object.TryGetValue(out UTexture2D icon, "Icon")) - _icon = Utils.GetBitmap(icon).Resize(25); - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawIcon(c); - - return new []{ret}; - } - - private void DrawIcon(SKCanvas c) - { - if (_icon == null) return; - c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint); - } + Width = 738; + Height = 1024; } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description")) + Description = description.Text; + if (Object.TryGetValue(out UMaterialInstanceConstant portrait, "Portrait")) + Preview = Utils.GetBitmap(portrait); + if (Object.TryGetValue(out UTexture2D icon, "Icon")) + _icon = Utils.GetBitmap(icon).Resize(25); + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawIcon(c); + + return new[] { ret }; + } + + private void DrawIcon(SKCanvas c) + { + if (_icon == null) return; + c.DrawBitmap(_icon, new SKPoint(5, 5), ImagePaint); + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/SB/BaseLeague.cs b/FModel/Creator/Bases/SB/BaseLeague.cs index 6547fe3e..583e165c 100644 --- a/FModel/Creator/Bases/SB/BaseLeague.cs +++ b/FModel/Creator/Bases/SB/BaseLeague.cs @@ -3,53 +3,52 @@ using CUE4Parse.UE4.Objects.Core.i18N; using CUE4Parse.UE4.Objects.UObject; using SkiaSharp; -namespace FModel.Creator.Bases.SB +namespace FModel.Creator.Bases.SB; + +public class BaseLeague : UCreator { - public class BaseLeague : UCreator + private int _promotionXp, _xpLostPerMatch; + + public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style) { - private int _promotionXp, _xpLostPerMatch; - - public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style) - { - _promotionXp = 0; - _xpLostPerMatch = 0; - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out int promotionXp, "PromotionXP")) - _promotionXp = promotionXp; - if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch")) - _xpLostPerMatch = xpLostPerMatch; - - if (Object.TryGetValue(out FPackageIndex division, "Division") && - Utils.TryGetPackageIndexExport(division, out UObject div)) - { - var d = new BaseDivision(div, Style); - d.ParseForInfo(); - Preview = d.Preview; - Background = d.Background; - Border = d.Border; - } - - if (Object.TryGetValue(out FText displayName, "DisplayName")) - DisplayName = displayName.Text; - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - - DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}"); - DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}"); - - return new []{ret}; - } + _promotionXp = 0; + _xpLostPerMatch = 0; } -} + + public override void ParseForInfo() + { + if (Object.TryGetValue(out int promotionXp, "PromotionXP")) + _promotionXp = promotionXp; + if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch")) + _xpLostPerMatch = xpLostPerMatch; + + if (Object.TryGetValue(out FPackageIndex division, "Division") && + Utils.TryGetPackageIndexExport(division, out UObject div)) + { + var d = new BaseDivision(div, Style); + d.ParseForInfo(); + Preview = d.Preview; + Background = d.Background; + Border = d.Border; + } + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + + DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}"); + DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}"); + + return new[] { ret }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/SB/BaseSpellIcon.cs b/FModel/Creator/Bases/SB/BaseSpellIcon.cs index 61a3ca82..a9461c13 100644 --- a/FModel/Creator/Bases/SB/BaseSpellIcon.cs +++ b/FModel/Creator/Bases/SB/BaseSpellIcon.cs @@ -7,92 +7,91 @@ using CUE4Parse.UE4.Objects.UObject; using FModel.Creator.Bases.FN; using SkiaSharp; -namespace FModel.Creator.Bases.SB +namespace FModel.Creator.Bases.SB; + +public class BaseSpellIcon : BaseIcon { - public class BaseSpellIcon : BaseIcon + private SKBitmap _seriesBackground2; + + private readonly SKPaint _overlayPaint = new() { - private SKBitmap _seriesBackground2; + FilterQuality = SKFilterQuality.High, + IsAntialias = true, + Color = SKColors.Transparent.WithAlpha(75) + }; - private readonly SKPaint _overlayPaint = new() + public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style) + { + Background = new[] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") }; + Border = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") }; + Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512; + Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FName rarity, "Rarity")) + GetRarity(rarity); + + if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture")) + Preview = Utils.GetBitmap(preview); + else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture")) + Preview = Utils.GetBitmap(icon); + + if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description")) + Description = description.Text; + + SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox"); + _seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT"); + } + + public override SKBitmap[] Draw() + { + var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackgrounds(c); + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + + return new[] { ret }; + } + + private void DrawBackgrounds(SKCanvas c) + { + if (SeriesBackground != null) + c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint); + if (_seriesBackground2 != null) + c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint); + + var x = Margin * (int) 2.5; + const int radi = 15; + c.DrawCircle(x + radi, x + radi, radi, new SKPaint { - FilterQuality = SKFilterQuality.High, IsAntialias = true, - Color = SKColors.Transparent.WithAlpha(75) - }; + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient( + new SKPoint(radi, radi), radi * 2 / 5 * 4, + Background, SKShaderTileMode.Clamp) + }); + } - public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style) + private void GetRarity(FName n) + { + if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return; + + if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row)) { - Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")}; - Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")}; - Width = Object.ExportType.StartsWith("GCosmeticCard") ? 1536 : 512; - Height = Object.ExportType.StartsWith("GCosmeticCard") ? 450 : 512; - } - - public override void ParseForInfo() - { - if (Object.TryGetValue(out FName rarity, "Rarity")) - GetRarity(rarity); - - if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture", "OfferTexture", "PortraitTexture")) - Preview = Utils.GetBitmap(preview); - else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture", "OfferTexture", "PortraitTexture")) - Preview = Utils.GetBitmap(icon); - - if (Object.TryGetValue(out FText displayName, "DisplayName", "Title", "Name")) - DisplayName = displayName.Text; - if (Object.TryGetValue(out FText description, "Description")) - Description = description.Text; - - SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox"); - _seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT"); - } - - public override SKBitmap[] Draw() - { - var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - DrawBackgrounds(c); - DrawBackground(c); - DrawPreview(c); - DrawTextBackground(c); - DrawDisplayName(c); - DrawDescription(c); - - return new []{ret}; - } - - private void DrawBackgrounds(SKCanvas c) - { - if (SeriesBackground != null) - c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint); - if (_seriesBackground2 != null) - c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint); - - var x = Margin * (int) 2.5; - const int radi = 15; - c.DrawCircle(x + radi, x + radi, radi, new SKPaint + if (row.TryGetValue(out FLinearColor[] colors, "Colors")) { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(radi, radi), radi * 2 / 5 * 4, - Background, SKShaderTileMode.Clamp) - }); - } - - private void GetRarity(FName n) - { - if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return; - - if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row)) - { - if (row.TryGetValue(out FLinearColor[] colors, "Colors")) - { - Background = new[] {SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex)}; - Border = new[] {SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex)}; - } + Background = new[] { SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex) }; + Border = new[] { SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex) }; } } } -} +} \ No newline at end of file diff --git a/FModel/Creator/Bases/UCreator.cs b/FModel/Creator/Bases/UCreator.cs index cbd3b156..d0577b37 100644 --- a/FModel/Creator/Bases/UCreator.cs +++ b/FModel/Creator/Bases/UCreator.cs @@ -6,225 +6,224 @@ using FModel.Framework; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator.Bases +namespace FModel.Creator.Bases; + +public abstract class UCreator { - public abstract class UCreator + protected UObject Object { get; } + protected EIconStyle Style { get; } + public SKBitmap DefaultPreview { get; set; } + public SKBitmap Preview { get; set; } + public SKColor[] Background { get; protected set; } + public SKColor[] Border { get; protected set; } + public string DisplayName { get; protected set; } + public string Description { get; protected set; } + public int Margin { get; protected set; } + public int Width { get; protected set; } + public int Height { get; protected set; } + + public abstract void ParseForInfo(); + public abstract SKBitmap[] Draw(); + + protected UCreator(UObject uObject, EIconStyle style) { - protected UObject Object { get; } - protected EIconStyle Style { get; } - public SKBitmap DefaultPreview { get; set; } - public SKBitmap Preview { get; set; } - public SKColor[] Background { get; protected set; } - public SKColor[] Border { get; protected set; } - public string DisplayName { get; protected set; } - public string Description { get; protected set; } - public int Margin { get; protected set; } - public int Width { get; protected set; } - public int Height { get; protected set; } + DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); + Background = new[] { SKColor.Parse("5BFD00"), SKColor.Parse("003700") }; + Border = new[] { SKColor.Parse("1E8500"), SKColor.Parse("5BFD00") }; + DisplayName = string.Empty; + Description = string.Empty; + Width = 512; + Height = 512; + Margin = 2; + Object = uObject; + Style = style; + } - public abstract void ParseForInfo(); - public abstract SKBitmap[] Draw(); + private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15; + protected readonly SKPaint DisplayNamePaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE, + Color = SKColors.White, TextAlign = SKTextAlign.Center + }; + protected readonly SKPaint DescriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Description, TextSize = 13, + Color = SKColors.White + }; + protected readonly SKPaint ImagePaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High + }; + private readonly SKPaint _textBackgroundPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75) + }; + private readonly SKPaint _shortDescriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColors.White + }; - protected UCreator(UObject uObject, EIconStyle style) - { - DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); - Background = new[] {SKColor.Parse("5BFD00"), SKColor.Parse("003700")}; - Border = new[] {SKColor.Parse("1E8500"), SKColor.Parse("5BFD00")}; - DisplayName = string.Empty; - Description = string.Empty; - Width = 512; - Height = 512; - Margin = 2; - Object = uObject; - Style = style; - } + public void DrawBackground(SKCanvas c) + { + // reverse doesn't affect basic rarities + if (Background[0] == Background[1]) Background[0] = Border[0]; + Background[0].ToHsl(out _, out _, out var l1); + Background[1].ToHsl(out _, out _, out var l2); + var reverse = l1 > l2; - private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15; - protected readonly SKPaint DisplayNamePaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE, - Color = SKColors.White, TextAlign = SKTextAlign.Center - }; - protected readonly SKPaint DescriptionPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Typeface = Utils.Typefaces.Description, TextSize = 13, - Color = SKColors.White - }; - protected readonly SKPaint ImagePaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High - }; - private readonly SKPaint _textBackgroundPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75) - }; - private readonly SKPaint _shortDescriptionPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Color = SKColors.White - }; - - public void DrawBackground(SKCanvas c) - { - // reverse doesn't affect basic rarities - if (Background[0] == Background[1]) Background[0] = Border[0]; - Background[0].ToHsl(out _, out _, out var l1); - Background[1].ToHsl(out _, out _, out var l2); - var reverse = l1 > l2; - - // border - c.DrawRect(new SKRect(0, 0, Width, Height), - new SKPaint - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient( - new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp) - }); - - if (this is BaseIcon {SeriesBackground: { }} baseIcon) - c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin, - baseIcon.Height - baseIcon.Margin), ImagePaint); - else + // border + c.DrawRect(new SKRect(0, 0, Width, Height), + new SKPaint { - switch (Style) - { - case EIconStyle.Flat: - { - c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), - new SKPaint - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), - new[] {Background[reverse ? 0 : 1].WithAlpha(150), Border[0]}, SKShaderTileMode.Clamp) - }); - if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp) + }); - var pathTop = new SKPath {FillType = SKPathFillType.EvenOdd}; - pathTop.MoveTo(Margin, Margin); - pathTop.LineTo(Margin + Width / 17 * 10, Margin); - pathTop.LineTo(Margin, Margin + Height / 17); - pathTop.Close(); - c.DrawPath(pathTop, new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = Background[1].WithAlpha(75) - }); - break; - } - default: - { - c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), - new SKPaint - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4, - new[] {Background[reverse ? 0 : 1], Background[reverse ? 1 : 0]}, - SKShaderTileMode.Clamp) - }); - break; - } - } - } - } - - protected void DrawPreview(SKCanvas c) - => c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint); - - protected void DrawTextBackground(SKCanvas c) + if (this is BaseIcon { SeriesBackground: { } } baseIcon) + c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin, + baseIcon.Height - baseIcon.Margin), ImagePaint); + else { - if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; switch (Style) { case EIconStyle.Flat: { - var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd}; - pathBottom.MoveTo(Margin, Height - Margin); - pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f); - pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f); - pathBottom.LineTo(Width - Margin, Height - Margin); - pathBottom.Close(); - c.DrawPath(pathBottom, _textBackgroundPaint); + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), + new SKPaint + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), + new[] { Background[reverse ? 0 : 1].WithAlpha(150), Border[0] }, SKShaderTileMode.Clamp) + }); + if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + + var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathTop.MoveTo(Margin, Margin); + pathTop.LineTo(Margin + Width / 17 * 10, Margin); + pathTop.LineTo(Margin, Margin + Height / 17); + pathTop.Close(); + c.DrawPath(pathTop, new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = Background[1].WithAlpha(75) + }); break; } default: - c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint); - break; - } - } - - protected void DrawDisplayName(SKCanvas c) - { - if (string.IsNullOrWhiteSpace(DisplayName)) return; - - while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) - { - DisplayNamePaint.TextSize -= 1; - } - - var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); - var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); - var x = (Width - shapedText.Points[^1].X) / 2; - var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; - - switch (Style) - { - case EIconStyle.Flat: { - DisplayNamePaint.TextAlign = SKTextAlign.Right; - x = Width - Margin * 2 - shapedText.Points[^1].X; + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), + new SKPaint + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4, + new[] { Background[reverse ? 0 : 1], Background[reverse ? 1 : 0] }, + SKShaderTileMode.Clamp) + }); break; } } - -#if DEBUG - c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true}); - c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true}); - c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true}); -#endif - - c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint); - } - - protected void DrawDescription(SKCanvas c) - { - if (string.IsNullOrWhiteSpace(Description)) return; - - var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4; - var side = SKTextAlign.Center; - switch (Style) - { - case EIconStyle.Flat: - side = SKTextAlign.Right; - break; - } - - Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side, - new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint); - } - - protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text) - { - if (string.IsNullOrEmpty(text)) return; - - _shortDescriptionPaint.TextAlign = side; - _shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13; - switch (side) - { - case SKTextAlign.Left: - _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; - var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface); - shaper.Shape(text, _shortDescriptionPaint); - - c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); - break; - case SKTextAlign.Right: - _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default; - c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); - break; - } } } -} + + protected void DrawPreview(SKCanvas c) + => c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint); + + protected void DrawTextBackground(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + switch (Style) + { + case EIconStyle.Flat: + { + var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; + pathBottom.MoveTo(Margin, Height - Margin); + pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f); + pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f); + pathBottom.LineTo(Width - Margin, Height - Margin); + pathBottom.Close(); + c.DrawPath(pathBottom, _textBackgroundPaint); + break; + } + default: + c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint); + break; + } + } + + protected void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(DisplayName)) return; + + while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + DisplayNamePaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); + var x = (Width - shapedText.Points[^1].X) / 2; + var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; + + switch (Style) + { + case EIconStyle.Flat: + { + DisplayNamePaint.TextAlign = SKTextAlign.Right; + x = Width - Margin * 2 - shapedText.Points[^1].X; + break; + } + } + +#if DEBUG + c.DrawLine(x, 0, x, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true }); + c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint { Color = SKColors.Blue, IsStroke = true }); + c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint { Color = SKColors.Blue, IsStroke = true }); +#endif + + c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint); + } + + protected void DrawDescription(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(Description)) return; + + var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4; + var side = SKTextAlign.Center; + switch (Style) + { + case EIconStyle.Flat: + side = SKTextAlign.Right; + break; + } + + Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side, + new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint); + } + + protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text) + { + if (string.IsNullOrEmpty(text)) return; + + _shortDescriptionPaint.TextAlign = side; + _shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13; + switch (side) + { + case SKTextAlign.Left: + _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; + var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface); + shaper.Shape(text, _shortDescriptionPaint); + + c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); + break; + case SKTextAlign.Right: + _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default; + c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); + break; + } + } +} \ No newline at end of file diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index c19bea00..22a842e7 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -6,247 +6,246 @@ using FModel.Creator.Bases.BB; using FModel.Creator.Bases.FN; using FModel.Creator.Bases.SB; -namespace FModel.Creator +namespace FModel.Creator; + +public class CreatorPackage : IDisposable { - public class CreatorPackage : IDisposable + private UObject _object; + private EIconStyle _style; + + public CreatorPackage(UObject uObject, EIconStyle style) { - private UObject _object; - private EIconStyle _style; + _object = uObject; + _style = style; + } - public CreatorPackage(UObject uObject, EIconStyle style) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UCreator ConstructCreator() + { + TryConstructCreator(out var creator); + return creator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryConstructCreator(out UCreator creator) + { + switch (_object.ExportType) { - _object = uObject; - _style = style; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UCreator ConstructCreator() - { - TryConstructCreator(out var creator); - return creator; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryConstructCreator(out UCreator creator) - { - switch (_object.ExportType) - { - // Fortnite - case "FortCreativeWeaponMeleeItemDefinition": - case "AthenaConsumableEmoteItemDefinition": - case "AthenaSkyDiveContrailItemDefinition": - case "AthenaLoadingScreenItemDefinition": - case "AthenaVictoryPoseItemDefinition": - case "AthenaPetCarrierItemDefinition": - case "AthenaMusicPackItemDefinition": - case "AthenaBattleBusItemDefinition": - case "AthenaCharacterItemDefinition": - case "AthenaMapMarkerItemDefinition": - case "AthenaBackpackItemDefinition": - case "AthenaPickaxeItemDefinition": - case "AthenaGadgetItemDefinition": - case "AthenaGliderItemDefinition": - case "AthenaSprayItemDefinition": - case "AthenaDanceItemDefinition": - case "AthenaEmojiItemDefinition": - case "AthenaItemWrapDefinition": - case "AthenaToyItemDefinition": - case "FortHeroType": - case "FortTokenType": - case "FortAbilityKit": - case "FortWorkerType": - case "RewardGraphToken": - case "FortBannerTokenType": - case "FortVariantTokenType": - case "FortDecoItemDefinition": - case "FortStatItemDefinition": - case "FortAmmoItemDefinition": - case "FortEmoteItemDefinition": - case "FortBadgeItemDefinition": - case "FortAwardItemDefinition": - case "FortGadgetItemDefinition": - case "AthenaCharmItemDefinition": - case "FortPlaysetItemDefinition": - case "FortGiftBoxItemDefinition": - case "FortOutpostItemDefinition": - case "FortVehicleItemDefinition": - case "FortCardPackItemDefinition": - case "FortDefenderItemDefinition": - case "FortCurrencyItemDefinition": - case "FortResourceItemDefinition": - case "FortBackpackItemDefinition": - case "FortEventQuestMapDataAsset": - case "FortWeaponModItemDefinition": - case "FortCodeTokenItemDefinition": - case "FortSchematicItemDefinition": - case "FortWorldMultiItemDefinition": - case "FortAlterationItemDefinition": - case "FortExpeditionItemDefinition": - case "FortIngredientItemDefinition": - case "FortAccountBuffItemDefinition": - case "FortWeaponMeleeItemDefinition": - case "FortPlayerPerksItemDefinition": - case "FortPlaysetPropItemDefinition": - case "FortHomebaseNodeItemDefinition": - case "FortNeverPersistItemDefinition": - case "RadioContentSourceItemDefinition": - case "FortPlaysetGrenadeItemDefinition": - case "FortPersonalVehicleItemDefinition": - case "FortGameplayModifierItemDefinition": - case "FortHardcoreModifierItemDefinition": - case "FortConsumableAccountItemDefinition": - case "FortConversionControlItemDefinition": - case "FortAccountBuffCreditItemDefinition": - case "FortEventCurrencyItemDefinitionRedir": - case "FortPersistentResourceItemDefinition": - case "FortHomebaseBannerIconItemDefinition": - case "FortCampaignHeroLoadoutItemDefinition": - case "FortConditionalResourceItemDefinition": - case "FortChallengeBundleScheduleDefinition": - case "FortWeaponMeleeDualWieldItemDefinition": - case "FortDailyRewardScheduleTokenDefinition": - case "FortCreativeRealEstatePlotItemDefinition": - case "AthenaDanceItemDefinition_AdHocSquadsJoin_C": - creator = _style switch - { - EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"), - _ => new BaseIcon(_object, _style) - }; - return true; - case "FortTandemCharacterData": - creator = new BaseTandem(_object, _style); - return true; - case "FortTrapItemDefinition": - case "FortSpyTechItemDefinition": - case "FortAccoladeItemDefinition": - case "FortContextTrapItemDefinition": - case "FortWeaponRangedItemDefinition": - case "Daybreak_LevelExitVehicle_PartItemDefinition_C": - creator = new BaseIconStats(_object, _style); - return true; - case "FortItemSeriesDefinition": - creator = new BaseSeries(_object, _style); - return true; - case "MaterialInstanceConstant" - when _object.Owner != null && - (_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) || - _object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) || - _object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)): - creator = new BaseMaterialInstance(_object, _style); - return true; - case "AthenaItemShopOfferDisplayData": - creator = new BaseOfferDisplayData(_object, _style); - return true; - case "FortMtxOfferData": - creator = new BaseMtxOffer(_object, _style); - return true; - case "FortPlaylistAthena": - creator = new BasePlaylist(_object, _style); - return true; - case "FortFeatItemDefinition": - case "FortQuestItemDefinition": - case "FortQuestItemDefinition_Athena": - case "AthenaDailyQuestDefinition": - case "FortUrgentQuestItemDefinition": - creator = new BaseQuest(_object, _style); - return true; - case "FortCompendiumItemDefinition": - case "FortChallengeBundleItemDefinition": - creator = new BaseBundle(_object, _style); - return true; - case "AthenaSeasonItemDefinition": - creator = new BaseSeason(_object, _style); - return true; - case "FortItemAccessTokenType": - creator = new BaseItemAccessToken(_object, _style); - return true; - case "PlaylistUserOptionEnum": - case "PlaylistUserOptionBool": - case "PlaylistUserOptionString": - case "PlaylistUserOptionIntEnum": - case "PlaylistUserOptionIntRange": - case "PlaylistUserOptionColorEnum": - case "PlaylistUserOptionFloatEnum": - case "PlaylistUserOptionFloatRange": - case "PlaylistUserTintedIconIntEnum": - case "PlaylistUserOptionPrimaryAsset": - case "PlaylistUserOptionCollisionProfileEnum": - creator = new BaseUserControl(_object, _style); - return true; - // Battle Breakers - case "WExpGenericAccountItemDefinition": - case "WExpGearAccountItemDefinition": - case "WExpHQWorkerLodgesDefinition": - case "WExpPersonalEventDefinition": - case "WExpUpgradePotionDefinition": - case "WExpAccountRewardDefinition": - case "WExpHQBlacksmithDefinition": - case "WExpHQSecretShopDefinition": - case "WExpHQMonsterPitDefinition": - case "WExpHQHeroTowerDefinition": - case "WExpVoucherItemDefinition": - case "WExpTreasureMapDefinition": - case "WExpHammerChestDefinition": - case "WExpHQWorkshopDefinition": - case "WExpUnlockableDefinition": - case "WExpHQSmelterDefinition": - case "WExpContainerDefinition": - case "WExpCharacterDefinition": - case "WExpHQMarketDefinition": - case "WExpGiftboxDefinition": - case "WExpStandInDefinition": - case "WExpRegionDefinition": - case "WExpHQMineDefinition": - case "WExpXpBookDefinition": - case "WExpTokenDefinition": - case "WExpItemDefinition": - case "WExpZoneDefinition": - creator = new BaseBreakersIcon(_object, EIconStyle.Default); - return true; - // Spellbreak - case "GTargetedTeleportActiveSkill": - case "GChronomasterV2ActiveSkill": - case "GShadowstepActiveSkill": - case "GGatewayActiveSkill": - case "GStealthActiveSkill": - case "GFeatherActiveSkill": - case "GCosmeticDropTrail": - case "GFlightActiveSkill": - case "GCosmeticRunTrail": - case "GCosmeticArtifact": - case "GCosmeticTriumph": - case "GWolfsbloodSkill": - case "GDashActiveSkill": - case "GCharacterPerk": - case "GCosmeticTitle": - case "GCosmeticBadge": - case "GRMTStoreOffer": - case "GCosmeticEmote": - case "GCosmeticCard": - case "GCosmeticSkin": - case "GStoreOffer": - case "GAccolade": - case "GRuneItem": - case "GQuest": - creator = new BaseSpellIcon(_object, EIconStyle.Default); - return true; - case "GLeagueTier": - creator = new BaseLeague(_object, EIconStyle.Default); - return true; - case "GLeagueDivision": - creator = new BaseDivision(_object, EIconStyle.Default); - return true; - default: - creator = null; - return false; - } - } - - public override string ToString() => $"{_object.ExportType} | {_style}"; - - public void Dispose() - { - _object = null; + // Fortnite + case "FortCreativeWeaponMeleeItemDefinition": + case "AthenaConsumableEmoteItemDefinition": + case "AthenaSkyDiveContrailItemDefinition": + case "AthenaLoadingScreenItemDefinition": + case "AthenaVictoryPoseItemDefinition": + case "AthenaPetCarrierItemDefinition": + case "AthenaMusicPackItemDefinition": + case "AthenaBattleBusItemDefinition": + case "AthenaCharacterItemDefinition": + case "AthenaMapMarkerItemDefinition": + case "AthenaBackpackItemDefinition": + case "AthenaPickaxeItemDefinition": + case "AthenaGadgetItemDefinition": + case "AthenaGliderItemDefinition": + case "AthenaSprayItemDefinition": + case "AthenaDanceItemDefinition": + case "AthenaEmojiItemDefinition": + case "AthenaItemWrapDefinition": + case "AthenaToyItemDefinition": + case "FortHeroType": + case "FortTokenType": + case "FortAbilityKit": + case "FortWorkerType": + case "RewardGraphToken": + case "FortBannerTokenType": + case "FortVariantTokenType": + case "FortDecoItemDefinition": + case "FortStatItemDefinition": + case "FortAmmoItemDefinition": + case "FortEmoteItemDefinition": + case "FortBadgeItemDefinition": + case "FortAwardItemDefinition": + case "FortGadgetItemDefinition": + case "AthenaCharmItemDefinition": + case "FortPlaysetItemDefinition": + case "FortGiftBoxItemDefinition": + case "FortOutpostItemDefinition": + case "FortVehicleItemDefinition": + case "FortCardPackItemDefinition": + case "FortDefenderItemDefinition": + case "FortCurrencyItemDefinition": + case "FortResourceItemDefinition": + case "FortBackpackItemDefinition": + case "FortEventQuestMapDataAsset": + case "FortWeaponModItemDefinition": + case "FortCodeTokenItemDefinition": + case "FortSchematicItemDefinition": + case "FortWorldMultiItemDefinition": + case "FortAlterationItemDefinition": + case "FortExpeditionItemDefinition": + case "FortIngredientItemDefinition": + case "FortAccountBuffItemDefinition": + case "FortWeaponMeleeItemDefinition": + case "FortPlayerPerksItemDefinition": + case "FortPlaysetPropItemDefinition": + case "FortHomebaseNodeItemDefinition": + case "FortNeverPersistItemDefinition": + case "RadioContentSourceItemDefinition": + case "FortPlaysetGrenadeItemDefinition": + case "FortPersonalVehicleItemDefinition": + case "FortGameplayModifierItemDefinition": + case "FortHardcoreModifierItemDefinition": + case "FortConsumableAccountItemDefinition": + case "FortConversionControlItemDefinition": + case "FortAccountBuffCreditItemDefinition": + case "FortEventCurrencyItemDefinitionRedir": + case "FortPersistentResourceItemDefinition": + case "FortHomebaseBannerIconItemDefinition": + case "FortCampaignHeroLoadoutItemDefinition": + case "FortConditionalResourceItemDefinition": + case "FortChallengeBundleScheduleDefinition": + case "FortWeaponMeleeDualWieldItemDefinition": + case "FortDailyRewardScheduleTokenDefinition": + case "FortCreativeRealEstatePlotItemDefinition": + case "AthenaDanceItemDefinition_AdHocSquadsJoin_C": + creator = _style switch + { + EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"), + _ => new BaseIcon(_object, _style) + }; + return true; + case "FortTandemCharacterData": + creator = new BaseTandem(_object, _style); + return true; + case "FortTrapItemDefinition": + case "FortSpyTechItemDefinition": + case "FortAccoladeItemDefinition": + case "FortContextTrapItemDefinition": + case "FortWeaponRangedItemDefinition": + case "Daybreak_LevelExitVehicle_PartItemDefinition_C": + creator = new BaseIconStats(_object, _style); + return true; + case "FortItemSeriesDefinition": + creator = new BaseSeries(_object, _style); + return true; + case "MaterialInstanceConstant" + when _object.Owner != null && + (_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}", StringComparison.OrdinalIgnoreCase) || + _object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}", StringComparison.OrdinalIgnoreCase) || + _object.Owner.Name.EndsWith($"/MI_BPTile/{_object.Name}", StringComparison.OrdinalIgnoreCase)): + creator = new BaseMaterialInstance(_object, _style); + return true; + case "AthenaItemShopOfferDisplayData": + creator = new BaseOfferDisplayData(_object, _style); + return true; + case "FortMtxOfferData": + creator = new BaseMtxOffer(_object, _style); + return true; + case "FortPlaylistAthena": + creator = new BasePlaylist(_object, _style); + return true; + case "FortFeatItemDefinition": + case "FortQuestItemDefinition": + case "FortQuestItemDefinition_Athena": + case "AthenaDailyQuestDefinition": + case "FortUrgentQuestItemDefinition": + creator = new BaseQuest(_object, _style); + return true; + case "FortCompendiumItemDefinition": + case "FortChallengeBundleItemDefinition": + creator = new BaseBundle(_object, _style); + return true; + case "AthenaSeasonItemDefinition": + creator = new BaseSeason(_object, _style); + return true; + case "FortItemAccessTokenType": + creator = new BaseItemAccessToken(_object, _style); + return true; + case "PlaylistUserOptionEnum": + case "PlaylistUserOptionBool": + case "PlaylistUserOptionString": + case "PlaylistUserOptionIntEnum": + case "PlaylistUserOptionIntRange": + case "PlaylistUserOptionColorEnum": + case "PlaylistUserOptionFloatEnum": + case "PlaylistUserOptionFloatRange": + case "PlaylistUserTintedIconIntEnum": + case "PlaylistUserOptionPrimaryAsset": + case "PlaylistUserOptionCollisionProfileEnum": + creator = new BaseUserControl(_object, _style); + return true; + // Battle Breakers + case "WExpGenericAccountItemDefinition": + case "WExpGearAccountItemDefinition": + case "WExpHQWorkerLodgesDefinition": + case "WExpPersonalEventDefinition": + case "WExpUpgradePotionDefinition": + case "WExpAccountRewardDefinition": + case "WExpHQBlacksmithDefinition": + case "WExpHQSecretShopDefinition": + case "WExpHQMonsterPitDefinition": + case "WExpHQHeroTowerDefinition": + case "WExpVoucherItemDefinition": + case "WExpTreasureMapDefinition": + case "WExpHammerChestDefinition": + case "WExpHQWorkshopDefinition": + case "WExpUnlockableDefinition": + case "WExpHQSmelterDefinition": + case "WExpContainerDefinition": + case "WExpCharacterDefinition": + case "WExpHQMarketDefinition": + case "WExpGiftboxDefinition": + case "WExpStandInDefinition": + case "WExpRegionDefinition": + case "WExpHQMineDefinition": + case "WExpXpBookDefinition": + case "WExpTokenDefinition": + case "WExpItemDefinition": + case "WExpZoneDefinition": + creator = new BaseBreakersIcon(_object, EIconStyle.Default); + return true; + // Spellbreak + case "GTargetedTeleportActiveSkill": + case "GChronomasterV2ActiveSkill": + case "GShadowstepActiveSkill": + case "GGatewayActiveSkill": + case "GStealthActiveSkill": + case "GFeatherActiveSkill": + case "GCosmeticDropTrail": + case "GFlightActiveSkill": + case "GCosmeticRunTrail": + case "GCosmeticArtifact": + case "GCosmeticTriumph": + case "GWolfsbloodSkill": + case "GDashActiveSkill": + case "GCharacterPerk": + case "GCosmeticTitle": + case "GCosmeticBadge": + case "GRMTStoreOffer": + case "GCosmeticEmote": + case "GCosmeticCard": + case "GCosmeticSkin": + case "GStoreOffer": + case "GAccolade": + case "GRuneItem": + case "GQuest": + creator = new BaseSpellIcon(_object, EIconStyle.Default); + return true; + case "GLeagueTier": + creator = new BaseLeague(_object, EIconStyle.Default); + return true; + case "GLeagueDivision": + creator = new BaseDivision(_object, EIconStyle.Default); + return true; + default: + creator = null; + return false; } } -} + + public override string ToString() => $"{_object.ExportType} | {_style}"; + + public void Dispose() + { + _object = null; + } +} \ No newline at end of file diff --git a/FModel/Creator/Typefaces.cs b/FModel/Creator/Typefaces.cs index 45f5e552..2b84b600 100644 --- a/FModel/Creator/Typefaces.cs +++ b/FModel/Creator/Typefaces.cs @@ -6,274 +6,273 @@ using FModel.Settings; using FModel.ViewModels; using SkiaSharp; -namespace FModel.Creator +namespace FModel.Creator; + +public class Typefaces { - public class Typefaces + private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); + private const string _EXT = ".ufont"; + + // FortniteGame + private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/"; + private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite + private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian + private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black"; + private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig + private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium"; + private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset"; + private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite + private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji"; + private const string _NOTO_SANS_BOLD = "NotoSans-Bold"; + private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold"; + private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic"; + private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular"; + private const string _NOTO_SANS_ITALIC = "NotoSans-Italic"; + private const string _NOTO_SANS_REGULAR = "NotoSans-Regular"; + private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite + private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold"; + private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular"; + private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; + private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular"; + private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite + private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular"; + private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite + private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular"; + private const string _BURBANK_SMALL_BLACK = "burbanksmall-black"; + private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold"; + + // Spellbreak + private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/"; + private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold"; + private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic"; + private const string _NANUM_GOTHIC = "NanumGothic"; + private const string _QUADRAT_BOLD = "Quadrat_Bold"; + private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic"; + + // WorldExplorers + private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/"; + private const string _HEMIHEAD426 = "HemiHead426"; + private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular"; + private const string _LATO_BLACK = "Lato-Black"; + private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic"; + private const string _LATO_LIGHT = "Lato-Light"; + private const string _LATO_MEDIUM = "Lato-Medium"; + + private readonly CUE4ParseViewModel _viewModel; + + public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...) + public readonly SKTypeface DisplayName; + public readonly SKTypeface Description; + public readonly SKTypeface Bottom; // must be null for non-latin base languages + public readonly SKTypeface Bundle; + public readonly SKTypeface BundleNumber; + public readonly SKTypeface TandemDisplayName; + public readonly SKTypeface TandemGenDescription; + public readonly SKTypeface TandemAddDescription; + + public Typefaces(CUE4ParseViewModel viewModel) { - private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); - private const string _EXT = ".ufont"; + byte[] data; + _viewModel = viewModel; + var language = UserSettings.Default.AssetLanguage; + Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); - // FortniteGame - private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/"; - private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite - private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian - private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black"; - private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig - private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium"; - private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset"; - private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite - private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji"; - private const string _NOTO_SANS_BOLD = "NotoSans-Bold"; - private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold"; - private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic"; - private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular"; - private const string _NOTO_SANS_ITALIC = "NotoSans-Italic"; - private const string _NOTO_SANS_REGULAR = "NotoSans-Regular"; - private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite - private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold"; - private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular"; - private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; - private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular"; - private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite - private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular"; - private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite - private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular"; - private const string _BURBANK_SMALL_BLACK = "burbanksmall-black"; - private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold"; - - // Spellbreak - private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/"; - private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold"; - private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic"; - private const string _NANUM_GOTHIC = "NanumGothic"; - private const string _QUADRAT_BOLD = "Quadrat_Bold"; - private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic"; - - // WorldExplorers - private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/"; - private const string _HEMIHEAD426 = "HemiHead426"; - private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular"; - private const string _LATO_BLACK = "Lato-Black"; - private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic"; - private const string _LATO_LIGHT = "Lato-Light"; - private const string _LATO_MEDIUM = "Lato-Medium"; - - private readonly CUE4ParseViewModel _viewModel; - - public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...) - public readonly SKTypeface DisplayName; - public readonly SKTypeface Description; - public readonly SKTypeface Bottom; // must be null for non-latin base languages - public readonly SKTypeface Bundle; - public readonly SKTypeface BundleNumber; - public readonly SKTypeface TandemDisplayName; - public readonly SKTypeface TandemGenDescription; - public readonly SKTypeface TandemAddDescription; - - public Typefaces(CUE4ParseViewModel viewModel) + switch (viewModel.Game) { - byte[] data; - _viewModel = viewModel; - var language = UserSettings.Default.AssetLanguage; - Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); - - switch (viewModel.Game) + case FGame.FortniteGame: { - case FGame.FortniteGame: + var namePath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => string.Empty + }; + if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) { - var namePath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _ASIA_ERINM, - ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, - ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, - ELanguage.Chinese => _NOTO_SANS_SC_BLACK, - _ => string.Empty - }; - if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - DisplayName = SKTypeface.FromStream(m); - } - else DisplayName = Default; + var m = new MemoryStream(data) { Position = 0 }; + DisplayName = SKTypeface.FromStream(m); + } + else DisplayName = Default; - var descriptionPath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _NOTO_SANS_KR_REGULAR, - ELanguage.Japanese => _NOTO_SANS_JP_BOLD, - ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR, - ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, - _ => _NOTO_SANS_REGULAR - }; - if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - Description = SKTypeface.FromStream(m); - } - else Description = Default; + var descriptionPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _NOTO_SANS_KR_REGULAR, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, + ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR, + ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, + _ => _NOTO_SANS_REGULAR + }; + if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + Description = SKTypeface.FromStream(m); + } + else Description = Default; - var bottomPath = _FORTNITE_BASE_PATH + + var bottomPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => string.Empty, + ELanguage.Japanese => string.Empty, + ELanguage.Arabic => string.Empty, + ELanguage.TraditionalChinese => string.Empty, + ELanguage.Chinese => string.Empty, + _ => _BURBANK_SMALL_BOLD + }; + if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + Bottom = SKTypeface.FromStream(m); + } + // else keep it null + + if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + BundleNumber = SKTypeface.FromStream(m); + } + else BundleNumber = Default; + + var bundleNamePath = _FORTNITE_BASE_PATH + language switch { - ELanguage.Korean => string.Empty, - ELanguage.Japanese => string.Empty, - ELanguage.Arabic => string.Empty, - ELanguage.TraditionalChinese => string.Empty, - ELanguage.Chinese => string.Empty, - _ => _BURBANK_SMALL_BOLD + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => string.Empty }; - if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - Bottom = SKTypeface.FromStream(m); - } - // else keep it null - - if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - BundleNumber = SKTypeface.FromStream(m); - } - else BundleNumber = Default; - - var bundleNamePath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _ASIA_ERINM, - ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, - ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, - ELanguage.Chinese => _NOTO_SANS_SC_BLACK, - _ => string.Empty - }; - if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - Bundle = SKTypeface.FromStream(m); - } - else Bundle = BundleNumber; - - var tandemDisplayNamePath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _ASIA_ERINM, - ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, - ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, - ELanguage.Chinese => _NOTO_SANS_SC_BLACK, - _ => _BURBANK_BIG_REGULAR_BLACK - }; - if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - TandemDisplayName = SKTypeface.FromStream(m); - } - else TandemDisplayName = Default; - - var tandemGeneralDescPath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _ASIA_ERINM, - ELanguage.Japanese => _NIS_JYAU, - ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, - ELanguage.Chinese => _NOTO_SANS_SC_BLACK, - _ => _BURBANK_SMALL_BLACK - }; - if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - TandemGenDescription = SKTypeface.FromStream(m); - } - else TandemGenDescription = Default; - - var tandemAdditionalDescPath = _FORTNITE_BASE_PATH + - language switch - { - ELanguage.Korean => _ASIA_ERINM, - ELanguage.Japanese => _NIS_JYAU, - ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, - ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, - ELanguage.Chinese => _NOTO_SANS_SC_BLACK, - _ => _BURBANK_SMALL_BOLD - }; - if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - TandemAddDescription = SKTypeface.FromStream(m); - } - else TandemAddDescription = Default; - - break; - } - case FGame.WorldExplorers: + if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data)) { - var namePath = _BATTLE_BREAKERS_BASE_PATH + - language switch - { - ELanguage.Korean => _NOTO_SANS_KR_REGULAR, - ELanguage.Russian => _LATO_BLACK, - ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, - ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, - _ => _HEMIHEAD426 - }; - if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - DisplayName = SKTypeface.FromStream(m); - } - else DisplayName = Default; - - var descriptionPath = _BATTLE_BREAKERS_BASE_PATH + - language switch - { - ELanguage.Korean => _NOTO_SANS_KR_REGULAR, - ELanguage.Russian => _LATO_BLACK, - ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, - ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, - _ => _HEMIHEAD426 - }; - if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - Description = SKTypeface.FromStream(m); - } - else Description = Default; - - break; + var m = new MemoryStream(data) { Position = 0 }; + Bundle = SKTypeface.FromStream(m); } - case FGame.g3: + else Bundle = BundleNumber; + + var tandemDisplayNamePath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => _BURBANK_BIG_REGULAR_BLACK + }; + if (viewModel.Provider.TrySaveAsset(tandemDisplayNamePath + _EXT, out data)) { - if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - DisplayName = SKTypeface.FromStream(m); - } - else DisplayName = Default; - - if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data)) - { - var m = new MemoryStream(data) { Position = 0 }; - Description = SKTypeface.FromStream(m); - } - else Description = Default; - - break; + var m = new MemoryStream(data) { Position = 0 }; + TandemDisplayName = SKTypeface.FromStream(m); } + else TandemDisplayName = Default; + + var tandemGeneralDescPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => _BURBANK_SMALL_BLACK + }; + if (viewModel.Provider.TrySaveAsset(tandemGeneralDescPath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + TandemGenDescription = SKTypeface.FromStream(m); + } + else TandemGenDescription = Default; + + var tandemAdditionalDescPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => _BURBANK_SMALL_BOLD + }; + if (viewModel.Provider.TrySaveAsset(tandemAdditionalDescPath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + TandemAddDescription = SKTypeface.FromStream(m); + } + else TandemAddDescription = Default; + + break; + } + case FGame.WorldExplorers: + { + var namePath = _BATTLE_BREAKERS_BASE_PATH + + language switch + { + ELanguage.Korean => _NOTO_SANS_KR_REGULAR, + ELanguage.Russian => _LATO_BLACK, + ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, + ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, + _ => _HEMIHEAD426 + }; + if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + DisplayName = SKTypeface.FromStream(m); + } + else DisplayName = Default; + + var descriptionPath = _BATTLE_BREAKERS_BASE_PATH + + language switch + { + ELanguage.Korean => _NOTO_SANS_KR_REGULAR, + ELanguage.Russian => _LATO_BLACK, + ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, + ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, + _ => _HEMIHEAD426 + }; + if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + Description = SKTypeface.FromStream(m); + } + else Description = Default; + + break; + } + case FGame.g3: + { + if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + DisplayName = SKTypeface.FromStream(m); + } + else DisplayName = Default; + + if (viewModel.Provider.TrySaveAsset(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT, out data)) + { + var m = new MemoryStream(data) { Position = 0 }; + Description = SKTypeface.FromStream(m); + } + else Description = Default; + + break; } } - - public SKTypeface OnTheFly(string path) - { - if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default; - var m = new MemoryStream(data) { Position = 0 }; - return SKTypeface.FromStream(m); - } } -} + + public SKTypeface OnTheFly(string path) + { + if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default; + var m = new MemoryStream(data) { Position = 0 }; + return SKTypeface.FromStream(m); + } +} \ No newline at end of file diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs index 57ebfe14..96757c73 100644 --- a/FModel/Creator/Utils.cs +++ b/FModel/Creator/Utils.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; -using System.Windows; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.Texture; @@ -19,382 +18,377 @@ using FModel.ViewModels; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.Creator +namespace FModel.Creator; + +public static class Utils { - public static class Utils + private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private static readonly Regex _htmlRegex = new("<.*?>"); + public static Typefaces Typefaces; + + public static string RemoveHtmlTags(string s) { - private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - private static readonly Regex _htmlRegex = new("<.*?>"); - public static Typefaces Typefaces; - - public static string RemoveHtmlTags(string s) + var match = _htmlRegex.Match(s); + while (match.Success) { - var match = _htmlRegex.Match(s); - while (match.Success) - { - s = s.Replace(match.Value, string.Empty); - match = match.NextMatch(); - } - - return s; + s = s.Replace(match.Value, string.Empty); + match = match.NextMatch(); } - public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview) + return s; + } + + public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview) + { + if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon")) { - if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon")) - { - preview = GetBitmap(sidePanelIcon); - return preview != null; - } - - var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}"; - if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition - { - if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation - TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition - } - - preview = GetBitmap(material); + preview = GetBitmap(sidePanelIcon); return preview != null; } - public static SKBitmap GetBitmap(FPackageIndex packageIndex) + var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}"; + if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition { - while (true) + if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation + TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition + } + + preview = GetBitmap(material); + return preview != null; + } + + public static SKBitmap GetBitmap(FPackageIndex packageIndex) + { + while (true) + { + if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null; + switch (export) { - if (!TryGetPackageIndexExport(packageIndex, out UObject export)) return null; - switch (export) + case UTexture2D texture: + return GetBitmap(texture); + case UMaterialInstanceConstant material: + return GetBitmap(material); + default: { - case UTexture2D texture: - return GetBitmap(texture); - case UMaterialInstanceConstant material: - return GetBitmap(material); - default: + if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage); + if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview); + if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage")) { - if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage); - if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview); - if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage")) - { - packageIndex = smallPreview; - continue; - } - - return null; + packageIndex = smallPreview; + continue; } + + return null; } } } - public static SKBitmap GetBitmap(UMaterialInstanceConstant material) - { - if (material == null) return null; - foreach (var textureParameter in material.TextureParameterValues) - { - if (!textureParameter.ParameterValue.TryLoad(out var texture)) continue; - switch (textureParameter.ParameterInfo.Name.Text) - { - case "MainTex": - case "TextureA": - case "TextureB": - case "OfferImage": - case "KeyArtTexture": - case "NPC-Portrait": - { - return GetBitmap(texture); - } - } - } - - return null; - } - public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) {Position = 0}); - public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text); - public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null; - public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform); - public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data); - - public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height) - { - var ratioX = width / me.Width; - var ratioY = height / me.Height; - var ratio = ratioX < ratioY ? ratioX : ratioY; - return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio)); - } - public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size); - public static SKBitmap Resize(this SKBitmap me, int width, int height) - { - var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels); - using var pixmap = bmp.PeekPixels(); - me.ScalePixels(pixmap, SKFilterQuality.Medium); - return bmp; - } - - public static void ClearToTransparent(this SKBitmap me, SKColor colorToDelete) { - var colors = me.Pixels; - for (var n = 0; n < colors.Length; n++) { - if (colors[n] != colorToDelete) continue; - colors[n] = SKColors.Transparent; - } - me.Pixels = colors; - } - - public static bool TryGetPackageIndexExport(FPackageIndex packageIndex, out T export) where T : UObject - { - return packageIndex.TryLoad(out export); - } - - // fullpath must be either without any extension or with the export objectname - public static bool TryLoadObject(string fullPath, out T export) where T : UObject - { - return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export); - } - - public static IEnumerable LoadExports(string fullPath) - { - return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath); - } - - public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f) - { - var max = maxFont; - var min = 0f; - var last = -1f; - float value; - while (true) - { - value = min + ((max - min) / 2); - using (SKFont ft = new SKFont(typeface, value)) - using (SKPaint paint = new SKPaint(ft)) - { - if (paint.MeasureText(text) > sectorSize) - { - last = value; - max = value; - } - else - { - min = value; - if (Math.Abs(last - value) <= degreeOfCertainty) - return last; - - last = value; - } - } - } - } - - public static string GetLocalizedResource(string @namespace, string key, string defaultValue) - { - return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue); - } - - public static string GetFullPath(string partialPath) - { - var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled); - foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys) - { - if (regex.IsMatch(path)) - { - return path; - } - } - - return string.Empty; - } - - public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint) - { - var lineHeight = paint.TextSize * 1.2f; - var lines = SplitLines(text, paint, area.Width - margin); - -#if DEBUG - c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true}); -#endif - - if (lines == null) return; - if (lines.Count <= maxCount) maxCount = lines.Count; - var height = maxCount * lineHeight; - var y = area.MidY - height / 2; - - var shaper = new CustomSKShaper(paint.Typeface); - for (var i = 0; i < maxCount; i++) - { - var line = lines[i]; - if (line == null) continue; - - var lineText = line.Trim(); - var shapedText = shaper.Shape(lineText, paint); - - y += lineHeight; - var x = side switch - { - SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, - SKTextAlign.Right => size - margin - shapedText.Points[^1].X, - SKTextAlign.Left => margin, - _ => throw new NotImplementedException() - }; - - c.DrawShapedText(shaper, lineText, x, y, paint); - } - } - - public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos) - { - yPos = area.Top; - var lineHeight = paint.TextSize * 1.2f; - var lines = SplitLines(text, paint, area.Width); - if (lines == null) return; - - foreach (var line in lines) - { - var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line); - if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages) - { - paint.TextSize = fontSize; - lineHeight = paint.TextSize * 1.2f; - } - - if (line == null) continue; - var lineText = line.Trim(); - var shaper = new CustomSKShaper(paint.Typeface); - var shapedText = shaper.Shape(lineText, paint); - - var x = side switch - { - SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, - SKTextAlign.Right => size - margin - shapedText.Points[^1].X, - SKTextAlign.Left => area.Left, - _ => throw new NotImplementedException() - }; - - c.DrawShapedText(shaper, lineText, x, yPos, paint); - yPos += lineHeight; - } - -#if DEBUG - c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true}); -#endif - } - - #region Chinese, Korean and Japanese text split - // https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs - - static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)"; - static string keywords = @"(\ |[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+)"; - static string periods = @"([\.\,。、!\!?\?]+)$"; - static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])"; - static string bracketsEnd = @"([〉》」』」))\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])"; - - public static string[] SplitCKJText(string str) - { - - var line1 = Regex.Split(str, keywords).ToList(); - var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList(); - var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList(); - var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList(); - var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList(); - - var prevType = string.Empty; - var prevWord = string.Empty; - List result = new List(); - - words.ForEach(word => - { - var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi); - - if (Regex.IsMatch(word, bracketsBegin)) - { - prevType = "braketBegin"; - prevWord = word; - return; - } - - if (Regex.IsMatch(word, bracketsEnd)) - { - result[result.Count - 1] += word; - prevType = "braketEnd"; - prevWord = word; - return; - } - - if (prevType == "braketBegin") - { - word = prevWord + word; - prevWord = string.Empty; - prevType = string.Empty; - } - - // すでに文字が入っている上で助詞が続く場合は結合する - if (result.Count > 0 && token && prevType == string.Empty) - { - result[result.Count - 1] += word; - prevType = "keyword"; - prevWord = word; - return; - } - - // 単語のあとの文字がひらがななら結合する - if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+"))) - { - result[result.Count - 1] += word; - prevType = string.Empty; - prevWord = word; - return; - } - - result.Add(word); - prevType = "keyword"; - prevWord = word; - }); - return result.ToArray(); - } - #endregion - - public static List SplitLines(string text, SKPaint paint, float maxWidth) - { - if (string.IsNullOrEmpty(text)) return null; - - var spaceWidth = paint.MeasureText(" "); - var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - var ret = new List(lines.Length); - foreach (var line in lines) - { - if (string.IsNullOrWhiteSpace(line)) continue; - - float width = 0; - bool isCJK = false; - var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); - - if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese) - { - words = SplitCKJText(line); - isCJK = true; - } - - var lineResult = new StringBuilder(); - foreach (var word in words) - { - var wordWidth = paint.MeasureText(word); - var wordWithSpaceWidth = wordWidth + spaceWidth; - var wordWithSpace = isCJK ? word : word + " "; - - if (width + wordWidth > maxWidth) - { - ret.Add(lineResult.ToString()); - lineResult = new StringBuilder(wordWithSpace); - width = wordWithSpaceWidth; - } - else - { - lineResult.Append(wordWithSpace); - width += wordWithSpaceWidth; - } - } - - ret.Add(lineResult.ToString()); - } - - return ret; - } } -} + + public static SKBitmap GetBitmap(UMaterialInstanceConstant material) + { + if (material == null) return null; + foreach (var textureParameter in material.TextureParameterValues) + { + if (!textureParameter.ParameterValue.TryLoad(out var texture)) continue; + switch (textureParameter.ParameterInfo.Name.Text) + { + case "MainTex": + case "TextureA": + case "TextureB": + case "OfferImage": + case "KeyArtTexture": + case "NPC-Portrait": + { + return GetBitmap(texture); + } + } + } + + return null; + } + + public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 }); + public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text); + public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null; + public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform); + public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data); + + public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height) + { + var ratioX = width / me.Width; + var ratioY = height / me.Height; + var ratio = ratioX < ratioY ? ratioX : ratioY; + return me.Resize(Convert.ToInt32(me.Width * ratio), Convert.ToInt32(me.Height * ratio)); + } + + public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size); + + public static SKBitmap Resize(this SKBitmap me, int width, int height) + { + var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels); + using var pixmap = bmp.PeekPixels(); + me.ScalePixels(pixmap, SKFilterQuality.Medium); + return bmp; + } + + public static bool TryGetPackageIndexExport(FPackageIndex packageIndex, out T export) where T : UObject + { + return packageIndex.TryLoad(out export); + } + + // fullpath must be either without any extension or with the export objectname + public static bool TryLoadObject(string fullPath, out T export) where T : UObject + { + return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export); + } + + public static IEnumerable LoadExports(string fullPath) + { + return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath); + } + + public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f) + { + var max = maxFont; + var min = 0f; + var last = -1f; + float value; + while (true) + { + value = min + ((max - min) / 2); + using (SKFont ft = new SKFont(typeface, value)) + using (SKPaint paint = new SKPaint(ft)) + { + if (paint.MeasureText(text) > sectorSize) + { + last = value; + max = value; + } + else + { + min = value; + if (Math.Abs(last - value) <= degreeOfCertainty) + return last; + + last = value; + } + } + } + } + + public static string GetLocalizedResource(string @namespace, string key, string defaultValue) + { + return _applicationView.CUE4Parse.Provider.GetLocalizedString(@namespace, key, defaultValue); + } + + public static string GetFullPath(string partialPath) + { + var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled); + foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys) + { + if (regex.IsMatch(path)) + { + return path; + } + } + + return string.Empty; + } + + public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint) + { + var lineHeight = paint.TextSize * 1.2f; + var lines = SplitLines(text, paint, area.Width - margin); + +#if DEBUG + c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint { Color = SKColors.Red, IsStroke = true }); +#endif + + if (lines == null) return; + if (lines.Count <= maxCount) maxCount = lines.Count; + var height = maxCount * lineHeight; + var y = area.MidY - height / 2; + + var shaper = new CustomSKShaper(paint.Typeface); + for (var i = 0; i < maxCount; i++) + { + var line = lines[i]; + if (line == null) continue; + + var lineText = line.Trim(); + var shapedText = shaper.Shape(lineText, paint); + + y += lineHeight; + var x = side switch + { + SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, + SKTextAlign.Right => size - margin - shapedText.Points[^1].X, + SKTextAlign.Left => margin, + _ => throw new NotImplementedException() + }; + + c.DrawShapedText(shaper, lineText, x, y, paint); + } + } + + public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos) + { + yPos = area.Top; + var lineHeight = paint.TextSize * 1.2f; + var lines = SplitLines(text, paint, area.Width); + if (lines == null) return; + + foreach (var line in lines) + { + var fontSize = GetMaxFontSize(area.Width, paint.Typeface, line); + if (paint.TextSize > fontSize) // if the text is not fitting in the line decrease the font size (CKJ languages) + { + paint.TextSize = fontSize; + lineHeight = paint.TextSize * 1.2f; + } + + if (line == null) continue; + var lineText = line.Trim(); + var shaper = new CustomSKShaper(paint.Typeface); + var shapedText = shaper.Shape(lineText, paint); + + var x = side switch + { + SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, + SKTextAlign.Right => size - margin - shapedText.Points[^1].X, + SKTextAlign.Left => area.Left, + _ => throw new NotImplementedException() + }; + + c.DrawShapedText(shaper, lineText, x, yPos, paint); + yPos += lineHeight; + } + +#if DEBUG + c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint { Color = SKColors.Red, IsStroke = true }); +#endif + } + + #region Chinese, Korean and Japanese text split + + // https://github.com/YoungjaeKim/mikan.sharp/blob/master/MikanSharp/Mikan/Mikan.cs + + static string joshi = @"(でなければ|について|かしら|くらい|けれど|なのか|ばかり|ながら|ことよ|こそ|こと|さえ|しか|した|たり|だけ|だに|だの|つつ|ても|てよ|でも|とも|から|など|なり|ので|のに|ほど|まで|もの|やら|より|って|で|と|な|に|ね|の|も|は|ば|へ|や|わ|を|か|が|さ|し|ぞ|て)"; + static string keywords = @"(\ |[a-zA-Z0-9]+\.[a-z]{2,}|[一-龠々〆ヵヶゝ]+|[ぁ-んゝ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[a-zA-Z0-9]+)"; + static string periods = @"([\.\,。、!\!?\?]+)$"; + static string bracketsBegin = @"([〈《「『「((\[【〔〚〖〘❮❬❪❨(<{❲❰{❴])"; + static string bracketsEnd = @"([〉》」』」))\]】〕〗〙〛}>\)❩❫❭❯❱❳❵}])"; + + public static string[] SplitCKJText(string str) + { + var line1 = Regex.Split(str, keywords).ToList(); + var line2 = line1.SelectMany((o, _) => Regex.Split(o, joshi)).ToList(); + var line3 = line2.SelectMany((o, _) => Regex.Split(o, bracketsBegin)).ToList(); + var line4 = line3.SelectMany((o, _) => Regex.Split(o, bracketsEnd)).ToList(); + var words = line4.Where(o => !string.IsNullOrEmpty(o)).ToList(); + + var prevType = string.Empty; + var prevWord = string.Empty; + List result = new List(); + + words.ForEach(word => + { + var token = Regex.IsMatch(word, periods) || Regex.IsMatch(word, joshi); + + if (Regex.IsMatch(word, bracketsBegin)) + { + prevType = "braketBegin"; + prevWord = word; + return; + } + + if (Regex.IsMatch(word, bracketsEnd)) + { + result[result.Count - 1] += word; + prevType = "braketEnd"; + prevWord = word; + return; + } + + if (prevType == "braketBegin") + { + word = prevWord + word; + prevWord = string.Empty; + prevType = string.Empty; + } + + // すでに文字が入っている上で助詞が続く場合は結合する + if (result.Count > 0 && token && prevType == string.Empty) + { + result[result.Count - 1] += word; + prevType = "keyword"; + prevWord = word; + return; + } + + // 単語のあとの文字がひらがななら結合する + if (result.Count > 1 && token || (prevType == "keyword" && Regex.IsMatch(word, @"[ぁ-んゝ]+"))) + { + result[result.Count - 1] += word; + prevType = string.Empty; + prevWord = word; + return; + } + + result.Add(word); + prevType = "keyword"; + prevWord = word; + }); + return result.ToArray(); + } + + #endregion + + public static List SplitLines(string text, SKPaint paint, float maxWidth) + { + if (string.IsNullOrEmpty(text)) return null; + + var spaceWidth = paint.MeasureText(" "); + var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + var ret = new List(lines.Length); + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line)) continue; + + float width = 0; + var isCJK = false; + var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + if (words.Length <= 1 && UserSettings.Default.AssetLanguage is ELanguage.Japanese or ELanguage.Korean or ELanguage.Chinese or ELanguage.TraditionalChinese) + { + words = SplitCKJText(line); + isCJK = true; + } + + var lineResult = new StringBuilder(); + foreach (var word in words) + { + var wordWidth = paint.MeasureText(word); + var wordWithSpaceWidth = wordWidth + spaceWidth; + var wordWithSpace = isCJK ? word : word + " "; + + if (width + wordWidth > maxWidth) + { + ret.Add(lineResult.ToString()); + lineResult = new StringBuilder(wordWithSpace); + width = wordWithSpaceWidth; + } + else + { + lineResult.Append(wordWithSpace); + width += wordWithSpaceWidth; + } + } + + ret.Add(lineResult.ToString()); + } + + return ret; + } +} \ No newline at end of file diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 1e8c9ed5..04fff3f1 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -1,142 +1,141 @@ using System.ComponentModel; -namespace FModel +namespace FModel; + +public enum EBuildKind { - public enum EBuildKind - { - Debug, - Release, - Unknown - } - - public enum EErrorKind - { - Ignore, - Restart, - ResetSettings - } - - public enum SettingsOut - { - Restart, - ReloadLocres, - CheckForUpdates, - Nothing - } - - public enum EStatusKind - { - Ready, // ready - Loading, // doing stuff - Stopping, // trying to stop - Stopped, // stopped - Failed, // crashed - Completed // worked - } - - public enum EAesReload - { - [Description("Always")] - Always, - [Description("Never")] - Never, - [Description("Once Per Day")] - OncePerDay - } - - public enum EDiscordRpc - { - [Description("Always")] - Always, - [Description("Never")] - Never - } - - public enum FGame - { - [Description("Unknown")] - Unknown, - [Description("Fortnite")] - FortniteGame, - [Description("Valorant")] - ShooterGame, - [Description("Dead By Daylight")] - DeadByDaylight, - [Description("Borderlands 3")] - OakGame, - [Description("Minecraft Dungeons")] - Dungeons, - [Description("Battle Breakers")] - WorldExplorers, - [Description("Spellbreak")] - g3, - [Description("State Of Decay 2")] - StateOfDecay2, - [Description("The Cycle")] - Prospect, - [Description("The Outer Worlds")] - Indiana, - [Description("Rogue Company")] - RogueCompany, - [Description("Star Wars: Jedi Fallen Order")] - SwGame, - [Description("Core")] - Platform, - [Description("Days Gone")] - BendGame, - [Description("PLAYERUNKNOWN'S BATTLEGROUNDS")] - TslGame, - [Description("Splitgate")] - PortalWars, - [Description("GTA: The Trilogy - Definitive Edition")] - Gameface, - [Description("Sea of Thieves")] - Athena - } - - public enum ELoadingMode - { - [Description("Single")] - Single, - [Description("Multiple")] - Multiple, - [Description("All")] - All, - [Description("All (New)")] - AllButNew, - [Description("All (Modified)")] - AllButModified - } - - public enum EUpdateMode - { - [Description("Stable")] - Stable, - [Description("Beta")] - Beta - } - - public enum ECompressedAudio - { - [Description("Play the decompressed data")] - PlayDecompressed, - [Description("Play the compressed data (might not always be a valid audio data)")] - PlayCompressed - } - - public enum EIconStyle - { - [Description("Default")] - Default, - [Description("No Background")] - NoBackground, - [Description("No Text")] - NoText, - [Description("Flat")] - Flat, - [Description("Cataba")] - Cataba, - // [Description("Community")] - // CommunityMade - } + Debug, + Release, + Unknown } + +public enum EErrorKind +{ + Ignore, + Restart, + ResetSettings +} + +public enum SettingsOut +{ + Restart, + ReloadLocres, + CheckForUpdates, + Nothing +} + +public enum EStatusKind +{ + Ready, // ready + Loading, // doing stuff + Stopping, // trying to stop + Stopped, // stopped + Failed, // crashed + Completed // worked +} + +public enum EAesReload +{ + [Description("Always")] + Always, + [Description("Never")] + Never, + [Description("Once Per Day")] + OncePerDay +} + +public enum EDiscordRpc +{ + [Description("Always")] + Always, + [Description("Never")] + Never +} + +public enum FGame +{ + [Description("Unknown")] + Unknown, + [Description("Fortnite")] + FortniteGame, + [Description("Valorant")] + ShooterGame, + [Description("Dead By Daylight")] + DeadByDaylight, + [Description("Borderlands 3")] + OakGame, + [Description("Minecraft Dungeons")] + Dungeons, + [Description("Battle Breakers")] + WorldExplorers, + [Description("Spellbreak")] + g3, + [Description("State Of Decay 2")] + StateOfDecay2, + [Description("The Cycle")] + Prospect, + [Description("The Outer Worlds")] + Indiana, + [Description("Rogue Company")] + RogueCompany, + [Description("Star Wars: Jedi Fallen Order")] + SwGame, + [Description("Core")] + Platform, + [Description("Days Gone")] + BendGame, + [Description("PLAYERUNKNOWN'S BATTLEGROUNDS")] + TslGame, + [Description("Splitgate")] + PortalWars, + [Description("GTA: The Trilogy - Definitive Edition")] + Gameface, + [Description("Sea of Thieves")] + Athena +} + +public enum ELoadingMode +{ + [Description("Single")] + Single, + [Description("Multiple")] + Multiple, + [Description("All")] + All, + [Description("All (New)")] + AllButNew, + [Description("All (Modified)")] + AllButModified +} + +public enum EUpdateMode +{ + [Description("Stable")] + Stable, + [Description("Beta")] + Beta +} + +public enum ECompressedAudio +{ + [Description("Play the decompressed data")] + PlayDecompressed, + [Description("Play the compressed data (might not always be a valid audio data)")] + PlayCompressed +} + +public enum EIconStyle +{ + [Description("Default")] + Default, + [Description("No Background")] + NoBackground, + [Description("No Text")] + NoText, + [Description("Flat")] + Flat, + [Description("Cataba")] + Cataba, + // [Description("Community")] + // CommunityMade +} \ No newline at end of file diff --git a/FModel/Extensions/AvalonExtensions.cs b/FModel/Extensions/AvalonExtensions.cs index 55c6449c..412eb7b8 100644 --- a/FModel/Extensions/AvalonExtensions.cs +++ b/FModel/Extensions/AvalonExtensions.cs @@ -4,47 +4,46 @@ using System.Xml; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; -namespace FModel.Extensions +namespace FModel.Extensions; + +public static class AvalonExtensions { - public static class AvalonExtensions + private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd"); + private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd"); + private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd"); + private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd"); + private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd"); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IHighlightingDefinition LoadHighlighter(string resourceName) { - private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd"); - private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd"); - private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd"); - private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd"); - private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd"); + var executingAssembly = Assembly.GetExecutingAssembly(); + using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}"); + using var reader = new XmlTextReader(stream); + return HighlightingLoader.Load(reader, HighlightingManager.Instance); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static IHighlightingDefinition LoadHighlighter(string resourceName) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IHighlightingDefinition HighlighterSelector(string ext) + { + switch (ext) { - var executingAssembly = Assembly.GetExecutingAssembly(); - using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}"); - using var reader = new XmlTextReader(stream); - return HighlightingLoader.Load(reader, HighlightingManager.Instance); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IHighlightingDefinition HighlighterSelector(string ext) - { - switch (ext) - { - case "ini": - case "csv": - return _iniHighlighter; - case "xml": - return _xmlHighlighter; - case "h": - case "cpp": - return _cppHighlighter; - case "changelog": - return _changelogHighlighter; - case "bat": - case "txt": - case "po": - return null; - default: - return _jsonHighlighter; - } + case "ini": + case "csv": + return _iniHighlighter; + case "xml": + return _xmlHighlighter; + case "h": + case "cpp": + return _cppHighlighter; + case "changelog": + return _changelogHighlighter; + case "bat": + case "txt": + case "po": + return null; + default: + return _jsonHighlighter; } } } \ No newline at end of file diff --git a/FModel/Extensions/ClipboardExtensions.cs b/FModel/Extensions/ClipboardExtensions.cs index 557784b2..5ba68b0a 100644 --- a/FModel/Extensions/ClipboardExtensions.cs +++ b/FModel/Extensions/ClipboardExtensions.cs @@ -1,5 +1,4 @@ using SkiaSharp; - using System; using System.Drawing; using System.Drawing.Imaging; @@ -8,191 +7,192 @@ using System.Runtime.CompilerServices; using System.Text; using System.Windows; -namespace FModel.Extensions +namespace FModel.Extensions; + +public static class ClipboardExtensions { - public static class ClipboardExtensions + public static void SetImage(byte[] pngBytes, string fileName = null) { - public static void SetImage(byte[] pngBytes, string fileName = null) + Clipboard.Clear(); + var data = new DataObject(); + using var pngMs = new MemoryStream(pngBytes); + using var image = Image.FromStream(pngMs); + // As standard bitmap, without transparency support + data.SetData(DataFormats.Bitmap, image, true); + // As PNG. Gimp will prefer this over the other two + data.SetData("PNG", pngMs, false); + // As DIB. This is (wrongly) accepted as ARGB by many applications + using var dibMemStream = new MemoryStream(ConvertToDib(image)); + data.SetData(DataFormats.Dib, dibMemStream, false); + // Optional fileName + if (!string.IsNullOrEmpty(fileName)) { - Clipboard.Clear(); - var data = new DataObject(); - using var pngMs = new MemoryStream(pngBytes); - using var image = Image.FromStream(pngMs); - // As standard bitmap, without transparency support - data.SetData(DataFormats.Bitmap, image, true); - // As PNG. Gimp will prefer this over the other two - data.SetData("PNG", pngMs, false); - // As DIB. This is (wrongly) accepted as ARGB by many applications - using var dibMemStream = new MemoryStream(ConvertToDib(image)); - data.SetData(DataFormats.Dib, dibMemStream, false); - // Optional fileName - if (!string.IsNullOrEmpty(fileName)) + var htmlFragment = GenerateHTMLFragment($""); + data.SetData(DataFormats.Html, htmlFragment); + } + // The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation + Clipboard.SetDataObject(data, true); + } + + public static byte[] ConvertToDib(Image image) + { + byte[] bm32bData; + var width = image.Width; + var height = image.Height; + + // Ensure image is 32bppARGB by painting it on a new 32bppARGB image. + using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb)) + { + using (var gr = Graphics.FromImage(bm32b)) { - var htmlFragment = GenerateHTMLFragment($""); - data.SetData(DataFormats.Html, htmlFragment); + gr.DrawImage(image, new Rectangle(0, 0, width, height)); } - // The 'copy=true' argument means the MemoryStreams can be safely disposed after the operation - Clipboard.SetDataObject(data, true); + + // Bitmap format has its lines reversed. + bm32b.RotateFlip(RotateFlipType.Rotate180FlipX); + bm32bData = GetRawBytes(bm32b); } - public static byte[] ConvertToDib(Image image) + // BITMAPINFOHEADER struct for DIB. + const int hdrSize = 0x28; + var fullImage = new byte[hdrSize + 12 + bm32bData.Length]; + //Int32 biSize; + WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize); + //Int32 biWidth; + WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width); + //Int32 biHeight; + WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height); + //Int16 biPlanes; + WriteIntToByteArray(fullImage, 0x0C, 2, true, 1); + //Int16 biBitCount; + WriteIntToByteArray(fullImage, 0x0E, 2, true, 32); + //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS; + WriteIntToByteArray(fullImage, 0x10, 4, true, 3); + //Int32 biSizeImage; + WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length); + // These are all 0. Since .net clears new arrays, don't bother writing them. + //Int32 biXPelsPerMeter = 0; + //Int32 biYPelsPerMeter = 0; + //Int32 biClrUsed = 0; + //Int32 biClrImportant = 0; + + // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. + WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000); + WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00); + WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF); + + Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length); + return fullImage; + } + + private static byte[] ConvertToDib(byte[] pngBytes = null) + { + byte[] bm32bData; + int width, height; + + using (var skBmp = SKBitmap.Decode(pngBytes)) { - byte[] bm32bData; - var width = image.Width; - var height = image.Height; - - // Ensure image is 32bppARGB by painting it on a new 32bppARGB image. - using (var bm32b = new Bitmap(width, height, PixelFormat.Format32bppPArgb)) - { - using (var gr = Graphics.FromImage(bm32b)) - { - gr.DrawImage(image, new Rectangle(0, 0, width, height)); - } - // Bitmap format has its lines reversed. - bm32b.RotateFlip(RotateFlipType.Rotate180FlipX); - bm32bData = GetRawBytes(bm32b); - } - - // BITMAPINFOHEADER struct for DIB. - const int hdrSize = 0x28; - var fullImage = new byte[hdrSize + 12 + bm32bData.Length]; - //Int32 biSize; - WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize); - //Int32 biWidth; - WriteIntToByteArray(fullImage, 0x04, 4, true, (uint)width); - //Int32 biHeight; - WriteIntToByteArray(fullImage, 0x08, 4, true, (uint)height); - //Int16 biPlanes; - WriteIntToByteArray(fullImage, 0x0C, 2, true, 1); - //Int16 biBitCount; - WriteIntToByteArray(fullImage, 0x0E, 2, true, 32); - //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS; - WriteIntToByteArray(fullImage, 0x10, 4, true, 3); - //Int32 biSizeImage; - WriteIntToByteArray(fullImage, 0x14, 4, true, (uint)bm32bData.Length); - // These are all 0. Since .net clears new arrays, don't bother writing them. - //Int32 biXPelsPerMeter = 0; - //Int32 biYPelsPerMeter = 0; - //Int32 biClrUsed = 0; - //Int32 biClrImportant = 0; - - // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. - WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000); - WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00); - WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF); - - Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint)bm32bData.Length); - return fullImage; + width = skBmp.Width; + height = skBmp.Height; + using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType)); + using var canvas = new SKCanvas(rotated); + canvas.Scale(1, -1, 0, height / 2.0f); + canvas.DrawBitmap(skBmp, SKPoint.Empty); + canvas.Flush(); + bm32bData = rotated.Bytes; } - private static byte[] ConvertToDib(byte[] pngBytes = null) + // BITMAPINFOHEADER struct for DIB. + const int hdrSize = 0x28; + var fullImage = new byte[hdrSize + 12 + bm32bData.Length]; + //Int32 biSize; + WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize); + //Int32 biWidth; + WriteIntToByteArray(fullImage, 0x04, 4, true, (uint) width); + //Int32 biHeight; + WriteIntToByteArray(fullImage, 0x08, 4, true, (uint) height); + //Int16 biPlanes; + WriteIntToByteArray(fullImage, 0x0C, 2, true, 1); + //Int16 biBitCount; + WriteIntToByteArray(fullImage, 0x0E, 2, true, 32); + //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS; + WriteIntToByteArray(fullImage, 0x10, 4, true, 3); + //Int32 biSizeImage; + WriteIntToByteArray(fullImage, 0x14, 4, true, (uint) bm32bData.Length); + // These are all 0. Since .net clears new arrays, don't bother writing them. + //Int32 biXPelsPerMeter = 0; + //Int32 biYPelsPerMeter = 0; + //Int32 biClrUsed = 0; + //Int32 biClrImportant = 0; + + // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. + WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000); + WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00); + WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF); + + Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint) bm32bData.Length); + return fullImage; + } + + public static unsafe byte[] GetRawBytes(Bitmap bmp) + { + var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); + var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); + var bytes = (uint) (Math.Abs(bmpData.Stride) * bmp.Height); + var buffer = new byte[bytes]; + fixed (byte* pBuffer = buffer) { - byte[] bm32bData; - int width, height; - - using (var skBmp = SKBitmap.Decode(pngBytes)) - { - width = skBmp.Width; - height = skBmp.Height; - using var rotated = new SKBitmap(new SKImageInfo(width, height, skBmp.ColorType)); - using var canvas = new SKCanvas(rotated); - canvas.Scale(1, -1, 0, height / 2.0f); - canvas.DrawBitmap(skBmp, SKPoint.Empty); - canvas.Flush(); - bm32bData = rotated.Bytes; - } - - // BITMAPINFOHEADER struct for DIB. - const int hdrSize = 0x28; - var fullImage = new byte[hdrSize + 12 + bm32bData.Length]; - //Int32 biSize; - WriteIntToByteArray(fullImage, 0x00, 4, true, hdrSize); - //Int32 biWidth; - WriteIntToByteArray(fullImage, 0x04, 4, true, (uint)width); - //Int32 biHeight; - WriteIntToByteArray(fullImage, 0x08, 4, true, (uint)height); - //Int16 biPlanes; - WriteIntToByteArray(fullImage, 0x0C, 2, true, 1); - //Int16 biBitCount; - WriteIntToByteArray(fullImage, 0x0E, 2, true, 32); - //BITMAPCOMPRESSION biCompression = BITMAPCOMPRESSION.BITFIELDS; - WriteIntToByteArray(fullImage, 0x10, 4, true, 3); - //Int32 biSizeImage; - WriteIntToByteArray(fullImage, 0x14, 4, true, (uint)bm32bData.Length); - // These are all 0. Since .net clears new arrays, don't bother writing them. - //Int32 biXPelsPerMeter = 0; - //Int32 biYPelsPerMeter = 0; - //Int32 biClrUsed = 0; - //Int32 biClrImportant = 0; - - // The aforementioned "BITFIELDS": colour masks applied to the Int32 pixel value to get the R, G and B values. - WriteIntToByteArray(fullImage, hdrSize + 0, 4, true, 0x00FF0000); - WriteIntToByteArray(fullImage, hdrSize + 4, 4, true, 0x0000FF00); - WriteIntToByteArray(fullImage, hdrSize + 8, 4, true, 0x000000FF); - - Unsafe.CopyBlockUnaligned(ref fullImage[hdrSize + 12], ref bm32bData[0], (uint)bm32bData.Length); - return fullImage; + Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes); } - public static unsafe byte[] GetRawBytes(Bitmap bmp) + bmp.UnlockBits(bmpData); + return buffer; + } + + private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value) + { + var lastByte = bytes - 1; + + if (data.Length < startIndex + bytes) { - var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); - var bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); - var bytes = (uint)(Math.Abs(bmpData.Stride) * bmp.Height); - var buffer = new byte[bytes]; - fixed (byte* pBuffer = buffer) - { - Unsafe.CopyBlockUnaligned(pBuffer, bmpData.Scan0.ToPointer(), bytes); - } - bmp.UnlockBits(bmpData); - return buffer; + throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + "."); } - private static void WriteIntToByteArray(byte[] data, int startIndex, int bytes, bool littleEndian, uint value) + for (var index = 0; index < bytes; index++) { - var lastByte = bytes - 1; - - if (data.Length < startIndex + bytes) - { - throw new ArgumentOutOfRangeException(nameof(startIndex), "Data array is too small to write a " + bytes + "-byte value at offset " + startIndex + "."); - } - - for (var index = 0; index < bytes; index++) - { - var offs = startIndex + (littleEndian ? index : lastByte - index); - data[offs] = (byte)(value >> 8 * index & 0xFF); - } - } - - private static string GenerateHTMLFragment(string html) - { - var sb = new StringBuilder(); - - const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n"; - const string startHTML = "\r\n\r\n"; - const string startFragment = ""; - const string endFragment = ""; - const string endHTML = "\r\n\r\n"; - - sb.Append(header); - - var startHTMLLength = header.Length; - var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length; - var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html); - var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length; - - sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10")); - sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10")); - sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10")); - sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10")); - - sb.Append(startHTML); - sb.Append(startFragment); - sb.Append(html); - sb.Append(endFragment); - sb.Append(endHTML); - - return sb.ToString(); + var offs = startIndex + (littleEndian ? index : lastByte - index); + data[offs] = (byte) (value >> 8 * index & 0xFF); } } + + private static string GenerateHTMLFragment(string html) + { + var sb = new StringBuilder(); + + const string header = "Version:0.9\r\nStartHTML:<<<<<<<<<1\r\nEndHTML:<<<<<<<<<2\r\nStartFragment:<<<<<<<<<3\r\nEndFragment:<<<<<<<<<4\r\n"; + const string startHTML = "\r\n\r\n"; + const string startFragment = ""; + const string endFragment = ""; + const string endHTML = "\r\n\r\n"; + + sb.Append(header); + + var startHTMLLength = header.Length; + var startFragmentLength = startHTMLLength + startHTML.Length + startFragment.Length; + var endFragmentLength = startFragmentLength + Encoding.UTF8.GetByteCount(html); + var endHTMLLength = endFragmentLength + endFragment.Length + endHTML.Length; + + sb.Replace("<<<<<<<<<1", startHTMLLength.ToString("D10")); + sb.Replace("<<<<<<<<<2", endHTMLLength.ToString("D10")); + sb.Replace("<<<<<<<<<3", startFragmentLength.ToString("D10")); + sb.Replace("<<<<<<<<<4", endFragmentLength.ToString("D10")); + + sb.Append(startHTML); + sb.Append(startFragment); + sb.Append(html); + sb.Append(endFragment); + sb.Append(endHTML); + + return sb.ToString(); + } } \ No newline at end of file diff --git a/FModel/Extensions/CollectionExtensions.cs b/FModel/Extensions/CollectionExtensions.cs index b26a33f5..3964f2a0 100644 --- a/FModel/Extensions/CollectionExtensions.cs +++ b/FModel/Extensions/CollectionExtensions.cs @@ -2,36 +2,35 @@ using System.Linq; using System.Runtime.CompilerServices; -namespace FModel.Extensions +namespace FModel.Extensions; + +public static class CollectionExtensions { - public static class CollectionExtensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this IList collection, T value) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Next(this IList collection, T value) - { - var i = collection.IndexOf(value) + 1; - return i >= collection.Count ? collection.First() : collection[i]; - } + var i = collection.IndexOf(value) + 1; + return i >= collection.Count ? collection.First() : collection[i]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Next(this IList collection, int index) - { - var i = index + 1; - return i >= collection.Count ? collection.First() : collection[i]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this IList collection, int index) + { + var i = index + 1; + return i >= collection.Count ? collection.First() : collection[i]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Previous(this IList collection, T value) - { - var i = collection.IndexOf(value) - 1; - return i < 0 ? collection.Last() : collection[i]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this IList collection, T value) + { + var i = collection.IndexOf(value) - 1; + return i < 0 ? collection.Last() : collection[i]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Previous(this IList collection, int index) - { - var i = index - 1; - return i < 0 ? collection.Last() : collection[i]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this IList collection, int index) + { + var i = index - 1; + return i < 0 ? collection.Last() : collection[i]; } } \ No newline at end of file diff --git a/FModel/Extensions/EnumExtensions.cs b/FModel/Extensions/EnumExtensions.cs index 400d91b1..3c3fa9a9 100644 --- a/FModel/Extensions/EnumExtensions.cs +++ b/FModel/Extensions/EnumExtensions.cs @@ -1,84 +1,46 @@ using System; using System.ComponentModel; -using System.Resources; using System.Runtime.CompilerServices; -using FModel.Properties; -namespace FModel.Extensions +namespace FModel.Extensions; + +public static class EnumExtensions { - public static class EnumExtensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDescription(this Enum value) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetDescription(this Enum value) - { - var fi = value.GetType().GetField(value.ToString()); - if (fi == null) return $"{value} ({value:D})"; - var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false); - return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})"; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetLocalizedDescription(this Enum value) => value.GetLocalizedDescription(Resources.ResourceManager); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetLocalizedDescription(this Enum value, ResourceManager resourceManager) - { - var resourceName = value.GetType().Name + "_" + value; - var description = resourceManager.GetString(resourceName); - - if (string.IsNullOrEmpty(description)) - { - description = value.GetDescription(); - } - - return description; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetLocalizedCategory(this Enum value) => value.GetLocalizedCategory(Resources.ResourceManager); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetLocalizedCategory(this Enum value, ResourceManager resourceManager) - { - var resourceName = value.GetType().Name + "_" + value + "_Category"; - var description = resourceManager.GetString(resourceName); - - return description; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T ToEnum(this string value, T defaultValue) where T : struct - { - if (!Enum.TryParse(value, true, out T ret)) - return defaultValue; - - return ret; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool HasAnyFlags(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIndex(this Enum value) - { - var values = Enum.GetValues(value.GetType()); - return Array.IndexOf(values, value); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Next(this Enum value) - { - var values = Enum.GetValues(value.GetType()); - var i = Array.IndexOf(values, value) + 1; - return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T Previous(this Enum value) - { - var values = Enum.GetValues(value.GetType()); - var i = Array.IndexOf(values, value) - 1; - return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i); - } + var fi = value.GetType().GetField(value.ToString()); + if (fi == null) return $"{value} ({value:D})"; + var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})"; } -} + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ToEnum(this string value, T defaultValue) where T : struct => !Enum.TryParse(value, true, out T ret) ? defaultValue : ret; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAnyFlags(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetIndex(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + return Array.IndexOf(values, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + var i = Array.IndexOf(values, value) + 1; + return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + var i = Array.IndexOf(values, value) - 1; + return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i); + } +} \ No newline at end of file diff --git a/FModel/Extensions/StreamExtensions.cs b/FModel/Extensions/StreamExtensions.cs index 21c95978..21517936 100644 --- a/FModel/Extensions/StreamExtensions.cs +++ b/FModel/Extensions/StreamExtensions.cs @@ -2,30 +2,29 @@ using System.IO; using System.Runtime.CompilerServices; -namespace FModel.Extensions +namespace FModel.Extensions; + +public enum Endianness { - public enum Endianness - { - LittleEndian, - BigEndian, - } + LittleEndian, + BigEndian +} - public static class StreamExtensions +public static class StreamExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian) + var b1 = s.ReadByte(); + var b2 = s.ReadByte(); + var b3 = s.ReadByte(); + var b4 = s.ReadByte(); + + return endian switch { - var b1 = s.ReadByte(); - var b2 = s.ReadByte(); - var b3 = s.ReadByte(); - var b4 = s.ReadByte(); - - return endian switch - { - Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1), - Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4), - _ => throw new Exception("unknown endianness") - }; - } + Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1), + Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4), + _ => throw new Exception("unknown endianness") + }; } } \ No newline at end of file diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index 700c1ee2..cbfa8ddd 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -2,133 +2,132 @@ using System.IO; using System.Runtime.CompilerServices; -namespace FModel.Extensions +namespace FModel.Extensions; + +public static class StringExtensions { - public static class StringExtensions + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetReadableSize(double size) { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetReadableSize(double size) + if (size == 0) return "0 B"; + + string[] sizes = { "B", "KB", "MB", "GB", "TB" }; + var order = 0; + while (size >= 1024 && order < sizes.Length - 1) { - if (size == 0) return "0 B"; - - string[] sizes = {"B", "KB", "MB", "GB", "TB"}; - var order = 0; - while (size >= 1024 && order < sizes.Length - 1) - { - order++; - size /= 1024; - } - - return $"{size:# ###.##} {sizes[order]}".TrimStart(); + order++; + size /= 1024; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBefore(this string s, char delimiter) + return $"{size:# ###.##} {sizes[order]}".TrimStart(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBefore(this string s, char delimiter) + { + var index = s.IndexOf(delimiter); + return index == -1 ? s : s[..index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.IndexOf(delimiter, comparisonType); + return index == -1 ? s : s[..index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringAfter(this string s, char delimiter) + { + var index = s.IndexOf(delimiter); + return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.IndexOf(delimiter, comparisonType); + return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeLast(this string s, char delimiter) + { + var index = s.LastIndexOf(delimiter); + return index == -1 ? s : s[..index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.LastIndexOf(delimiter, comparisonType); + return index == -1 ? s : s[..index]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeWithLast(this string s, char delimiter) + { + var index = s.LastIndexOf(delimiter); + return index == -1 ? s : s[..(index + 1)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.LastIndexOf(delimiter, comparisonType); + return index == -1 ? s : s[..(index + 1)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringAfterLast(this string s, char delimiter) + { + var index = s.LastIndexOf(delimiter); + return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.LastIndexOf(delimiter, comparisonType); + return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetLineNumber(this string s, string lineToFind) + { + if (int.TryParse(lineToFind, out var index)) + return s.GetLineNumber(index); + + lineToFind = $" \"Name\": \"{lineToFind}\","; + using var reader = new StringReader(s); + var lineNum = 0; + string line; + while ((line = reader.ReadLine()) != null) { - var index = s.IndexOf(delimiter); - return index == -1 ? s : s[..index]; + lineNum++; + if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) + return lineNum; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + return 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLineNumber(this string s, int index) + { + using var reader = new StringReader(s); + var lineNum = 0; + string line; + while ((line = reader.ReadLine()) != null) { - var index = s.IndexOf(delimiter, comparisonType); - return index == -1 ? s : s[..index]; + lineNum++; + if (line.Equals(" {")) + index--; + + if (index == -1) + return lineNum + 1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringAfter(this string s, char delimiter) - { - var index = s.IndexOf(delimiter); - return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) - { - var index = s.IndexOf(delimiter, comparisonType); - return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBeforeLast(this string s, char delimiter) - { - var index = s.LastIndexOf(delimiter); - return index == -1 ? s : s[..index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) - { - var index = s.LastIndexOf(delimiter, comparisonType); - return index == -1 ? s : s[..index]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBeforeWithLast(this string s, char delimiter) - { - var index = s.LastIndexOf(delimiter); - return index == -1 ? s : s[..(index + 1)]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) - { - var index = s.LastIndexOf(delimiter, comparisonType); - return index == -1 ? s : s[..(index + 1)]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringAfterLast(this string s, char delimiter) - { - var index = s.LastIndexOf(delimiter); - return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) - { - var index = s.LastIndexOf(delimiter, comparisonType); - return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetLineNumber(this string s, string lineToFind) - { - if (int.TryParse(lineToFind, out var index)) - return s.GetLineNumber(index); - - lineToFind = $" \"Name\": \"{lineToFind}\","; - using var reader = new StringReader(s); - var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) - { - lineNum++; - if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) - return lineNum; - } - - return 1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetLineNumber(this string s, int index) - { - using var reader = new StringReader(s); - var lineNum = 0; - string line; - while ((line = reader.ReadLine()) != null) - { - lineNum++; - if (line.Equals(" {")) - index--; - - if (index == -1) - return lineNum + 1; - } - - return 1; - } + return 1; } } \ No newline at end of file diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 4ad0944b..0ed78b14 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -2,7 +2,7 @@ WinExe - net6.0-windows + net7.0-windows true FModel.ico 4.2.2 diff --git a/FModel/Framework/AsyncQueue.cs b/FModel/Framework/AsyncQueue.cs index a24d30ea..78de380b 100644 --- a/FModel/Framework/AsyncQueue.cs +++ b/FModel/Framework/AsyncQueue.cs @@ -2,32 +2,31 @@ using System.Threading; using System.Threading.Tasks.Dataflow; -namespace FModel.Framework +namespace FModel.Framework; + +public class AsyncQueue : IAsyncEnumerable { - public class AsyncQueue : IAsyncEnumerable + private readonly SemaphoreSlim _semaphore = new(1); + private readonly BufferBlock _buffer = new(); + + public int Count => _buffer.Count; + + public void Enqueue(T item) => _buffer.Post(item); + + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken token = default) { - private readonly SemaphoreSlim _semaphore = new(1); - private readonly BufferBlock _buffer = new(); - - public int Count => _buffer.Count; - - public void Enqueue(T item) => _buffer.Post(item); - - public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken token = default) + await _semaphore.WaitAsync(token); + try { - await _semaphore.WaitAsync(token); - try + while (Count > 0) { - while (Count > 0) - { - token.ThrowIfCancellationRequested(); - yield return await _buffer.ReceiveAsync(token); - } - } - finally - { - _semaphore.Release(); + token.ThrowIfCancellationRequested(); + yield return await _buffer.ReceiveAsync(token); } } + finally + { + _semaphore.Release(); + } } -} +} \ No newline at end of file diff --git a/FModel/Framework/Command.cs b/FModel/Framework/Command.cs index 90396053..ed4b1d61 100644 --- a/FModel/Framework/Command.cs +++ b/FModel/Framework/Command.cs @@ -1,19 +1,18 @@ using System; using System.Windows.Input; -namespace FModel.Framework +namespace FModel.Framework; + +public abstract class Command : ICommand { - public abstract class Command : ICommand + public abstract void Execute(object parameter); + + public abstract bool CanExecute(object parameter); + + public void RaiseCanExecuteChanged() { - public abstract void Execute(object parameter); - - public abstract bool CanExecute(object parameter); - - public void RaiseCanExecuteChanged() - { - CanExecuteChanged?.Invoke(this, EventArgs.Empty); - } - - public event EventHandler CanExecuteChanged; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); } + + public event EventHandler CanExecuteChanged; } \ No newline at end of file diff --git a/FModel/Framework/CustomSKShaper.cs b/FModel/Framework/CustomSKShaper.cs index 6c92d43b..ecd8009c 100644 --- a/FModel/Framework/CustomSKShaper.cs +++ b/FModel/Framework/CustomSKShaper.cs @@ -4,93 +4,87 @@ using SkiaSharp; using SkiaSharp.HarfBuzz; using Buffer = HarfBuzzSharp.Buffer; -namespace FModel.Framework +namespace FModel.Framework; + +public class CustomSKShaper : SKShaper { - public class CustomSKShaper : SKShaper + private const int _FONT_SIZE_SCALE = 512; + private readonly Font _font; + + public CustomSKShaper(SKTypeface typeface) : base(typeface) { - private const int _FONT_SIZE_SCALE = 512; - private readonly Font _font; - private readonly Buffer _buffer; + using var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob(); + using var face = new Face(blob, index); + face.Index = index; + face.UnitsPerEm = Typeface.UnitsPerEm; - public CustomSKShaper(SKTypeface typeface) : base(typeface) + _font = new Font(face); + _font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE); + _font.SetFunctionsOpenType(); + } + + public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (paint == null) + throw new ArgumentNullException(nameof(paint)); + + // do the shaping + _font.Shape(buffer); + + // get the shaping results + var len = buffer.Length; + var info = buffer.GlyphInfos; + var pos = buffer.GlyphPositions; + + // get the sizes + var textSizeY = paint.TextSize / _FONT_SIZE_SCALE; + var textSizeX = textSizeY * paint.TextScaleX; + + var points = new SKPoint[len]; + var clusters = new uint[len]; + var codepoints = new uint[len]; + + for (var i = 0; i < len; i++) { - using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob()) - using (var face = new Face(blob, index)) - { - face.Index = index; - face.UnitsPerEm = Typeface.UnitsPerEm; + // move the cursor + xOffset += pos[i].XAdvance * textSizeX; + yOffset += pos[i].YAdvance * textSizeY; - _font = new Font(face); - _font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE); - _font.SetFunctionsOpenType(); - } - - _buffer = new Buffer(); + codepoints[i] = info[i].Codepoint; + clusters[i] = info[i].Cluster; + points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY); } - public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint) + return new Result(codepoints, clusters, points); + } + + public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint); + + public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint) + { + if (string.IsNullOrEmpty(text)) + return new Result(); + + using var buffer = new Buffer(); + switch (paint.TextEncoding) { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - if (paint == null) - throw new ArgumentNullException(nameof(paint)); - - // do the shaping - _font.Shape(buffer); - - // get the shaping results - var len = buffer.Length; - var info = buffer.GlyphInfos; - var pos = buffer.GlyphPositions; - - // get the sizes - var textSizeY = paint.TextSize / _FONT_SIZE_SCALE; - var textSizeX = textSizeY * paint.TextScaleX; - - var points = new SKPoint[len]; - var clusters = new uint[len]; - var codepoints = new uint[len]; - - for (var i = 0; i < len; i++) - { - // move the cursor - xOffset += pos[i].XAdvance * textSizeX; - yOffset += pos[i].YAdvance * textSizeY; - - codepoints[i] = info[i].Codepoint; - clusters[i] = info[i].Cluster; - points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY); - } - - return new Result(codepoints, clusters, points); + case SKTextEncoding.Utf8: + buffer.AddUtf8(text); + break; + case SKTextEncoding.Utf16: + buffer.AddUtf16(text); + break; + case SKTextEncoding.Utf32: + buffer.AddUtf32(text); + break; + default: + throw new NotSupportedException("TextEncoding of type GlyphId is not supported."); } - public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint); - - public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint) - { - if (string.IsNullOrEmpty(text)) - return new Result(); - - using var buffer = new Buffer(); - switch (paint.TextEncoding) - { - case SKTextEncoding.Utf8: - buffer.AddUtf8(text); - break; - case SKTextEncoding.Utf16: - buffer.AddUtf16(text); - break; - case SKTextEncoding.Utf32: - buffer.AddUtf32(text); - break; - default: - throw new NotSupportedException("TextEncoding of type GlyphId is not supported."); - } - - buffer.GuessSegmentProperties(); - return Shape(buffer, xOffset, yOffset, paint); - } + buffer.GuessSegmentProperties(); + return Shape(buffer, xOffset, yOffset, paint); } } \ No newline at end of file diff --git a/FModel/Framework/FullyObservableCollection.cs b/FModel/Framework/FullyObservableCollection.cs index bc1e399e..abb00e09 100644 --- a/FModel/Framework/FullyObservableCollection.cs +++ b/FModel/Framework/FullyObservableCollection.cs @@ -4,114 +4,113 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; -namespace FModel.Framework +namespace FModel.Framework; + +public class FullyObservableCollection : ObservableCollection where T : INotifyPropertyChanged { - public class FullyObservableCollection : ObservableCollection where T : INotifyPropertyChanged + /// + /// Occurs when a property is changed within an item. + /// + public event EventHandler ItemPropertyChanged; + + public FullyObservableCollection() { - /// - /// Occurs when a property is changed within an item. - /// - public event EventHandler ItemPropertyChanged; + } - public FullyObservableCollection() + public FullyObservableCollection(List list) : base(list) + { + ObserveAll(); + } + + public FullyObservableCollection(IEnumerable enumerable) : base(enumerable) + { + ObserveAll(); + } + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (e.Action is NotifyCollectionChangedAction.Remove or + NotifyCollectionChangedAction.Replace) { - } - - public FullyObservableCollection(List list) : base(list) - { - ObserveAll(); - } - - public FullyObservableCollection(IEnumerable enumerable) : base(enumerable) - { - ObserveAll(); - } - - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) - { - if (e.Action is NotifyCollectionChangedAction.Remove or - NotifyCollectionChangedAction.Replace) - { - foreach (T item in e.OldItems) - item.PropertyChanged -= ChildPropertyChanged; - } - - if (e.Action is NotifyCollectionChangedAction.Add or - NotifyCollectionChangedAction.Replace) - { - foreach (T item in e.NewItems) - item.PropertyChanged += ChildPropertyChanged; - } - - base.OnCollectionChanged(e); - } - - protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e) - { - ItemPropertyChanged?.Invoke(this, e); - } - - protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e) - { - OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e)); - } - - protected override void ClearItems() - { - foreach (T item in Items) + foreach (T item in e.OldItems) item.PropertyChanged -= ChildPropertyChanged; - - base.ClearItems(); } - private void ObserveAll() + if (e.Action is NotifyCollectionChangedAction.Add or + NotifyCollectionChangedAction.Replace) { - foreach (var item in Items) + foreach (T item in e.NewItems) item.PropertyChanged += ChildPropertyChanged; } - private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) - { - var typedSender = (T) sender; - var i = Items.IndexOf(typedSender); + base.OnCollectionChanged(e); + } - if (i < 0) - throw new ArgumentException("Received property notification from item not in collection"); + protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e) + { + ItemPropertyChanged?.Invoke(this, e); + } - OnItemPropertyChanged(i, e); - } + protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e) + { + OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e)); + } + + protected override void ClearItems() + { + foreach (T item in Items) + item.PropertyChanged -= ChildPropertyChanged; + + base.ClearItems(); + } + + private void ObserveAll() + { + foreach (var item in Items) + item.PropertyChanged += ChildPropertyChanged; + } + + private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var typedSender = (T) sender; + var i = Items.IndexOf(typedSender); + + if (i < 0) + throw new ArgumentException("Received property notification from item not in collection"); + + OnItemPropertyChanged(i, e); + } +} + +/// +/// Provides data for the event. +/// +public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs +{ + /// + /// Gets the index in the collection for which the property change has occurred. + /// + /// + /// Index in parent collection. + /// + public int CollectionIndex { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The index in the collection of changed item. + /// The name of the property that changed. + public ItemPropertyChangedEventArgs(int index, string name) : base(name) + { + CollectionIndex = index; } /// - /// Provides data for the event. + /// Initializes a new instance of the class. /// - public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs + /// The index. + /// The instance containing the event data. + public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName) { - /// - /// Gets the index in the collection for which the property change has occurred. - /// - /// - /// Index in parent collection. - /// - public int CollectionIndex { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The index in the collection of changed item. - /// The name of the property that changed. - public ItemPropertyChangedEventArgs(int index, string name) : base(name) - { - CollectionIndex = index; - } - - /// - /// Initializes a new instance of the class. - /// - /// The index. - /// The instance containing the event data. - public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName) - { - } } } \ No newline at end of file diff --git a/FModel/Framework/Hotkey.cs b/FModel/Framework/Hotkey.cs index f20116a2..11445f9c 100644 --- a/FModel/Framework/Hotkey.cs +++ b/FModel/Framework/Hotkey.cs @@ -1,50 +1,49 @@ using System.Text; using System.Windows.Input; -namespace FModel.Framework +namespace FModel.Framework; + +public class Hotkey : ViewModel { - public class Hotkey : ViewModel + private Key _key; + public Key Key { - private Key _key; - public Key Key - { - get => _key; - set => SetProperty(ref _key, value); - } + get => _key; + set => SetProperty(ref _key, value); + } - private ModifierKeys _modifiers; - public ModifierKeys Modifiers - { - get => _modifiers; - set => SetProperty(ref _modifiers, value); - } + private ModifierKeys _modifiers; + public ModifierKeys Modifiers + { + get => _modifiers; + set => SetProperty(ref _modifiers, value); + } - public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None) - { - Key = key; - Modifiers = modifiers; - } + public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None) + { + Key = key; + Modifiers = modifiers; + } - public bool IsTriggered(Key e) - { - return e == Key && Keyboard.Modifiers.HasFlag(Modifiers); - } + public bool IsTriggered(Key e) + { + return e == Key && Keyboard.Modifiers.HasFlag(Modifiers); + } - public override string ToString() - { - var str = new StringBuilder(); + public override string ToString() + { + var str = new StringBuilder(); - if (Modifiers.HasFlag(ModifierKeys.Control)) - str.Append("Ctrl + "); - if (Modifiers.HasFlag(ModifierKeys.Shift)) - str.Append("Shift + "); - if (Modifiers.HasFlag(ModifierKeys.Alt)) - str.Append("Alt + "); - if (Modifiers.HasFlag(ModifierKeys.Windows)) - str.Append("Win + "); + if (Modifiers.HasFlag(ModifierKeys.Control)) + str.Append("Ctrl + "); + if (Modifiers.HasFlag(ModifierKeys.Shift)) + str.Append("Shift + "); + if (Modifiers.HasFlag(ModifierKeys.Alt)) + str.Append("Alt + "); + if (Modifiers.HasFlag(ModifierKeys.Windows)) + str.Append("Win + "); - str.Append(Key); - return str.ToString(); - } + str.Append(Key); + return str.ToString(); } } \ No newline at end of file diff --git a/FModel/Framework/JsonNetSerializer.cs b/FModel/Framework/JsonNetSerializer.cs index 9d9e50b8..6bc2ee4d 100644 --- a/FModel/Framework/JsonNetSerializer.cs +++ b/FModel/Framework/JsonNetSerializer.cs @@ -4,40 +4,39 @@ using Newtonsoft.Json.Serialization; using RestSharp; using RestSharp.Serialization; -namespace FModel.Framework +namespace FModel.Framework; + +public class JsonNetSerializer : IRestSerializer { - public class JsonNetSerializer : IRestSerializer + public static readonly JsonSerializerSettings SerializerSettings = new() { - public static readonly JsonSerializerSettings SerializerSettings = new() - { - NullValueHandling = NullValueHandling.Ignore, - MissingMemberHandling = MissingMemberHandling.Ignore, - ContractResolver = new CamelCasePropertyNamesContractResolver() - }; + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; - public string Serialize(object obj) - { - return JsonConvert.SerializeObject(obj); - } - - [Obsolete] - public string Serialize(Parameter parameter) - { - return JsonConvert.SerializeObject(parameter.Value); - } - - public T Deserialize(IRestResponse response) - { - return JsonConvert.DeserializeObject(response.Content, SerializerSettings); - } - - public string[] SupportedContentTypes { get; } = - { - "application/json", "application/json; charset=UTF-8" - }; - - public string ContentType { get; set; } = "application/json; charset=UTF-8"; - - public DataFormat DataFormat => DataFormat.Json; + public string Serialize(object obj) + { + return JsonConvert.SerializeObject(obj); } + + [Obsolete] + public string Serialize(Parameter parameter) + { + return JsonConvert.SerializeObject(parameter.Value); + } + + public T Deserialize(IRestResponse response) + { + return JsonConvert.DeserializeObject(response.Content, SerializerSettings); + } + + public string[] SupportedContentTypes { get; } = + { + "application/json", "application/json; charset=UTF-8" + }; + + public string ContentType { get; set; } = "application/json; charset=UTF-8"; + + public DataFormat DataFormat => DataFormat.Json; } \ No newline at end of file diff --git a/FModel/Framework/NavigationList.cs b/FModel/Framework/NavigationList.cs index 7e32beea..77a45467 100644 --- a/FModel/Framework/NavigationList.cs +++ b/FModel/Framework/NavigationList.cs @@ -1,34 +1,46 @@ using System.Collections.Generic; -namespace FModel.Framework +namespace FModel.Framework; + +public class NavigationList : List { - public class NavigationList : List + private int _currentIndex; + public int CurrentIndex { - private int _currentIndex = 0; - public int CurrentIndex + get { - get + if (_currentIndex > Count - 1) { - if (_currentIndex > Count - 1) { _currentIndex = Count - 1; } - if (_currentIndex < 0) { _currentIndex = 0; } - return _currentIndex; + _currentIndex = Count - 1; } - set { _currentIndex = value; } - } - public T MoveNext - { - get { _currentIndex++; return this[CurrentIndex]; } - } + if (_currentIndex < 0) + { + _currentIndex = 0; + } - public T MovePrevious - { - get { _currentIndex--; return this[CurrentIndex]; } + return _currentIndex; } + set => _currentIndex = value; + } - public T Current + public T MoveNext + { + get { - get { return this[CurrentIndex]; } + _currentIndex++; + return this[CurrentIndex]; } } -} + + public T MovePrevious + { + get + { + _currentIndex--; + return this[CurrentIndex]; + } + } + + public T Current => this[CurrentIndex]; +} \ No newline at end of file diff --git a/FModel/Framework/RangeObservableCollection.cs b/FModel/Framework/RangeObservableCollection.cs index 130683c1..1372b522 100644 --- a/FModel/Framework/RangeObservableCollection.cs +++ b/FModel/Framework/RangeObservableCollection.cs @@ -3,40 +3,39 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; -namespace FModel.Framework +namespace FModel.Framework; + +public sealed class RangeObservableCollection : ObservableCollection { - public sealed class RangeObservableCollection : ObservableCollection + private bool _suppressNotification; + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { - private bool _suppressNotification; + if (!_suppressNotification) + base.OnCollectionChanged(e); + } - protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) - { - if (!_suppressNotification) - base.OnCollectionChanged(e); - } + public void AddRange(IEnumerable list) + { + if (list == null) + throw new ArgumentNullException(nameof(list)); - public void AddRange(IEnumerable list) - { - if (list == null) - throw new ArgumentNullException(nameof(list)); + _suppressNotification = true; - _suppressNotification = true; + foreach (var item in list) + Add(item); - foreach (var item in list) - Add(item); + _suppressNotification = false; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } - _suppressNotification = false; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } + public void SetSuppressionState(bool state) + { + _suppressNotification = state; + } - public void SetSuppressionState(bool state) - { - _suppressNotification = state; - } - - public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset) - { - OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction)); - } + public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction)); } } \ No newline at end of file diff --git a/FModel/Framework/ViewModel.cs b/FModel/Framework/ViewModel.cs index e1a55336..1efeaa8c 100644 --- a/FModel/Framework/ViewModel.cs +++ b/FModel/Framework/ViewModel.cs @@ -6,71 +6,70 @@ using System.Linq; using System.Runtime.CompilerServices; using Newtonsoft.Json; -namespace FModel.Framework +namespace FModel.Framework; + +public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo { - public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo + private readonly Dictionary> _validationErrors = new(); + + public string this[string propertyName] { - private readonly Dictionary> _validationErrors = new(); - - public string this[string propertyName] - { - get - { - if (string.IsNullOrEmpty(propertyName)) - return Error; - - return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty; - } - } - - [JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors()); - [JsonIgnore] public bool HasErrors => _validationErrors.Any(); - - public IEnumerable GetErrors(string propertyName) + get { if (string.IsNullOrEmpty(propertyName)) - return _validationErrors.SelectMany(kvp => kvp.Value); + return Error; - return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty(); - } - - private IEnumerable GetAllErrors() - { - return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e)); - } - - public void AddValidationError(string propertyName, string errorMessage) - { - if (!_validationErrors.ContainsKey(propertyName)) - _validationErrors.Add(propertyName, new List()); - - _validationErrors[propertyName].Add(errorMessage); - } - - public void ClearValidationErrors(string propertyName) - { - if (_validationErrors.ContainsKey(propertyName)) - _validationErrors.Remove(propertyName); - } - - public event PropertyChangedEventHandler PropertyChanged; -#pragma warning disable 67 - public event EventHandler ErrorsChanged; -#pragma warning disable 67 - - protected void RaisePropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) - { - if (EqualityComparer.Default.Equals(storage, value)) - return false; - - storage = value; - RaisePropertyChanged(propertyName); - return true; + return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty; } } + + [JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors()); + [JsonIgnore] public bool HasErrors => _validationErrors.Any(); + + public IEnumerable GetErrors(string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + return _validationErrors.SelectMany(kvp => kvp.Value); + + return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty(); + } + + private IEnumerable GetAllErrors() + { + return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e)); + } + + public void AddValidationError(string propertyName, string errorMessage) + { + if (!_validationErrors.ContainsKey(propertyName)) + _validationErrors.Add(propertyName, new List()); + + _validationErrors[propertyName].Add(errorMessage); + } + + public void ClearValidationErrors(string propertyName) + { + if (_validationErrors.ContainsKey(propertyName)) + _validationErrors.Remove(propertyName); + } + + public event PropertyChangedEventHandler PropertyChanged; +#pragma warning disable 67 + public event EventHandler ErrorsChanged; +#pragma warning disable 67 + + protected void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(storage, value)) + return false; + + storage = value; + RaisePropertyChanged(propertyName); + return true; + } } \ No newline at end of file diff --git a/FModel/Framework/ViewModelCommand.cs b/FModel/Framework/ViewModelCommand.cs index 4d064f2e..66aed244 100644 --- a/FModel/Framework/ViewModelCommand.cs +++ b/FModel/Framework/ViewModelCommand.cs @@ -1,50 +1,49 @@ using System; -namespace FModel.Framework +namespace FModel.Framework; + +public abstract class ViewModelCommand : Command where TContextViewModel : ViewModel { - public abstract class ViewModelCommand : Command where TContextViewModel : ViewModel + private WeakReference _parent; + + public TContextViewModel ContextViewModel { - private WeakReference _parent; - - public TContextViewModel ContextViewModel + get { - get - { - if (_parent is {IsAlive: true}) - return (TContextViewModel) _parent.Target; + if (_parent is { IsAlive: true }) + return (TContextViewModel) _parent.Target; - return null; - } - - private set - { - if (ContextViewModel == value) - return; - - _parent = value != null ? new WeakReference(value) : null; - } + return null; } - protected ViewModelCommand(TContextViewModel contextViewModel) + private set { - ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/; - } + if (ContextViewModel == value) + return; - public sealed override void Execute(object parameter) - { - Execute(ContextViewModel, parameter); - } - - public abstract void Execute(TContextViewModel contextViewModel, object parameter); - - public sealed override bool CanExecute(object parameter) - { - return CanExecute(ContextViewModel, parameter); - } - - public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter) - { - return true; + _parent = value != null ? new WeakReference(value) : null; } } + + protected ViewModelCommand(TContextViewModel contextViewModel) + { + ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/; + } + + public sealed override void Execute(object parameter) + { + Execute(ContextViewModel, parameter); + } + + public abstract void Execute(TContextViewModel contextViewModel, object parameter); + + public sealed override bool CanExecute(object parameter) + { + return CanExecute(ContextViewModel, parameter); + } + + public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter) + { + return true; + } } \ No newline at end of file diff --git a/FModel/Helper.cs b/FModel/Helper.cs index f300e538..8e1586c8 100644 --- a/FModel/Helper.cs +++ b/FModel/Helper.cs @@ -3,86 +3,85 @@ using System.Linq; using System.Runtime.InteropServices; using System.Windows; -namespace FModel +namespace FModel; + +public static class Helper { - public static class Helper + [StructLayout(LayoutKind.Explicit)] + private struct NanUnion { - [StructLayout(LayoutKind.Explicit)] - private struct NanUnion + [FieldOffset(0)] + internal double DoubleValue; + [FieldOffset(0)] + internal readonly ulong UlongValue; + } + + public static void OpenWindow(string windowName, Action action) where T : Window + { + if (!IsWindowOpen(windowName)) { - [FieldOffset(0)] - internal double DoubleValue; - [FieldOffset(0)] - internal readonly ulong UlongValue; + action(); } - - public static void OpenWindow(string windowName, Action action) where T : Window + else { - if (!IsWindowOpen(windowName)) - { - action(); - } - else - { - var w = GetOpenedWindow(windowName); - if (windowName == "Search View") w.WindowState = WindowState.Normal; - w.Focus(); - } - } - - public static T GetWindow(string windowName, Action action) where T : Window - { - if (!IsWindowOpen(windowName)) - { - action(); - } - - var ret = (T) GetOpenedWindow(windowName); - ret.Focus(); - ret.Activate(); - return ret; - } - - public static void CloseWindow(string windowName) where T : Window - { - if (!IsWindowOpen(windowName)) return; - GetOpenedWindow(windowName).Close(); - } - - private static bool IsWindowOpen(string name = "") where T : Window - { - return string.IsNullOrEmpty(name) - ? Application.Current.Windows.OfType().Any() - : Application.Current.Windows.OfType().Any(w => w.Title.Equals(name)); - } - - private static Window GetOpenedWindow(string name) where T : Window - { - return Application.Current.Windows.OfType().FirstOrDefault(w => w.Title.Equals(name)); - } - - public static bool IsNaN(double value) - { - var t = new NanUnion {DoubleValue = value}; - var exp = t.UlongValue & 0xfff0000000000000; - var man = t.UlongValue & 0x000fffffffffffff; - return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0; - } - - public static bool AreVirtuallyEqual(double d1, double d2) - { - if (double.IsPositiveInfinity(d1)) - return double.IsPositiveInfinity(d2); - - if (double.IsNegativeInfinity(d1)) - return double.IsNegativeInfinity(d2); - - if (IsNaN(d1)) - return IsNaN(d2); - - var n = d1 - d2; - var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15; - return -d < n && d > n; + var w = GetOpenedWindow(windowName); + if (windowName == "Search View") w.WindowState = WindowState.Normal; + w.Focus(); } } -} + + public static T GetWindow(string windowName, Action action) where T : Window + { + if (!IsWindowOpen(windowName)) + { + action(); + } + + var ret = (T) GetOpenedWindow(windowName); + ret.Focus(); + ret.Activate(); + return ret; + } + + public static void CloseWindow(string windowName) where T : Window + { + if (!IsWindowOpen(windowName)) return; + GetOpenedWindow(windowName).Close(); + } + + private static bool IsWindowOpen(string name = "") where T : Window + { + return string.IsNullOrEmpty(name) + ? Application.Current.Windows.OfType().Any() + : Application.Current.Windows.OfType().Any(w => w.Title.Equals(name)); + } + + private static Window GetOpenedWindow(string name) where T : Window + { + return Application.Current.Windows.OfType().FirstOrDefault(w => w.Title.Equals(name)); + } + + public static bool IsNaN(double value) + { + var t = new NanUnion { DoubleValue = value }; + var exp = t.UlongValue & 0xfff0000000000000; + var man = t.UlongValue & 0x000fffffffffffff; + return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0; + } + + public static bool AreVirtuallyEqual(double d1, double d2) + { + if (double.IsPositiveInfinity(d1)) + return double.IsPositiveInfinity(d2); + + if (double.IsNegativeInfinity(d1)) + return double.IsNegativeInfinity(d2); + + if (IsNaN(d1)) + return IsNaN(d2); + + var n = d1 - d2; + var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15; + return -d < n && d > n; + } +} \ No newline at end of file diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 1d483d2c..b6c46edf 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -1,249 +1 @@ -using System; -using System.ComponentModel; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using AdonisUI.Controls; -using FModel.Extensions; -using FModel.Services; -using FModel.Settings; -using FModel.ViewModels; -using FModel.Views; -using FModel.Views.Resources.Controls; -using ICSharpCode.AvalonEdit.Editing; - -namespace FModel -{ - /// - /// Interaction logic for MainWindow.xaml - /// - public partial class MainWindow - { - public static MainWindow YesWeCats; - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - private DiscordHandler _discordHandler => DiscordService.DiscordHandler; - - public MainWindow() - { - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers)}), OnAutoTriggerExecuted)); - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers)}), OnAutoTriggerExecuted)); - CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection - {new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers)}), OnAutoTriggerExecuted)); - CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection {new KeyGesture(Key.F12)}), OnMappingsReload)); - CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (s, e) => OnOpenAvalonFinder())); - - DataContext = _applicationView; - InitializeComponent(); - - FLogger.Logger = LogRtbName; - YesWeCats = this; - } - - private void OnClosing(object sender, CancelEventArgs e) - { - _applicationView.CustomDirectories.Save(); - _discordHandler.Dispose(); - } - - private async void OnLoaded(object sender, RoutedEventArgs e) - { -#if !DEBUG - ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); -#endif - - switch (UserSettings.Default.AesReload) - { - case EAesReload.Always: - await _applicationView.CUE4Parse.RefreshAes(); - break; - case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today: - UserSettings.Default.LastAesReload = DateTime.Today; - await _applicationView.CUE4Parse.RefreshAes(); - break; - } - - await _applicationView.CUE4Parse.InitInformation(); - await _applicationView.CUE4Parse.Initialize(); - await _applicationView.AesManager.InitAes(); - await _applicationView.AesManager.UpdateProvider(true); - await _applicationView.CUE4Parse.InitBenMappings(); - await _applicationView.InitVgmStream(); - await _applicationView.InitOodle(); - - if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always) - _discordHandler.Initialize(_applicationView.CUE4Parse.Game); - } - - private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e) - { - RootGrid.ColumnDefinitions[0].Width = GridLength.Auto; - } - - private void OnWindowKeyDown(object sender, KeyEventArgs e) - { - if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) - return; - - if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape) - { - _applicationView.Status = EStatusKind.Stopping; - _threadWorkerView.Cancel(); - } - else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) - OnSearchViewClick(null, null); - else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage) - _applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage(); - else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage) - _applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage(); - else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key)) - _applicationView.CUE4Parse.TabControl.AddTab(); - else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key)) - _applicationView.CUE4Parse.TabControl.RemoveTab(); - else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key)) - _applicationView.CUE4Parse.TabControl.GoLeftTab(); - else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key)) - _applicationView.CUE4Parse.TabControl.GoRightTab(); - else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0) - LeftTabControl.SelectedIndex--; - else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1) - LeftTabControl.SelectedIndex++; - } - - private void OnSearchViewClick(object sender, RoutedEventArgs e) - { - Helper.OpenWindow("Search View", () => new SearchView().Show()); - } - - private void OnTabItemChange(object sender, SelectionChangedEventArgs e) - { - if (e.OriginalSource is not TabControl tabControl) - return; - - (tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus(); - } - - private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e) - { - await _applicationView.CUE4Parse.InitBenMappings(); - } - - private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e) - { - switch ((e.Command as RoutedCommand)?.Name) - { - case "AutoSaveProps": - UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps; - break; - case "AutoSaveTextures": - UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures; - break; - case "AutoOpenSounds": - UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds; - break; - } - } - - private void OnOpenAvalonFinder() - { - _applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true; - AvalonEditor.YesWeSearch.Focus(); - AvalonEditor.YesWeSearch.SelectAll(); - } - - private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (sender is not TreeView {SelectedItem: TreeItem treeItem} || treeItem.Folders.Count > 0) return; - - LeftTabControl.SelectedIndex++; - } - - private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (sender is not ListBox listBox) return; - - var selectedItems = listBox.SelectedItems.Cast().ToList(); - await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); - } - - private async void OnFolderExtractClick(object sender, RoutedEventArgs e) - { - if (AssetsFolderName.SelectedItem is TreeItem folder) - { - await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); }); - } - } - - private async void OnFolderExportClick(object sender, RoutedEventArgs e) - { - if (AssetsFolderName.SelectedItem is TreeItem folder) - { - await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); }); - } - } - - private async void OnFolderSaveClick(object sender, RoutedEventArgs e) - { - if (AssetsFolderName.SelectedItem is TreeItem folder) - { - await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); }); - } - } - - private void OnSaveDirectoryClick(object sender, RoutedEventArgs e) - { - if (AssetsFolderName.SelectedItem is not TreeItem folder) return; - - _applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint)); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true); - } - - private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e) - { - if (AssetsFolderName.SelectedItem is not TreeItem folder) return; - Clipboard.SetText(folder.PathAtThisPoint); - } - - private void OnDeleteSearchClick(object sender, RoutedEventArgs e) - { - AssetsSearchName.Text = string.Empty; - AssetsListName.ScrollIntoView(AssetsListName.SelectedItem); - } - - private void OnFilterTextChanged(object sender, TextChangedEventArgs e) - { - if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder) - return; - - var filters = textBox.Text.Trim().Split(' '); - folder.AssetsList.AssetsView.Filter = o => - { - return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); - }; - } - - private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - if (!_applicationView.IsReady || sender is not ListBox listBox) return; - UserSettings.Default.LoadingMode = ELoadingMode.Multiple; - _applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems); - } - - private async void OnPreviewKeyDown(object sender, KeyEventArgs e) - { - if (!_applicationView.IsReady || sender is not ListBox listBox) return; - - switch (e.Key) - { - case Key.Enter: - var selectedItems = listBox.SelectedItems.Cast().ToList(); - await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); - break; - } - } - } -} +using System; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using AdonisUI.Controls; using FModel.Extensions; using FModel.Services; using FModel.Settings; using FModel.ViewModels; using FModel.Views; using FModel.Views.Resources.Controls; using ICSharpCode.AvalonEdit.Editing; namespace FModel; /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow { public static MainWindow YesWeCats; private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; private DiscordHandler _discordHandler => DiscordService.DiscordHandler; public MainWindow() { CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveProps", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoSaveProps.Key, UserSettings.Default.AutoSaveProps.Modifiers) }), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoSaveTextures", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoSaveTextures.Key, UserSettings.Default.AutoSaveTextures.Modifiers) }), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("AutoOpenSounds", typeof(MainWindow), new InputGestureCollection { new KeyGesture(UserSettings.Default.AutoOpenSounds.Key, UserSettings.Default.AutoOpenSounds.Modifiers) }), OnAutoTriggerExecuted)); CommandBindings.Add(new CommandBinding(new RoutedCommand("ReloadMappings", typeof(MainWindow), new InputGestureCollection { new KeyGesture(Key.F12) }), OnMappingsReload)); CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, (_, _) => OnOpenAvalonFinder())); DataContext = _applicationView; InitializeComponent(); FLogger.Logger = LogRtbName; YesWeCats = this; } private void OnClosing(object sender, CancelEventArgs e) { _applicationView.CustomDirectories.Save(); _discordHandler.Dispose(); } private async void OnLoaded(object sender, RoutedEventArgs e) { #if !DEBUG ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); #endif switch (UserSettings.Default.AesReload) { case EAesReload.Always: await _applicationView.CUE4Parse.RefreshAes(); break; case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today: UserSettings.Default.LastAesReload = DateTime.Today; await _applicationView.CUE4Parse.RefreshAes(); break; } await _applicationView.CUE4Parse.InitInformation(); await _applicationView.CUE4Parse.Initialize(); await _applicationView.AesManager.InitAes(); await _applicationView.AesManager.UpdateProvider(true); await _applicationView.CUE4Parse.InitBenMappings(); await _applicationView.InitVgmStream(); await _applicationView.InitOodle(); if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always) _discordHandler.Initialize(_applicationView.CUE4Parse.Game); } private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e) { RootGrid.ColumnDefinitions[0].Width = GridLength.Auto; } private void OnWindowKeyDown(object sender, KeyEventArgs e) { if (e.OriginalSource is TextBox || e.OriginalSource is TextArea && Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) return; if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape) { _applicationView.Status = EStatusKind.Stopping; _threadWorkerView.Cancel(); } else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) OnSearchViewClick(null, null); else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage) _applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage(); else if (e.Key == Key.Right && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage) _applicationView.CUE4Parse.TabControl.SelectedTab.GoNextImage(); else if (UserSettings.Default.AssetAddTab.IsTriggered(e.Key)) _applicationView.CUE4Parse.TabControl.AddTab(); else if (UserSettings.Default.AssetRemoveTab.IsTriggered(e.Key)) _applicationView.CUE4Parse.TabControl.RemoveTab(); else if (UserSettings.Default.AssetLeftTab.IsTriggered(e.Key)) _applicationView.CUE4Parse.TabControl.GoLeftTab(); else if (UserSettings.Default.AssetRightTab.IsTriggered(e.Key)) _applicationView.CUE4Parse.TabControl.GoRightTab(); else if (UserSettings.Default.DirLeftTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex > 0) LeftTabControl.SelectedIndex--; else if (UserSettings.Default.DirRightTab.IsTriggered(e.Key) && LeftTabControl.SelectedIndex < LeftTabControl.Items.Count - 1) LeftTabControl.SelectedIndex++; } private void OnSearchViewClick(object sender, RoutedEventArgs e) { Helper.OpenWindow("Search View", () => new SearchView().Show()); } private void OnTabItemChange(object sender, SelectionChangedEventArgs e) { if (e.OriginalSource is not TabControl tabControl) return; (tabControl.SelectedItem as System.Windows.Controls.TabItem)?.Focus(); } private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e) { await _applicationView.CUE4Parse.InitBenMappings(); } private void OnAutoTriggerExecuted(object sender, ExecutedRoutedEventArgs e) { switch ((e.Command as RoutedCommand)?.Name) { case "AutoSaveProps": UserSettings.Default.IsAutoSaveProps = !UserSettings.Default.IsAutoSaveProps; break; case "AutoSaveTextures": UserSettings.Default.IsAutoSaveTextures = !UserSettings.Default.IsAutoSaveTextures; break; case "AutoOpenSounds": UserSettings.Default.IsAutoOpenSounds = !UserSettings.Default.IsAutoOpenSounds; break; } } private void OnOpenAvalonFinder() { _applicationView.CUE4Parse.TabControl.SelectedTab.HasSearchOpen = true; AvalonEditor.YesWeSearch.Focus(); AvalonEditor.YesWeSearch.SelectAll(); } private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e) { if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return; LeftTabControl.SelectedIndex++; } private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e) { if (sender is not ListBox listBox) return; var selectedItems = listBox.SelectedItems.Cast().ToList(); await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); } private async void OnFolderExtractClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is TreeItem folder) { await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractFolder(cancellationToken, folder); }); } } private async void OnFolderExportClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is TreeItem folder) { await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); }); } } private async void OnFolderSaveClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is TreeItem folder) { await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); }); } } private void OnSaveDirectoryClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is not TreeItem folder) return; _applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint)); FLogger.AppendInformation(); FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true); } private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is not TreeItem folder) return; Clipboard.SetText(folder.PathAtThisPoint); } private void OnDeleteSearchClick(object sender, RoutedEventArgs e) { AssetsSearchName.Text = string.Empty; AssetsListName.ScrollIntoView(AssetsListName.SelectedItem); } private void OnFilterTextChanged(object sender, TextChangedEventArgs e) { if (sender is not TextBox textBox || AssetsFolderName.SelectedItem is not TreeItem folder) return; var filters = textBox.Text.Trim().Split(' '); folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FullPath.SubstringAfterLast('/').Contains(x, StringComparison.OrdinalIgnoreCase)); }; } private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e) { if (!_applicationView.IsReady || sender is not ListBox listBox) return; UserSettings.Default.LoadingMode = ELoadingMode.Multiple; _applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems); } private async void OnPreviewKeyDown(object sender, KeyEventArgs e) { if (!_applicationView.IsReady || sender is not ListBox listBox) return; switch (e.Key) { case Key.Enter: var selectedItems = listBox.SelectedItems.Cast().ToList(); await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); }); break; } } } \ No newline at end of file diff --git a/FModel/ViewModels/AesManagerViewModel.cs b/FModel/ViewModels/AesManagerViewModel.cs index e962d0a0..1a7b0df0 100644 --- a/FModel/ViewModels/AesManagerViewModel.cs +++ b/FModel/ViewModels/AesManagerViewModel.cs @@ -10,144 +10,143 @@ using FModel.Settings; using FModel.ViewModels.ApiEndpoints.Models; using Serilog; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class AesManagerViewModel : ViewModel { - public class AesManagerViewModel : ViewModel + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + + public FullyObservableCollection AesKeys { get; private set; } // holds all aes keys even the main one + public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui + public bool HasChange { get; set; } + + private AesResponse _keysFromSettings; + private HashSet _uniqueGuids; + private readonly CUE4ParseViewModel _cue4Parse; + private readonly FileItem _mainKey = new("Main Static Key", 0) { Guid = Constants.ZERO_GUID }; // just so main key gets refreshed in the ui + + public AesManagerViewModel(CUE4ParseViewModel cue4Parse) { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + _cue4Parse = cue4Parse; + HasChange = false; + } - public FullyObservableCollection AesKeys { get; private set; } // holds all aes keys even the main one - public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui - public bool HasChange { get; set; } - - private AesResponse _keysFromSettings; - private HashSet _uniqueGuids; - private readonly CUE4ParseViewModel _cue4Parse; - private readonly FileItem _mainKey = new("Main Static Key", 0) {Guid = Constants.ZERO_GUID}; // just so main key gets refreshed in the ui - - public AesManagerViewModel(CUE4ParseViewModel cue4Parse) + public async Task InitAes() + { + await _threadWorkerView.Begin(_ => { - _cue4Parse = cue4Parse; - HasChange = false; - } - - public async Task InitAes() - { - await _threadWorkerView.Begin(_ => + if (_cue4Parse.Game == FGame.Unknown && + UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings)) { - if (_cue4Parse.Game == FGame.Unknown && - UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings)) - { - _keysFromSettings = settings.AesKeys; - } - else - { - _keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game]; - } - - _keysFromSettings ??= new AesResponse - { - MainKey = string.Empty, - DynamicKeys = null - }; - - _mainKey.Key = FixKey(_keysFromSettings.MainKey); - AesKeys = new FullyObservableCollection(EnumerateAesKeys()); - AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged; - AesKeysView = new ListCollectionView(AesKeys) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}}; - }); - } - - private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e) - { - if (e.PropertyName != "Key" || sender is not FullyObservableCollection collection) - return; - - var key = FixKey(collection[e.CollectionIndex].Key); - if (e.CollectionIndex == 0) - { - if (!HasChange) - HasChange = FixKey(_keysFromSettings.MainKey) != key; - - _keysFromSettings.MainKey = key; - } - else if (!_keysFromSettings.HasDynamicKeys) - { - HasChange = true; - _keysFromSettings.DynamicKeys = new List - { - new() - { - Key = key, - FileName = collection[e.CollectionIndex].Name, - Guid = collection[e.CollectionIndex].Guid.ToString() - } - }; - } - else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d) - { - if (!HasChange) - HasChange = FixKey(d.Key) != key; - - d.Key = key; + _keysFromSettings = settings.AesKeys; } else { - HasChange = true; - _keysFromSettings.DynamicKeys.Add(new DynamicKey + _keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game]; + } + + _keysFromSettings ??= new AesResponse + { + MainKey = string.Empty, + DynamicKeys = null + }; + + _mainKey.Key = FixKey(_keysFromSettings.MainKey); + AesKeys = new FullyObservableCollection(EnumerateAesKeys()); + AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged; + AesKeysView = new ListCollectionView(AesKeys) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } }; + }); + } + + private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e) + { + if (e.PropertyName != "Key" || sender is not FullyObservableCollection collection) + return; + + var key = FixKey(collection[e.CollectionIndex].Key); + if (e.CollectionIndex == 0) + { + if (!HasChange) + HasChange = FixKey(_keysFromSettings.MainKey) != key; + + _keysFromSettings.MainKey = key; + } + else if (!_keysFromSettings.HasDynamicKeys) + { + HasChange = true; + _keysFromSettings.DynamicKeys = new List + { + new() { Key = key, FileName = collection[e.CollectionIndex].Name, Guid = collection[e.CollectionIndex].Guid.ToString() - }); - } - } - - public async Task UpdateProvider(bool isLaunch) - { - if (!isLaunch && !HasChange) return; - - _cue4Parse.ClearProvider(); - await _cue4Parse.LoadVfs(AesKeys); - - if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings; - else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings; - - Log.Information("{@Json}", UserSettings.Default); - } - - private string FixKey(string key) - { - if (string.IsNullOrEmpty(key)) - return string.Empty; - - if (key.StartsWith("0x")) - key = key[2..]; - - return "0x" + key.ToUpper().Trim(); - } - - private IEnumerable EnumerateAesKeys() - { - yield return _mainKey; - _uniqueGuids = new HashSet {Constants.ZERO_GUID}; - - var hasDynamicKeys = _keysFromSettings.HasDynamicKeys; - foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles) - { - if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid)) - continue; - - var k = string.Empty; - if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey) - { - k = dynamicKey.Key; } + }; + } + else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d) + { + if (!HasChange) + HasChange = FixKey(d.Key) != key; - file.Key = FixKey(k); - yield return file; - } + d.Key = key; + } + else + { + HasChange = true; + _keysFromSettings.DynamicKeys.Add(new DynamicKey + { + Key = key, + FileName = collection[e.CollectionIndex].Name, + Guid = collection[e.CollectionIndex].Guid.ToString() + }); } } -} + + public async Task UpdateProvider(bool isLaunch) + { + if (!isLaunch && !HasChange) return; + + _cue4Parse.ClearProvider(); + await _cue4Parse.LoadVfs(AesKeys); + + if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) + UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings; + else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings; + + Log.Information("{@Json}", UserSettings.Default); + } + + private string FixKey(string key) + { + if (string.IsNullOrEmpty(key)) + return string.Empty; + + if (key.StartsWith("0x")) + key = key[2..]; + + return "0x" + key.ToUpper().Trim(); + } + + private IEnumerable EnumerateAesKeys() + { + yield return _mainKey; + _uniqueGuids = new HashSet { Constants.ZERO_GUID }; + + var hasDynamicKeys = _keysFromSettings.HasDynamicKeys; + foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles) + { + if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid)) + continue; + + var k = string.Empty; + if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey) + { + k = dynamicKey.Key; + } + + file.Key = FixKey(k); + yield return file; + } + } +} \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpointViewModel.cs b/FModel/ViewModels/ApiEndpointViewModel.cs index 8730dbd5..94b66268 100644 --- a/FModel/ViewModels/ApiEndpointViewModel.cs +++ b/FModel/ViewModels/ApiEndpointViewModel.cs @@ -2,29 +2,28 @@ using FModel.ViewModels.ApiEndpoints; using RestSharp; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class ApiEndpointViewModel { - public class ApiEndpointViewModel + private readonly IRestClient _client = new RestClient { - private readonly IRestClient _client = new RestClient - { - UserAgent = $"FModel/{Constants.APP_VERSION}", - Timeout = 3 * 1000 - }.UseSerializer(); + UserAgent = $"FModel/{Constants.APP_VERSION}", + Timeout = 3 * 1000 + }.UseSerializer(); - public FortniteApiEndpoint FortniteApi { get; } - public ValorantApiEndpoint ValorantApi { get; } - public BenbotApiEndpoint BenbotApi { get; } - public EpicApiEndpoint EpicApi { get; } - public FModelApi FModelApi { get; } + public FortniteApiEndpoint FortniteApi { get; } + public ValorantApiEndpoint ValorantApi { get; } + public BenbotApiEndpoint BenbotApi { get; } + public EpicApiEndpoint EpicApi { get; } + public FModelApi FModelApi { get; } - public ApiEndpointViewModel() - { - FortniteApi = new FortniteApiEndpoint(_client); - ValorantApi = new ValorantApiEndpoint(_client); - BenbotApi = new BenbotApiEndpoint(_client); - EpicApi = new EpicApiEndpoint(_client); - FModelApi = new FModelApi(_client); - } + public ApiEndpointViewModel() + { + FortniteApi = new FortniteApiEndpoint(_client); + ValorantApi = new ValorantApiEndpoint(_client); + BenbotApi = new BenbotApiEndpoint(_client); + EpicApi = new EpicApiEndpoint(_client); + FModelApi = new FModelApi(_client); } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs b/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs index e2992f89..c66aa0f7 100644 --- a/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs +++ b/FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs @@ -1,14 +1,13 @@ using RestSharp; -namespace FModel.ViewModels.ApiEndpoints -{ - public abstract class AbstractApiProvider - { - protected readonly IRestClient _client; +namespace FModel.ViewModels.ApiEndpoints; - public AbstractApiProvider(IRestClient client) - { - _client = client; - } +public abstract class AbstractApiProvider +{ + protected readonly IRestClient _client; + + protected AbstractApiProvider(IRestClient client) + { + _client = client; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/BenbotApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/BenbotApiEndpoint.cs index e6fea119..8dc631f0 100644 --- a/FModel/ViewModels/ApiEndpoints/BenbotApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/BenbotApiEndpoint.cs @@ -6,73 +6,72 @@ using FModel.ViewModels.ApiEndpoints.Models; using RestSharp; using Serilog; -namespace FModel.ViewModels.ApiEndpoints +namespace FModel.ViewModels.ApiEndpoints; + +public class BenbotApiEndpoint : AbstractApiProvider { - public class BenbotApiEndpoint : AbstractApiProvider + public BenbotApiEndpoint(IRestClient client) : base(client) { - public BenbotApiEndpoint(IRestClient client) : base(client) - { - } + } - public async Task GetAesKeysAsync(CancellationToken token) + public async Task GetAesKeysAsync(CancellationToken token) + { + var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET) { - var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET) - { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } - }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public AesResponse GetAesKeys(CancellationToken token) - { - return GetAesKeysAsync(token).GetAwaiter().GetResult(); - } + public AesResponse GetAesKeys(CancellationToken token) + { + return GetAesKeysAsync(token).GetAwaiter().GetResult(); + } - public async Task GetMappingsAsync(CancellationToken token) + public async Task GetMappingsAsync(CancellationToken token) + { + var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET) { - var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET) - { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } - }; - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public MappingsResponse[] GetMappings(CancellationToken token) - { - return GetMappingsAsync(token).GetAwaiter().GetResult(); - } + public MappingsResponse[] GetMappings(CancellationToken token) + { + return GetMappingsAsync(token).GetAwaiter().GetResult(); + } - public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en-US") + public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en-US") + { + var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET) { - var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET) - { - OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } - }; - request.AddParameter("lang", language); - var response = await _client.ExecuteAsync>>(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int)response.StatusCode, request.Resource); - return response.Data; - } + OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; } + }; + request.AddParameter("lang", language); + var response = await _client.ExecuteAsync>>(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public Dictionary> GetHotfixes(CancellationToken token, string language = "en-US") - { - return GetHotfixesAsync(token, language).GetAwaiter().GetResult(); - } + public Dictionary> GetHotfixes(CancellationToken token, string language = "en-US") + { + return GetHotfixesAsync(token, language).GetAwaiter().GetResult(); + } - public async Task DownloadFileAsync(string fileLink, string installationPath) - { - var request = new RestRequest(fileLink, Method.GET); - var data = _client.DownloadData(request); - await File.WriteAllBytesAsync(installationPath, data); - } + public async Task DownloadFileAsync(string fileLink, string installationPath) + { + var request = new RestRequest(fileLink, Method.GET); + var data = _client.DownloadData(request); + await File.WriteAllBytesAsync(installationPath, data); + } - public void DownloadFile(string fileLink, string installationPath) - { - DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult(); - } + public void DownloadFile(string fileLink, string installationPath) + { + DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult(); } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs index 3dfb89d3..95e0bf8f 100644 --- a/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs @@ -7,55 +7,54 @@ using FModel.ViewModels.ApiEndpoints.Models; using RestSharp; using Serilog; -namespace FModel.ViewModels.ApiEndpoints +namespace FModel.ViewModels.ApiEndpoints; + +public class EpicApiEndpoint : AbstractApiProvider { - public class EpicApiEndpoint : AbstractApiProvider + private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token"; + private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE="; + private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live"; + + public EpicApiEndpoint(IRestClient client) : base(client) { - private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token"; - private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE="; - private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live"; + } - public EpicApiEndpoint(IRestClient client) : base(client) + public async Task GetManifestAsync(CancellationToken token) + { + if (IsExpired()) { - } - - public async Task GetManifestAsync(CancellationToken token) - { - if (IsExpired()) + var auth = await GetAuthAsync(token); + if (auth != null) { - var auth = await GetAuthAsync(token); - if (auth != null) - { - UserSettings.Default.LastAuthResponse = auth; - } + UserSettings.Default.LastAuthResponse = auth; } - - var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET); - request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}"); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return new ManifestInfo(response.Content); } - public ManifestInfo GetManifest(CancellationToken token) - { - return GetManifestAsync(token).GetAwaiter().GetResult(); - } + var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET); + request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}"); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return new ManifestInfo(response.Content); + } - private async Task GetAuthAsync(CancellationToken token) - { - var request = new RestRequest(_OAUTH_URL, Method.POST); - request.AddHeader("Authorization", _BASIC_TOKEN); - request.AddParameter("grant_type", "client_credentials"); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + public ManifestInfo GetManifest(CancellationToken token) + { + return GetManifestAsync(token).GetAwaiter().GetResult(); + } - private bool IsExpired() - { - if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true; - return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt; - } + private async Task GetAuthAsync(CancellationToken token) + { + var request = new RestRequest(_OAUTH_URL, Method.POST); + request.AddHeader("Authorization", _BASIC_TOKEN); + request.AddParameter("grant_type", "client_credentials"); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } + + private bool IsExpired() + { + if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true; + return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/FModelApi.cs b/FModel/ViewModels/ApiEndpoints/FModelApi.cs index 07d3bd7d..003adf65 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApi.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApi.cs @@ -17,169 +17,168 @@ using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; using MessageBoxResult = AdonisUI.Controls.MessageBoxResult; -namespace FModel.ViewModels.ApiEndpoints +namespace FModel.ViewModels.ApiEndpoints; + +public class FModelApi : AbstractApiProvider { - public class FModelApi : AbstractApiProvider + private News _news; + private Info _infos; + private Backup[] _backups; + private Game _game; + private readonly IDictionary _communityDesigns = new Dictionary(); + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public FModelApi(IRestClient client) : base(client) { - private News _news; - private Info _infos; - private Backup[] _backups; - private Game _game; - private readonly IDictionary _communityDesigns = new Dictionary(); - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + } - public FModelApi(IRestClient client) : base(client) - { - } + public async Task GetNewsAsync(CancellationToken token) + { + var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public async Task GetNewsAsync(CancellationToken token) - { - var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + public News GetNews(CancellationToken token) + { + return _news ??= GetNewsAsync(token).GetAwaiter().GetResult(); + } - public News GetNews(CancellationToken token) - { - return _news ??= GetNewsAsync(token).GetAwaiter().GetResult(); - } + public async Task GetInfosAsync(CancellationToken token, EUpdateMode updateMode) + { + var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public async Task GetInfosAsync(CancellationToken token, EUpdateMode updateMode) - { - var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + public Info GetInfos(CancellationToken token, EUpdateMode updateMode) + { + return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult(); + } - public Info GetInfos(CancellationToken token, EUpdateMode updateMode) - { - return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult(); - } + public async Task GetBackupsAsync(CancellationToken token, string gameName) + { + var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public async Task GetBackupsAsync(CancellationToken token, string gameName) - { - var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + public Backup[] GetBackups(CancellationToken token, string gameName) + { + return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult(); + } - public Backup[] GetBackups(CancellationToken token, string gameName) - { - return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult(); - } + public async Task GetGamesAsync(CancellationToken token, string gameName) + { + var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data; + } - public async Task GetGamesAsync(CancellationToken token, string gameName) - { - var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data; - } + public Game GetGames(CancellationToken token, string gameName) + { + return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult(); + } - public Game GetGames(CancellationToken token, string gameName) - { - return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult(); - } + public async Task GetDesignAsync(string designName) + { + var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET); + var response = await _client.ExecuteAsync(request).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); + return response.Data != null ? new CommunityDesign(response.Data) : null; + } - public async Task GetDesignAsync(string designName) - { - var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET); - var response = await _client.ExecuteAsync(request).ConfigureAwait(false); - Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource); - return response.Data != null ? new CommunityDesign(response.Data) : null; - } - - public CommunityDesign GetDesign(string designName) - { - if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null) - return communityDesign; - - communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult(); - _communityDesigns[designName] = communityDesign; + public CommunityDesign GetDesign(string designName) + { + if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null) return communityDesign; - } - public void CheckForUpdates(EUpdateMode updateMode) - { - AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent; - AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent; - AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}"); - } + communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult(); + _communityDesigns[designName] = communityDesign; + return communityDesign; + } - private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) + public void CheckForUpdates(EUpdateMode updateMode) + { + AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent; + AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent; + AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}"); + } + + private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args) + { + _infos = JsonConvert.DeserializeObject(args.RemoteData); + if (_infos != null) { - _infos = JsonConvert.DeserializeObject(args.RemoteData); - if (_infos != null) + args.UpdateInfo = new UpdateInfoEventArgs { - args.UpdateInfo = new UpdateInfoEventArgs - { - CurrentVersion = _infos.Version, - ChangelogURL = _infos.ChangelogUrl, - DownloadURL = _infos.DownloadUrl - }; - } - } - - private void CheckForUpdateEvent(UpdateInfoEventArgs args) - { - if (args is {CurrentVersion: { }}) - { - var currentVersion = new System.Version(args.CurrentVersion); - if (currentVersion == args.InstalledVersion) - { - if (UserSettings.Default.ShowChangelog) - ShowChangelog(args); - return; - } - - var downgrade = currentVersion < args.InstalledVersion; - var messageBox = new MessageBoxModel - { - Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?", - Caption = $"{(downgrade ? "Downgrade" : "Update")} Available", - Icon = MessageBoxImage.Question, - Buttons = MessageBoxButtons.YesNo(), - IsSoundEnabled = false - }; - - MessageBox.Show(messageBox); - if (messageBox.Result != MessageBoxResult.Yes) return; - - try - { - if (AutoUpdater.DownloadUpdate(args)) - { - UserSettings.Default.ShowChangelog = true; - Application.Current.Shutdown(); - } - } - catch (Exception exception) - { - UserSettings.Default.ShowChangelog = false; - MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error); - } - } - else - { - MessageBox.Show( - "There is a problem reaching the update server, please check your internet connection or try again later.", - "Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error); - } - } - - private void ShowChangelog(UpdateInfoEventArgs args) - { - var request = new RestRequest(args.ChangelogURL, Method.GET); - var response = _client.Execute(request); - if (string.IsNullOrEmpty(response.Content)) return; - - _applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}"); - _applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog"); - _applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false); - UserSettings.Default.ShowChangelog = false; + CurrentVersion = _infos.Version, + ChangelogURL = _infos.ChangelogUrl, + DownloadURL = _infos.DownloadUrl + }; } } -} + + private void CheckForUpdateEvent(UpdateInfoEventArgs args) + { + if (args is { CurrentVersion: { } }) + { + var currentVersion = new System.Version(args.CurrentVersion); + if (currentVersion == args.InstalledVersion) + { + if (UserSettings.Default.ShowChangelog) + ShowChangelog(args); + return; + } + + var downgrade = currentVersion < args.InstalledVersion; + var messageBox = new MessageBoxModel + { + Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?", + Caption = $"{(downgrade ? "Downgrade" : "Update")} Available", + Icon = MessageBoxImage.Question, + Buttons = MessageBoxButtons.YesNo(), + IsSoundEnabled = false + }; + + MessageBox.Show(messageBox); + if (messageBox.Result != MessageBoxResult.Yes) return; + + try + { + if (AutoUpdater.DownloadUpdate(args)) + { + UserSettings.Default.ShowChangelog = true; + Application.Current.Shutdown(); + } + } + catch (Exception exception) + { + UserSettings.Default.ShowChangelog = false; + MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error); + } + } + else + { + MessageBox.Show( + "There is a problem reaching the update server, please check your internet connection or try again later.", + "Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + + private void ShowChangelog(UpdateInfoEventArgs args) + { + var request = new RestRequest(args.ChangelogURL, Method.GET); + var response = _client.Execute(request); + if (string.IsNullOrEmpty(response.Content)) return; + + _applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}"); + _applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog"); + _applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false); + UserSettings.Default.ShowChangelog = false; + } +} \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs index 8048673c..ced45e01 100644 --- a/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs @@ -3,31 +3,30 @@ using FModel.ViewModels.ApiEndpoints.Models; using RestSharp; using System.Threading.Tasks; -namespace FModel.ViewModels.ApiEndpoints +namespace FModel.ViewModels.ApiEndpoints; + +public class FortniteApiEndpoint : AbstractApiProvider { - public class FortniteApiEndpoint : AbstractApiProvider + public FortniteApiEndpoint(IRestClient client) : base(client) { - public FortniteApiEndpoint(IRestClient client) : base(client) - { - } + } - public async Task GetPlaylistAsync(string playlistId) - { - var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET); - var response = await _client.ExecuteAsync(request).ConfigureAwait(false); - return response.Data; - } + public async Task GetPlaylistAsync(string playlistId) + { + var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET); + var response = await _client.ExecuteAsync(request).ConfigureAwait(false); + return response.Data; + } - public PlaylistResponse GetPlaylist(string playlistId) - { - return GetPlaylistAsync(playlistId).GetAwaiter().GetResult(); - } + public PlaylistResponse GetPlaylist(string playlistId) + { + return GetPlaylistAsync(playlistId).GetAwaiter().GetResult(); + } - public bool TryGetBytes(Uri link, out byte[] data) - { - var request = new RestRequest(link, Method.GET); - data = _client.DownloadData(request); - return data != null; - } + public bool TryGetBytes(Uri link, out byte[] data) + { + var request = new RestRequest(link, Method.GET); + data = _client.DownloadData(request); + return data != null; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs index 64b8e410..1dd3f68e 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs @@ -2,23 +2,22 @@ using System.Diagnostics; using J = Newtonsoft.Json.JsonPropertyAttribute; -namespace FModel.ViewModels.ApiEndpoints.Models +namespace FModel.ViewModels.ApiEndpoints.Models; + +[DebuggerDisplay("{" + nameof(Version) + "}")] +public class AesResponse { - [DebuggerDisplay("{" + nameof(Version) + "}")] - public class AesResponse - { - [J("version")] public string Version { get; private set; } - [J("mainKey")] public string MainKey { get; set; } - [J("dynamicKeys")] public List DynamicKeys { get; set; } + [J("version")] public string Version { get; private set; } + [J("mainKey")] public string MainKey { get; set; } + [J("dynamicKeys")] public List DynamicKeys { get; set; } - public bool HasDynamicKeys => DynamicKeys is {Count: > 0}; - } + public bool HasDynamicKeys => DynamicKeys is { Count: > 0 }; +} - [DebuggerDisplay("{" + nameof(Key) + "}")] - public class DynamicKey - { - [J("fileName")] public string FileName { get; set; } - [J("guid")] public string Guid { get; set; } - [J("key")] public string Key { get; set; } - } +[DebuggerDisplay("{" + nameof(Key) + "}")] +public class DynamicKey +{ + [J("fileName")] public string FileName { get; set; } + [J("guid")] public string Guid { get; set; } + [J("key")] public string Key { get; set; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/Models/EpicResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/EpicResponse.cs index 36519253..3ff3b55d 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/EpicResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/EpicResponse.cs @@ -2,12 +2,11 @@ using System.Diagnostics; using J = Newtonsoft.Json.JsonPropertyAttribute; -namespace FModel.ViewModels.ApiEndpoints.Models +namespace FModel.ViewModels.ApiEndpoints.Models; + +[DebuggerDisplay("{" + nameof(AccessToken) + "}")] +public class AuthResponse { - [DebuggerDisplay("{" + nameof(AccessToken) + "}")] - public class AuthResponse - { - [J("access_token")] public string AccessToken { get; set; } - [J("expires_at")] public DateTime ExpiresAt { get; set; } - } + [J("access_token")] public string AccessToken { get; set; } + [J("expires_at")] public DateTime ExpiresAt { get; set; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs index 7f082ec3..4ba2f926 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs @@ -6,193 +6,193 @@ using FModel.Extensions; using SkiaSharp; using J = Newtonsoft.Json.JsonPropertyAttribute; -namespace FModel.ViewModels.ApiEndpoints.Models +namespace FModel.ViewModels.ApiEndpoints.Models; + +[DebuggerDisplay("{" + nameof(Messages) + "}")] +public class News { - [DebuggerDisplay("{" + nameof(Messages) + "}")] - public class News - { - [J] public string[] Messages { get; private set; } - [J] public string[] Colors { get; private set; } - [J] public string[] NewLines { get; private set; } - } + [J] public string[] Messages { get; private set; } + [J] public string[] Colors { get; private set; } + [J] public string[] NewLines { get; private set; } +} - [DebuggerDisplay("{" + nameof(FileName) + "}")] - public class Backup - { - [J] public string GameName { get; private set; } - [J] public string FileName { get; private set; } - [J] public string DownloadUrl { get; private set; } - [J] public long FileSize { get; private set; } - } - - [DebuggerDisplay("{" + nameof(DisplayName) + "}")] - public class Game - { - [J] public string DisplayName { get; private set; } - [J] public Dictionary Versions { get; private set; } - } - - [DebuggerDisplay("{" + nameof(GameEnum) + "}")] - public class Version - { - [J("game")] public string GameEnum { get; private set; } - [J] public int UeVer { get; private set; } - [J] public Dictionary CustomVersions { get; private set; } - [J] public Dictionary Options { get; private set; } - } +[DebuggerDisplay("{" + nameof(FileName) + "}")] +public class Backup +{ + [J] public string GameName { get; private set; } + [J] public string FileName { get; private set; } + [J] public string DownloadUrl { get; private set; } + [J] public long FileSize { get; private set; } +} - [DebuggerDisplay("{" + nameof(Mode) + "}")] - public class Info - { - [J] public string Mode { get; private set; } - [J] public string Version { get; private set; } - [J] public string DownloadUrl { get; private set; } - [J] public string ChangelogUrl { get; private set; } - [J] public string CommunityDesign { get; private set; } - [J] public string CommunityPreview { get; private set; } - } +[DebuggerDisplay("{" + nameof(DisplayName) + "}")] +public class Game +{ + [J] public string DisplayName { get; private set; } + [J] public Dictionary Versions { get; private set; } +} - [DebuggerDisplay("{" + nameof(Name) + "}")] - public class Community - { - [J] public string Name { get; private set; } - [J] public bool DrawSource { get; private set; } - [J] public bool DrawSeason { get; private set; } - [J] public bool DrawSeasonShort { get; private set; } - [J] public bool DrawSet { get; private set; } - [J] public bool DrawSetShort { get; private set; } - [J] public IDictionary Fonts { get; private set; } - [J] public GameplayTag GameplayTags { get; private set; } - [J] public IDictionary Rarities { get; private set; } - } +[DebuggerDisplay("{" + nameof(GameEnum) + "}")] +public class Version +{ + [J("game")] public string GameEnum { get; private set; } + [J] public int UeVer { get; private set; } + [J] public Dictionary CustomVersions { get; private set; } + [J] public Dictionary Options { get; private set; } +} - public class Font - { - [J] public IDictionary Typeface { get; private set; } - [J] public float FontSize { get; private set; } - [J] public float FontScale { get; private set; } - [J] public string FontColor { get; private set; } - [J] public float SkewValue { get; private set; } - [J] public byte ShadowValue { get; private set; } - [J] public int MaxLineCount { get; private set; } - [J] public string Alignment { get; private set; } - [J] public int X { get; private set; } - [J] public int Y { get; private set; } - } +[DebuggerDisplay("{" + nameof(Mode) + "}")] +public class Info +{ + [J] public string Mode { get; private set; } + [J] public string Version { get; private set; } + [J] public string DownloadUrl { get; private set; } + [J] public string ChangelogUrl { get; private set; } + [J] public string CommunityDesign { get; private set; } + [J] public string CommunityPreview { get; private set; } +} - public class FontDesign - { - [J] public IDictionary Typeface { get; set; } - [J] public float FontSize { get; set; } - [J] public float FontScale { get; set; } - [J] public SKColor FontColor { get; set; } - [J] public float SkewValue { get; set; } - [J] public byte ShadowValue { get; set; } - [J] public int MaxLineCount { get; set; } - [J] public SKTextAlign Alignment { get; set; } - [J] public int X { get; set; } - [J] public int Y { get; set; } - } +[DebuggerDisplay("{" + nameof(Name) + "}")] +public class Community +{ + [J] public string Name { get; private set; } + [J] public bool DrawSource { get; private set; } + [J] public bool DrawSeason { get; private set; } + [J] public bool DrawSeasonShort { get; private set; } + [J] public bool DrawSet { get; private set; } + [J] public bool DrawSetShort { get; private set; } + [J] public IDictionary Fonts { get; private set; } + [J] public GameplayTag GameplayTags { get; private set; } + [J] public IDictionary Rarities { get; private set; } +} - public class GameplayTag - { - [J] public int X { get; private set; } - [J] public int Y { get; private set; } - [J] public bool DrawCustomOnly { get; private set; } - [J] public string Custom { get; private set; } - [J] public IDictionary Tags { get; private set; } - } +public class Font +{ + [J] public IDictionary Typeface { get; private set; } + [J] public float FontSize { get; private set; } + [J] public float FontScale { get; private set; } + [J] public string FontColor { get; private set; } + [J] public float SkewValue { get; private set; } + [J] public byte ShadowValue { get; private set; } + [J] public int MaxLineCount { get; private set; } + [J] public string Alignment { get; private set; } + [J] public int X { get; private set; } + [J] public int Y { get; private set; } +} - public class GameplayTagDesign - { - [J] public int X { get; set; } - [J] public int Y { get; set; } - [J] public bool DrawCustomOnly { get; set; } - [J] public SKBitmap Custom { get; set; } - [J] public IDictionary Tags { get; set; } - } +public class FontDesign +{ + [J] public IDictionary Typeface { get; set; } + [J] public float FontSize { get; set; } + [J] public float FontScale { get; set; } + [J] public SKColor FontColor { get; set; } + [J] public float SkewValue { get; set; } + [J] public byte ShadowValue { get; set; } + [J] public int MaxLineCount { get; set; } + [J] public SKTextAlign Alignment { get; set; } + [J] public int X { get; set; } + [J] public int Y { get; set; } +} - public class Rarity - { - [J] public string Background { get; private set; } - [J] public string Upper { get; private set; } - [J] public string Lower { get; private set; } - } +public class GameplayTag +{ + [J] public int X { get; private set; } + [J] public int Y { get; private set; } + [J] public bool DrawCustomOnly { get; private set; } + [J] public string Custom { get; private set; } + [J] public IDictionary Tags { get; private set; } +} - public class RarityDesign - { - [J] public SKBitmap Background { get; set; } - [J] public SKBitmap Upper { get; set; } - [J] public SKBitmap Lower { get; set; } - } +public class GameplayTagDesign +{ + [J] public int X { get; set; } + [J] public int Y { get; set; } + [J] public bool DrawCustomOnly { get; set; } + [J] public SKBitmap Custom { get; set; } + [J] public IDictionary Tags { get; set; } +} - public class CommunityDesign - { - public bool DrawSource { get; } - public bool DrawSeason { get; } - public bool DrawSeasonShort { get; } - public bool DrawSet { get; } - public bool DrawSetShort { get; } - public IDictionary Fonts { get; } - public GameplayTagDesign GameplayTags { get; } - public IDictionary Rarities { get; } +public class Rarity +{ + [J] public string Background { get; private set; } + [J] public string Upper { get; private set; } + [J] public string Lower { get; private set; } +} - public CommunityDesign(Community response) +public class RarityDesign +{ + [J] public SKBitmap Background { get; set; } + [J] public SKBitmap Upper { get; set; } + [J] public SKBitmap Lower { get; set; } +} + +public class CommunityDesign +{ + public bool DrawSource { get; } + public bool DrawSeason { get; } + public bool DrawSeasonShort { get; } + public bool DrawSet { get; } + public bool DrawSetShort { get; } + public IDictionary Fonts { get; } + public GameplayTagDesign GameplayTags { get; } + public IDictionary Rarities { get; } + + public CommunityDesign(Community response) + { + DrawSource = response.DrawSource; + DrawSeason = response.DrawSeason; + DrawSeasonShort = response.DrawSeasonShort; + DrawSet = response.DrawSet; + DrawSetShort = response.DrawSetShort; + + Fonts = new Dictionary(); + foreach (var (k, font) in response.Fonts) { - DrawSource = response.DrawSource; - DrawSeason = response.DrawSeason; - DrawSeasonShort = response.DrawSeasonShort; - DrawSet = response.DrawSet; - DrawSetShort = response.DrawSetShort; - - Fonts = new Dictionary(); - foreach (var (k, font) in response.Fonts) + var typeface = new Dictionary(); + foreach (var (key, value) in font.Typeface) { - var typeface = new Dictionary(); - foreach (var (key, value) in font.Typeface) - { - typeface[key.ToEnum(ELanguage.English)] = value; - } - - Fonts[k] = new FontDesign - { - Typeface = typeface, - FontSize = font.FontSize, - FontScale = font.FontScale, - FontColor = SKColor.Parse(font.FontColor), - SkewValue = font.SkewValue, - ShadowValue = font.ShadowValue, - MaxLineCount = font.MaxLineCount, - Alignment = font.Alignment.ToEnum(SKTextAlign.Center), - X = font.X, - Y = font.Y - }; + typeface[key.ToEnum(ELanguage.English)] = value; } - var tags = new Dictionary(); - foreach (var (key, value) in response.GameplayTags.Tags) + Fonts[k] = new FontDesign { - tags[key] = Utils.GetB64Bitmap(value); - } - GameplayTags = new GameplayTagDesign - { - X = response.GameplayTags.X, - Y = response.GameplayTags.Y, - DrawCustomOnly = response.GameplayTags.DrawCustomOnly, - Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom), - Tags = tags + Typeface = typeface, + FontSize = font.FontSize, + FontScale = font.FontScale, + FontColor = SKColor.Parse(font.FontColor), + SkewValue = font.SkewValue, + ShadowValue = font.ShadowValue, + MaxLineCount = font.MaxLineCount, + Alignment = font.Alignment.ToEnum(SKTextAlign.Center), + X = font.X, + Y = font.Y }; + } - Rarities = new Dictionary(); - foreach (var (key, value) in response.Rarities) + var tags = new Dictionary(); + foreach (var (key, value) in response.GameplayTags.Tags) + { + tags[key] = Utils.GetB64Bitmap(value); + } + + GameplayTags = new GameplayTagDesign + { + X = response.GameplayTags.X, + Y = response.GameplayTags.Y, + DrawCustomOnly = response.GameplayTags.DrawCustomOnly, + Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom), + Tags = tags + }; + + Rarities = new Dictionary(); + foreach (var (key, value) in response.Rarities) + { + Rarities[key] = new RarityDesign { - Rarities[key] = new RarityDesign - { - Background = Utils.GetB64Bitmap(value.Background), - Upper = Utils.GetB64Bitmap(value.Upper), - Lower = Utils.GetB64Bitmap(value.Lower) - }; - } + Background = Utils.GetB64Bitmap(value.Background), + Upper = Utils.GetB64Bitmap(value.Upper), + Lower = Utils.GetB64Bitmap(value.Lower) + }; } } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs index be0fcd8f..2a89a8ff 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs @@ -1,23 +1,22 @@ using System.Diagnostics; using J = Newtonsoft.Json.JsonPropertyAttribute; -namespace FModel.ViewModels.ApiEndpoints.Models -{ - [DebuggerDisplay("{" + nameof(FileName) + "}")] - public class MappingsResponse - { - [J] public string Url { get; private set; } - [J] public string FileName { get; private set; } - [J] public string Hash { get; private set; } - [J] public long Length { get; private set; } - [J] public string Uploaded { get; private set; } - [J] public Meta Meta { get; private set; } - } +namespace FModel.ViewModels.ApiEndpoints.Models; - [DebuggerDisplay("{" + nameof(CompressionMethod) + "}")] - public class Meta - { - [J] public string Version { get; private set; } - [J] public string CompressionMethod { get; private set; } - } +[DebuggerDisplay("{" + nameof(FileName) + "}")] +public class MappingsResponse +{ + [J] public string Url { get; private set; } + [J] public string FileName { get; private set; } + [J] public string Hash { get; private set; } + [J] public long Length { get; private set; } + [J] public string Uploaded { get; private set; } + [J] public Meta Meta { get; private set; } +} + +[DebuggerDisplay("{" + nameof(CompressionMethod) + "}")] +public class Meta +{ + [J] public string Version { get; private set; } + [J] public string CompressionMethod { get; private set; } } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/Models/PlaylistResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/PlaylistResponse.cs index b0b316a7..cc290894 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/PlaylistResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/PlaylistResponse.cs @@ -3,34 +3,33 @@ using System.Diagnostics; using J = Newtonsoft.Json.JsonPropertyAttribute; using I = Newtonsoft.Json.JsonIgnoreAttribute; -namespace FModel.ViewModels.ApiEndpoints.Models +namespace FModel.ViewModels.ApiEndpoints.Models; + +[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] +public class PlaylistResponse { - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class PlaylistResponse - { - [J] public int Status { get; private set; } - [J] public Playlist Data { get; private set; } - [J] public string Error { get; private set; } + [J] public int Status { get; private set; } + [J] public Playlist Data { get; private set; } + [J] public string Error { get; private set; } - public bool IsSuccess => Status == 200; - public bool HasError => Error != null; + public bool IsSuccess => Status == 200; + public bool HasError => Error != null; - private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}"; - } + private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}"; +} - [DebuggerDisplay("{" + nameof(Id) + "}")] - public class Playlist - { - [J] public string Id { get; private set; } - [J] public PlaylistImages Images { get; private set; } - } +[DebuggerDisplay("{" + nameof(Id) + "}")] +public class Playlist +{ + [J] public string Id { get; private set; } + [J] public PlaylistImages Images { get; private set; } +} - public class PlaylistImages - { - [J] public Uri Showcase { get; private set; } - [J] public Uri MissionIcon { get; private set; } +public class PlaylistImages +{ + [J] public Uri Showcase { get; private set; } + [J] public Uri MissionIcon { get; private set; } - [I] public bool HasShowcase => Showcase != null; - [I] public bool HasMissionIcon => MissionIcon != null; - } + [I] public bool HasShowcase => Showcase != null; + [I] public bool HasMissionIcon => MissionIcon != null; } \ No newline at end of file diff --git a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs index 77b147e4..3a836d2c 100644 --- a/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs @@ -1,12 +1,8 @@ using CUE4Parse.UE4.Exceptions; using CUE4Parse.UE4.Readers; - using FModel.Settings; - using Ionic.Zlib; - using RestSharp; - using System; using System.Collections.Generic; using System.IO; @@ -18,295 +14,300 @@ using System.Text; using System.Threading; using System.Threading.Tasks; -namespace FModel.ViewModels.ApiEndpoints +namespace FModel.ViewModels.ApiEndpoints; + +public class ValorantApiEndpoint : AbstractApiProvider { - public class ValorantApiEndpoint : AbstractApiProvider + private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest"; + + public ValorantApiEndpoint(IRestClient client) : base(client) { - private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest"; - - public ValorantApiEndpoint(IRestClient client) : base(client) { } - - public async Task GetManifestAsync(CancellationToken token) - { - var request = new RestRequest(_URL, Method.GET); - var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); - return new VManifest(response.RawBytes); - } - - public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult(); } - public class VManifest + public async Task GetManifestAsync(CancellationToken token) { - private readonly HttpClient _client; - public readonly VHeader Header; - public readonly VChunk[] Chunks; - public readonly VPak[] Paks; + var request = new RestRequest(_URL, Method.GET); + var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false); + return new VManifest(response.RawBytes); + } - public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { } + public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult(); +} - private VManifest(FArchive Ar) +public class VManifest +{ + private readonly HttpClient _client; + public readonly VHeader Header; + public readonly VChunk[] Chunks; + public readonly VPak[] Paks; + + public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) + { + } + + private VManifest(FArchive Ar) + { + using (Ar) { - using (Ar) - { - Header = new VHeader(Ar); - var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize); - var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer); - if (uncompressedBuffer.Length != Header.UncompressedSize) - throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}"); + Header = new VHeader(Ar); + var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize); + var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer); + if (uncompressedBuffer.Length != Header.UncompressedSize) + throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}"); - using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer); - Chunks = manifest.ReadArray((int) Header.ChunkCount); - Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest)); + using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer); + Chunks = manifest.ReadArray((int) Header.ChunkCount); + Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest)); - if (manifest.Position != manifest.Length) - throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}"); - } - - _client = new HttpClient(new HttpClientHandler - { - UseProxy = false, - UseCookies = false, - AutomaticDecompression = DecompressionMethods.All, - CheckCertificateRevocationList = false, - PreAuthenticate = false, - MaxConnectionsPerServer = 1337, - UseDefaultCredentials = false, - AllowAutoRedirect = false - }); + if (manifest.Position != manifest.Length) + throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}"); } - public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken) + _client = new HttpClient(new HttpClientHandler + { + UseProxy = false, + UseCookies = false, + AutomaticDecompression = DecompressionMethods.All, + CheckCertificateRevocationList = false, + PreAuthenticate = false, + MaxConnectionsPerServer = 1337, + UseDefaultCredentials = false, + AllowAutoRedirect = false + }); + } + + public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken) + { + var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk"); + if (File.Exists(chunkPath)) return; + using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false); + + if (response.StatusCode == HttpStatusCode.OK) + { + await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); + await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + } + } + + public async Task GetChunkBytes(VChunk chunk, CancellationToken cancellationToken) + { + var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk"); + byte[] chunkBytes; + + if (File.Exists(chunkPath)) + { + chunkBytes = new byte[chunk.Size]; + await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read); + await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false); + } + else { - var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk"); - if (File.Exists(chunkPath)) return; using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false); if (response.StatusCode == HttpStatusCode.OK) { - await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); - } - } - - public async Task GetChunkBytes(VChunk chunk, CancellationToken cancellationToken) - { - var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk"); - byte[] chunkBytes; - - if (File.Exists(chunkPath)) - { - chunkBytes = new byte[chunk.Size]; - await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read); - await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false); + chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); + await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); + await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); } else { - using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); - await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); - } - else - { - chunkBytes = null; // Maybe add logging? - } + chunkBytes = null; // Maybe add logging? } - - return chunkBytes; } - public Stream GetPakStream(int index) => new VPakStream(this, index); + return chunkBytes; } - public readonly struct VHeader + public Stream GetPakStream(int index) => new VPakStream(this, index); +} + +public readonly struct VHeader +{ + private const uint _MAGIC = 0xC3D088F7u; + + public readonly uint Magic; + public readonly uint HeaderSize; + public readonly ulong ManifestId; + public readonly uint UncompressedSize; + public readonly uint CompressedSize; + public readonly uint ChunkCount; + public readonly uint PakCount; + public readonly string GameVersion; + + public VHeader(FArchive Ar) { - private const uint _MAGIC = 0xC3D088F7u; + Magic = Ar.Read(); - public readonly uint Magic; - public readonly uint HeaderSize; - public readonly ulong ManifestId; - public readonly uint UncompressedSize; - public readonly uint CompressedSize; - public readonly uint ChunkCount; - public readonly uint PakCount; - public readonly string GameVersion; + if (Magic != _MAGIC) + throw new ParserException(Ar, "Invalid manifest magic"); - public VHeader(FArchive Ar) - { - Magic = Ar.Read(); - - if (Magic != _MAGIC) - throw new ParserException(Ar, "Invalid manifest magic"); - - HeaderSize = Ar.Read(); - ManifestId = Ar.Read(); - UncompressedSize = Ar.Read(); - CompressedSize = Ar.Read(); - ChunkCount = Ar.Read(); - PakCount = Ar.Read(); - var gameVersionLength = Ar.ReadByte(); - GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength)); - Ar.Position = HeaderSize; - } + HeaderSize = Ar.Read(); + ManifestId = Ar.Read(); + UncompressedSize = Ar.Read(); + CompressedSize = Ar.Read(); + ChunkCount = Ar.Read(); + PakCount = Ar.Read(); + var gameVersionLength = Ar.ReadByte(); + GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength)); + Ar.Position = HeaderSize; } +} - public readonly struct VPak +public readonly struct VPak +{ + public readonly ulong Id; + public readonly uint Size; + public readonly uint[] ChunkIndices; + public readonly string Name; + + public VPak(FArchive Ar) { - public readonly ulong Id; - public readonly uint Size; - public readonly uint[] ChunkIndices; - public readonly string Name; - - public VPak(FArchive Ar) - { - Id = Ar.Read(); - Size = Ar.Read(); - ChunkIndices = Ar.ReadArray(Ar.Read()); - Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte())); - } - - public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}"; + Id = Ar.Read(); + Size = Ar.Read(); + ChunkIndices = Ar.ReadArray(Ar.Read()); + Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte())); } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public readonly struct VChunk + public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}"; +} + +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public readonly struct VChunk +{ + public readonly ulong Id; + public readonly uint Size; + + public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}"; +} + +public class VPakStream : Stream, ICloneable +{ + private readonly VManifest _manifest; + private readonly int _pakIndex; + private readonly VChunk[] _chunks; + + public VPakStream(VManifest manifest, int pakIndex, long position = 0L) { - public readonly ulong Id; - public readonly uint Size; + _manifest = manifest; + _pakIndex = pakIndex; + _position = position; - public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}"; + var pak = manifest.Paks[pakIndex]; + _chunks = new VChunk[pak.ChunkIndices.Length]; + for (var i = 0; i < _chunks.Length; i++) + { + _chunks[i] = manifest.Chunks[pak.ChunkIndices[i]]; + } + + Length = pak.Size; } - public class VPakStream : Stream, ICloneable + public object Clone() => new VPakStream(_manifest, _pakIndex, _position); + + public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - private readonly VManifest _manifest; - private readonly int _pakIndex; - private readonly VChunk[] _chunks; + var (i, startPos) = GetChunkIndex(_position); + if (i == -1) return 0; - public VPakStream(VManifest manifest, int pakIndex, long position = 0L) + await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false); + var bytesRead = 0; + + while (true) { - _manifest = manifest; - _pakIndex = pakIndex; - _position = position; + var chunk = _chunks[i]; + var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false); + var chunkBytes = chunk.Size - startPos; + var bytesLeft = count - bytesRead; - var pak = manifest.Paks[pakIndex]; - _chunks = new VChunk[pak.ChunkIndices.Length]; - for (var i = 0; i < _chunks.Length; i++) + if (bytesLeft <= chunkBytes) { - _chunks[i] = manifest.Chunks[pak.ChunkIndices[i]]; + Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint) bytesLeft); + bytesRead += bytesLeft; + break; } - Length = pak.Size; + Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes); + bytesRead += (int) chunkBytes; + startPos = 0u; + + if (++i == _chunks.Length) break; } - public object Clone() => new VPakStream(_manifest, _pakIndex, _position); - - public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var (i, startPos) = GetChunkIndex(_position); - if (i == -1) return 0; - - await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false); - var bytesRead = 0; - - while (true) - { - var chunk = _chunks[i]; - var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false); - var chunkBytes = chunk.Size - startPos; - var bytesLeft = count - bytesRead; - - if (bytesLeft <= chunkBytes) - { - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft); - bytesRead += bytesLeft; - break; - } - - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes); - bytesRead += (int)chunkBytes; - startPos = 0u; - - if (++i == _chunks.Length) break; - } - - _position += bytesRead; - return bytesRead; - } - - private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4) - { - var tasks = new List(); - var s = new SemaphoreSlim(concurrentDownloads); - while (count > 0) - { - await s.WaitAsync(cancellationToken).ConfigureAwait(false); - - var chunk = _chunks[i++]; - tasks.Add(PrefetchChunkAsync(chunk)); - - if (i == _chunks.Length) break; - count -= chunk.Size - startPos; - startPos = 0u; - } - await Task.WhenAll(tasks).ConfigureAwait(false); - s.Dispose(); - async Task PrefetchChunkAsync(VChunk chunk) - { - await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false); - s.Release(); // This is intended - } - } - - private (int Index, uint ChunkPos) GetChunkIndex(long position) - { - for (var i = 0; i < _chunks.Length; i++) - { - var size = _chunks[i].Size; - if (position < size) return (i, (uint) position); - position -= size; - } - - return (-1, 0u); - } - - private long _position; - public override long Position - { - get => _position; - set - { - if (value >= Length || value < 0) - throw new ArgumentOutOfRangeException(nameof(value)); - - _position = value; - } - } - - public override long Seek(long offset, SeekOrigin origin) - { - Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => offset + _position, - SeekOrigin.End => Length + offset, - _ => throw new ArgumentOutOfRangeException(nameof(offset)) - }; - return _position; - } - - public override long Length { get; } - public override bool CanRead => true; - public override bool CanSeek => true; - public override bool CanWrite => false; - public override void Flush() => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + _position += bytesRead; + return bytesRead; } + + private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4) + { + var tasks = new List(); + var s = new SemaphoreSlim(concurrentDownloads); + while (count > 0) + { + await s.WaitAsync(cancellationToken).ConfigureAwait(false); + + var chunk = _chunks[i++]; + tasks.Add(PrefetchChunkAsync(chunk)); + + if (i == _chunks.Length) break; + count -= chunk.Size - startPos; + startPos = 0u; + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + s.Dispose(); + + async Task PrefetchChunkAsync(VChunk chunk) + { + await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false); + s.Release(); // This is intended + } + } + + private (int Index, uint ChunkPos) GetChunkIndex(long position) + { + for (var i = 0; i < _chunks.Length; i++) + { + var size = _chunks[i].Size; + if (position < size) return (i, (uint) position); + position -= size; + } + + return (-1, 0u); + } + + private long _position; + public override long Position + { + get => _position; + set + { + if (value >= Length || value < 0) + throw new ArgumentOutOfRangeException(nameof(value)); + + _position = value; + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + Position = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.Current => offset + _position, + SeekOrigin.End => Length + offset, + _ => throw new ArgumentOutOfRangeException(nameof(offset)) + }; + return _position; + } + + public override long Length { get; } + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + public override void Flush() => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } \ No newline at end of file diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index 4a4d0308..75984606 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -5,202 +5,200 @@ using FModel.Settings; using FModel.ViewModels.Commands; using FModel.Views; using FModel.Views.Resources.Controls; - using Ionic.Zip; - using Oodle.NET; - using System; using System.Diagnostics; using System.IO; using System.Threading.Tasks; using System.Windows; - using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxButton = AdonisUI.Controls.MessageBoxButton; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; using OodleCUE4 = CUE4Parse.Compression.Oodle; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class ApplicationViewModel : ViewModel { - public class ApplicationViewModel : ViewModel + private EBuildKind _build; + + public EBuildKind Build { - private EBuildKind _build; - public EBuildKind Build + get => _build; + private set { - get => _build; - private set - { - SetProperty(ref _build, value); - RaisePropertyChanged(nameof(TitleExtra)); - } - } - - private bool _isReady; - public bool IsReady - { - get => _isReady; - private set => SetProperty(ref _isReady, value); - } - - private EStatusKind _status; - public EStatusKind Status - { - get => _status; - set - { - SetProperty(ref _status, value); - IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping; - } - } - - public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this); - private RightClickMenuCommand _rightClickMenuCommand; - public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this); - private MenuCommand _menuCommand; - public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this); - private CopyCommand _copyCommand; - - public string TitleExtra => - $"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build}) - $"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" + - $"{(Build != EBuildKind.Release ? $" ({Build})" : "")}"; - - public LoadingModesViewModel LoadingModes { get; } - public CustomDirectoriesViewModel CustomDirectories { get; } - public CUE4ParseViewModel CUE4Parse { get; } - public SettingsViewModel SettingsView { get; } - public AesManagerViewModel AesManager { get; } - public AudioPlayerViewModel AudioPlayer { get; } - public MapViewerViewModel MapViewer { get; } - public ModelViewerViewModel ModelViewer { get; } - private OodleCompressor _oodle; - - public ApplicationViewModel() - { - Status = EStatusKind.Loading; -#if DEBUG - Build = EBuildKind.Debug; -#elif RELEASE - Build = EBuildKind.Release; -#else - Build = EBuildKind.Unknown; -#endif - LoadingModes = new LoadingModesViewModel(); - - AvoidEmptyGameDirectoryAndSetEGame(false); - CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory); - CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory); - SettingsView = new SettingsViewModel(CUE4Parse.Game); - AesManager = new AesManagerViewModel(CUE4Parse); - MapViewer = new MapViewerViewModel(CUE4Parse); - AudioPlayer = new AudioPlayerViewModel(); - ModelViewer = new ModelViewerViewModel(CUE4Parse.Game); - Status = EStatusKind.Ready; - } - - public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched) - { - var gameDirectory = UserSettings.Default.GameDirectory; - if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return; - - var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory); - var result = new DirectorySelector(gameLauncherViewModel).ShowDialog(); - if (!result.HasValue || !result.Value) return; - - UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory; - if (!bAlreadyLaunched || gameDirectory.Equals(gameLauncherViewModel.SelectedDetectedGame.GameDirectory)) return; - - RestartWithWarning(); - } - - public void RestartWithWarning() - { - MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning); - Restart(); - } - - public void Restart() - { - var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]); - if (path.EndsWith(".dll")) - { - new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"", - UseShellExecute = false, - RedirectStandardOutput = false, - RedirectStandardError = false, - CreateNoWindow = true - } - }.Start(); - } - else if (path.EndsWith(".exe")) - { - new Process - { - StartInfo = new ProcessStartInfo - { - FileName = path, - UseShellExecute = false, - RedirectStandardOutput = false, - RedirectStandardError = false, - CreateNoWindow = true - } - }.Start(); - } - - Application.Current.Shutdown(); - } - - public async Task InitVgmStream() - { - var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip"); - if (File.Exists(vgmZipFilePath)) return; - - await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath); - if (new FileInfo(vgmZipFilePath).Length > 0) - { - var zip = ZipFile.Read(vgmZipFilePath); - var zipDir = vgmZipFilePath.SubstringBeforeLast("\\"); - foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently); - } - else - { - FLogger.AppendError(); - FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true); - } - } - - public async Task InitOodle() - { - var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); - var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME); - - if (File.Exists(OodleCUE4.OODLE_DLL_NAME)) - { - File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true); - } - else if (!File.Exists(oodlePath)) - { - var result = await OodleCUE4.DownloadOodleDll(oodlePath); - if (!result) return; - } - - if (File.Exists("oo2core_8_win64.dll")) - File.Delete("oo2core_8_win64.dll"); - - _oodle = new OodleCompressor(oodlePath); - - unsafe - { - OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) => - _oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize, - (OodleLZ_FuzzSafe)a, (OodleLZ_CheckCRC)b, (OodleLZ_Verbosity)c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase)threadModule); - } + SetProperty(ref _build, value); + RaisePropertyChanged(nameof(TitleExtra)); } } -} + + private bool _isReady; + + public bool IsReady + { + get => _isReady; + private set => SetProperty(ref _isReady, value); + } + + private EStatusKind _status; + + public EStatusKind Status + { + get => _status; + set + { + SetProperty(ref _status, value); + IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping; + } + } + + public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this); + private RightClickMenuCommand _rightClickMenuCommand; + public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this); + private MenuCommand _menuCommand; + public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this); + private CopyCommand _copyCommand; + + public string TitleExtra => + $"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build}) + $"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" + + $"{(Build != EBuildKind.Release ? $" ({Build})" : "")}"; + + public LoadingModesViewModel LoadingModes { get; } + public CustomDirectoriesViewModel CustomDirectories { get; } + public CUE4ParseViewModel CUE4Parse { get; } + public SettingsViewModel SettingsView { get; } + public AesManagerViewModel AesManager { get; } + public AudioPlayerViewModel AudioPlayer { get; } + public MapViewerViewModel MapViewer { get; } + public ModelViewerViewModel ModelViewer { get; } + private OodleCompressor _oodle; + + public ApplicationViewModel() + { + Status = EStatusKind.Loading; +#if DEBUG + Build = EBuildKind.Debug; +#elif RELEASE + Build = EBuildKind.Release; +#else + Build = EBuildKind.Unknown; +#endif + LoadingModes = new LoadingModesViewModel(); + + AvoidEmptyGameDirectoryAndSetEGame(false); + CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory); + CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory); + SettingsView = new SettingsViewModel(CUE4Parse.Game); + AesManager = new AesManagerViewModel(CUE4Parse); + MapViewer = new MapViewerViewModel(CUE4Parse); + AudioPlayer = new AudioPlayerViewModel(); + ModelViewer = new ModelViewerViewModel(CUE4Parse.Game); + Status = EStatusKind.Ready; + } + + public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched) + { + var gameDirectory = UserSettings.Default.GameDirectory; + if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return; + + var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory); + var result = new DirectorySelector(gameLauncherViewModel).ShowDialog(); + if (!result.HasValue || !result.Value) return; + + UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory; + if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return; + + RestartWithWarning(); + } + + public void RestartWithWarning() + { + MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning); + Restart(); + } + + public void Restart() + { + var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]); + if (path.EndsWith(".dll")) + { + new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"", + UseShellExecute = false, + RedirectStandardOutput = false, + RedirectStandardError = false, + CreateNoWindow = true + } + }.Start(); + } + else if (path.EndsWith(".exe")) + { + new Process + { + StartInfo = new ProcessStartInfo + { + FileName = path, + UseShellExecute = false, + RedirectStandardOutput = false, + RedirectStandardError = false, + CreateNoWindow = true + } + }.Start(); + } + + Application.Current.Shutdown(); + } + + public async Task InitVgmStream() + { + var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip"); + if (File.Exists(vgmZipFilePath)) return; + + await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath); + if (new FileInfo(vgmZipFilePath).Length > 0) + { + var zip = ZipFile.Read(vgmZipFilePath); + var zipDir = vgmZipFilePath.SubstringBeforeLast("\\"); + foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently); + } + else + { + FLogger.AppendError(); + FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true); + } + } + + public async Task InitOodle() + { + var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); + var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME); + + if (File.Exists(OodleCUE4.OODLE_DLL_NAME)) + { + File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true); + } + else if (!File.Exists(oodlePath)) + { + var result = await OodleCUE4.DownloadOodleDll(oodlePath); + if (!result) return; + } + + if (File.Exists("oo2core_8_win64.dll")) + File.Delete("oo2core_8_win64.dll"); + + _oodle = new OodleCompressor(oodlePath); + + unsafe + { + OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) => + _oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize, + (OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule); + } + } +} \ No newline at end of file diff --git a/FModel/ViewModels/AssetsFolderViewModel.cs b/FModel/ViewModels/AssetsFolderViewModel.cs index 763759cc..acd7e7f1 100644 --- a/FModel/ViewModels/AssetsFolderViewModel.cs +++ b/FModel/ViewModels/AssetsFolderViewModel.cs @@ -10,162 +10,161 @@ using CUE4Parse.UE4.Vfs; using FModel.Framework; using FModel.Services; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class TreeItem : ViewModel { - public class TreeItem : ViewModel + private string _header; + public string Header { - private string _header; - public string Header - { - get => _header; - private set => SetProperty(ref _header, value); - } - - private bool _isExpanded; - public bool IsExpanded - { - get => _isExpanded; - set => SetProperty(ref _isExpanded, value); - } - - private bool _isSelected; - public bool IsSelected - { - get => _isSelected; - set => SetProperty(ref _isSelected, value); - } - - private string _package; - public string Package - { - get => _package; - private set => SetProperty(ref _package, value); - } - - private string _mountPoint; - public string MountPoint - { - get => _mountPoint; - private set => SetProperty(ref _mountPoint, value); - } - - private int _version; - public int Version - { - get => _version; - private set => SetProperty(ref _version, value); - } - - public string PathAtThisPoint { get; } - public AssetsListViewModel AssetsList { get; } - public RangeObservableCollection Folders { get; } - public ICollectionView FoldersView { get; } - - public TreeItem(string header, string package, string mountPoint, int version, string pathHere) - { - Header = header; - Package = package; - MountPoint = mountPoint; - Version = version; - PathAtThisPoint = pathHere; - AssetsList = new AssetsListViewModel(); - Folders = new RangeObservableCollection(); - FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}}; - } - - public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files"; + get => _header; + private set => SetProperty(ref _header, value); } - public class AssetsFolderViewModel + private bool _isExpanded; + public bool IsExpanded { - public RangeObservableCollection Folders { get; } - public ICollectionView FoldersView { get; } + get => _isExpanded; + set => SetProperty(ref _isExpanded, value); + } - public AssetsFolderViewModel() + private bool _isSelected; + public bool IsSelected + { + get => _isSelected; + set => SetProperty(ref _isSelected, value); + } + + private string _package; + public string Package + { + get => _package; + private set => SetProperty(ref _package, value); + } + + private string _mountPoint; + public string MountPoint + { + get => _mountPoint; + private set => SetProperty(ref _mountPoint, value); + } + + private int _version; + public int Version + { + get => _version; + private set => SetProperty(ref _version, value); + } + + public string PathAtThisPoint { get; } + public AssetsListViewModel AssetsList { get; } + public RangeObservableCollection Folders { get; } + public ICollectionView FoldersView { get; } + + public TreeItem(string header, string package, string mountPoint, int version, string pathHere) + { + Header = header; + Package = package; + MountPoint = mountPoint; + Version = version; + PathAtThisPoint = pathHere; + AssetsList = new AssetsListViewModel(); + Folders = new RangeObservableCollection(); + FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } }; + } + + public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files"; +} + +public class AssetsFolderViewModel +{ + public RangeObservableCollection Folders { get; } + public ICollectionView FoldersView { get; } + + public AssetsFolderViewModel() + { + Folders = new RangeObservableCollection(); + FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } }; + } + + public void BulkPopulate(IReadOnlyCollection entries) + { + if (entries == null || entries.Count == 0) + return; + + Application.Current.Dispatcher.Invoke(() => { - Folders = new RangeObservableCollection(); - FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}}; - } + var treeItems = new RangeObservableCollection(); + treeItems.SetSuppressionState(true); + var items = new List(entries.Count); - public void BulkPopulate(IReadOnlyCollection entries) - { - if (entries == null || entries.Count == 0) - return; - - Application.Current.Dispatcher.Invoke(() => + foreach (var entry in entries) { - var treeItems = new RangeObservableCollection(); - treeItems.SetSuppressionState(true); - var items = new List(entries.Count); + var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod); + items.Add(item); - foreach (var entry in entries) { - var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod); - items.Add(item); + TreeItem lastNode = null; + var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries); + var builder = new StringBuilder(64); + var parentNode = treeItems; + for (var i = 0; i < folders.Length - 1; i++) { - TreeItem lastNode = null; - var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries); - var builder = new StringBuilder(64); - var parentNode = treeItems; + var folder = folders[i]; + builder.Append(folder).Append('/'); + lastNode = FindByHeaderOrNull(parentNode, folder); - for (var i = 0; i < folders.Length - 1; i++) + static TreeItem FindByHeaderOrNull(IReadOnlyList list, string header) { - var folder = folders[i]; - builder.Append(folder).Append('/'); - lastNode = FindByHeaderOrNull(parentNode, folder); - - static TreeItem FindByHeaderOrNull(IReadOnlyList list, string header) + for (var i = 0; i < list.Count; i++) { - for (var i = 0; i < list.Count; i++) - { - if (list[i].Header == header) - return list[i]; - } - - return null; + if (list[i].Header == header) + return list[i]; } - if (lastNode == null) - { - var nodePath = builder.ToString(); - lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]); - lastNode.Folders.SetSuppressionState(true); - lastNode.AssetsList.Assets.SetSuppressionState(true); - parentNode.Add(lastNode); - } - - parentNode = lastNode.Folders; + return null; } - lastNode?.AssetsList.Assets.Add(item); + if (lastNode == null) + { + var nodePath = builder.ToString(); + lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]); + lastNode.Folders.SetSuppressionState(true); + lastNode.AssetsList.Assets.SetSuppressionState(true); + parentNode.Add(lastNode); + } + + parentNode = lastNode.Folders; } + + lastNode?.AssetsList.Assets.Add(item); } + } - Folders.AddRange(treeItems); - ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items); + Folders.AddRange(treeItems); + ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items); - foreach (var folder in Folders) - InvokeOnCollectionChanged(folder); + foreach (var folder in Folders) + InvokeOnCollectionChanged(folder); - static void InvokeOnCollectionChanged(TreeItem item) + static void InvokeOnCollectionChanged(TreeItem item) + { + item.Folders.SetSuppressionState(false); + item.AssetsList.Assets.SetSuppressionState(false); + + if (item.Folders.Count != 0) { item.Folders.SetSuppressionState(false); - item.AssetsList.Assets.SetSuppressionState(false); + item.Folders.InvokeOnCollectionChanged(); - if (item.Folders.Count != 0) - { - item.Folders.SetSuppressionState(false); - item.Folders.InvokeOnCollectionChanged(); - - foreach (var folderItem in item.Folders) - InvokeOnCollectionChanged(folderItem); - } - - if (item.AssetsList.Assets.Count != 0) - item.AssetsList.Assets.InvokeOnCollectionChanged(); + foreach (var folderItem in item.Folders) + InvokeOnCollectionChanged(folderItem); } - }); - } + + if (item.AssetsList.Assets.Count != 0) + item.AssetsList.Assets.InvokeOnCollectionChanged(); + } + }); } -} +} \ No newline at end of file diff --git a/FModel/ViewModels/AssetsListViewModel.cs b/FModel/ViewModels/AssetsListViewModel.cs index 1f28d2d7..ceea53ca 100644 --- a/FModel/ViewModels/AssetsListViewModel.cs +++ b/FModel/ViewModels/AssetsListViewModel.cs @@ -3,77 +3,76 @@ using System.Windows.Data; using CUE4Parse.Compression; using FModel.Framework; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class AssetItem : ViewModel { - public class AssetItem : ViewModel + private string _fullPath; + public string FullPath { - private string _fullPath; - public string FullPath - { - get => _fullPath; - private set => SetProperty(ref _fullPath, value); - } - - private bool _isEncrypted; - public bool IsEncrypted - { - get => _isEncrypted; - private set => SetProperty(ref _isEncrypted, value); - } - - private long _offset; - public long Offset - { - get => _offset; - private set => SetProperty(ref _offset, value); - } - - private long _size; - public long Size - { - get => _size; - private set => SetProperty(ref _size, value); - } - - private string _package; - public string Package - { - get => _package; - private set => SetProperty(ref _package, value); - } - - private CompressionMethod _compression; - public CompressionMethod Compression - { - get => _compression; - private set => SetProperty(ref _compression, value); - } - - public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression) - { - FullPath = fullPath; - IsEncrypted = isEncrypted; - Offset = offset; - Size = size; - Package = package; - Compression = compression; - } - - public override string ToString() => FullPath; + get => _fullPath; + private set => SetProperty(ref _fullPath, value); } - public class AssetsListViewModel + private bool _isEncrypted; + public bool IsEncrypted { - public RangeObservableCollection Assets { get; } - public ICollectionView AssetsView { get; } + get => _isEncrypted; + private set => SetProperty(ref _isEncrypted, value); + } - public AssetsListViewModel() + private long _offset; + public long Offset + { + get => _offset; + private set => SetProperty(ref _offset, value); + } + + private long _size; + public long Size + { + get => _size; + private set => SetProperty(ref _size, value); + } + + private string _package; + public string Package + { + get => _package; + private set => SetProperty(ref _package, value); + } + + private CompressionMethod _compression; + public CompressionMethod Compression + { + get => _compression; + private set => SetProperty(ref _compression, value); + } + + public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression) + { + FullPath = fullPath; + IsEncrypted = isEncrypted; + Offset = offset; + Size = size; + Package = package; + Compression = compression; + } + + public override string ToString() => FullPath; +} + +public class AssetsListViewModel +{ + public RangeObservableCollection Assets { get; } + public ICollectionView AssetsView { get; } + + public AssetsListViewModel() + { + Assets = new RangeObservableCollection(); + AssetsView = new ListCollectionView(Assets) { - Assets = new RangeObservableCollection(); - AssetsView = new ListCollectionView(Assets) - { - SortDescriptions = {new SortDescription("FullPath", ListSortDirection.Ascending)} - }; - } + SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) } + }; } } \ No newline at end of file diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index 5d096225..36190bf6 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -23,561 +23,561 @@ using FModel.Views.Resources.Controls.Aup; using Microsoft.Win32; using Serilog; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class AudioFile : ViewModel { - public class AudioFile : ViewModel + private string _filePath; + public string FilePath { - private string _filePath; - public string FilePath - { - get => _filePath; - private set => SetProperty(ref _filePath, value); - } - - private string _fileName; - public string FileName - { - get => _fileName; - private set => SetProperty(ref _fileName, value); - } - - private long _length; - public long Length - { - get => _length; - private set => SetProperty(ref _length, value); - } - - private TimeSpan _duration = TimeSpan.Zero; - public TimeSpan Duration - { - get => _duration; - set => SetProperty(ref _duration, value); - } - - private TimeSpan _position = TimeSpan.Zero; - public TimeSpan Position - { - get => _position; - set => SetProperty(ref _position, value); - } - - private AudioEncoding _encoding = AudioEncoding.Unknown; - public AudioEncoding Encoding - { - get => _encoding; - set => SetProperty(ref _encoding, value); - } - - private PlaybackState _playbackState = PlaybackState.Stopped; - public PlaybackState PlaybackState - { - get => _playbackState; - set => SetProperty(ref _playbackState, value); - } - - private int _bytesPerSecond; - public int BytesPerSecond - { - get => _bytesPerSecond; - set => SetProperty(ref _bytesPerSecond, value); - } - - public int Id { get; set; } - public byte[] Data { get; set; } - public string Extension { get; } - - public AudioFile(int id, byte[] data, string filePath) - { - Id = id; - FilePath = filePath; - FileName = filePath.SubstringAfterLast("/"); - Length = data.Length; - Duration = TimeSpan.Zero; - Position = TimeSpan.Zero; - Encoding = AudioEncoding.Unknown; - PlaybackState = PlaybackState.Stopped; - BytesPerSecond = 0; - Extension = filePath.SubstringAfterLast("."); - Data = data; - } - - public AudioFile(int id, string fileName) - { - Id = id; - FilePath = string.Empty; - FileName = fileName; - Length = 0; - Duration = TimeSpan.Zero; - Position = TimeSpan.Zero; - Encoding = AudioEncoding.Unknown; - PlaybackState = PlaybackState.Stopped; - BytesPerSecond = 0; - Extension = string.Empty; - Data = null; - } - - public AudioFile(int id, FileInfo fileInfo) - { - Id = id; - FilePath = fileInfo.FullName.Replace('\\', '/'); - FileName = fileInfo.Name; - Length = fileInfo.Length; - Duration = TimeSpan.Zero; - Position = TimeSpan.Zero; - Encoding = AudioEncoding.Unknown; - PlaybackState = PlaybackState.Stopped; - BytesPerSecond = 0; - Extension = fileInfo.Extension[1..]; - Data = File.ReadAllBytes(fileInfo.FullName); - } - - public AudioFile(AudioFile audioFile, IAudioSource wave) - { - Id = audioFile.Id; - FilePath = audioFile.FilePath; - FileName = audioFile.FileName; - Length = audioFile.Length; - Duration = wave.GetLength(); - Position = audioFile.Position; - Encoding = wave.WaveFormat.WaveFormatTag; - PlaybackState = audioFile.PlaybackState; - BytesPerSecond = wave.WaveFormat.BytesPerSecond; - Extension = audioFile.Extension; - Data = audioFile.Data; - } - - public override string ToString() - { - return $"{Id} | {FileName} | {Length}"; - } + get => _filePath; + private set => SetProperty(ref _filePath, value); } - public class AudioPlayerViewModel : ViewModel, ISource, IDisposable + private string _fileName; + public string FileName { - private DiscordHandler _discordHandler => DiscordService.DiscordHandler; - private static IWaveSource _waveSource; - private static ISoundOut _soundOut; - private Timer _sourceTimer; - - private TimeSpan _length => _waveSource?.GetLength() ?? TimeSpan.Zero; - private TimeSpan _position => _waveSource?.GetPosition() ?? TimeSpan.Zero; - private PlaybackState _playbackState => _soundOut?.PlaybackState ?? PlaybackState.Stopped; - - public SpectrumProvider Spectrum { get; private set; } - public float[] FftData { get; private set; } - - private AudioFile _playedFile = new(-1, "No audio file"); - public AudioFile PlayedFile - { - get => _playedFile; - private set => SetProperty(ref _playedFile, value); - } - - private AudioFile _selectedAudioFile; - public AudioFile SelectedAudioFile - { - get => _selectedAudioFile; - set => SetProperty(ref _selectedAudioFile, value); - } - - private MMDevice _selectedAudioDevice; - public MMDevice SelectedAudioDevice - { - get => _selectedAudioDevice; - set => SetProperty(ref _selectedAudioDevice, value); - } - - private AudioCommand _audioCommand; - public AudioCommand AudioCommand => _audioCommand ??= new AudioCommand(this); - - public bool IsStopped => PlayedFile.PlaybackState == PlaybackState.Stopped; - public bool IsPlaying => PlayedFile.PlaybackState == PlaybackState.Playing; - public bool IsPaused => PlayedFile.PlaybackState == PlaybackState.Paused; - - private readonly ObservableCollection _audioFiles; - public ICollectionView AudioFilesView { get; } - public ICollectionView AudioDevicesView { get; } - - public AudioPlayerViewModel() - { - _sourceTimer = new Timer(TimerTick, null, 0, 10); - _audioFiles = new ObservableCollection(); - AudioFilesView = new ListCollectionView(_audioFiles); - - var audioDevices = new ObservableCollection(EnumerateDevices()); - AudioDevicesView = new ListCollectionView(audioDevices) {SortDescriptions = {new SortDescription("FriendlyName", ListSortDirection.Ascending)}}; - SelectedAudioDevice ??= audioDevices.FirstOrDefault(); - } - - public void Load() - { - Application.Current.Dispatcher.Invoke(() => - { - if (!ConvertIfNeeded()) - return; - - _waveSource = new CustomCodecFactory().GetCodec(SelectedAudioFile.Data, SelectedAudioFile.Extension); - if (_waveSource == null) - return; - - PlayedFile = new AudioFile(SelectedAudioFile, _waveSource); - Spectrum = new SpectrumProvider(_waveSource.WaveFormat.Channels, _waveSource.WaveFormat.SampleRate, FftSize.Fft4096); - - var notificationSource = new SingleBlockNotificationStream(_waveSource.ToSampleSource()); - notificationSource.SingleBlockRead += (s, a) => Spectrum.Add(a.Left, a.Right); - _waveSource = notificationSource.ToWaveSource(16); - - RaiseSourceEvent(ESourceEventType.Loading); - LoadSoundOut(); - }); - } - - public void AddToPlaylist(byte[] data, string filePath) - { - Application.Current.Dispatcher.Invoke(() => - { - _audioFiles.Add(new AudioFile(_audioFiles.Count, data, filePath)); - if (_audioFiles.Count > 1) return; - - SelectedAudioFile = _audioFiles.Last(); - Load(); - Play(); - }); - } - - public void AddToPlaylist(string filePath) - { - Application.Current.Dispatcher.Invoke(() => - { - _audioFiles.Add(new AudioFile(_audioFiles.Count, new FileInfo(filePath))); - if (_audioFiles.Count > 1) return; - - SelectedAudioFile = _audioFiles.Last(); - Load(); - Play(); - }); - } - - public void Remove() - { - if (_audioFiles.Count < 1) return; - Application.Current.Dispatcher.Invoke(() => - { - _audioFiles.RemoveAt(SelectedAudioFile.Id); - for (var i = 0; i < _audioFiles.Count; i++) - { - _audioFiles[i].Id = i; - } - }); - } - - public void Replace(AudioFile newAudio) - { - if (_audioFiles.Count < 1) return; - Application.Current.Dispatcher.Invoke(() => - { - _audioFiles.Insert(SelectedAudioFile.Id, newAudio); - _audioFiles.RemoveAt(SelectedAudioFile.Id + 1); - SelectedAudioFile = newAudio; - }); - } - - public void SavePlaylist() - { - if (_audioFiles.Count < 1) return; - Application.Current.Dispatcher.Invoke(() => - { - foreach (var a in _audioFiles) - { - Save(a, true); - } - }); - } - - public void Save(AudioFile file = null, bool auto = false) - { - var fileToSave = file ?? SelectedAudioFile; - if (_audioFiles.Count < 1 || fileToSave?.Data == null) return; - var path = fileToSave.FilePath; - - if (!auto) - { - var saveFileDialog = new SaveFileDialog - { - Title = "Save Audio", - FileName = fileToSave.FileName, - InitialDirectory = UserSettings.Default.AudioDirectory - }; - if (!(bool) saveFileDialog.ShowDialog()) return; - path = saveFileDialog.FileName; - } - else - { - Directory.CreateDirectory(path.SubstringBeforeLast('/')); - } - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write)) - using (var writer = new BinaryWriter(stream)) - { - writer.Write(fileToSave.Data); - writer.Flush(); - } - - if (File.Exists(path)) - { - Log.Information("{FileName} successfully saved", fileToSave.FileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved '{fileToSave.FileName}'", Constants.WHITE, true); - } - else - { - Log.Error("{FileName} could not be saved", fileToSave.FileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true); - } - } - - public void PlayPauseOnStart() - { - if (IsStopped) - { - Load(); - Play(); - } - else if (IsPaused) - { - Play(); - } - else if (IsPlaying) - { - Pause(); - } - } - - public void PlayPauseOnForce() - { - if (_audioFiles.Count < 1 || SelectedAudioFile.Id == PlayedFile.Id) return; - - Stop(); - Load(); - Play(); - } - - public void Next() - { - if (_audioFiles.Count < 1) return; - - Stop(); - SelectedAudioFile = _audioFiles.Next(PlayedFile.Id); - Load(); - Play(); - } - - public void Previous() - { - if (_audioFiles.Count < 1) return; - - Stop(); - SelectedAudioFile = _audioFiles.Previous(PlayedFile.Id); - Load(); - Play(); - } - - public void Play() - { - if (_soundOut == null || IsPlaying) return; - _discordHandler.UpdateButDontSavePresence(null, $"Audio Player: {PlayedFile.FileName} ({PlayedFile.Duration:g})"); - _soundOut.Play(); - } - - public void Pause() - { - if (_soundOut == null || IsPaused) return; - _soundOut.Pause(); - } - - public void Resume() - { - if (_soundOut == null || !IsPaused) return; - _soundOut.Resume(); - } - - public void Stop() - { - if (_soundOut == null || IsStopped) return; - _soundOut.Stop(); - } - - public void SkipTo(double percentage) - { - if (_soundOut == null || _waveSource == null) return; - _waveSource.Position = (long) (_waveSource.Length * percentage); - } - - public void Volume() - { - if (_soundOut == null) return; - _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; - } - - public void Device() - { - if (_soundOut == null) return; - - Pause(); - LoadSoundOut(); - Play(); - } - - public void Dispose() - { - Application.Current.Dispatcher.Invoke(() => - { - if (_waveSource != null) - { - _waveSource.Dispose(); - _waveSource = null; - } - - if (_soundOut != null) - { - _soundOut.Dispose(); - _soundOut = null; - } - - if (Spectrum != null) - Spectrum = null; - - foreach (var a in _audioFiles) - { - a.Data = null; - } - - _audioFiles.Clear(); - PlayedFile = new AudioFile(-1, "No audio file"); - }); - } - - private void TimerTick(object state) - { - if (_waveSource == null || _soundOut == null) return; - - if (_position != PlayedFile.Position) - { - PlayedFile.Position = _position; - RaiseSourcePropertyChangedEvent(ESourceProperty.Position, PlayedFile.Position); - } - - if (_playbackState != PlayedFile.PlaybackState) - { - PlayedFile.PlaybackState = _playbackState; - RaiseSourcePropertyChangedEvent(ESourceProperty.PlaybackState, PlayedFile.PlaybackState); - } - - if (Spectrum != null && PlayedFile.PlaybackState == PlaybackState.Playing) - { - FftData = new float[4096]; - Spectrum.GetFftData(FftData); - RaiseSourcePropertyChangedEvent(ESourceProperty.FftData, FftData); - } - } - - private void LoadSoundOut() - { - if (_waveSource == null) return; - _soundOut = new WasapiOut(true, AudioClientShareMode.Shared, 100, ThreadPriority.Highest) {Device = SelectedAudioDevice}; - _soundOut.Initialize(_waveSource.ToSampleSource().ToWaveSource(16)); - _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; - } - - private IEnumerable EnumerateDevices() - { - using var deviceEnumerator = new MMDeviceEnumerator(); - using var deviceCollection = deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active); - foreach (var device in deviceCollection) - { - if (device.DeviceID == UserSettings.Default.AudioDeviceId) - SelectedAudioDevice = device; - - yield return device; - } - } - - public event EventHandler SourceEvent; - public event EventHandler SourcePropertyChangedEvent = (sender, args) => - { - if (sender is not AudioPlayerViewModel viewModel) return; - switch (args.Property) - { - case ESourceProperty.PlaybackState: - { - if (viewModel._position == viewModel._length && (PlaybackState) args.Value == PlaybackState.Stopped) - viewModel.Next(); - - break; - } - } - }; - - private void RaiseSourceEvent(ESourceEventType e) - { - SourceEvent?.Invoke(this, new SourceEventArgs(e)); - } - - private void RaiseSourcePropertyChangedEvent(ESourceProperty property, object value) - { - SourcePropertyChangedEvent?.Invoke(this, new SourcePropertyChangedEventArgs(property, value)); - } - - private bool ConvertIfNeeded() - { - if (SelectedAudioFile?.Data == null) - return false; - - switch (SelectedAudioFile.Extension) - { - case "adpcm": - case "opus": - case "wem": - { - if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath)) - { - var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); - Replace(newAudio); - return true; - } - - return false; - } - } - - return true; - } - - private bool TryConvert(out string wavFilePath) - { - wavFilePath = string.Empty; - var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); - if (!File.Exists(vgmFilePath)) return false; - - Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/")); - File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data); - - wavFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav"); - var vgmProcess = Process.Start(new ProcessStartInfo - { - FileName = vgmFilePath, - Arguments = $"-o \"{wavFilePath}\" \"{SelectedAudioFile.FilePath}\"", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }); - vgmProcess?.WaitForExit(); - - File.Delete(SelectedAudioFile.FilePath); - return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath); - } + get => _fileName; + private set => SetProperty(ref _fileName, value); + } + + private long _length; + public long Length + { + get => _length; + private set => SetProperty(ref _length, value); + } + + private TimeSpan _duration = TimeSpan.Zero; + public TimeSpan Duration + { + get => _duration; + set => SetProperty(ref _duration, value); + } + + private TimeSpan _position = TimeSpan.Zero; + public TimeSpan Position + { + get => _position; + set => SetProperty(ref _position, value); + } + + private AudioEncoding _encoding = AudioEncoding.Unknown; + public AudioEncoding Encoding + { + get => _encoding; + set => SetProperty(ref _encoding, value); + } + + private PlaybackState _playbackState = PlaybackState.Stopped; + public PlaybackState PlaybackState + { + get => _playbackState; + set => SetProperty(ref _playbackState, value); + } + + private int _bytesPerSecond; + public int BytesPerSecond + { + get => _bytesPerSecond; + set => SetProperty(ref _bytesPerSecond, value); + } + + public int Id { get; set; } + public byte[] Data { get; set; } + public string Extension { get; } + + public AudioFile(int id, byte[] data, string filePath) + { + Id = id; + FilePath = filePath; + FileName = filePath.SubstringAfterLast("/"); + Length = data.Length; + Duration = TimeSpan.Zero; + Position = TimeSpan.Zero; + Encoding = AudioEncoding.Unknown; + PlaybackState = PlaybackState.Stopped; + BytesPerSecond = 0; + Extension = filePath.SubstringAfterLast("."); + Data = data; + } + + public AudioFile(int id, string fileName) + { + Id = id; + FilePath = string.Empty; + FileName = fileName; + Length = 0; + Duration = TimeSpan.Zero; + Position = TimeSpan.Zero; + Encoding = AudioEncoding.Unknown; + PlaybackState = PlaybackState.Stopped; + BytesPerSecond = 0; + Extension = string.Empty; + Data = null; + } + + public AudioFile(int id, FileInfo fileInfo) + { + Id = id; + FilePath = fileInfo.FullName.Replace('\\', '/'); + FileName = fileInfo.Name; + Length = fileInfo.Length; + Duration = TimeSpan.Zero; + Position = TimeSpan.Zero; + Encoding = AudioEncoding.Unknown; + PlaybackState = PlaybackState.Stopped; + BytesPerSecond = 0; + Extension = fileInfo.Extension[1..]; + Data = File.ReadAllBytes(fileInfo.FullName); + } + + public AudioFile(AudioFile audioFile, IAudioSource wave) + { + Id = audioFile.Id; + FilePath = audioFile.FilePath; + FileName = audioFile.FileName; + Length = audioFile.Length; + Duration = wave.GetLength(); + Position = audioFile.Position; + Encoding = wave.WaveFormat.WaveFormatTag; + PlaybackState = audioFile.PlaybackState; + BytesPerSecond = wave.WaveFormat.BytesPerSecond; + Extension = audioFile.Extension; + Data = audioFile.Data; + } + + public override string ToString() + { + return $"{Id} | {FileName} | {Length}"; } } + +public class AudioPlayerViewModel : ViewModel, ISource, IDisposable +{ + private DiscordHandler _discordHandler => DiscordService.DiscordHandler; + private static IWaveSource _waveSource; + private static ISoundOut _soundOut; + private Timer _sourceTimer; + + private TimeSpan _length => _waveSource?.GetLength() ?? TimeSpan.Zero; + private TimeSpan _position => _waveSource?.GetPosition() ?? TimeSpan.Zero; + private PlaybackState _playbackState => _soundOut?.PlaybackState ?? PlaybackState.Stopped; + + public SpectrumProvider Spectrum { get; private set; } + public float[] FftData { get; private set; } + + private AudioFile _playedFile = new(-1, "No audio file"); + public AudioFile PlayedFile + { + get => _playedFile; + private set => SetProperty(ref _playedFile, value); + } + + private AudioFile _selectedAudioFile; + public AudioFile SelectedAudioFile + { + get => _selectedAudioFile; + set => SetProperty(ref _selectedAudioFile, value); + } + + private MMDevice _selectedAudioDevice; + public MMDevice SelectedAudioDevice + { + get => _selectedAudioDevice; + set => SetProperty(ref _selectedAudioDevice, value); + } + + private AudioCommand _audioCommand; + public AudioCommand AudioCommand => _audioCommand ??= new AudioCommand(this); + + public bool IsStopped => PlayedFile.PlaybackState == PlaybackState.Stopped; + public bool IsPlaying => PlayedFile.PlaybackState == PlaybackState.Playing; + public bool IsPaused => PlayedFile.PlaybackState == PlaybackState.Paused; + + private readonly ObservableCollection _audioFiles; + public ICollectionView AudioFilesView { get; } + public ICollectionView AudioDevicesView { get; } + + public AudioPlayerViewModel() + { + _sourceTimer = new Timer(TimerTick, null, 0, 10); + _audioFiles = new ObservableCollection(); + AudioFilesView = new ListCollectionView(_audioFiles); + + var audioDevices = new ObservableCollection(EnumerateDevices()); + AudioDevicesView = new ListCollectionView(audioDevices) { SortDescriptions = { new SortDescription("FriendlyName", ListSortDirection.Ascending) } }; + SelectedAudioDevice ??= audioDevices.FirstOrDefault(); + } + + public void Load() + { + Application.Current.Dispatcher.Invoke(() => + { + if (!ConvertIfNeeded()) + return; + + _waveSource = new CustomCodecFactory().GetCodec(SelectedAudioFile.Data, SelectedAudioFile.Extension); + if (_waveSource == null) + return; + + PlayedFile = new AudioFile(SelectedAudioFile, _waveSource); + Spectrum = new SpectrumProvider(_waveSource.WaveFormat.Channels, _waveSource.WaveFormat.SampleRate, FftSize.Fft4096); + + var notificationSource = new SingleBlockNotificationStream(_waveSource.ToSampleSource()); + notificationSource.SingleBlockRead += (s, a) => Spectrum.Add(a.Left, a.Right); + _waveSource = notificationSource.ToWaveSource(16); + + RaiseSourceEvent(ESourceEventType.Loading); + LoadSoundOut(); + }); + } + + public void AddToPlaylist(byte[] data, string filePath) + { + Application.Current.Dispatcher.Invoke(() => + { + _audioFiles.Add(new AudioFile(_audioFiles.Count, data, filePath)); + if (_audioFiles.Count > 1) return; + + SelectedAudioFile = _audioFiles.Last(); + Load(); + Play(); + }); + } + + public void AddToPlaylist(string filePath) + { + Application.Current.Dispatcher.Invoke(() => + { + _audioFiles.Add(new AudioFile(_audioFiles.Count, new FileInfo(filePath))); + if (_audioFiles.Count > 1) return; + + SelectedAudioFile = _audioFiles.Last(); + Load(); + Play(); + }); + } + + public void Remove() + { + if (_audioFiles.Count < 1) return; + Application.Current.Dispatcher.Invoke(() => + { + _audioFiles.RemoveAt(SelectedAudioFile.Id); + for (var i = 0; i < _audioFiles.Count; i++) + { + _audioFiles[i].Id = i; + } + }); + } + + public void Replace(AudioFile newAudio) + { + if (_audioFiles.Count < 1) return; + Application.Current.Dispatcher.Invoke(() => + { + _audioFiles.Insert(SelectedAudioFile.Id, newAudio); + _audioFiles.RemoveAt(SelectedAudioFile.Id + 1); + SelectedAudioFile = newAudio; + }); + } + + public void SavePlaylist() + { + if (_audioFiles.Count < 1) return; + Application.Current.Dispatcher.Invoke(() => + { + foreach (var a in _audioFiles) + { + Save(a, true); + } + }); + } + + public void Save(AudioFile file = null, bool auto = false) + { + var fileToSave = file ?? SelectedAudioFile; + if (_audioFiles.Count < 1 || fileToSave?.Data == null) return; + var path = fileToSave.FilePath; + + if (!auto) + { + var saveFileDialog = new SaveFileDialog + { + Title = "Save Audio", + FileName = fileToSave.FileName, + InitialDirectory = UserSettings.Default.AudioDirectory + }; + if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return; + path = saveFileDialog.FileName; + } + else + { + Directory.CreateDirectory(path.SubstringBeforeLast('/')); + } + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write)) + using (var writer = new BinaryWriter(stream)) + { + writer.Write(fileToSave.Data); + writer.Flush(); + } + + if (File.Exists(path)) + { + Log.Information("{FileName} successfully saved", fileToSave.FileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved '{fileToSave.FileName}'", Constants.WHITE, true); + } + else + { + Log.Error("{FileName} could not be saved", fileToSave.FileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true); + } + } + + public void PlayPauseOnStart() + { + if (IsStopped) + { + Load(); + Play(); + } + else if (IsPaused) + { + Play(); + } + else if (IsPlaying) + { + Pause(); + } + } + + public void PlayPauseOnForce() + { + if (_audioFiles.Count < 1 || SelectedAudioFile.Id == PlayedFile.Id) return; + + Stop(); + Load(); + Play(); + } + + public void Next() + { + if (_audioFiles.Count < 1) return; + + Stop(); + SelectedAudioFile = _audioFiles.Next(PlayedFile.Id); + Load(); + Play(); + } + + public void Previous() + { + if (_audioFiles.Count < 1) return; + + Stop(); + SelectedAudioFile = _audioFiles.Previous(PlayedFile.Id); + Load(); + Play(); + } + + public void Play() + { + if (_soundOut == null || IsPlaying) return; + _discordHandler.UpdateButDontSavePresence(null, $"Audio Player: {PlayedFile.FileName} ({PlayedFile.Duration:g})"); + _soundOut.Play(); + } + + public void Pause() + { + if (_soundOut == null || IsPaused) return; + _soundOut.Pause(); + } + + public void Resume() + { + if (_soundOut == null || !IsPaused) return; + _soundOut.Resume(); + } + + public void Stop() + { + if (_soundOut == null || IsStopped) return; + _soundOut.Stop(); + } + + public void SkipTo(double percentage) + { + if (_soundOut == null || _waveSource == null) return; + _waveSource.Position = (long) (_waveSource.Length * percentage); + } + + public void Volume() + { + if (_soundOut == null) return; + _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; + } + + public void Device() + { + if (_soundOut == null) return; + + Pause(); + LoadSoundOut(); + Play(); + } + + public void Dispose() + { + Application.Current.Dispatcher.Invoke(() => + { + if (_waveSource != null) + { + _waveSource.Dispose(); + _waveSource = null; + } + + if (_soundOut != null) + { + _soundOut.Dispose(); + _soundOut = null; + } + + if (Spectrum != null) + Spectrum = null; + + foreach (var a in _audioFiles) + { + a.Data = null; + } + + _audioFiles.Clear(); + PlayedFile = new AudioFile(-1, "No audio file"); + }); + } + + private void TimerTick(object state) + { + if (_waveSource == null || _soundOut == null) return; + + if (_position != PlayedFile.Position) + { + PlayedFile.Position = _position; + RaiseSourcePropertyChangedEvent(ESourceProperty.Position, PlayedFile.Position); + } + + if (_playbackState != PlayedFile.PlaybackState) + { + PlayedFile.PlaybackState = _playbackState; + RaiseSourcePropertyChangedEvent(ESourceProperty.PlaybackState, PlayedFile.PlaybackState); + } + + if (Spectrum != null && PlayedFile.PlaybackState == PlaybackState.Playing) + { + FftData = new float[4096]; + Spectrum.GetFftData(FftData); + RaiseSourcePropertyChangedEvent(ESourceProperty.FftData, FftData); + } + } + + private void LoadSoundOut() + { + if (_waveSource == null) return; + _soundOut = new WasapiOut(true, AudioClientShareMode.Shared, 100, ThreadPriority.Highest) { Device = SelectedAudioDevice }; + _soundOut.Initialize(_waveSource.ToSampleSource().ToWaveSource(16)); + _soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100; + } + + private IEnumerable EnumerateDevices() + { + using var deviceEnumerator = new MMDeviceEnumerator(); + using var deviceCollection = deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active); + foreach (var device in deviceCollection) + { + if (device.DeviceID == UserSettings.Default.AudioDeviceId) + SelectedAudioDevice = device; + + yield return device; + } + } + + public event EventHandler SourceEvent; + + public event EventHandler SourcePropertyChangedEvent = (sender, args) => + { + if (sender is not AudioPlayerViewModel viewModel) return; + switch (args.Property) + { + case ESourceProperty.PlaybackState: + { + if (viewModel._position == viewModel._length && (PlaybackState) args.Value == PlaybackState.Stopped) + viewModel.Next(); + + break; + } + } + }; + + private void RaiseSourceEvent(ESourceEventType e) + { + SourceEvent?.Invoke(this, new SourceEventArgs(e)); + } + + private void RaiseSourcePropertyChangedEvent(ESourceProperty property, object value) + { + SourcePropertyChangedEvent?.Invoke(this, new SourcePropertyChangedEventArgs(property, value)); + } + + private bool ConvertIfNeeded() + { + if (SelectedAudioFile?.Data == null) + return false; + + switch (SelectedAudioFile.Extension) + { + case "adpcm": + case "opus": + case "wem": + { + if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath)) + { + var newAudio = new AudioFile(SelectedAudioFile.Id, new FileInfo(wavFilePath)); + Replace(newAudio); + return true; + } + + return false; + } + } + + return true; + } + + private bool TryConvert(out string wavFilePath) + { + wavFilePath = string.Empty; + var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); + if (!File.Exists(vgmFilePath)) return false; + + Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/")); + File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data); + + wavFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav"); + var vgmProcess = Process.Start(new ProcessStartInfo + { + FileName = vgmFilePath, + Arguments = $"-o \"{wavFilePath}\" \"{SelectedAudioFile.FilePath}\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }); + vgmProcess?.WaitForExit(); + + File.Delete(SelectedAudioFile.FilePath); + return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/BackupManagerViewModel.cs b/FModel/ViewModels/BackupManagerViewModel.cs index b7951379..78be7f6b 100644 --- a/FModel/ViewModels/BackupManagerViewModel.cs +++ b/FModel/ViewModels/BackupManagerViewModel.cs @@ -16,102 +16,101 @@ using K4os.Compression.LZ4; using K4os.Compression.LZ4.Streams; using Serilog; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class BackupManagerViewModel : ViewModel { - public class BackupManagerViewModel : ViewModel + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private readonly string _gameName; + + private Backup _selectedBackup; + public Backup SelectedBackup { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - private readonly string _gameName; + get => _selectedBackup; + set => SetProperty(ref _selectedBackup, value); + } - private Backup _selectedBackup; - public Backup SelectedBackup + public ObservableCollection Backups { get; } + public ICollectionView BackupsView { get; } + + public BackupManagerViewModel(string gameName) + { + _gameName = gameName; + Backups = new ObservableCollection(); + BackupsView = new ListCollectionView(Backups) { SortDescriptions = { new SortDescription("FileName", ListSortDirection.Ascending) } }; + } + + public async Task Initialize() + { + await _threadWorkerView.Begin(cancellationToken => { - get => _selectedBackup; - set => SetProperty(ref _selectedBackup, value); - } + var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName); + if (backups == null) return; - public ObservableCollection Backups { get; } - public ICollectionView BackupsView { get; } - - public BackupManagerViewModel(string gameName) - { - _gameName = gameName; - Backups = new ObservableCollection(); - BackupsView = new ListCollectionView(Backups) {SortDescriptions = {new SortDescription("FileName", ListSortDirection.Ascending)}}; - } - - public async Task Initialize() - { - await _threadWorkerView.Begin(cancellationToken => + Application.Current.Dispatcher.Invoke(() => { - var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName); - if (backups == null) return; - - Application.Current.Dispatcher.Invoke(() => - { - foreach (var backup in backups) Backups.Add(backup); - SelectedBackup = Backups.LastOrDefault(); - }); + foreach (var backup in backups) Backups.Add(backup); + SelectedBackup = Backups.LastOrDefault(); }); - } + }); + } - public async Task CreateBackup() + public async Task CreateBackup() + { + await _threadWorkerView.Begin(_ => { - await _threadWorkerView.Begin(_ => + var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"); + var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp"; + var fullPath = Path.Combine(backupFolder, fileName); + + using var fileStream = new FileStream(fullPath, FileMode.Create); + using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST); + using var writer = new BinaryWriter(compressedStream); + foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values) { - var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"); - var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp"; - var fullPath = Path.Combine(backupFolder, fileName); + if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || + entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) + continue; - using var fileStream = new FileStream(fullPath, FileMode.Create); - using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST); - using var writer = new BinaryWriter(compressedStream); - foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values) - { - if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || - entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) - continue; - - writer.Write((long) 0); - writer.Write((long) 0); - writer.Write(entry.Size); - writer.Write(entry.IsEncrypted); - writer.Write(0); - writer.Write($"/{entry.Path.ToLower()}"); - writer.Write(0); - } - - SaveCheck(fullPath, fileName, "created", "create"); - }); - } - - public async Task Download() - { - if (SelectedBackup == null) return; - await _threadWorkerView.Begin(_ => - { - var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName); - _apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath); - SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download"); - }); - } - - private void SaveCheck(string fullPath, string fileName, string type1, string type2) - { - if (new FileInfo(fullPath).Length > 0) - { - Log.Information("{FileName} successfully {Type}", fileName, type1); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true); - } - else - { - Log.Error("{FileName} could not be {Type}", fileName, type1); - FLogger.AppendError(); - FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true); + writer.Write((long) 0); + writer.Write((long) 0); + writer.Write(entry.Size); + writer.Write(entry.IsEncrypted); + writer.Write(0); + writer.Write($"/{entry.Path.ToLower()}"); + writer.Write(0); } + + SaveCheck(fullPath, fileName, "created", "create"); + }); + } + + public async Task Download() + { + if (SelectedBackup == null) return; + await _threadWorkerView.Begin(_ => + { + var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName); + _apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath); + SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download"); + }); + } + + private void SaveCheck(string fullPath, string fileName, string type1, string type2) + { + if (new FileInfo(fullPath).Length > 0) + { + Log.Information("{FileName} successfully {Type}", fileName, type1); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true); + } + else + { + Log.Error("{FileName} could not be {Type}", fileName, type1); + FLogger.AppendError(); + FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true); } } -} +} \ No newline at end of file diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 712be0ee..a1467ff2 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -41,786 +41,811 @@ using Newtonsoft.Json; using Serilog; using SkiaSharp; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class CUE4ParseViewModel : ViewModel { - public class CUE4ParseViewModel : ViewModel + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private readonly Regex _package = new(@"^(?!global|pakchunk.+optional\-).+(pak|utoc)$", // should be universal + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|20.*|\w+)-WindowsClient|global)\.(pak|utoc)$", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + private FGame _game; + + public FGame Game { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; - private readonly Regex _package = new(@"^(?!global|pakchunk.+optional\-).+(pak|utoc)$", // should be universal - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|\w+)-WindowsClient|global)\.(pak|utoc)$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + get => _game; + set => SetProperty(ref _game, value); + } - private FGame _game; - public FGame Game + private bool _modelIsOverwritingMaterial; + public bool ModelIsOverwritingMaterial + { + get => _modelIsOverwritingMaterial; + set => SetProperty(ref _modelIsOverwritingMaterial, value); + } + + public AbstractVfsFileProvider Provider { get; } + public GameDirectoryViewModel GameDirectory { get; } + public AssetsFolderViewModel AssetsFolder { get; } + public SearchViewModel SearchVm { get; } + public TabControlViewModel TabControl { get; } + public int LocalizedResourcesCount { get; set; } + public bool HotfixedResourcesDone { get; set; } + public int VirtualPathCount { get; set; } + + public CUE4ParseViewModel(string gameDirectory) + { + switch (gameDirectory) { - get => _game; - set => SetProperty(ref _game, value); - } - - private bool _modelIsOverwritingMaterial; - public bool ModelIsOverwritingMaterial - { - get => _modelIsOverwritingMaterial; - set => SetProperty(ref _modelIsOverwritingMaterial, value); - } - - public AbstractVfsFileProvider Provider { get; } - public GameDirectoryViewModel GameDirectory { get; } - public AssetsFolderViewModel AssetsFolder { get; } - public SearchViewModel SearchVm { get; } - public TabControlViewModel TabControl { get; } - public int LocalizedResourcesCount { get; set; } - public bool HotfixedResourcesDone { get; set; } - public int VirtualPathCount { get; set; } - - public CUE4ParseViewModel(string gameDirectory) - { - switch (gameDirectory) + case Constants._FN_LIVE_TRIGGER: { - case Constants._FN_LIVE_TRIGGER: - { - Game = FGame.FortniteGame; - Provider = new StreamedFileProvider("FortniteLive", true, - new VersionContainer( - UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, - customVersions: UserSettings.Default.OverridedCustomVersions[Game], - optionOverrides: UserSettings.Default.OverridedOptions[Game])); - break; - } - case Constants._VAL_LIVE_TRIGGER: - { - Game = FGame.ShooterGame; - Provider = new StreamedFileProvider("ValorantLive", true, - new VersionContainer( - UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, - customVersions: UserSettings.Default.OverridedCustomVersions[Game], - optionOverrides: UserSettings.Default.OverridedOptions[Game])); - break; - } - default: - { - Game = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\").ToEnum(FGame.Unknown); - var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, + Game = FGame.FortniteGame; + Provider = new StreamedFileProvider("FortniteLive", true, + new VersionContainer( + UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, customVersions: UserSettings.Default.OverridedCustomVersions[Game], - optionOverrides: UserSettings.Default.OverridedOptions[Game]); + optionOverrides: UserSettings.Default.OverridedOptions[Game])); + break; + } + case Constants._VAL_LIVE_TRIGGER: + { + Game = FGame.ShooterGame; + Provider = new StreamedFileProvider("ValorantLive", true, + new VersionContainer( + UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, + customVersions: UserSettings.Default.OverridedCustomVersions[Game], + optionOverrides: UserSettings.Default.OverridedOptions[Game])); + break; + } + default: + { + Game = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\").ToEnum(FGame.Unknown); + var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, + customVersions: UserSettings.Default.OverridedCustomVersions[Game], + optionOverrides: UserSettings.Default.OverridedOptions[Game]); - switch (Game) + switch (Game) + { + case FGame.StateOfDecay2: { - case FGame.StateOfDecay2: + Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List + { + new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), + new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") + }, + SearchOption.AllDirectories, true, versions); + break; + } + case FGame.FortniteGame: + Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List + { + new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\FortniteGame\\Saved\\PersistentDownloadDir\\InstalledBundles"), + }, + SearchOption.AllDirectories, true, versions); + break; + case FGame.Unknown when UserSettings.Default.ManualGames.TryGetValue(gameDirectory, out var settings): + { + versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform, + customVersions: settings.OverridedCustomVersions, + optionOverrides: settings.OverridedOptions); + goto default; + } + default: + { + Provider = new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versions); + break; + } + } + + break; + } + } + + GameDirectory = new GameDirectoryViewModel(); + AssetsFolder = new AssetsFolderViewModel(); + SearchVm = new SearchViewModel(); + TabControl = new TabControlViewModel(); + } + + public async Task Initialize() + { + await _threadWorkerView.Begin(cancellationToken => + { + switch (Provider) + { + case StreamedFileProvider p: + switch (p.LiveGame) + { + case "FortniteLive": { - Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List - { - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") - }, - SearchOption.AllDirectories, true, versions); + var manifestInfo = _apiEndpointView.EpicApi.GetManifest(cancellationToken); + if (manifestInfo == null) + { + throw new Exception("Could not load latest Fortnite manifest, you may have to switch to your local installation."); + } + + byte[] manifestData; + var chunksDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); + var manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.FileName); + if (File.Exists(manifestPath)) + { + manifestData = File.ReadAllBytes(manifestPath); + } + else + { + manifestData = manifestInfo.DownloadManifestData(); + File.WriteAllBytes(manifestPath, manifestData); + } + + var manifest = new Manifest(manifestData, new ManifestOptions + { + ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV4/", UriKind.Absolute), + ChunkCacheDirectory = chunksDir + }); + + foreach (var fileManifest in manifest.FileManifests) + { + if (!_fnLive.IsMatch(fileManifest.Name)) continue; + + //var casStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".utoc", ".ucas"))); + //p.Initialize(fileManifest.Name, new[] {fileManifest.GetStream(), casStream.GetStream()}); + p.Initialize(fileManifest.Name, new Stream[] { fileManifest.GetStream() } + , it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions)); + } + + FLogger.AppendInformation(); + FLogger.AppendText($"Fortnite has been loaded successfully in {manifest.ParseTime.TotalMilliseconds}ms", Constants.WHITE, true); + FLogger.AppendWarning(); + FLogger.AppendText($"Mappings must match '{manifest.BuildVersion}' in order to avoid errors", Constants.WHITE, true); break; } - case FGame.FortniteGame: - Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List - { - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\FortniteGame\\Saved\\PersistentDownloadDir\\InstalledBundles"), - }, - SearchOption.AllDirectories, true, versions); - break; - case FGame.Unknown when UserSettings.Default.ManualGames.TryGetValue(gameDirectory, out var settings): + case "ValorantLive": { - versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform, - customVersions: settings.OverridedCustomVersions, - optionOverrides: settings.OverridedOptions); - goto default; - } - default: - { - Provider = new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versions); + var manifestInfo = _apiEndpointView.ValorantApi.GetManifest(cancellationToken); + if (manifestInfo == null) + { + throw new Exception("Could not load latest Valorant manifest, you may have to switch to your local installation."); + } + + for (var i = 0; i < manifestInfo.Paks.Length; i++) + { + p.Initialize(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) }); + } + + FLogger.AppendInformation(); + FLogger.AppendText($"Valorant '{manifestInfo.Header.GameVersion}' has been loaded successfully", Constants.WHITE, true); break; } } break; - } + case DefaultFileProvider d: + d.Initialize(); + break; } - GameDirectory = new GameDirectoryViewModel(); - AssetsFolder = new AssetsFolderViewModel(); - SearchVm = new SearchViewModel(); - TabControl = new TabControlViewModel(); - } - - public async Task Initialize() - { - await _threadWorkerView.Begin(cancellationToken => + foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui { - switch (Provider) - { - case StreamedFileProvider p: - switch (p.LiveGame) - { - case "FortniteLive": - { - var manifestInfo = _apiEndpointView.EpicApi.GetManifest(cancellationToken); - if (manifestInfo == null) - { - throw new Exception("Could not load latest Fortnite manifest, you may have to switch to your local installation."); - } + cancellationToken.ThrowIfCancellationRequested(); + if (vfs.Length <= 365 || !_package.IsMatch(vfs.Name)) continue; - byte[] manifestData; - var chunksDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); - var manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.FileName); - if (File.Exists(manifestPath)) - { - manifestData = File.ReadAllBytes(manifestPath); - } - else - { - manifestData = manifestInfo.DownloadManifestData(); - File.WriteAllBytes(manifestPath, manifestData); - } - - var manifest = new Manifest(manifestData, new ManifestOptions - { - ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV4/", UriKind.Absolute), - ChunkCacheDirectory = chunksDir - }); - - foreach (var fileManifest in manifest.FileManifests) - { - if (!_fnLive.IsMatch(fileManifest.Name)) continue; - - //var casStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".utoc", ".ucas"))); - //p.Initialize(fileManifest.Name, new[] {fileManifest.GetStream(), casStream.GetStream()}); - p.Initialize(fileManifest.Name, new Stream[] { fileManifest.GetStream() } - , it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions)); - } - - FLogger.AppendInformation(); - FLogger.AppendText($"Fortnite has been loaded successfully in {manifest.ParseTime.TotalMilliseconds}ms", Constants.WHITE, true); - FLogger.AppendWarning(); - FLogger.AppendText($"Mappings must match '{manifest.BuildVersion}' in order to avoid errors", Constants.WHITE, true); - break; - } - case "ValorantLive": - { - var manifestInfo = _apiEndpointView.ValorantApi.GetManifest(cancellationToken); - if (manifestInfo == null) - { - throw new Exception("Could not load latest Valorant manifest, you may have to switch to your local installation."); - } - - for (var i = 0; i < manifestInfo.Paks.Length; i++) - { - p.Initialize(manifestInfo.Paks[i].GetFullName(), new[] {manifestInfo.GetPakStream(i)}); - } - - FLogger.AppendInformation(); - FLogger.AppendText($"Valorant '{manifestInfo.Header.GameVersion}' has been loaded successfully", Constants.WHITE, true); - break; - } - } - - break; - case DefaultFileProvider d: - d.Initialize(); - break; - } - - foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui - { - cancellationToken.ThrowIfCancellationRequested(); - if (vfs.Length <= 365 || !_package.IsMatch(vfs.Name)) continue; - - GameDirectory.Add(vfs); - } - }); - } - - /// - /// load virtual files system from GameDirectory - /// - /// - public async Task LoadVfs(IEnumerable aesKeys) - { - await _threadWorkerView.Begin(cancellationToken => - { - GameDirectory.DeactivateAll(); - - // load files using UnloadedVfs to include non-encrypted vfs - foreach (var key in aesKeys) - { - cancellationToken.ThrowIfCancellationRequested(); // cancel if needed - - var k = key.Key.Trim(); - if (k.Length != 66) k = Constants.ZERO_64_CHAR; - Provider.SubmitKey(key.Guid, new FAesKey(k)); - } - - // files in MountedVfs will be enabled - foreach (var file in GameDirectory.DirectoryFiles) - { - cancellationToken.ThrowIfCancellationRequested(); - if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs) - { - if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store) - file.FileCount = (int)store.Info.TocEntryCount - 1; - - continue; - } - - file.IsEnabled = true; - file.MountPoint = vfs.MountPoint; - file.FileCount = vfs.FileCount; - } - - // Game = Provider.GameName.ToEnum(Game); - }); - } - - public void ClearProvider() - { - if (Provider == null) return; - - AssetsFolder.Folders.Clear(); - SearchVm.SearchResults.Clear(); - Helper.CloseWindow("Search View"); - Provider.UnloadAllVfs(); - GC.Collect(); - } - - public async Task RefreshAes() - { - if (Game == FGame.FortniteGame) // game directory dependent, we don't have the provider game name yet since we don't have aes keys - { - await _threadWorkerView.Begin(cancellationToken => - { - var aes = _apiEndpointView.BenbotApi.GetAesKeys(cancellationToken); - if (aes?.MainKey == null && aes?.DynamicKeys == null && aes?.Version == null) return; - - UserSettings.Default.AesKeys[Game] = aes; - }); + GameDirectory.Add(vfs); } - } + }); + } - public async Task InitInformation() + /// + /// load virtual files system from GameDirectory + /// + /// + public async Task LoadVfs(IEnumerable aesKeys) + { + await _threadWorkerView.Begin(cancellationToken => { - await _threadWorkerView.Begin(cancellationToken => - { - var info = _apiEndpointView.FModelApi.GetNews(cancellationToken); - if (info == null) return; + GameDirectory.DeactivateAll(); - for (var i = 0; i < info.Messages.Length; i++) + // load files using UnloadedVfs to include non-encrypted vfs + foreach (var key in aesKeys) + { + cancellationToken.ThrowIfCancellationRequested(); // cancel if needed + + var k = key.Key.Trim(); + if (k.Length != 66) k = Constants.ZERO_64_CHAR; + Provider.SubmitKey(key.Guid, new FAesKey(k)); + } + + // files in MountedVfs will be enabled + foreach (var file in GameDirectory.DirectoryFiles) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs) { - FLogger.AppendText(info.Messages[i], info.Colors[i], bool.Parse(info.NewLines[i])); + if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store) + file.FileCount = (int) store.Info.TocEntryCount - 1; + + continue; } - }); - } - public async Task InitBenMappings() - { - if (Game == FGame.FortniteGame) - { - await _threadWorkerView.Begin(cancellationToken => - { - if (UserSettings.Default.OverwriteMapping && File.Exists(UserSettings.Default.MappingFilePath)) - { - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(UserSettings.Default.MappingFilePath); - FLogger.AppendInformation(); - FLogger.AppendText($"Mappings pulled from '{UserSettings.Default.MappingFilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true); - } - else - { - var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); - var mappings = _apiEndpointView.BenbotApi.GetMappings(cancellationToken); - if (mappings is {Length: > 0}) - { - foreach (var mapping in mappings) - { - if (mapping.Meta.CompressionMethod != "Oodle") continue; - - var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); - if (!File.Exists(mappingPath)) - { - _apiEndpointView.BenbotApi.DownloadFile(mapping.Url, mappingPath); - } - - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); - FLogger.AppendInformation(); - FLogger.AppendText($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true); - break; - } - } - else - { - var latestUsmaps = new DirectoryInfo(mappingsFolder).GetFiles("*_oo.usmap"); - if (Provider.MappingsContainer != null || latestUsmaps.Length <= 0) return; - - var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); - Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); - FLogger.AppendWarning(); - FLogger.AppendText($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true); - } - } - }); + file.IsEnabled = true; + file.MountPoint = vfs.MountPoint; + file.FileCount = vfs.FileCount; } - } - public async Task LoadLocalizedResources() - { - await LoadGameLocalizedResources(); - await LoadHotfixedLocalizedResources(); - if (LocalizedResourcesCount > 0) - { - FLogger.AppendInformation(); - FLogger.AppendText($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true); - } - else - { - FLogger.AppendWarning(); - FLogger.AppendText($"Could not load localized resources in '{UserSettings.Default.AssetLanguage.GetDescription()}', language may not exist", Constants.WHITE, true); - } - } + // Game = Provider.GameName.ToEnum(Game); + }); + } - private async Task LoadGameLocalizedResources() + public void ClearProvider() + { + if (Provider == null) return; + + AssetsFolder.Folders.Clear(); + SearchVm.SearchResults.Clear(); + Helper.CloseWindow("Search View"); + Provider.UnloadAllVfs(); + GC.Collect(); + } + + public async Task RefreshAes() + { + if (Game == FGame.FortniteGame) // game directory dependent, we don't have the provider game name yet since we don't have aes keys { - if (LocalizedResourcesCount > 0) return; await _threadWorkerView.Begin(cancellationToken => { - LocalizedResourcesCount = Provider.LoadLocalization(UserSettings.Default.AssetLanguage, cancellationToken); - Utils.Typefaces = new Typefaces(this); + var aes = _apiEndpointView.BenbotApi.GetAesKeys(cancellationToken); + if (aes?.MainKey == null && aes?.DynamicKeys == null && aes?.Version == null) return; + + UserSettings.Default.AesKeys[Game] = aes; }); } + } - /// - /// Load hotfixed localized resources - /// - /// Functions only when LoadLocalizedResources is used prior to this (Asval: Why?). - private async Task LoadHotfixedLocalizedResources() + public async Task InitInformation() + { + await _threadWorkerView.Begin(cancellationToken => + { + var info = _apiEndpointView.FModelApi.GetNews(cancellationToken); + if (info == null) return; + + for (var i = 0; i < info.Messages.Length; i++) + { + FLogger.AppendText(info.Messages[i], info.Colors[i], bool.Parse(info.NewLines[i])); + } + }); + } + + public async Task InitBenMappings() + { + if (Game == FGame.FortniteGame) { - if (Game != FGame.FortniteGame || HotfixedResourcesDone) return; await _threadWorkerView.Begin(cancellationToken => { - var hotfixes = ApplicationService.ApiEndpointView.BenbotApi.GetHotfixes(cancellationToken, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); - if (hotfixes == null) return; - - HotfixedResourcesDone = true; - foreach (var entries in hotfixes) - { - cancellationToken.ThrowIfCancellationRequested(); - if (!Provider.LocalizedResources.ContainsKey(entries.Key)) - Provider.LocalizedResources[entries.Key] = new Dictionary(); - - foreach (var keyValue in entries.Value) - { - cancellationToken.ThrowIfCancellationRequested(); - Provider.LocalizedResources[entries.Key][keyValue.Key] = keyValue.Value; - LocalizedResourcesCount++; - } - } - }); - } - - public async Task LoadVirtualPaths() - { - if (VirtualPathCount > 0) return; - await _threadWorkerView.Begin(cancellationToken => - { - VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion(), cancellationToken); - if (VirtualPathCount > 0) + if (UserSettings.Default.OverwriteMapping && File.Exists(UserSettings.Default.MappingFilePath)) { + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(UserSettings.Default.MappingFilePath); FLogger.AppendInformation(); - FLogger.AppendText($"{VirtualPathCount} virtual paths loaded", Constants.WHITE, true); + FLogger.AppendText($"Mappings pulled from '{UserSettings.Default.MappingFilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true); } else { - FLogger.AppendWarning(); - FLogger.AppendText("Could not load virtual paths, plugin manifest may not exist", Constants.WHITE, true); + var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data"); + var mappings = _apiEndpointView.BenbotApi.GetMappings(cancellationToken); + if (mappings is { Length: > 0 }) + { + foreach (var mapping in mappings) + { + if (mapping.Meta.CompressionMethod != "Oodle") continue; + + var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); + if (!File.Exists(mappingPath)) + { + _apiEndpointView.BenbotApi.DownloadFile(mapping.Url, mappingPath); + } + + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); + FLogger.AppendInformation(); + FLogger.AppendText($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true); + break; + } + } + else + { + var latestUsmaps = new DirectoryInfo(mappingsFolder).GetFiles("*_oo.usmap"); + if (Provider.MappingsContainer != null || latestUsmaps.Length <= 0) return; + + var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); + Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); + FLogger.AppendWarning(); + FLogger.AppendText($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true); + } } }); } + } - public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder) + public async Task LoadLocalizedResources() + { + await LoadGameLocalizedResources(); + await LoadHotfixedLocalizedResources(); + if (LocalizedResourcesCount > 0) { - foreach (var asset in folder.AssetsList.Assets) - { - Thread.Sleep(10); - cancellationToken.ThrowIfCancellationRequested(); - try {Extract(asset.FullPath, TabControl.HasNoTabs);} catch {/**/} - } - - foreach (var f in folder.Folders) ExtractFolder(cancellationToken, f); + FLogger.AppendInformation(); + FLogger.AppendText($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true); } - - public void ExportFolder(CancellationToken cancellationToken, TreeItem folder) + else { - foreach (var asset in folder.AssetsList.Assets) - { - Thread.Sleep(10); - cancellationToken.ThrowIfCancellationRequested(); - ExportData(asset.FullPath); - } - - foreach (var f in folder.Folders) ExportFolder(cancellationToken, f); + FLogger.AppendWarning(); + FLogger.AppendText($"Could not load localized resources in '{UserSettings.Default.AssetLanguage.GetDescription()}', language may not exist", Constants.WHITE, true); } + } - public void SaveFolder(CancellationToken cancellationToken, TreeItem folder) + private async Task LoadGameLocalizedResources() + { + if (LocalizedResourcesCount > 0) return; + await _threadWorkerView.Begin(cancellationToken => { - foreach (var asset in folder.AssetsList.Assets) + LocalizedResourcesCount = Provider.LoadLocalization(UserSettings.Default.AssetLanguage, cancellationToken); + Utils.Typefaces = new Typefaces(this); + }); + } + + /// + /// Load hotfixed localized resources + /// + /// Functions only when LoadLocalizedResources is used prior to this. + private async Task LoadHotfixedLocalizedResources() + { + if (Game != FGame.FortniteGame || HotfixedResourcesDone) return; + await _threadWorkerView.Begin(cancellationToken => + { + var hotfixes = ApplicationService.ApiEndpointView.BenbotApi.GetHotfixes(cancellationToken, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); + if (hotfixes == null) return; + + HotfixedResourcesDone = true; + foreach (var entries in hotfixes) { - Thread.Sleep(10); cancellationToken.ThrowIfCancellationRequested(); - try {Extract(asset.FullPath, TabControl.HasNoTabs, true);} catch {/**/} + if (!Provider.LocalizedResources.ContainsKey(entries.Key)) + Provider.LocalizedResources[entries.Key] = new Dictionary(); + + foreach (var keyValue in entries.Value) + { + cancellationToken.ThrowIfCancellationRequested(); + Provider.LocalizedResources[entries.Key][keyValue.Key] = keyValue.Value; + LocalizedResourcesCount++; + } } + }); + } - foreach (var f in folder.Folders) SaveFolder(cancellationToken, f); - } - - public void ExtractSelected(CancellationToken cancellationToken, IEnumerable assetItems) + public async Task LoadVirtualPaths() + { + if (VirtualPathCount > 0) return; + await _threadWorkerView.Begin(cancellationToken => { - foreach (var asset in assetItems) + VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion(), cancellationToken); + if (VirtualPathCount > 0) + { + FLogger.AppendInformation(); + FLogger.AppendText($"{VirtualPathCount} virtual paths loaded", Constants.WHITE, true); + } + else + { + FLogger.AppendWarning(); + FLogger.AppendText("Could not load virtual paths, plugin manifest may not exist", Constants.WHITE, true); + } + }); + } + + public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder) + { + foreach (var asset in folder.AssetsList.Assets) + { + Thread.Sleep(10); + cancellationToken.ThrowIfCancellationRequested(); + try { - Thread.Sleep(10); - cancellationToken.ThrowIfCancellationRequested(); Extract(asset.FullPath, TabControl.HasNoTabs); } - } - - public void Extract(string fullPath, bool addNewTab = false, bool bulkSave = false) - { - Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", fullPath); - - var directory = fullPath.SubstringBeforeLast('/'); - var fileName = fullPath.SubstringAfterLast('/'); - var ext = fullPath.SubstringAfterLast('.').ToLower(); - - if (addNewTab && TabControl.CanAddTabs) + catch { - TabControl.AddTab(fileName, directory); - } - else - { - TabControl.SelectedTab.Header = fileName; - TabControl.SelectedTab.Directory = directory; - } - - TabControl.SelectedTab.ClearImages(); - TabControl.SelectedTab.ResetDocumentText(); - TabControl.SelectedTab.ScrollTrigger = null; - TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(ext); - switch (ext) - { - case "uasset": - case "umap": - { - var exports = Provider.LoadObjectExports(fullPath); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), bulkSave); - if (bulkSave) break; - - foreach (var e in exports) - { - if (CheckExport(e)) - break; - } - break; - } - case "upluginmanifest": - case "uproject": - case "manifest": - case "uplugin": - case "archive": - case "html": - case "json": - case "ini": - case "txt": - case "log": - case "bat": - case "dat": - case "cfg": - case "ide": - case "ipl": - case "zon": - case "xml": - case "css": - case "csv": - case "js": - case "po": - case "h": - { - if (Provider.TrySaveAsset(fullPath, out var data)) - { - using var stream = new MemoryStream(data) {Position = 0}; - using var reader = new StreamReader(stream); - - TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), bulkSave); - } - break; - } - case "locmeta": - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var metadata = new FTextLocalizationMetaDataResource(archive); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), bulkSave); - } - break; - } - case "locres": - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var locres = new FTextLocalizationResource(archive); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), bulkSave); - } - break; - } - case "bin" when fileName.Contains("AssetRegistry"): - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var registry = new FAssetRegistryState(archive); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), bulkSave); - } - break; - } - case "bnk": - case "pck": - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var wwise = new WwiseReader(archive); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), bulkSave); - foreach (var (name, data) in wwise.WwiseEncodedMedias) - { - SaveAndPlaySound(fullPath.SubstringBeforeWithLast("/") + name, "WEM", data); - } - } - break; - } - case "wem": - { - if (Provider.TrySaveAsset(fullPath, out var input)) - SaveAndPlaySound(fullPath, "WEM", input); - - break; - } - case "udic": - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var header = new FOodleDictionaryArchive(archive).Header; - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), bulkSave); - } - break; - } - case "png": - case "jpg": - case "bmp": - { - if (Provider.TrySaveAsset(fullPath, out var data)) - { - using var stream = new MemoryStream(data) {Position = 0}; - TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream)); - } - break; - } - case "svg": - { - if (Provider.TrySaveAsset(fullPath, out var data)) - { - using var stream = new MemoryStream(data) { Position = 0 }; - var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512)); - svg.Load(stream); - - var bitmap = new SKBitmap(512, 512); - using (var canvas = new SKCanvas(bitmap)) - using (var paint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.Medium }) - { - canvas.DrawPicture(svg.Picture, paint); - } - TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, bitmap); - } - break; - } - case "ufont": - case "otf": - case "ttf": - FLogger.AppendWarning(); - FLogger.AppendText($"Export '{fileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true); - break; - case "ushaderbytecode": - case "ushadercode": - { - if (Provider.TryCreateReader(fullPath, out var archive)) - { - var ar = new FShaderCodeArchive(archive); - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), bulkSave); - } - break; - } - default: - { - FLogger.AppendWarning(); - FLogger.AppendText($"The package '{fileName}' is of an unknown type.", Constants.WHITE, true); - break; - } + // ignore } } - public void ExtractAndScroll(string fullPath, string objectName) + foreach (var f in folder.Folders) ExtractFolder(cancellationToken, f); + } + + public void ExportFolder(CancellationToken cancellationToken, TreeItem folder) + { + foreach (var asset in folder.AssetsList.Assets) { - Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath); - TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/')); - TabControl.SelectedTab.ScrollTrigger = objectName; + Thread.Sleep(10); + cancellationToken.ThrowIfCancellationRequested(); + ExportData(asset.FullPath); + } - var exports = Provider.LoadObjectExports(fullPath); - TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json - TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false); + foreach (var f in folder.Folders) ExportFolder(cancellationToken, f); + } - foreach (var e in exports) + public void SaveFolder(CancellationToken cancellationToken, TreeItem folder) + { + foreach (var asset in folder.AssetsList.Assets) + { + Thread.Sleep(10); + cancellationToken.ThrowIfCancellationRequested(); + try { - if (CheckExport(e)) - break; + Extract(asset.FullPath, TabControl.HasNoTabs, true); + } + catch + { + // ignore } } - private bool CheckExport(UObject export) // return true once you wanna stop searching for exports - { - switch (export) - { - case UTexture2D texture: - { - TabControl.SelectedTab.AddImage(texture); - return false; - } - case UAkMediaAssetData: - case USoundWave: - { - var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; - export.Decode(shouldDecompress, out var audioFormat, out var data); - if (data == null || string.IsNullOrEmpty(audioFormat) || export.Owner == null) - return false; + foreach (var f in folder.Folders) SaveFolder(cancellationToken, f); + } - SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); - return false; - } - case UStaticMesh when UserSettings.Default.PreviewStaticMeshes: - case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes: - case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && - !(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || - export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) || - export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))): + public void ExtractSelected(CancellationToken cancellationToken, IEnumerable assetItems) + { + foreach (var asset in assetItems) + { + Thread.Sleep(10); + cancellationToken.ThrowIfCancellationRequested(); + Extract(asset.FullPath, TabControl.HasNoTabs); + } + } + + public void Extract(string fullPath, bool addNewTab = false, bool bulkSave = false) + { + Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", fullPath); + + var directory = fullPath.SubstringBeforeLast('/'); + var fileName = fullPath.SubstringAfterLast('/'); + var ext = fullPath.SubstringAfterLast('.').ToLower(); + + if (addNewTab && TabControl.CanAddTabs) + { + TabControl.AddTab(fileName, directory); + } + else + { + TabControl.SelectedTab.Header = fileName; + TabControl.SelectedTab.Directory = directory; + } + + TabControl.SelectedTab.ClearImages(); + TabControl.SelectedTab.ResetDocumentText(); + TabControl.SelectedTab.ScrollTrigger = null; + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(ext); + switch (ext) + { + case "uasset": + case "umap": + { + var exports = Provider.LoadObjectExports(fullPath); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), bulkSave); + if (bulkSave) break; + + foreach (var e in exports) { - Application.Current.Dispatcher.Invoke(delegate + if (CheckExport(e)) + break; + } + + break; + } + case "upluginmanifest": + case "uproject": + case "manifest": + case "uplugin": + case "archive": + case "html": + case "json": + case "ini": + case "txt": + case "log": + case "bat": + case "dat": + case "cfg": + case "ide": + case "ipl": + case "zon": + case "xml": + case "css": + case "csv": + case "js": + case "po": + case "h": + { + if (Provider.TrySaveAsset(fullPath, out var data)) + { + using var stream = new MemoryStream(data) { Position = 0 }; + using var reader = new StreamReader(stream); + + TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), bulkSave); + } + + break; + } + case "locmeta": + { + if (Provider.TryCreateReader(fullPath, out var archive)) + { + var metadata = new FTextLocalizationMetaDataResource(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), bulkSave); + } + + break; + } + case "locres": + { + if (Provider.TryCreateReader(fullPath, out var archive)) + { + var locres = new FTextLocalizationResource(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), bulkSave); + } + + break; + } + case "bin" when fileName.Contains("AssetRegistry"): + { + if (Provider.TryCreateReader(fullPath, out var archive)) + { + var registry = new FAssetRegistryState(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), bulkSave); + } + + break; + } + case "bnk": + case "pck": + { + if (Provider.TryCreateReader(fullPath, out var archive)) + { + var wwise = new WwiseReader(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), bulkSave); + foreach (var (name, data) in wwise.WwiseEncodedMedias) { - var modelViewer = Helper.GetWindow("Model Viewer", () => new ModelViewer().Show()); - modelViewer.Load(export); - }); - return true; + SaveAndPlaySound(fullPath.SubstringBeforeWithLast("/") + name, "WEM", data); + } } - case UMaterialInstance m when ModelIsOverwritingMaterial: + + break; + } + case "wem": + { + if (Provider.TrySaveAsset(fullPath, out var input)) + SaveAndPlaySound(fullPath, "WEM", input); + + break; + } + case "udic": + { + if (Provider.TryCreateReader(fullPath, out var archive)) { - Application.Current.Dispatcher.Invoke(delegate + var header = new FOodleDictionaryArchive(archive).Header; + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), bulkSave); + } + + break; + } + case "png": + case "jpg": + case "bmp": + { + if (Provider.TrySaveAsset(fullPath, out var data)) + { + using var stream = new MemoryStream(data) { Position = 0 }; + TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream)); + } + + break; + } + case "svg": + { + if (Provider.TrySaveAsset(fullPath, out var data)) + { + using var stream = new MemoryStream(data) { Position = 0 }; + var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512)); + svg.Load(stream); + + var bitmap = new SKBitmap(512, 512); + using (var canvas = new SKCanvas(bitmap)) + using (var paint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.Medium }) { - var modelViewer = Helper.GetWindow("Model Viewer", () => new ModelViewer().Show()); - modelViewer.Overwrite(m); - }); - return true; + canvas.DrawPicture(svg.Picture, paint); + } + + TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, bitmap); } - case UStaticMesh when UserSettings.Default.SaveStaticMeshes: - case USkeletalMesh when UserSettings.Default.SaveSkeletalMeshes: - case UMaterialInstance when UserSettings.Default.SaveMaterials: - case USkeleton when UserSettings.Default.SaveSkeletonAsMesh: - case UAnimSequence when UserSettings.Default.SaveAnimations: - { - SaveExport(export); - return true; - } - default: - { - using var package = new CreatorPackage(export, UserSettings.Default.CosmeticStyle); - if (!package.TryConstructCreator(out var creator)) return false; - creator.ParseForInfo(); - TabControl.SelectedTab.AddImage(export.Name, false, creator.Draw()); - return true; - } + break; } - } - - private void SaveAndPlaySound(string fullPath, string ext, byte[] data) - { - if (fullPath.StartsWith("/")) fullPath = fullPath[1..]; - var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, - UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLower()}"; - - if (!UserSettings.Default.IsAutoOpenSounds) - { - Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); - using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write); - using var writer = new BinaryWriter(stream); - writer.Write(data); - writer.Flush(); - return; - } - - // TODO - // since we are currently in a thread, the audio player's lifetime (memory-wise) will keep the current thread up and running until fmodel itself closes - // the solution would be to kill the current thread at this line and then open the audio player without "Application.Current.Dispatcher.Invoke" - // but the ThreadWorkerViewModel is an idiot and doesn't understand we want to kill the current thread inside the current thread and continue the code - Application.Current.Dispatcher.Invoke(delegate - { - var audioPlayer = Helper.GetWindow("Audio Player", () => new AudioPlayer().Show()); - audioPlayer.Load(data, savedAudioPath); - }); - } - - private void SaveExport(UObject export) - { - var exportOptions = new ExporterOptions() - { - TextureFormat = UserSettings.Default.TextureExportFormat, - LodFormat = UserSettings.Default.LodExportFormat, - MeshFormat = UserSettings.Default.MeshExportFormat, - Platform = UserSettings.Default.OverridedPlatform - }; - var toSave = new Exporter(export, exportOptions); - var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory); - if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName)) - { - Log.Information("Successfully saved {FileName}", savedFileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); - } - else - { - Log.Warning("{FileName} could not be saved", export.Name); + case "ufont": + case "otf": + case "ttf": FLogger.AppendWarning(); - FLogger.AppendText($"Could not save '{export.Name}'", Constants.WHITE, true); - } - } - - public void ExportData(string fullPath) - { - var fileName = fullPath.SubstringAfterLast('/'); - if (Provider.TrySavePackage(fullPath, out var assets)) + FLogger.AppendText($"Export '{fileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true); + break; + case "ushaderbytecode": + case "ushadercode": { - foreach (var kvp in assets) + if (Provider.TryCreateReader(fullPath, out var archive)) { - var path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/'); - Directory.CreateDirectory(path.SubstringBeforeLast('/')); - File.WriteAllBytes(path, kvp.Value); + var ar = new FShaderCodeArchive(archive); + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), bulkSave); } - Log.Information("{FileName} successfully exported", fileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully exported '{fileName}'", Constants.WHITE, true); + break; } - else + default: { - Log.Error("{FileName} could not be exported", fileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not export '{fileName}'", Constants.WHITE, true); + FLogger.AppendWarning(); + FLogger.AppendText($"The package '{fileName}' is of an unknown type.", Constants.WHITE, true); + break; } } } -} + + public void ExtractAndScroll(string fullPath, string objectName) + { + Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath); + TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/')); + TabControl.SelectedTab.ScrollTrigger = objectName; + + var exports = Provider.LoadObjectExports(fullPath); + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json + TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false); + + foreach (var e in exports) + { + if (CheckExport(e)) + break; + } + } + + private bool CheckExport(UObject export) // return true once you wanna stop searching for exports + { + switch (export) + { + case UTexture2D texture: + { + TabControl.SelectedTab.AddImage(texture); + return false; + } + case UAkMediaAssetData: + case USoundWave: + { + var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; + export.Decode(shouldDecompress, out var audioFormat, out var data); + if (data == null || string.IsNullOrEmpty(audioFormat) || export.Owner == null) + return false; + + SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); + return false; + } + case UStaticMesh when UserSettings.Default.PreviewStaticMeshes: + case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes: + case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && + !(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || + export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) || + export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))): + { + Application.Current.Dispatcher.Invoke(delegate + { + var modelViewer = Helper.GetWindow("Model Viewer", () => new ModelViewer().Show()); + modelViewer.Load(export); + }); + return true; + } + case UMaterialInstance m when ModelIsOverwritingMaterial: + { + Application.Current.Dispatcher.Invoke(delegate + { + var modelViewer = Helper.GetWindow("Model Viewer", () => new ModelViewer().Show()); + modelViewer.Overwrite(m); + }); + return true; + } + case UStaticMesh when UserSettings.Default.SaveStaticMeshes: + case USkeletalMesh when UserSettings.Default.SaveSkeletalMeshes: + case UMaterialInstance when UserSettings.Default.SaveMaterials: + case USkeleton when UserSettings.Default.SaveSkeletonAsMesh: + case UAnimSequence when UserSettings.Default.SaveAnimations: + { + SaveExport(export); + return true; + } + default: + { + using var package = new CreatorPackage(export, UserSettings.Default.CosmeticStyle); + if (!package.TryConstructCreator(out var creator)) return false; + + creator.ParseForInfo(); + TabControl.SelectedTab.AddImage(export.Name, false, creator.Draw()); + return true; + } + } + } + + private void SaveAndPlaySound(string fullPath, string ext, byte[] data) + { + if (fullPath.StartsWith("/")) fullPath = fullPath[1..]; + var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory, + UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLower()}"; + + if (!UserSettings.Default.IsAutoOpenSounds) + { + Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/')); + using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write); + using var writer = new BinaryWriter(stream); + writer.Write(data); + writer.Flush(); + return; + } + + // TODO + // since we are currently in a thread, the audio player's lifetime (memory-wise) will keep the current thread up and running until fmodel itself closes + // the solution would be to kill the current thread at this line and then open the audio player without "Application.Current.Dispatcher.Invoke" + // but the ThreadWorkerViewModel is an idiot and doesn't understand we want to kill the current thread inside the current thread and continue the code + Application.Current.Dispatcher.Invoke(delegate + { + var audioPlayer = Helper.GetWindow("Audio Player", () => new AudioPlayer().Show()); + audioPlayer.Load(data, savedAudioPath); + }); + } + + private void SaveExport(UObject export) + { + var exportOptions = new ExporterOptions() + { + TextureFormat = UserSettings.Default.TextureExportFormat, + LodFormat = UserSettings.Default.LodExportFormat, + MeshFormat = UserSettings.Default.MeshExportFormat, + Platform = UserSettings.Default.OverridedPlatform + }; + var toSave = new Exporter(export, exportOptions); + var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory); + if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName)) + { + Log.Information("Successfully saved {FileName}", savedFileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); + } + else + { + Log.Warning("{FileName} could not be saved", export.Name); + FLogger.AppendWarning(); + FLogger.AppendText($"Could not save '{export.Name}'", Constants.WHITE, true); + } + } + + public void ExportData(string fullPath) + { + var fileName = fullPath.SubstringAfterLast('/'); + if (Provider.TrySavePackage(fullPath, out var assets)) + { + foreach (var kvp in assets) + { + var path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/'); + Directory.CreateDirectory(path.SubstringBeforeLast('/')); + File.WriteAllBytes(path, kvp.Value); + } + + Log.Information("{FileName} successfully exported", fileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully exported '{fileName}'", Constants.WHITE, true); + } + else + { + Log.Error("{FileName} could not be exported", fileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not export '{fileName}'", Constants.WHITE, true); + } + } +} \ No newline at end of file diff --git a/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs b/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs index f09a3697..8258bfba 100644 --- a/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs +++ b/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs @@ -2,34 +2,33 @@ using FModel.Framework; using FModel.Views; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class AddEditDirectoryCommand : ViewModelCommand { - public class AddEditDirectoryCommand : ViewModelCommand + public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) { - public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) - { - } + } - public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) - { - if (parameter is not CustomDirectory customDir) - customDir = new CustomDirectory(); + public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) + { + if (parameter is not CustomDirectory customDir) + customDir = new CustomDirectory(); - Helper.OpenWindow("Custom Directory", () => + Helper.OpenWindow("Custom Directory", () => + { + var index = contextViewModel.GetIndex(customDir); + var input = new CustomDir(customDir); + var result = input.ShowDialog(); + if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath)) + return; + + if (index > 1) { - var index = contextViewModel.GetIndex(customDir); - var input = new CustomDir(customDir); - var result = input.ShowDialog(); - if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath)) - return; - - if (index > 1) - { - contextViewModel.Edit(index, customDir); - } - else - contextViewModel.Add(customDir); - }); - } + contextViewModel.Edit(index, customDir); + } + else + contextViewModel.Add(customDir); + }); } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/AddTabCommand.cs b/FModel/ViewModels/Commands/AddTabCommand.cs index 454604d4..6f0822fd 100644 --- a/FModel/ViewModels/Commands/AddTabCommand.cs +++ b/FModel/ViewModels/Commands/AddTabCommand.cs @@ -1,16 +1,15 @@ using FModel.Framework; -namespace FModel.ViewModels.Commands -{ - public class AddTabCommand : ViewModelCommand - { - public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel) - { - } +namespace FModel.ViewModels.Commands; - public override void Execute(TabControlViewModel contextViewModel, object parameter) - { - contextViewModel.AddTab(); - } +public class AddTabCommand : ViewModelCommand +{ + public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel) + { + } + + public override void Execute(TabControlViewModel contextViewModel, object parameter) + { + contextViewModel.AddTab(); } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/AudioCommand.cs b/FModel/ViewModels/Commands/AudioCommand.cs index e104990c..5ac55b4f 100644 --- a/FModel/ViewModels/Commands/AudioCommand.cs +++ b/FModel/ViewModels/Commands/AudioCommand.cs @@ -1,45 +1,44 @@ using FModel.Framework; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class AudioCommand : ViewModelCommand { - public class AudioCommand : ViewModelCommand + public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel) { - public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel) - { - } + } - public override void Execute(AudioPlayerViewModel contextViewModel, object parameter) - { - if (parameter is not string s) - return; + public override void Execute(AudioPlayerViewModel contextViewModel, object parameter) + { + if (parameter is not string s) + return; - switch (s) - { - case "Previous": - contextViewModel.Previous(); - break; - case "PlayPause": - contextViewModel.PlayPauseOnStart(); - break; - case "ForcePlayPause": - contextViewModel.PlayPauseOnForce(); - break; - case "Stop": - contextViewModel.Stop(); - break; - case "Next": - contextViewModel.Next(); - break; - case "Remove": - contextViewModel.Remove(); - break; - case "Save": - contextViewModel.Save(); - break; - case "Save_Playlist": - contextViewModel.SavePlaylist(); - break; - } + switch (s) + { + case "Previous": + contextViewModel.Previous(); + break; + case "PlayPause": + contextViewModel.PlayPauseOnStart(); + break; + case "ForcePlayPause": + contextViewModel.PlayPauseOnForce(); + break; + case "Stop": + contextViewModel.Stop(); + break; + case "Next": + contextViewModel.Next(); + break; + case "Remove": + contextViewModel.Remove(); + break; + case "Save": + contextViewModel.Save(); + break; + case "Save_Playlist": + contextViewModel.SavePlaylist(); + break; } } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/CopyCommand.cs b/FModel/ViewModels/Commands/CopyCommand.cs index cce1746f..e9131335 100644 --- a/FModel/ViewModels/Commands/CopyCommand.cs +++ b/FModel/ViewModels/Commands/CopyCommand.cs @@ -5,43 +5,42 @@ using System.Windows; using FModel.Extensions; using FModel.Framework; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class CopyCommand : ViewModelCommand { - public class CopyCommand : ViewModelCommand + public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) { - public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) + } + + public override void Execute(ApplicationViewModel contextViewModel, object parameter) + { + if (parameter is not object[] parameters || parameters[0] is not string trigger) + return; + + var assetItems = ((IList) parameters[1]).Cast().ToArray(); + if (!assetItems.Any()) return; + + var sb = new StringBuilder(); + switch (trigger) { + case "File_Path": + foreach (var asset in assetItems) sb.AppendLine(asset.FullPath); + break; + case "File_Name": + foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/')); + break; + case "Directory_Path": + foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/')); + break; + case "File_Path_No_Extension": + foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.')); + break; + case "File_Name_No_Extension": + foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.')); + break; } - public override void Execute(ApplicationViewModel contextViewModel, object parameter) - { - if (parameter is not object[] parameters || parameters[0] is not string trigger) - return; - - var assetItems = ((IList) parameters[1]).Cast().ToArray(); - if (!assetItems.Any()) return; - - var sb = new StringBuilder(); - switch (trigger) - { - case "File_Path": - foreach (var asset in assetItems) sb.AppendLine(asset.FullPath); - break; - case "File_Name": - foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/')); - break; - case "Directory_Path": - foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/')); - break; - case "File_Path_No_Extension": - foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.')); - break; - case "File_Name_No_Extension": - foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.')); - break; - } - - Clipboard.SetText(sb.ToString().TrimEnd()); - } + Clipboard.SetText(sb.ToString().TrimEnd()); } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/DeleteDirectoryCommand.cs b/FModel/ViewModels/Commands/DeleteDirectoryCommand.cs index 0837fca7..10755eb7 100644 --- a/FModel/ViewModels/Commands/DeleteDirectoryCommand.cs +++ b/FModel/ViewModels/Commands/DeleteDirectoryCommand.cs @@ -1,21 +1,20 @@ using FModel.Framework; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class DeleteDirectoryCommand : ViewModelCommand { - public class DeleteDirectoryCommand : ViewModelCommand + public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) { - public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) - { - } + } - public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) - { - if (parameter is not CustomDirectory customDir) return; + public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) + { + if (parameter is not CustomDirectory customDir) return; - var index = contextViewModel.GetIndex(customDir); - if (index < 2) return; + var index = contextViewModel.GetIndex(customDir); + if (index < 2) return; - contextViewModel.Delete(index); - } + contextViewModel.Delete(index); } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/GoToCommand.cs b/FModel/ViewModels/Commands/GoToCommand.cs index 1b9845c3..ff788c38 100644 --- a/FModel/ViewModels/Commands/GoToCommand.cs +++ b/FModel/ViewModels/Commands/GoToCommand.cs @@ -2,57 +2,56 @@ using FModel.Framework; using FModel.Services; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class GoToCommand : ViewModelCommand { - public class GoToCommand : ViewModelCommand + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + } - public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel) + public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) + { + if (parameter is not string s || string.IsNullOrEmpty(s)) return; + + JumpTo(s); + } + + public TreeItem JumpTo(string directory) + { + MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab + var root = _applicationView.CUE4Parse.AssetsFolder.Folders; + if (root is not { Count: > 0 }) return null; + + var i = 0; + var done = false; + var folders = directory.Split('/'); + while (!done) { - } - - public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter) - { - if (parameter is not string s || string.IsNullOrEmpty(s)) return; - - JumpTo(s); - } - - public TreeItem JumpTo(string directory) - { - MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab - var root = _applicationView.CUE4Parse.AssetsFolder.Folders; - if (root is not {Count: > 0}) return null; - - var i = 0; - var done = false; - var folders = directory.Split('/'); - while (!done) + foreach (var folder in root) { - foreach (var folder in root) + if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) + continue; + + folder.IsExpanded = true; // folder found = expand + + // is this the last folder aka the one we want to jump in + if (i >= folders.Length - 1) { - if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) - continue; - - folder.IsExpanded = true; // folder found = expand - - // is this the last folder aka the one we want to jump in - if (i >= folders.Length - 1) - { - folder.IsSelected = true; // select it - return folder; - } - - root = folder.Folders; // grab his subfolders - break; + folder.IsSelected = true; // select it + return folder; } - i++; - done = i == folders.Length || root.Count == 0; + root = folder.Folders; // grab his subfolders + break; } - return null; + i++; + done = i == folders.Length || root.Count == 0; } + + return null; } } \ No newline at end of file diff --git a/FModel/ViewModels/Commands/ImageCommand.cs b/FModel/ViewModels/Commands/ImageCommand.cs index dff136db..7cef0c8a 100644 --- a/FModel/ViewModels/Commands/ImageCommand.cs +++ b/FModel/ViewModels/Commands/ImageCommand.cs @@ -2,49 +2,47 @@ using FModel.Extensions; using FModel.Framework; using FModel.Views.Resources.Controls; -using System.IO; using System.Windows; using System.Windows.Media; using FModel.Views.Resources.Converters; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class ImageCommand : ViewModelCommand { - public class ImageCommand : ViewModelCommand + public ImageCommand(TabItem contextViewModel) : base(contextViewModel) { - public ImageCommand(TabItem contextViewModel) : base(contextViewModel) - { - } + } - public override void Execute(TabItem contextViewModel, object parameter) - { - if (parameter == null || !contextViewModel.HasImage) return; + public override void Execute(TabItem contextViewModel, object parameter) + { + if (parameter == null || !contextViewModel.HasImage) return; - switch (parameter) + switch (parameter) + { + case "Open": { - case "Open": + Helper.OpenWindow(contextViewModel.SelectedImage.ExportName + " (Image)", () => { - Helper.OpenWindow(contextViewModel.SelectedImage.ExportName + " (Image)", () => + var popout = new ImagePopout { - var popout = new ImagePopout - { - Title = contextViewModel.SelectedImage.ExportName + " (Image)", - Width = contextViewModel.SelectedImage.Image.Width, - Height = contextViewModel.SelectedImage.Image.Height, - WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, - ImageCtrl = {Source = contextViewModel.SelectedImage.Image} - }; - RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor)); - popout.Show(); - }); - break; - } - case "Copy": - ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png"); - break; - case "Save": - contextViewModel.SaveImage(false); - break; + Title = contextViewModel.SelectedImage.ExportName + " (Image)", + Width = contextViewModel.SelectedImage.Image.Width, + Height = contextViewModel.SelectedImage.Image.Height, + WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal, + ImageCtrl = { Source = contextViewModel.SelectedImage.Image } + }; + RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor)); + popout.Show(); + }); + break; } + case "Copy": + ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png"); + break; + case "Save": + contextViewModel.SaveImage(false); + break; } } -} +} \ No newline at end of file diff --git a/FModel/ViewModels/Commands/LoadCommand.cs b/FModel/ViewModels/Commands/LoadCommand.cs index 8b30d7c0..da7b6d90 100644 --- a/FModel/ViewModels/Commands/LoadCommand.cs +++ b/FModel/ViewModels/Commands/LoadCommand.cs @@ -18,210 +18,209 @@ using FModel.Views.Resources.Controls; using K4os.Compression.LZ4.Streams; using Microsoft.Win32; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +/// +/// this will always load all files no matter the loading mode +/// however what this does is filtering what to show to the user +/// +public class LoadCommand : ViewModelCommand { - /// - /// this will always load all files no matter the loading mode - /// however what this does is filtering what to show to the user - /// - public class LoadCommand : ViewModelCommand + private const uint _IS_LZ4 = 0x184D2204u; + + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private DiscordHandler _discordHandler => DiscordService.DiscordHandler; + + public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel) { - private const uint _IS_LZ4 = 0x184D2204u; + } - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - private DiscordHandler _discordHandler => DiscordService.DiscordHandler; - - public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel) + public override async void Execute(LoadingModesViewModel contextViewModel, object parameter) + { + if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return; + if (_applicationView.CUE4Parse.Provider.Files.Count <= 0) { + FLogger.AppendError(); + FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true); + return; } - public override async void Execute(LoadingModesViewModel contextViewModel, object parameter) + if (_applicationView.CUE4Parse.Game == FGame.FortniteGame && + _applicationView.CUE4Parse.Provider.MappingsContainer == null) { - if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return; - if (_applicationView.CUE4Parse.Provider.Files.Count <= 0) - { - FLogger.AppendError(); - FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true); - return; - } - - if (_applicationView.CUE4Parse.Game == FGame.FortniteGame && - _applicationView.CUE4Parse.Provider.MappingsContainer == null) - { - FLogger.AppendError(); - FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true); - } + FLogger.AppendError(); + FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true); + } #if DEBUG - var loadingTime = Stopwatch.StartNew(); + var loadingTime = Stopwatch.StartNew(); #endif - _applicationView.CUE4Parse.AssetsFolder.Folders.Clear(); - _applicationView.CUE4Parse.SearchVm.SearchResults.Clear(); - MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab + _applicationView.CUE4Parse.AssetsFolder.Folders.Clear(); + _applicationView.CUE4Parse.SearchVm.SearchResults.Clear(); + MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab - await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded - await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded - Helper.CloseWindow("Search View"); // close search window if opened + await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded + await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded + Helper.CloseWindow("Search View"); // close search window if opened - await _threadWorkerView.Begin(cancellationToken => - { - // filter what to show - switch (UserSettings.Default.LoadingMode) - { - case ELoadingMode.Single: - case ELoadingMode.Multiple: - { - var l = (IList) parameter; - if (l.Count < 1) return; - - var directoryFilesToShow = l.Cast(); - FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow); - break; - } - case ELoadingMode.All: - { - FilterDirectoryFilesToDisplay(cancellationToken, null); - break; - } - case ELoadingMode.AllButNew: - case ELoadingMode.AllButModified: - { - FilterNewOrModifiedFilesToDisplay(cancellationToken); - break; - } - default: throw new ArgumentOutOfRangeException(); - } - - _discordHandler.UpdatePresence(_applicationView.CUE4Parse); - }); -#if DEBUG - loadingTime.Stop(); - FLogger.AppendDebug(); - FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true); -#endif - } - - private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable directoryFiles) + await _threadWorkerView.Begin(cancellationToken => { - HashSet filter; - if (directoryFiles == null) - filter = null; - else - { - filter = new HashSet(); - foreach (var directoryFile in directoryFiles) - { - if (!directoryFile.IsEnabled) - continue; - - filter.Add(directoryFile.Name); - } - } - - var hasFilter = filter != null && filter.Count != 0; - var entries = new List(); - - foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values) - { - cancellationToken.ThrowIfCancellationRequested(); // cancel if needed - - if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) - continue; - - if (hasFilter) - { - if (filter.Contains(entry.Vfs.Name)) - entries.Add(entry); - } - else - entries.Add(entry); - } - - _applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries); - } - - private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken) - { - var openFileDialog = new OpenFileDialog - { - Title = "Select a backup file older than your current game version", - InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), - Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*", - Multiselect = false - }; - - if (!(bool) openFileDialog.ShowDialog()) return; - - FLogger.AppendInformation(); - FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true); - - using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open); - using var memoryStream = new MemoryStream(); - - if (fileStream.ReadUInt32() == _IS_LZ4) - { - fileStream.Position -= 4; - using var compressionStream = LZ4Stream.Decode(fileStream); - compressionStream.CopyTo(memoryStream); - } - else fileStream.CopyTo(memoryStream); - - memoryStream.Position = 0; - using var archive = new FStreamArchive(fileStream.Name, memoryStream); - var entries = new List(); - + // filter what to show switch (UserSettings.Default.LoadingMode) { - case ELoadingMode.AllButNew: + case ELoadingMode.Single: + case ELoadingMode.Multiple: { - var paths = new Dictionary(); - while (archive.Position < archive.Length) - { - cancellationToken.ThrowIfCancellationRequested(); - - archive.Position += 29; - paths[archive.ReadString().ToLower()[1..]] = 0; - archive.Position += 4; - } - - foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files) - { - cancellationToken.ThrowIfCancellationRequested(); - if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") || - entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue; - - entries.Add(entry); - } + var l = (IList) parameter; + if (l.Count < 1) return; + var directoryFilesToShow = l.Cast(); + FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow); break; } + case ELoadingMode.All: + { + FilterDirectoryFilesToDisplay(cancellationToken, null); + break; + } + case ELoadingMode.AllButNew: case ELoadingMode.AllButModified: { - while (archive.Position < archive.Length) - { - cancellationToken.ThrowIfCancellationRequested(); - - archive.Position += 16; - var uncompressedSize = archive.Read(); - var isEncrypted = archive.ReadFlag(); - archive.Position += 4; - var fullPath = archive.ReadString().ToLower()[1..]; - archive.Position += 4; - - if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") || - !_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry || - entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted) - continue; - - entries.Add(entry); - } - + FilterNewOrModifiedFilesToDisplay(cancellationToken); break; } + default: throw new ArgumentOutOfRangeException(); } - _applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries); - } + _discordHandler.UpdatePresence(_applicationView.CUE4Parse); + }); +#if DEBUG + loadingTime.Stop(); + FLogger.AppendDebug(); + FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true); +#endif } -} + + private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable directoryFiles) + { + HashSet filter; + if (directoryFiles == null) + filter = null; + else + { + filter = new HashSet(); + foreach (var directoryFile in directoryFiles) + { + if (!directoryFile.IsEnabled) + continue; + + filter.Add(directoryFile.Name); + } + } + + var hasFilter = filter != null && filter.Count != 0; + var entries = new List(); + + foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values) + { + cancellationToken.ThrowIfCancellationRequested(); // cancel if needed + + if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) + continue; + + if (hasFilter) + { + if (filter.Contains(entry.Vfs.Name)) + entries.Add(entry); + } + else + entries.Add(entry); + } + + _applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries); + } + + private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken) + { + var openFileDialog = new OpenFileDialog + { + Title = "Select a backup file older than your current game version", + InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), + Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*", + Multiselect = false + }; + + if (!openFileDialog.ShowDialog().GetValueOrDefault()) return; + + FLogger.AppendInformation(); + FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true); + + using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open); + using var memoryStream = new MemoryStream(); + + if (fileStream.ReadUInt32() == _IS_LZ4) + { + fileStream.Position -= 4; + using var compressionStream = LZ4Stream.Decode(fileStream); + compressionStream.CopyTo(memoryStream); + } + else fileStream.CopyTo(memoryStream); + + memoryStream.Position = 0; + using var archive = new FStreamArchive(fileStream.Name, memoryStream); + var entries = new List(); + + switch (UserSettings.Default.LoadingMode) + { + case ELoadingMode.AllButNew: + { + var paths = new Dictionary(); + while (archive.Position < archive.Length) + { + cancellationToken.ThrowIfCancellationRequested(); + + archive.Position += 29; + paths[archive.ReadString().ToLower()[1..]] = 0; + archive.Position += 4; + } + + foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files) + { + cancellationToken.ThrowIfCancellationRequested(); + if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") || + entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue; + + entries.Add(entry); + } + + break; + } + case ELoadingMode.AllButModified: + { + while (archive.Position < archive.Length) + { + cancellationToken.ThrowIfCancellationRequested(); + + archive.Position += 16; + var uncompressedSize = archive.Read(); + var isEncrypted = archive.ReadFlag(); + archive.Position += 4; + var fullPath = archive.ReadString().ToLower()[1..]; + archive.Position += 4; + + if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") || + !_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry || + entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted) + continue; + + entries.Add(entry); + } + + break; + } + } + + _applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/Commands/MenuCommand.cs b/FModel/ViewModels/Commands/MenuCommand.cs index d0778b7f..4fd88eac 100644 --- a/FModel/ViewModels/Commands/MenuCommand.cs +++ b/FModel/ViewModels/Commands/MenuCommand.cs @@ -9,105 +9,104 @@ using FModel.Views; using FModel.Views.Resources.Controls; using Newtonsoft.Json; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class MenuCommand : ViewModelCommand { - public class MenuCommand : ViewModelCommand + public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) { - public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) - { - } + } - public override async void Execute(ApplicationViewModel contextViewModel, object parameter) + public override async void Execute(ApplicationViewModel contextViewModel, object parameter) + { + switch (parameter) { - switch (parameter) - { - case "Directory_Selector": - contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true); - break; - case "Directory_AES": - Helper.OpenWindow("AES Manager", () => new AesManager().Show()); - break; - case "Directory_Backup": - Helper.OpenWindow("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show()); - break; - case "Directory_ArchivesInfo": - contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info"); - contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json"); - contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false); - break; - case "Views_AudioPlayer": - Helper.OpenWindow("Audio Player", () => new AudioPlayer().Show()); - break; - case "Views_MapViewer": - Helper.OpenWindow("Map Viewer", () => new MapViewer().Show()); - break; - case "Views_ImageMerger": - Helper.OpenWindow("Image Merger", () => new ImageMerger().Show()); - break; - case "Settings": - Helper.OpenWindow("Settings", () => new SettingsView().Show()); - break; - case "ModelSettings": - UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1; - Helper.OpenWindow("Settings", () => new SettingsView().Show()); - break; - case "Help_About": - Helper.OpenWindow("About", () => new About().Show()); - break; - case "Help_Donate": - Process.Start(new ProcessStartInfo {FileName = Constants.DONATE_LINK, UseShellExecute = true}); - break; - case "Help_Changelog": - UserSettings.Default.ShowChangelog = true; - ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); - break; - case "Help_BugsReport": - Process.Start(new ProcessStartInfo {FileName = Constants.ISSUE_LINK, UseShellExecute = true}); - break; - case "Help_Discord": - Process.Start(new ProcessStartInfo {FileName = Constants.DISCORD_LINK, UseShellExecute = true}); - break; - case "ToolBox_Clear_Logs": - FLogger.Logger.Text = string.Empty; - break; - case "ToolBox_Open_Output_Directory": - Process.Start(new ProcessStartInfo {FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true}); - break; - case "ToolBox_Expand_All": - await ApplicationService.ThreadWorkerView.Begin(cancellationToken => + case "Directory_Selector": + contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true); + break; + case "Directory_AES": + Helper.OpenWindow("AES Manager", () => new AesManager().Show()); + break; + case "Directory_Backup": + Helper.OpenWindow("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show()); + break; + case "Directory_ArchivesInfo": + contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info"); + contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json"); + contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false); + break; + case "Views_AudioPlayer": + Helper.OpenWindow("Audio Player", () => new AudioPlayer().Show()); + break; + case "Views_MapViewer": + Helper.OpenWindow("Map Viewer", () => new MapViewer().Show()); + break; + case "Views_ImageMerger": + Helper.OpenWindow("Image Merger", () => new ImageMerger().Show()); + break; + case "Settings": + Helper.OpenWindow("Settings", () => new SettingsView().Show()); + break; + case "ModelSettings": + UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1; + Helper.OpenWindow("Settings", () => new SettingsView().Show()); + break; + case "Help_About": + Helper.OpenWindow("About", () => new About().Show()); + break; + case "Help_Donate": + Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true }); + break; + case "Help_Changelog": + UserSettings.Default.ShowChangelog = true; + ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); + break; + case "Help_BugsReport": + Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true }); + break; + case "Help_Discord": + Process.Start(new ProcessStartInfo { FileName = Constants.DISCORD_LINK, UseShellExecute = true }); + break; + case "ToolBox_Clear_Logs": + FLogger.Logger.Text = string.Empty; + break; + case "ToolBox_Open_Output_Directory": + Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true }); + break; + case "ToolBox_Expand_All": + await ApplicationService.ThreadWorkerView.Begin(cancellationToken => + { + foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders) { - foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders) - { - LoopFolders(cancellationToken, folder, true); - } - }); - break; - case "ToolBox_Collapse_All": - await ApplicationService.ThreadWorkerView.Begin(cancellationToken => + LoopFolders(cancellationToken, folder, true); + } + }); + break; + case "ToolBox_Collapse_All": + await ApplicationService.ThreadWorkerView.Begin(cancellationToken => + { + foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders) { - foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders) - { - LoopFolders(cancellationToken, folder, false); - } - }); - break; - case TreeItem selectedFolder: - selectedFolder.IsSelected = false; - selectedFolder.IsSelected = true; - break; - } - } - - private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded) - { - if (parent.IsExpanded != isExpanded) - { - parent.IsExpanded = isExpanded; - Thread.Sleep(10); - } - - cancellationToken.ThrowIfCancellationRequested(); - foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded); + LoopFolders(cancellationToken, folder, false); + } + }); + break; + case TreeItem selectedFolder: + selectedFolder.IsSelected = false; + selectedFolder.IsSelected = true; + break; } } -} + + private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded) + { + if (parent.IsExpanded != isExpanded) + { + parent.IsExpanded = isExpanded; + Thread.Sleep(10); + } + + cancellationToken.ThrowIfCancellationRequested(); + foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index ca746894..ff41ba8e 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -3,60 +3,63 @@ using System.Linq; using FModel.Framework; using FModel.Services; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class RightClickMenuCommand : ViewModelCommand { - public class RightClickMenuCommand : ViewModelCommand + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + + public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - - public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) - { - } - - public override async void Execute(ApplicationViewModel contextViewModel, object parameter) - { - if (parameter is not object[] parameters || parameters[0] is not string trigger) - return; - - var assetItems = ((IList) parameters[1]).Cast().ToArray(); - if (!assetItems.Any()) return; - - await _threadWorkerView.Begin(cancellationToken => - { - switch (trigger) - { - case "Assets_Extract_New_Tab": - foreach (var asset in assetItems) - { - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(asset.FullPath, true); - } - break; - case "Assets_Export_Data": - foreach (var asset in assetItems) - { - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.ExportData(asset.FullPath); - } - break; - case "Assets_Save_Properties": - foreach (var asset in assetItems) - { - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(asset.FullPath); - contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false); - } - break; - case "Assets_Save_Texture": - foreach (var asset in assetItems) - { - cancellationToken.ThrowIfCancellationRequested(); - contextViewModel.CUE4Parse.Extract(asset.FullPath); - contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false); - } - break; - } - }); - } } -} + + public override async void Execute(ApplicationViewModel contextViewModel, object parameter) + { + if (parameter is not object[] parameters || parameters[0] is not string trigger) + return; + + var assetItems = ((IList) parameters[1]).Cast().ToArray(); + if (!assetItems.Any()) return; + + await _threadWorkerView.Begin(cancellationToken => + { + switch (trigger) + { + case "Assets_Extract_New_Tab": + foreach (var asset in assetItems) + { + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Extract(asset.FullPath, true); + } + + break; + case "Assets_Export_Data": + foreach (var asset in assetItems) + { + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.ExportData(asset.FullPath); + } + + break; + case "Assets_Save_Properties": + foreach (var asset in assetItems) + { + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Extract(asset.FullPath); + contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false); + } + + break; + case "Assets_Save_Texture": + foreach (var asset in assetItems) + { + cancellationToken.ThrowIfCancellationRequested(); + contextViewModel.CUE4Parse.Extract(asset.FullPath); + contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false); + } + + break; + } + }); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/Commands/TabCommand.cs b/FModel/ViewModels/Commands/TabCommand.cs index 969506e5..f97c4a89 100644 --- a/FModel/ViewModels/Commands/TabCommand.cs +++ b/FModel/ViewModels/Commands/TabCommand.cs @@ -4,46 +4,45 @@ using FModel.Framework; using FModel.Services; using FModel.Views.Resources.Controls; -namespace FModel.ViewModels.Commands +namespace FModel.ViewModels.Commands; + +public class TabCommand : ViewModelCommand { - public class TabCommand : ViewModelCommand + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public TabCommand(TabItem contextViewModel) : base(contextViewModel) { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + } - public TabCommand(TabItem contextViewModel) : base(contextViewModel) + public override void Execute(TabItem contextViewModel, object parameter) + { + switch (parameter) { - } - - public override void Execute(TabItem contextViewModel, object parameter) - { - switch (parameter) - { - case TabItem mdlClick: - _applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick); - break; - case "Close_Tab": - _applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel); - break; - case "Close_All_Tabs": - _applicationView.CUE4Parse.TabControl.RemoveAllTabs(); - break; - case "Close_Other_Tabs": - _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel); - break; - case "Open_Properties": - if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return; - Helper.OpenWindow(contextViewModel.Header + " (Properties)", () => + case TabItem mdlClick: + _applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick); + break; + case "Close_Tab": + _applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel); + break; + case "Close_All_Tabs": + _applicationView.CUE4Parse.TabControl.RemoveAllTabs(); + break; + case "Close_Other_Tabs": + _applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel); + break; + case "Open_Properties": + if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return; + Helper.OpenWindow(contextViewModel.Header + " (Properties)", () => + { + new PropertiesPopout(contextViewModel) { - new PropertiesPopout(contextViewModel) - { - Title = contextViewModel.Header + " (Properties)" - }.Show(); - }); - break; - case "Copy_Asset_Name": - Clipboard.SetText(contextViewModel.Header); - break; - } + Title = contextViewModel.Header + " (Properties)" + }.Show(); + }); + break; + case "Copy_Asset_Name": + Clipboard.SetText(contextViewModel.Header); + break; } } } \ No newline at end of file diff --git a/FModel/ViewModels/CustomDirectoriesViewModel.cs b/FModel/ViewModels/CustomDirectoriesViewModel.cs index 8e8c05f0..84efe771 100644 --- a/FModel/ViewModels/CustomDirectoriesViewModel.cs +++ b/FModel/ViewModels/CustomDirectoriesViewModel.cs @@ -9,163 +9,162 @@ using FModel.Framework; using FModel.Settings; using FModel.ViewModels.Commands; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class CustomDirectory : ViewModel { - public class CustomDirectory : ViewModel + private string _header; + public string Header { - private string _header; - public string Header - { - get => _header; - set => SetProperty(ref _header, value); - } - - private string _directoryPath; - public string DirectoryPath - { - get => _directoryPath; - set => SetProperty(ref _directoryPath, value); - } - - public CustomDirectory() - { - Header = string.Empty; - DirectoryPath = string.Empty; - } - - public CustomDirectory(string header, string path) - { - Header = header; - DirectoryPath = path; - } - - public override string ToString() => Header; + get => _header; + set => SetProperty(ref _header, value); } - public class CustomDirectoriesViewModel : ViewModel + private string _directoryPath; + public string DirectoryPath { - private GoToCommand _goToCommand; - public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this); - private AddEditDirectoryCommand _addEditDirectoryCommand; - public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this); - private DeleteDirectoryCommand _deleteDirectoryCommand; - public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this); - - private readonly ObservableCollection _directories; - public ReadOnlyObservableCollection Directories { get; } - - private readonly FGame _game; - private readonly string _gameDirectoryAtLaunch; - - public CustomDirectoriesViewModel(FGame game, string directory) - { - _game = game; - _gameDirectoryAtLaunch = directory; - _directories = new ObservableCollection(EnumerateDirectories()); - Directories = new ReadOnlyObservableCollection(_directories); - } - - public int GetIndex(CustomDirectory dir) - { - return _directories.IndexOf(_directories.FirstOrDefault(x => - x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath)); - } - - public void Add(CustomDirectory dir) - { - _directories.Add(new MenuItem {Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir)}); - } - - public void Edit(int index, CustomDirectory newDir) - { - if (_directories.ElementAt(index) is not MenuItem dir) return; - - dir.Header = newDir.Header; - dir.Tag = newDir.DirectoryPath; - } - - public void Delete(int index) - { - _directories.RemoveAt(index); - } - - public void Save() - { - var cd = new List(); - for (var i = 2; i < _directories.Count; i++) - { - if (_directories[i] is not MenuItem m) continue; - cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString())); - } - - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch)) - UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd; - else UserSettings.Default.CustomDirectories[_game] = cd; - } - - private IEnumerable EnumerateDirectories() - { - yield return new MenuItem - { - Header = "Add Directory", - Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative))}, - HorizontalContentAlignment = HorizontalAlignment.Left, - VerticalContentAlignment = VerticalAlignment.Center, - Command = AddEditDirectoryCommand - }; - yield return new Separator(); - - IList cd; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings)) - cd = settings.CustomDirectories; - else cd = UserSettings.Default.CustomDirectories[_game]; - - foreach (var setting in cd) - { - if (setting.DirectoryPath.EndsWith('/')) - setting.DirectoryPath = setting.DirectoryPath[..^1]; - - yield return new MenuItem - { - Header = setting.Header, - Tag = setting.DirectoryPath, - HorizontalContentAlignment = HorizontalAlignment.Left, - VerticalContentAlignment = VerticalAlignment.Center, - ItemsSource = EnumerateCommands(setting) - }; - } - } - - private IEnumerable EnumerateCommands(CustomDirectory dir) - { - yield return new MenuItem - { - Header = "Go To", - Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative))}, - HorizontalContentAlignment = HorizontalAlignment.Left, - VerticalContentAlignment = VerticalAlignment.Center, - Command = GoToCommand, - CommandParameter = dir.DirectoryPath - }; - yield return new MenuItem - { - Header = "Edit Directory", - Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative))}, - HorizontalContentAlignment = HorizontalAlignment.Left, - VerticalContentAlignment = VerticalAlignment.Center, - Command = AddEditDirectoryCommand, - CommandParameter = dir - }; - yield return new MenuItem - { - Header = "Delete Directory", - StaysOpenOnClick = true, - Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative))}, - HorizontalContentAlignment = HorizontalAlignment.Left, - VerticalContentAlignment = VerticalAlignment.Center, - Command = DeleteDirectoryCommand, - CommandParameter = dir - }; - } + get => _directoryPath; + set => SetProperty(ref _directoryPath, value); } + + public CustomDirectory() + { + Header = string.Empty; + DirectoryPath = string.Empty; + } + + public CustomDirectory(string header, string path) + { + Header = header; + DirectoryPath = path; + } + + public override string ToString() => Header; } + +public class CustomDirectoriesViewModel : ViewModel +{ + private GoToCommand _goToCommand; + public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this); + private AddEditDirectoryCommand _addEditDirectoryCommand; + public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this); + private DeleteDirectoryCommand _deleteDirectoryCommand; + public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this); + + private readonly ObservableCollection _directories; + public ReadOnlyObservableCollection Directories { get; } + + private readonly FGame _game; + private readonly string _gameDirectoryAtLaunch; + + public CustomDirectoriesViewModel(FGame game, string directory) + { + _game = game; + _gameDirectoryAtLaunch = directory; + _directories = new ObservableCollection(EnumerateDirectories()); + Directories = new ReadOnlyObservableCollection(_directories); + } + + public int GetIndex(CustomDirectory dir) + { + return _directories.IndexOf(_directories.FirstOrDefault(x => + x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath)); + } + + public void Add(CustomDirectory dir) + { + _directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) }); + } + + public void Edit(int index, CustomDirectory newDir) + { + if (_directories.ElementAt(index) is not MenuItem dir) return; + + dir.Header = newDir.Header; + dir.Tag = newDir.DirectoryPath; + } + + public void Delete(int index) + { + _directories.RemoveAt(index); + } + + public void Save() + { + var cd = new List(); + for (var i = 2; i < _directories.Count; i++) + { + if (_directories[i] is not MenuItem m) continue; + cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString())); + } + + if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch)) + UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd; + else UserSettings.Default.CustomDirectories[_game] = cd; + } + + private IEnumerable EnumerateDirectories() + { + yield return new MenuItem + { + Header = "Add Directory", + Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative)) }, + HorizontalContentAlignment = HorizontalAlignment.Left, + VerticalContentAlignment = VerticalAlignment.Center, + Command = AddEditDirectoryCommand + }; + yield return new Separator(); + + IList cd; + if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings)) + cd = settings.CustomDirectories; + else cd = UserSettings.Default.CustomDirectories[_game]; + + foreach (var setting in cd) + { + if (setting.DirectoryPath.EndsWith('/')) + setting.DirectoryPath = setting.DirectoryPath[..^1]; + + yield return new MenuItem + { + Header = setting.Header, + Tag = setting.DirectoryPath, + HorizontalContentAlignment = HorizontalAlignment.Left, + VerticalContentAlignment = VerticalAlignment.Center, + ItemsSource = EnumerateCommands(setting) + }; + } + } + + private IEnumerable EnumerateCommands(CustomDirectory dir) + { + yield return new MenuItem + { + Header = "Go To", + Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative)) }, + HorizontalContentAlignment = HorizontalAlignment.Left, + VerticalContentAlignment = VerticalAlignment.Center, + Command = GoToCommand, + CommandParameter = dir.DirectoryPath + }; + yield return new MenuItem + { + Header = "Edit Directory", + Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative)) }, + HorizontalContentAlignment = HorizontalAlignment.Left, + VerticalContentAlignment = VerticalAlignment.Center, + Command = AddEditDirectoryCommand, + CommandParameter = dir + }; + yield return new MenuItem + { + Header = "Delete Directory", + StaysOpenOnClick = true, + Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative)) }, + HorizontalContentAlignment = HorizontalAlignment.Left, + VerticalContentAlignment = VerticalAlignment.Center, + Command = DeleteDirectoryCommand, + CommandParameter = dir + }; + } +} \ No newline at end of file diff --git a/FModel/ViewModels/GameDirectoryViewModel.cs b/FModel/ViewModels/GameDirectoryViewModel.cs index 220d8ab0..4473cf95 100644 --- a/FModel/ViewModels/GameDirectoryViewModel.cs +++ b/FModel/ViewModels/GameDirectoryViewModel.cs @@ -6,110 +6,109 @@ using System.Windows.Data; using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Vfs; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class FileItem : ViewModel { - public class FileItem : ViewModel + private string _name; + public string Name { - private string _name; - public string Name - { - get => _name; - private set => SetProperty(ref _name, value); - } + get => _name; + private set => SetProperty(ref _name, value); + } - private long _length; - public long Length - { - get => _length; - private set => SetProperty(ref _length, value); - } + private long _length; + public long Length + { + get => _length; + private set => SetProperty(ref _length, value); + } - private int _fileCount; - public int FileCount - { - get => _fileCount; - set => SetProperty(ref _fileCount, value); - } + private int _fileCount; + public int FileCount + { + get => _fileCount; + set => SetProperty(ref _fileCount, value); + } - private string _mountPoint; - public string MountPoint - { - get => _mountPoint; - set => SetProperty(ref _mountPoint, value); - } + private string _mountPoint; + public string MountPoint + { + get => _mountPoint; + set => SetProperty(ref _mountPoint, value); + } - private bool _isEncrypted; - public bool IsEncrypted - { - get => _isEncrypted; - set => SetProperty(ref _isEncrypted, value); - } + private bool _isEncrypted; + public bool IsEncrypted + { + get => _isEncrypted; + set => SetProperty(ref _isEncrypted, value); + } - private bool _isEnabled; - public bool IsEnabled - { - get => _isEnabled; - set => SetProperty(ref _isEnabled, value); - } + private bool _isEnabled; + public bool IsEnabled + { + get => _isEnabled; + set => SetProperty(ref _isEnabled, value); + } - private string _key; - public string Key - { - get => _key; - set => SetProperty(ref _key, value); - } + private string _key; + public string Key + { + get => _key; + set => SetProperty(ref _key, value); + } - private FGuid _guid; - public FGuid Guid - { - get => _guid; - set => SetProperty(ref _guid, value); - } + private FGuid _guid; + public FGuid Guid + { + get => _guid; + set => SetProperty(ref _guid, value); + } - public FileItem(string name, long length) - { - Name = name; - Length = length; - } + public FileItem(string name, long length) + { + Name = name; + Length = length; + } - public override string ToString() + public override string ToString() + { + return $"{Name} | {Key}"; + } +} + +public class GameDirectoryViewModel : ViewModel +{ + public bool HasNoFile => DirectoryFiles.Count < 1; + public readonly ObservableCollection DirectoryFiles; + public ICollectionView DirectoryFilesView { get; } + + public GameDirectoryViewModel() + { + DirectoryFiles = new ObservableCollection(); + DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } }; + } + + public void DeactivateAll() + { + foreach (var file in DirectoryFiles) { - return $"{Name} | {Key}"; + file.IsEnabled = false; } } - public class GameDirectoryViewModel : ViewModel + public void Add(IAesVfsReader reader) { - public bool HasNoFile => DirectoryFiles.Count < 1; - public readonly ObservableCollection DirectoryFiles; - public ICollectionView DirectoryFilesView { get; } - - public GameDirectoryViewModel() + Application.Current.Dispatcher.Invoke(() => { - DirectoryFiles = new ObservableCollection(); - DirectoryFilesView = new ListCollectionView(DirectoryFiles) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}}; - } - - public void DeactivateAll() - { - foreach (var file in DirectoryFiles) + DirectoryFiles.Add(new FileItem(reader.Name, reader.Length) { - file.IsEnabled = false; - } - } - - public void Add(IAesVfsReader reader) - { - Application.Current.Dispatcher.Invoke(() => - { - DirectoryFiles.Add(new FileItem(reader.Name, reader.Length) - { - Guid = reader.EncryptionKeyGuid, - IsEncrypted = reader.IsEncrypted, - IsEnabled = false, - Key = string.Empty - }); + Guid = reader.EncryptionKeyGuid, + IsEncrypted = reader.IsEncrypted, + IsEnabled = false, + Key = string.Empty }); - } + }); } } \ No newline at end of file diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 89b52c44..b8840bde 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -14,356 +14,355 @@ using FModel.Settings; using FModel.ViewModels.ApiEndpoints.Models; using Microsoft.Win32; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class GameSelectorViewModel : ViewModel { - public class GameSelectorViewModel : ViewModel + public class DetectedGame { - public class DetectedGame + public string GameName { get; set; } + public string GameDirectory { get; set; } + public bool IsManual { get; set; } + + // the followings are only used when game is manually added + public AesResponse AesKeys { get; set; } + public EGame OverridedGame { get; set; } + public List OverridedCustomVersions { get; set; } + public Dictionary OverridedOptions { get; set; } + public IList CustomDirectories { get; set; } + } + + private DetectedGame _selectedDetectedGame; + public DetectedGame SelectedDetectedGame + { + get => _selectedDetectedGame; + set => SetProperty(ref _selectedDetectedGame, value); + } + + private readonly ObservableCollection _autoDetectedGames; + public ReadOnlyObservableCollection AutoDetectedGames { get; } + + public GameSelectorViewModel(string gameDirectory) + { + _autoDetectedGames = new ObservableCollection(EnumerateDetectedGames().Where(x => x != null)); + foreach (var game in UserSettings.Default.ManualGames.Values) { - public string GameName { get; set; } - public string GameDirectory { get; set; } - public bool IsManual { get; set; } - - // the followings are only used when game is manually added - public AesResponse AesKeys { get; set; } - public EGame OverridedGame { get; set; } - public List OverridedCustomVersions { get; set; } - public Dictionary OverridedOptions { get; set; } - public IList CustomDirectories { get; set; } - } - - private DetectedGame _selectedDetectedGame; - public DetectedGame SelectedDetectedGame - { - get => _selectedDetectedGame; - set => SetProperty(ref _selectedDetectedGame, value); - } - - private readonly ObservableCollection _autoDetectedGames; - public ReadOnlyObservableCollection AutoDetectedGames { get; } - - public GameSelectorViewModel(string gameDirectory) - { - _autoDetectedGames = new ObservableCollection(EnumerateDetectedGames().Where(x => x != null)); - foreach (var game in UserSettings.Default.ManualGames.Values) - { - _autoDetectedGames.Add(game); - } - - AutoDetectedGames = new ReadOnlyObservableCollection(_autoDetectedGames); - - if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame) - SelectedDetectedGame = detectedGame; - else if (!string.IsNullOrEmpty(gameDirectory)) - AddUnknownGame(gameDirectory); - else - SelectedDetectedGame = AutoDetectedGames.FirstOrDefault(); - } - - /// - /// dedicated to manual games - /// - public void AddUnknownGame(string gameName, string gameDirectory) - { - var game = new DetectedGame - { - GameName = gameName, - GameDirectory = gameDirectory, - IsManual = true, - AesKeys = null, - OverridedGame = EGame.GAME_UE4_LATEST, - OverridedCustomVersions = null, - OverridedOptions = null, - CustomDirectories = new List() - }; - - UserSettings.Default.ManualGames[gameDirectory] = game; _autoDetectedGames.Add(game); - SelectedDetectedGame = AutoDetectedGames.Last(); } - public void AddUnknownGame(string gameDirectory) - { - _autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory }); - SelectedDetectedGame = AutoDetectedGames.Last(); - } + AutoDetectedGames = new ReadOnlyObservableCollection(_autoDetectedGames); - public void DeleteSelectedGame() - { - UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem - _autoDetectedGames.Remove(SelectedDetectedGame); - SelectedDetectedGame = AutoDetectedGames.Last(); - } + if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame) + SelectedDetectedGame = detectedGame; + else if (!string.IsNullOrEmpty(gameDirectory)) + AddUnknownGame(gameDirectory); + else + SelectedDetectedGame = AutoDetectedGames.FirstOrDefault(); + } - private IEnumerable EnumerateDetectedGames() + /// + /// dedicated to manual games + /// + public void AddUnknownGame(string gameName, string gameDirectory) + { + var game = new DetectedGame { - yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks"); - yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER }; - yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks"); - yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks"); - yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks"); - yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks"); - yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content"); - yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks"); - yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks"); - yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks"); - yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks"); - yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content"); - yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks"); - yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER }; - yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks"); - yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight - yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG - yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate - yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves - yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks"); - yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks"); - yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks"); - } + GameName = gameName, + GameDirectory = gameDirectory, + IsManual = true, + AesKeys = null, + OverridedGame = EGame.GAME_UE4_LATEST, + OverridedCustomVersions = null, + OverridedOptions = null, + CustomDirectories = new List() + }; - private LauncherInstalled _launcherInstalled; - private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory) + UserSettings.Default.ManualGames[gameDirectory] = game; + _autoDetectedGames.Add(game); + SelectedDetectedGame = AutoDetectedGames.Last(); + } + + public void AddUnknownGame(string gameDirectory) + { + _autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory }); + SelectedDetectedGame = AutoDetectedGames.Last(); + } + + public void DeleteSelectedGame() + { + UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem + _autoDetectedGames.Remove(SelectedDetectedGame); + SelectedDetectedGame = AutoDetectedGames.Last(); + } + + private IEnumerable EnumerateDetectedGames() + { + yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks"); + yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER }; + yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks"); + yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks"); + yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks"); + yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks"); + yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content"); + yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks"); + yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks"); + yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks"); + yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks"); + yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content"); + yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks"); + yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER }; + yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks"); + yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight + yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG + yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate + yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves + yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks"); + yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks"); + yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks"); + } + + private LauncherInstalled _launcherInstalled; + private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory) + { + _launcherInstalled ??= GetDriveLauncherInstalls("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); + if (_launcherInstalled?.InstallationList != null) { - _launcherInstalled ??= GetDriveLauncherInstalls("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); - if (_launcherInstalled?.InstallationList != null) + foreach (var installationList in _launcherInstalled.InstallationList) { - foreach (var installationList in _launcherInstalled.InstallationList) - { - if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase)) - return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" }; - } + if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase)) + return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" }; } - - Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName); - return null; } - private RiotClientInstalls _riotClientInstalls; - private DetectedGame GetRiotGame(string gameName, string pakDirectory) + Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName); + return null; + } + + private RiotClientInstalls _riotClientInstalls; + private DetectedGame GetRiotGame(string gameName, string pakDirectory) + { + _riotClientInstalls ??= GetDriveLauncherInstalls("ProgramData\\Riot Games\\RiotClientInstalls.json"); + if (_riotClientInstalls is { AssociatedClient: { } }) { - _riotClientInstalls ??= GetDriveLauncherInstalls("ProgramData\\Riot Games\\RiotClientInstalls.json"); - if (_riotClientInstalls is { AssociatedClient: { } }) + foreach (var (key, _) in _riotClientInstalls.AssociatedClient) { - foreach (var (key, _) in _riotClientInstalls.AssociatedClient) - { - if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase)) - return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" }; - } + if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase)) + return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" }; } - - Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName); - return null; } - private LauncherSettings _launcherSettings; - private DetectedGame GetMojangGame(string gameName, string pakDirectory) + Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName); + return null; + } + + private LauncherSettings _launcherSettings; + private DetectedGame GetMojangGame(string gameName, string pakDirectory) + { + _launcherSettings ??= GetDataLauncherInstalls("\\.minecraft\\launcher_settings.json"); + if (_launcherSettings is { ProductLibraryDir: { } }) + return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" }; + + Log.Warning("Could not find {GameName} in launcher_settings.json", gameName); + return null; + } + + private DetectedGame GetSteamGame(int id, string pakDirectory) + { + var steamInfo = SteamDetection.GetSteamGameById(id); + if (steamInfo is not null) + return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" }; + + Log.Warning("Could not find {GameId} in steam manifests", id); + return null; + } + + private DetectedGame GetRockstarGamesGame(string key, string pakDirectory) + { + var installLocation = string.Empty; + try { - _launcherSettings ??= GetDataLauncherInstalls("\\.minecraft\\launcher_settings.json"); - if (_launcherSettings is { ProductLibraryDir: { } }) - return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" }; - - Log.Warning("Could not find {GameName} in launcher_settings.json", gameName); - return null; + installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine); } - - private DetectedGame GetSteamGame(int id, string pakDirectory) + catch { - var steamInfo = SteamDetection.GetSteamGameById(id); - if (steamInfo is not null) - return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" }; - - Log.Warning("Could not find {GameId} in steam manifests", id); - return null; + // ignored } - private DetectedGame GetRockstarGamesGame(string key, string pakDirectory) + if (!string.IsNullOrEmpty(installLocation)) + return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" }; + + Log.Warning("Could not find {GameName} in the registry", key); + return null; + } + + private T GetDriveLauncherInstalls(string jsonFile) + { + foreach (var drive in DriveInfo.GetDrives()) { - var installLocation = string.Empty; - try - { - installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine); - } - catch - { - // ignored - } + var launcher = $"{drive.Name}{jsonFile}"; + if (!File.Exists(launcher)) continue; - if (!string.IsNullOrEmpty(installLocation)) - return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" }; - - Log.Warning("Could not find {GameName} in the registry", key); - return null; + Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name); + return JsonConvert.DeserializeObject(File.ReadAllText(launcher)); } - private T GetDriveLauncherInstalls(string jsonFile) + Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile); + return default; + } + + private T GetDataLauncherInstalls(string jsonFile) + { + var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + var launcher = $"{appData}{jsonFile}"; + if (File.Exists(launcher)) { - foreach (var drive in DriveInfo.GetDrives()) - { - var launcher = $"{drive.Name}{jsonFile}"; - if (!File.Exists(launcher)) continue; - - Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name); - return JsonConvert.DeserializeObject(File.ReadAllText(launcher)); - } - - Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile); - return default; + Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData); + return JsonConvert.DeserializeObject(File.ReadAllText(launcher)); } - private T GetDataLauncherInstalls(string jsonFile) - { - var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var launcher = $"{appData}{jsonFile}"; - if (File.Exists(launcher)) - { - Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData); - return JsonConvert.DeserializeObject(File.ReadAllText(launcher)); - } - - Log.Warning("\"{Json}\" not found anywhere", jsonFile); - return default; - } + Log.Warning("\"{Json}\" not found anywhere", jsonFile); + return default; + } #pragma warning disable 649 - private class LauncherInstalled - { - public Installation[] InstallationList; - } + private class LauncherInstalled + { + public Installation[] InstallationList; + } - private class Installation - { - public string InstallLocation; - public string AppName; - public string AppVersion; - } + private class Installation + { + public string InstallLocation; + public string AppName; + public string AppVersion; + } - private class RiotClientInstalls - { - [JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary AssociatedClient; + private class RiotClientInstalls + { + [JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary AssociatedClient; - [JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary Patchlines; + [JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)] + public Dictionary Patchlines; - [JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)] - public string RcDefault; + [JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)] + public string RcDefault; - [JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)] - public string RcLive; - } + [JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)] + public string RcLive; + } - private class LauncherSettings - { - [JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)] - public string Channel; + private class LauncherSettings + { + [JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)] + public string Channel; - [JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)] - public object[] CustomChannels; + [JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)] + public object[] CustomChannels; - [JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)] - public string DeviceId; + [JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)] + public string DeviceId; - [JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)] - public int FormatVersion; + [JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)] + public int FormatVersion; - [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] - public string Locale; + [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] + public string Locale; - [JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)] - public string ProductLibraryDir; - } + [JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)] + public string ProductLibraryDir; + } #pragma warning restore 649 - // https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123 - public static class SteamDetection + // https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123 + public static class SteamDetection + { + private static readonly List _steamApps; + + static SteamDetection() { - private static readonly List _steamApps; + _steamApps = GetSteamApps(GetSteamLibs()); + } - static SteamDetection() + public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString()); + + private static List GetSteamApps(IEnumerable steamLibs) + { + var apps = new List(); + foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf"))) { - _steamApps = GetSteamApps(GetSteamLibs()); + apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null)); } - public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString()); + return apps; + } - private static List GetSteamApps(IEnumerable steamLibs) + private static AppInfo GetAppInfo(string appMetaFile) + { + var fileDataLines = File.ReadAllLines(appMetaFile); + var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var line in fileDataLines) { - var apps = new List(); - foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf"))) - { - apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null)); - } - - return apps; + var match = Regex.Match(line, @"\s*""(?\w+)""\s+""(?.*)"""); + if (!match.Success) continue; + var key = match.Groups["key"].Value; + var val = match.Groups["val"].Value; + dic[key] = val; } - private static AppInfo GetAppInfo(string appMetaFile) + if (dic.Keys.Count <= 0) return null; + AppInfo appInfo = new(); + var appId = dic["appid"]; + var name = dic["name"]; + var installDir = dic["installDir"]; + + var path = Path.GetDirectoryName(appMetaFile); + var libGameRoot = Path.Combine(path, "common", installDir); + + if (!Directory.Exists(libGameRoot)) return null; + + appInfo.Id = appId; + appInfo.Name = name; + appInfo.GameRoot = libGameRoot; + + return appInfo; + } + + private static List GetSteamLibs() + { + var steamPath = GetSteamPath(); + if (steamPath == null) return new List(); + var libraries = new List { steamPath }; + + var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf"); + if (!File.Exists(listFile)) return new List(); + var lines = File.ReadAllLines(listFile); + foreach (var line in lines) { - var fileDataLines = File.ReadAllLines(appMetaFile); - var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var line in fileDataLines) + var match = Regex.Match(line, @"""(?\w:\\\\.*)"""); + if (!match.Success) continue; + var path = match.Groups["path"].Value.Replace(@"\\", @"\"); + if (Directory.Exists(path)) { - var match = Regex.Match(line, @"\s*""(?\w+)""\s+""(?.*)"""); - if (!match.Success) continue; - var key = match.Groups["key"].Value; - var val = match.Groups["val"].Value; - dic[key] = val; + libraries.Add(path); } - - if (dic.Keys.Count <= 0) return null; - AppInfo appInfo = new(); - var appId = dic["appid"]; - var name = dic["name"]; - var installDir = dic["installDir"]; - - var path = Path.GetDirectoryName(appMetaFile); - var libGameRoot = Path.Combine(path, "common", installDir); - - if (!Directory.Exists(libGameRoot)) return null; - - appInfo.Id = appId; - appInfo.Name = name; - appInfo.GameRoot = libGameRoot; - - return appInfo; } - private static List GetSteamLibs() + return libraries; + } + + private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32 + + public class AppInfo + { + public string Id { get; internal set; } + public string Name { get; internal set; } + public string GameRoot { get; internal set; } + + public override string ToString() { - var steamPath = GetSteamPath(); - if (steamPath == null) return new List(); - var libraries = new List { steamPath }; - - var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf"); - if (!File.Exists(listFile)) return new List(); - var lines = File.ReadAllLines(listFile); - foreach (var line in lines) - { - var match = Regex.Match(line, @"""(?\w:\\\\.*)"""); - if (!match.Success) continue; - var path = match.Groups["path"].Value.Replace(@"\\", @"\"); - if (Directory.Exists(path)) - { - libraries.Add(path); - } - } - - return libraries; - } - - private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32 - - public class AppInfo - { - public string Id { get; internal set; } - public string Name { get; internal set; } - public string GameRoot { get; internal set; } - - public override string ToString() - { - return $"{Name} ({Id})"; - } + return $"{Name} ({Id})"; } } } -} +} \ No newline at end of file diff --git a/FModel/ViewModels/LoadingModesViewModel.cs b/FModel/ViewModels/LoadingModesViewModel.cs index b31cf110..eb2448de 100644 --- a/FModel/ViewModels/LoadingModesViewModel.cs +++ b/FModel/ViewModels/LoadingModesViewModel.cs @@ -4,20 +4,19 @@ using System.Collections.ObjectModel; using FModel.Framework; using FModel.ViewModels.Commands; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class LoadingModesViewModel : ViewModel { - public class LoadingModesViewModel : ViewModel + private LoadCommand _loadCommand; + public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this); + + public ReadOnlyObservableCollection Modes { get; } + + public LoadingModesViewModel() { - private LoadCommand _loadCommand; - public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this); - - public ReadOnlyObservableCollection Modes { get; } - - public LoadingModesViewModel() - { - Modes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLoadingModes())); - } - - private IEnumerable EnumerateLoadingModes() => Enum.GetValues(); + Modes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLoadingModes())); } -} + + private IEnumerable EnumerateLoadingModes() => Enum.GetValues(); +} \ No newline at end of file diff --git a/FModel/ViewModels/MapViewerViewModel.cs b/FModel/ViewModels/MapViewerViewModel.cs index 58c9f42b..e179ec3e 100644 --- a/FModel/ViewModels/MapViewerViewModel.cs +++ b/FModel/ViewModels/MapViewerViewModel.cs @@ -19,855 +19,859 @@ using FModel.Services; using SkiaSharp; using SkiaSharp.HarfBuzz; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class MapLayer { - public class MapLayer + public SKBitmap Layer; + public bool IsEnabled; +} + +public enum EWaypointType +{ + Parkour, + TimeTrials +} + +public class MapViewerViewModel : ViewModel +{ + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private DiscordHandler _discordHandler => DiscordService.DiscordHandler; + + #region BINDINGS + + private bool _brPois; + public bool BrPois { - public SKBitmap Layer; - public bool IsEnabled; + get => _brPois; + set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois"); } - public enum EWaypointType + private bool _brLandmarks; + public bool BrLandmarks { - Parkour, - TimeTrials + get => _brLandmarks; + set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks"); } - public class MapViewerViewModel : ViewModel + private bool _brTagsLocation; + public bool BrTagsLocation { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private DiscordHandler _discordHandler => DiscordService.DiscordHandler; + get => _brTagsLocation; + set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation"); + } - #region BINDINGS + private bool _brPatrolsPath; + public bool BrPatrolsPath + { + get => _brPatrolsPath; + set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath"); + } - private bool _brPois; - public bool BrPois + private bool _brUpgradeBenches; + public bool BrUpgradeBenches + { + get => _brUpgradeBenches; + set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches"); + } + + private bool _brPhonebooths; + public bool BrPhonebooths + { + get => _brPhonebooths; + set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths"); + } + + private bool _brVendingMachines; + public bool BrVendingMachines + { + get => _brVendingMachines; + set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines"); + } + + private bool _brBountyBoards; + public bool BrBountyBoards + { + get => _brBountyBoards; + set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards"); + } + + private bool _prLandmarks; + public bool PrLandmarks + { + get => _prLandmarks; + set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks"); + } + + private bool _prCannonball; + public bool PrCannonball + { + get => _prCannonball; + set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame"); + } + + private bool _prSkydive; + public bool PrSkydive + { + get => _prSkydive; + set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame"); + } + + private bool _prShootingTargets; + public bool PrShootingTargets + { + get => _prShootingTargets; + set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets"); + } + + private bool _prParkour; + public bool PrParkour + { + get => _prParkour; + set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame"); + } + + private bool _prTimeTrials; + public bool PrTimeTrials + { + get => _prTimeTrials; + set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials"); + } + + private bool _prVendingMachines; + public bool PrVendingMachines + { + get => _prVendingMachines; + set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines"); + } + + private bool _prMusicBlocks; + public bool PrMusicBlocks + { + get => _prMusicBlocks; + set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks"); + } + + #endregion + + #region BITMAP IMAGES + + private BitmapImage _brMiniMapImage; + private BitmapImage _prMiniMapImage; + private BitmapImage _mapImage; + public BitmapImage MapImage + { + get => _mapImage; + set => SetProperty(ref _mapImage, value); + } + + private BitmapImage _brLayerImage; + private BitmapImage _prLayerImage; + private BitmapImage _layerImage; + public BitmapImage LayerImage + { + get => _layerImage; + set => SetProperty(ref _layerImage, value); + } + + private const int _widthHeight = 2048; + private const int _brRadius = 135000; + private const int _prRadius = 51000; + private int _mapIndex; + public int MapIndex // 0 is BR, 1 is PR + { + get => _mapIndex; + set { - get => _brPois; - set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois"); - } - - private bool _brLandmarks; - public bool BrLandmarks - { - get => _brLandmarks; - set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks"); - } - - private bool _brTagsLocation; - public bool BrTagsLocation - { - get => _brTagsLocation; - set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation"); - } - - private bool _brPatrolsPath; - public bool BrPatrolsPath - { - get => _brPatrolsPath; - set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath"); - } - - private bool _brUpgradeBenches; - public bool BrUpgradeBenches - { - get => _brUpgradeBenches; - set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches"); - } - - private bool _brPhonebooths; - public bool BrPhonebooths - { - get => _brPhonebooths; - set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths"); - } - - private bool _brVendingMachines; - public bool BrVendingMachines - { - get => _brVendingMachines; - set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines"); - } - - private bool _brBountyBoards; - public bool BrBountyBoards - { - get => _brBountyBoards; - set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards"); - } - - private bool _prLandmarks; - public bool PrLandmarks - { - get => _prLandmarks; - set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks"); - } - - private bool _prCannonball; - public bool PrCannonball - { - get => _prCannonball; - set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame"); - } - - private bool _prSkydive; - public bool PrSkydive - { - get => _prSkydive; - set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame"); - } - - private bool _prShootingTargets; - public bool PrShootingTargets - { - get => _prShootingTargets; - set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets"); - } - - private bool _prParkour; - public bool PrParkour - { - get => _prParkour; - set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame"); - } - - private bool _prTimeTrials; - public bool PrTimeTrials - { - get => _prTimeTrials; - set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials"); - } - - private bool _prVendingMachines; - public bool PrVendingMachines - { - get => _prVendingMachines; - set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines"); - } - - private bool _prMusicBlocks; - public bool PrMusicBlocks - { - get => _prMusicBlocks; - set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks"); - } - - #endregion - - #region BITMAP IMAGES - - private BitmapImage _brMiniMapImage; - private BitmapImage _prMiniMapImage; - private BitmapImage _mapImage; - public BitmapImage MapImage - { - get => _mapImage; - set => SetProperty(ref _mapImage, value); - } - - private BitmapImage _brLayerImage; - private BitmapImage _prLayerImage; - private BitmapImage _layerImage; - public BitmapImage LayerImage - { - get => _layerImage; - set => SetProperty(ref _layerImage, value); - } - - private const int _widthHeight = 2048; - private const int _brRadius = 135000; - private const int _prRadius = 51000; - private int _mapIndex; - public int MapIndex // 0 is BR, 1 is PR - { - get => _mapIndex; - set - { - SetProperty(ref _mapIndex, value); - TriggerChange(); - } - } - - #endregion - - private const string _FIRST_BITMAP = "MapCheck"; - private readonly Dictionary[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map - private readonly CUE4ParseViewModel _cue4Parse; - - public MapViewerViewModel(CUE4ParseViewModel cue4Parse) - { - _bitmaps = new[] - { - new Dictionary(), - new Dictionary() - }; - _cue4Parse = cue4Parse; - } - - public async void Initialize() - { - Utils.Typefaces ??= new Typefaces(_cue4Parse); - _textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; - await LoadBrMiniMap(); - await LoadPrMiniMap(); + SetProperty(ref _mapIndex, value); TriggerChange(); } + } - public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true)); - private SKBitmap GetLayerBitmap(bool withMap) + #endregion + + private const string _FIRST_BITMAP = "MapCheck"; + private readonly Dictionary[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map + private readonly CUE4ParseViewModel _cue4Parse; + + public MapViewerViewModel(CUE4ParseViewModel cue4Parse) + { + _bitmaps = new[] { - var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); + new Dictionary(), + new Dictionary() + }; + _cue4Parse = cue4Parse; + } - foreach (var (key, value) in _bitmaps[MapIndex]) - { - if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP) - continue; + public async void Initialize() + { + Utils.Typefaces ??= new Typefaces(_cue4Parse); + _textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; + await LoadBrMiniMap(); + await LoadPrMiniMap(); + TriggerChange(); + } - c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight)); - } + public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true)); + + private SKBitmap GetLayerBitmap(bool withMap) + { + var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); - return ret; + foreach (var (key, value) in _bitmaps[MapIndex]) + { + if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP) + continue; + + c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight)); } - protected override bool SetProperty(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason + return ret; + } + + protected override bool SetProperty(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason + { + var ret = base.SetProperty(ref storage, value, propertyName); + if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b); + return ret; + } + + private async void GenericToggle(string key, bool enabled) + { + if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null) { - var ret = base.SetProperty(ref storage, value, propertyName); - if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b); - return ret; + layer.IsEnabled = enabled; + } + else if (enabled) // load layer + { + switch (key) + { + case "ApolloGameplay_MapPois": + case "ApolloGameplay_MapLandmarks": + case "PapayaGameplay_MapLandmarks": + await LoadQuestIndicatorData(); + break; + case "ApolloGameplay_TagsLocation": + await LoadTagsLocation(); + break; + case "ApolloGameplay_PatrolsPath": + await LoadPatrolsPath(); + break; + case "ApolloGameplay_UpgradeBenches": + await LoadUpgradeBenches(); + break; + case "ApolloGameplay_Phonebooths": + await LoadPhonebooths(); + break; + case "ApolloGameplay_VendingMachines": + await LoadBrVendingMachines(); + break; + case "ApolloGameplay_BountyBoards": + await LoadBountyBoards(); + break; + case "PapayaGameplay_CannonballGame": + await LoadCannonballGame(); + break; + case "PapayaGameplay_SkydiveGame": + await LoadSkydiveGame(); + break; + case "PapayaGameplay_ShootingTargets": + await LoadShootingTargets(); + break; + case "PapayaGameplay_ParkourGame": + await LoadWaypoint(EWaypointType.Parkour); + break; + case "PapayaGameplay_TimeTrials": + await LoadWaypoint(EWaypointType.TimeTrials); + break; + case "PapayaGameplay_VendingMachines": + await LoadPrVendingMachines(); + break; + case "PapayaGameplay_MusicBlocks": + await LoadMusicBlocks(); + break; + } + + _bitmaps[MapIndex][key].IsEnabled = true; } - private async void GenericToggle(string key, bool enabled) + switch (MapIndex) { - if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null) + case 0: + _brLayerImage = GetImageSource(GetLayerBitmap(false)); + break; + case 1: + _prLayerImage = GetImageSource(GetLayerBitmap(false)); + break; + } + + TriggerChange(); + } + + private BitmapImage GetImageSource(SKBitmap bitmap) + { + if (bitmap == null) return null; + using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100); + using var stream = data.AsStream(); + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = stream; + image.EndInit(); + image.Freeze(); + return image; + } + + private void TriggerChange() + { + var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled); + var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}"; + switch (_mapIndex) + { + case 0: + _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})"); + _mapImage = _brMiniMapImage; + _layerImage = _brLayerImage; + break; + case 1: + _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})"); + _mapImage = _prMiniMapImage; + _layerImage = _prLayerImage; + break; + } + + RaisePropertyChanged(nameof(MapImage)); + RaisePropertyChanged(nameof(LayerImage)); + } + + private readonly SKPaint _textPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26 + }; + + private readonly SKPaint _fillPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + IsStroke = true, Color = SKColors.Black, TextSize = 26, + TextAlign = SKTextAlign.Center + }; + + private readonly SKPaint _pathPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true, + Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red, + ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black) + }; + + private FVector2D GetMapPosition(FVector vector, int mapRadius) + { + var nx = (vector.Y + mapRadius) / (mapRadius * 2) * _widthHeight; + var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * _widthHeight; + return new FVector2D(nx, ny); + } + + private async Task LoadBrMiniMap() + { + if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null) + return; // if map already loaded + + await _threadWorkerView.Begin(_ => + { + if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || + !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial")) return; + var midTex = mapMaterial.GetFirstTexture(); + if ((midTex?.Name ?? string.Empty).Contains("Mask")) + midTex = mapMaterial.GetTextureAtIndex(1); + + if (midTex is not UTexture2D tex) return; + _bitmaps[0][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true }; + _brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer); + }); + } + + private async Task LoadPrMiniMap() + { + if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null) + return; // if map already loaded + + await _threadWorkerView.Begin(_ => + { + if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || + !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.GetFirstTexture() is not UTexture2D tex) return; + + _bitmaps[1][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true }; + _prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer); + }); + } + + private async Task LoadQuestIndicatorData() + { + await _threadWorkerView.Begin(_ => + { + var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var pois = new SKCanvas(poisBitmap); + using var brLandmarks = new SKCanvas(brLandmarksBitmap); + using var prLandmarks = new SKCanvas(prLandmarksBitmap); + + if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) && + indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) { - layer.IsEnabled = enabled; - } - else if (enabled) // load layer - { - switch (key) + foreach (var poiData in challengeMapPoiData) { - case "ApolloGameplay_MapPois": - case "ApolloGameplay_MapLandmarks": - case "PapayaGameplay_MapLandmarks": - await LoadQuestIndicatorData(); - break; - case "ApolloGameplay_TagsLocation": - await LoadTagsLocation(); - break; - case "ApolloGameplay_PatrolsPath": - await LoadPatrolsPath(); - break; - case "ApolloGameplay_UpgradeBenches": - await LoadUpgradeBenches(); - break; - case "ApolloGameplay_Phonebooths": - await LoadPhonebooths(); - break; - case "ApolloGameplay_VendingMachines": - await LoadBrVendingMachines(); - break; - case "ApolloGameplay_BountyBoards": - await LoadBountyBoards(); - break; - case "PapayaGameplay_CannonballGame": - await LoadCannonballGame(); - break; - case "PapayaGameplay_SkydiveGame": - await LoadSkydiveGame(); - break; - case "PapayaGameplay_ShootingTargets": - await LoadShootingTargets(); - break; - case "PapayaGameplay_ParkourGame": - await LoadWaypoint(EWaypointType.Parkour); - break; - case "PapayaGameplay_TimeTrials": - await LoadWaypoint(EWaypointType.TimeTrials); - break; - case "PapayaGameplay_VendingMachines": - await LoadPrVendingMachines(); - break; - case "PapayaGameplay_MusicBlocks": - await LoadMusicBlocks(); - break; - } - _bitmaps[MapIndex][key].IsEnabled = true; - } + if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") || + !poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) || + !poiData.TryGetValue(out FVector worldLocation, "WorldLocation") || + !poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue; - switch (MapIndex) - { - case 0: - _brLayerImage = GetImageSource(GetLayerBitmap(false)); - break; - case 1: - _prLayerImage = GetImageSource(GetLayerBitmap(false)); - break; - } - TriggerChange(); - } + var shaper = new CustomSKShaper(_textPaint.Typeface); + var shapedText = shaper.Shape(text.Text, _textPaint); - private BitmapImage GetImageSource(SKBitmap bitmap) - { - if (bitmap == null) return null; - using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100); - using var stream = data.AsStream(); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); - return image; - } - - private void TriggerChange() - { - var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled); - var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}"; - switch (_mapIndex) - { - case 0: - _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})"); - _mapImage = _brMiniMapImage; - _layerImage = _brLayerImage; - break; - case 1: - _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})"); - _mapImage = _prMiniMapImage; - _layerImage = _prLayerImage; - break; - } - - RaisePropertyChanged(nameof(MapImage)); - RaisePropertyChanged(nameof(LayerImage)); - } - - private readonly SKPaint _textPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26 - }; - private readonly SKPaint _fillPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - IsStroke = true, Color = SKColors.Black, TextSize = 26, - TextAlign = SKTextAlign.Center - }; - private readonly SKPaint _pathPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true, - Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red, - ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black) - }; - - private FVector2D GetMapPosition(FVector vector, int mapRadius) - { - var nx = (vector.Y + mapRadius) / (mapRadius * 2) * _widthHeight; - var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * _widthHeight; - return new FVector2D(nx, ny); - } - - private async Task LoadBrMiniMap() - { - if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null) - return; // if map already loaded - - await _threadWorkerView.Begin(_ => - { - if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || - !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial")) return; - var midTex = mapMaterial.GetFirstTexture(); - if ((midTex?.Name ?? string.Empty).Contains("Mask")) - midTex = mapMaterial.GetTextureAtIndex(1); - - if (midTex is not UTexture2D tex) return; - _bitmaps[0][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(tex), IsEnabled = true}; - _brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer); - }); - } - - private async Task LoadPrMiniMap() - { - if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null) - return; // if map already loaded - - await _threadWorkerView.Begin(_ => - { - if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || - !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.GetFirstTexture() is not UTexture2D tex) return; - - _bitmaps[1][_FIRST_BITMAP] = new MapLayer{Layer = Utils.GetBitmap(tex), IsEnabled = true}; - _prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer); - }); - } - - private async Task LoadQuestIndicatorData() - { - await _threadWorkerView.Begin(_ => - { - var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var pois = new SKCanvas(poisBitmap); - using var brLandmarks = new SKCanvas(brLandmarksBitmap); - using var prLandmarks = new SKCanvas(prLandmarksBitmap); - - if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) && - indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) - { - foreach (var poiData in challengeMapPoiData) + if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase)) { - if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") || - !poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) || - !poiData.TryGetValue(out FVector worldLocation, "WorldLocation") || - !poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue; - - var shaper = new CustomSKShaper(_textPaint.Typeface); - var shapedText = shaper.Shape(text.Text, _textPaint); - - if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase)) - { - _fillPaint.StrokeWidth = 5; - var vector = GetMapPosition(worldLocation, _prRadius); - prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); - prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint); - prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint); - } - else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase)) - { - _fillPaint.StrokeWidth = 5; - var vector = GetMapPosition(worldLocation, _brRadius); - brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); - brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint); - brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint); - } - else - { - _fillPaint.StrokeWidth = 10; - var vector = GetMapPosition(worldLocation, _brRadius); - pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _fillPaint); - pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _textPaint); - } + _fillPaint.StrokeWidth = 5; + var vector = GetMapPosition(worldLocation, _prRadius); + prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); + prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint); + prLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint); + } + else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase)) + { + _fillPaint.StrokeWidth = 5; + var vector = GetMapPosition(worldLocation, _brRadius); + brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); + brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _fillPaint); + brLandmarks.DrawShapedText(shaper, text.Text, vector.X - shapedText.Points[^1].X / 2, vector.Y - 12.5F, _textPaint); + } + else + { + _fillPaint.StrokeWidth = 10; + var vector = GetMapPosition(worldLocation, _brRadius); + pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _fillPaint); + pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X - shapedText.Points[^1].X / 2, vector.Y, _textPaint); } } + } - _bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer {Layer = poisBitmap, IsEnabled = false}; - _bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer {Layer = brLandmarksBitmap, IsEnabled = false}; - _bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer {Layer = prLandmarksBitmap, IsEnabled = false}; - }); - } + _bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer { Layer = poisBitmap, IsEnabled = false }; + _bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer { Layer = brLandmarksBitmap, IsEnabled = false }; + _bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer { Layer = prLandmarksBitmap, IsEnabled = false }; + }); + } - private async Task LoadPatrolsPath() + private async Task LoadPatrolsPath() + { + await _threadWorkerView.Begin(_ => { - await _threadWorkerView.Begin(_ => + _fillPaint.StrokeWidth = 5; + var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(patrolsPathBitmap); + + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S20_NPCLibrary"); + foreach (var export in exports) { - _fillPaint.StrokeWidth = 5; - var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(patrolsPathBitmap); + if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) || + !export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue; - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S20_NPCLibrary"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) || - !export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue; + var displayName = export.Name["FortAthenaPatrolPath_".Length..]; + if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0) + displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..]; - var displayName = export.Name["FortAthenaPatrolPath_".Length..]; - if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0) - displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..]; - - if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) || - !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var path = new SKPath(); - var vector = GetMapPosition(relativeLocation, _brRadius); - path.MoveTo(vector.X, vector.Y); - - for (var i = 1; i < patrolPoints.Length; i++) - { - if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) || - !uObject.TryGetValue(out rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue; - - vector = GetMapPosition(relativeLocation, _brRadius); - path.LineTo(vector.X, vector.Y); - } - - c.DrawPath(path, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer {Layer = patrolsPathBitmap, IsEnabled = false}; - }); - } - - private async Task LoadCannonballGame() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(cannonballBitmap); - - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball"); - var vector = GetMapPosition(relativeLocation, _prRadius); - - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer {Layer = cannonballBitmap, IsEnabled = false}; - }); - } - - private async Task LoadSkydiveGame() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(skydiveBitmap); - - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer {Layer = skydiveBitmap, IsEnabled = false}; - }); - } - - private async Task LoadShootingTargets() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(shootingTargetsBitmap); - - var bDone = false; - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (bDone) continue; - - bDone = true; - c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer {Layer = shootingTargetsBitmap, IsEnabled = false}; - }); - } - - private async Task LoadWaypoint(EWaypointType type) - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(waypointBitmap); - - string file; - string name; - switch (type) - { - case EWaypointType.Parkour: - file = "PapayaGameplay_ParkourGame"; - name = "Parkour"; - break; - case EWaypointType.TimeTrials: - file = "PapayaGameplay_TimeTrials"; - name = "Time Trials"; - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } + if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) || + !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; var path = new SKPath(); - var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}"); - foreach (var export in exports) + var vector = GetMapPosition(relativeLocation, _brRadius); + path.MoveTo(vector.X, vector.Y); + + for (var i = 1; i < patrolPoints.Length; i++) { - if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; + if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) || + !uObject.TryGetValue(out rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || + !uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue; - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || - !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial) - { - path.MoveTo(vector.X, vector.Y); - c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint); - } - else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial) - { - path.LineTo(vector.X, vector.Y); - c.DrawPath(path, _pathPaint); - path = new SKPath(); - } - else path.LineTo(vector.X, vector.Y); + vector = GetMapPosition(relativeLocation, _brRadius); + path.LineTo(vector.X, vector.Y); } - _bitmaps[1][file] = new MapLayer {Layer = waypointBitmap, IsEnabled = false}; - }); - } + c.DrawPath(path, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } - private async Task LoadPrVendingMachines() + _bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer { Layer = patrolsPathBitmap, IsEnabled = false }; + }); + } + + private async Task LoadCannonballGame() + { + await _threadWorkerView.Begin(_ => { - await _threadWorkerView.Begin(_ => + _fillPaint.StrokeWidth = 5; + var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(cannonballBitmap); + + var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame"); + foreach (var export in exports) { - _fillPaint.StrokeWidth = 5; - var set = new HashSet(); - var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(timeTrialsBitmap); + if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue; - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines"); - foreach (var export in exports) + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball"); + var vector = GetMapPosition(relativeLocation, _prRadius); + + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer { Layer = cannonballBitmap, IsEnabled = false }; + }); + } + + private async Task LoadSkydiveGame() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(skydiveBitmap); + + var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _prRadius); + + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer { Layer = skydiveBitmap, IsEnabled = false }; + }); + } + + private async Task LoadShootingTargets() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(shootingTargetsBitmap); + + var bDone = false; + var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _prRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + if (bDone) continue; + + bDone = true; + c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false }; + }); + } + + private async Task LoadWaypoint(EWaypointType type) + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(waypointBitmap); + + string file; + string name; + switch (type) + { + case EWaypointType.Parkour: + file = "PapayaGameplay_ParkourGame"; + name = "Parkour"; + break; + case EWaypointType.TimeTrials: + file = "PapayaGameplay_TimeTrials"; + name = "Time Trials"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + + var path = new SKPath(); + var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || + !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _prRadius); + if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial) { - if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || - !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C"); - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (!set.Add(name)) continue; - + path.MoveTo(vector.X, vector.Y); c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint); c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint); } - - _bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer {Layer = timeTrialsBitmap, IsEnabled = false}; - }); - } - - private async Task LoadMusicBlocks() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(shootingTargetsBitmap); - - var bDone = false; - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks"); - foreach (var export in exports) + else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial) { - if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (bDone) continue; - - bDone = true; - c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint); + path.LineTo(vector.X, vector.Y); + c.DrawPath(path, _pathPaint); + path = new SKPath(); } + else path.LineTo(vector.X, vector.Y); + } - _bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer {Layer = shootingTargetsBitmap, IsEnabled = false}; - }); - } - - private async Task LoadUpgradeBenches() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(upgradeBenchesBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = export.Name["B_Athena_Spawner_".Length..]; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer {Layer = upgradeBenchesBitmap, IsEnabled = false}; - }); - } - - private async Task LoadPhonebooths() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(phoneboothsBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = export.Name["B_Athena_Spawner_".Length..]; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer {Layer = phoneboothsBitmap, IsEnabled = false}; - }); - } - - private async Task LoadBrVendingMachines() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(vendingMachinesBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}"; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer {Layer = vendingMachinesBitmap, IsEnabled = false}; - }); - } - - private async Task LoadBountyBoards() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(bountyBoardsBitmap); - - var exports = Utils.LoadExports("Bounties/Maps/BB_Overlay_S19_ServiceStations.umap"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}"; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer {Layer = bountyBoardsBitmap, IsEnabled = false}; - }); - } - - private async Task LoadTagsLocation() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData)) - return; - - var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(tagsLocationBitmap); - - foreach (var (key, uObject) in locationData.RowMap) - { - if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) || - key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) || - key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) || - !uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue; - - var parts = key.Text.Split('.'); - var displayName = parts[^2]; - if (!int.TryParse(parts[^1], out var _)) - displayName += " " + parts[^1]; - - var vector = GetMapPosition(worldLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer {Layer = tagsLocationBitmap, IsEnabled = false}; - }); - } + _bitmaps[1][file] = new MapLayer { Layer = waypointBitmap, IsEnabled = false }; + }); } -} + + private async Task LoadPrVendingMachines() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var set = new HashSet(); + var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(timeTrialsBitmap); + + var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || + !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C"); + var vector = GetMapPosition(relativeLocation, _prRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + if (!set.Add(name)) continue; + + c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer { Layer = timeTrialsBitmap, IsEnabled = false }; + }); + } + + private async Task LoadMusicBlocks() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(shootingTargetsBitmap); + + var bDone = false; + var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _prRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + if (bDone) continue; + + bDone = true; + c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false }; + }); + } + + private async Task LoadUpgradeBenches() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(upgradeBenchesBitmap); + + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue; + var displayName = export.Name["B_Athena_Spawner_".Length..]; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _brRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer { Layer = upgradeBenchesBitmap, IsEnabled = false }; + }); + } + + private async Task LoadPhonebooths() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(phoneboothsBitmap); + + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue; + var displayName = export.Name["B_Athena_Spawner_".Length..]; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _brRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer { Layer = phoneboothsBitmap, IsEnabled = false }; + }); + } + + private async Task LoadBrVendingMachines() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(vendingMachinesBitmap); + + var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) && + !export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue; + var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}"; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _brRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer { Layer = vendingMachinesBitmap, IsEnabled = false }; + }); + } + + private async Task LoadBountyBoards() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(bountyBoardsBitmap); + + var exports = Utils.LoadExports("/Bounties/Maps/BB_Overlay_S19_ServiceStations"); + foreach (var export in exports) + { + if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue; + var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}"; + + if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || + !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || + !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; + + var vector = GetMapPosition(relativeLocation, _brRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer { Layer = bountyBoardsBitmap, IsEnabled = false }; + }); + } + + private async Task LoadTagsLocation() + { + await _threadWorkerView.Begin(_ => + { + _fillPaint.StrokeWidth = 5; + if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData)) + return; + + var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(tagsLocationBitmap); + + foreach (var (key, uObject) in locationData.RowMap) + { + if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) || + key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) || + key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) || + !uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue; + + var parts = key.Text.Split('.'); + var displayName = parts[^2]; + if (!int.TryParse(parts[^1], out var _)) + displayName += " " + parts[^1]; + + var vector = GetMapPosition(worldLocation, _brRadius); + c.DrawPoint(vector.X, vector.Y, _pathPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); + c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); + } + + _bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer { Layer = tagsLocationBitmap, IsEnabled = false }; + }); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/ModelViewerViewModel.cs b/FModel/ViewModels/ModelViewerViewModel.cs index 016186a6..071d0a7e 100644 --- a/FModel/ViewModels/ModelViewerViewModel.cs +++ b/FModel/ViewModels/ModelViewerViewModel.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Media.Media3D; +using CUE4Parse_Conversion; using CUE4Parse.UE4.Assets; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; @@ -38,735 +39,744 @@ using SharpGLTF.Schema2; using SharpGLTF.Transforms; using SkiaSharp; using Camera = HelixToolkit.Wpf.SharpDX.Camera; +using Exporter = CUE4Parse_Conversion.Exporter; using Geometry3D = HelixToolkit.SharpDX.Core.Geometry3D; using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera; using Vector2 = SharpDX.Vector2; using Vector3 = SharpDX.Vector3; using VERTEX = SharpGLTF.Geometry.VertexTypes.VertexPositionNormalTangent; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class ModelViewerViewModel : ViewModel { - public class ModelViewerViewModel : ViewModel + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + + private EffectsManager _effectManager; + public EffectsManager EffectManager { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + get => _effectManager; + set => SetProperty(ref _effectManager, value); + } - private EffectsManager _effectManager; - public EffectsManager EffectManager - { - get => _effectManager; - set => SetProperty(ref _effectManager, value); - } + private Camera _cam; + public Camera Cam + { + get => _cam; + set => SetProperty(ref _cam, value); + } - private Camera _cam; - public Camera Cam - { - get => _cam; - set => SetProperty(ref _cam, value); - } + private ModelAndCam _selectedModel; // selected mesh + public ModelAndCam SelectedModel + { + get => _selectedModel; + set => SetProperty(ref _selectedModel, value); + } - private ModelAndCam _selectedModel; // selected mesh - public ModelAndCam SelectedModel - { - get => _selectedModel; - set => SetProperty(ref _selectedModel, value); - } + private readonly ObservableCollection _loadedModels; // mesh list + public ICollectionView LoadedModelsView { get; } - private readonly ObservableCollection _loadedModels; // mesh list - public ICollectionView LoadedModelsView { get; } + private bool _appendMode; + public bool AppendMode + { + get => _appendMode; + set => SetProperty(ref _appendMode, value); + } - private bool _appendMode; - public bool AppendMode - { - get => _appendMode; - set => SetProperty(ref _appendMode, value); - } + public bool CanAppend => SelectedModel != null; - public bool CanAppend => SelectedModel != null; + public TextureModel HDRi { get; private set; } - public TextureModel HDRi { get; private set; } + private readonly FGame _game; + private readonly int[] _facesIndex = { 1, 0, 2 }; - private readonly FGame _game; - private readonly int[] _facesIndex = { 1, 0, 2 }; + public ModelViewerViewModel(FGame game) + { + _game = game; + _loadedModels = new ObservableCollection(); - public ModelViewerViewModel(FGame game) - { - _game = game; - _loadedModels = new ObservableCollection(); + EffectManager = new DefaultEffectsManager(); + LoadedModelsView = new ListCollectionView(_loadedModels); + Cam = new PerspectiveCamera { NearPlaneDistance = 0.1, FarPlaneDistance = double.PositiveInfinity, FieldOfView = 90 }; + LoadHDRi(); + } - EffectManager = new DefaultEffectsManager(); - LoadedModelsView = new ListCollectionView(_loadedModels); - Cam = new PerspectiveCamera { NearPlaneDistance = 0.1, FarPlaneDistance = double.PositiveInfinity, FieldOfView = 90 }; - LoadHDRi(); - } + private void LoadHDRi() + { + var cubeMap = Application.GetResourceStream(new Uri("/FModel;component/Resources/approaching_storm_cubemap.dds", UriKind.Relative)); + HDRi = TextureModel.Create(cubeMap?.Stream); + } - private void LoadHDRi() - { - var cubeMap = Application.GetResourceStream(new Uri("/FModel;component/Resources/approaching_storm_cubemap.dds", UriKind.Relative)); - HDRi = TextureModel.Create(cubeMap?.Stream); - } - - public void LoadExport(UObject export) - { + public void LoadExport(UObject export) + { #if DEBUG - LoadHDRi(); + LoadHDRi(); #endif - ModelAndCam p; - if (AppendMode && CanAppend) + ModelAndCam p; + if (AppendMode && CanAppend) + { + p = SelectedModel; + _loadedModels.Add(new ModelAndCam(export) { IsVisible = false }); + } + else + { + p = new ModelAndCam(export); + _loadedModels.Add(p); + } + + switch (export) + { + case UStaticMesh st: + LoadStaticMesh(st, p); + break; + case USkeletalMesh sk: + LoadSkeletalMesh(sk, p); + break; + case UMaterialInstance mi: + LoadMaterialInstance(mi, p); + break; + default: + throw new ArgumentOutOfRangeException(nameof(export)); + } + + if (AppendMode && CanAppend) return; + SelectedModel = p; + Cam.UpDirection = new Vector3D(0, 1, 0); + Cam.Position = p.Position; + Cam.LookDirection = p.LookDirection; + } + + #region PUBLIC METHODS + + public void RenderingToggle() + { + if (SelectedModel == null) return; + SelectedModel.RenderingToggle = !SelectedModel.RenderingToggle; + } + + public void WirefreameToggle() + { + if (SelectedModel == null) return; + SelectedModel.WireframeToggle = !SelectedModel.WireframeToggle; + } + + public void MaterialColorToggle() + { + if (SelectedModel == null) return; + SelectedModel.ShowMaterialColor = !SelectedModel.ShowMaterialColor; + } + + public void DiffuseOnlyToggle() + { + if (SelectedModel == null) return; + SelectedModel.DiffuseOnlyToggle = !SelectedModel.DiffuseOnlyToggle; + } + + public void FocusOnSelectedMesh() + { + Cam.AnimateTo(SelectedModel.Position, SelectedModel.LookDirection, new Vector3D(0, 1, 0), 500); + } + + public async Task SaveLoadedModels() + { + if (_loadedModels.Count < 1) return; + + var folderBrowser = new VistaFolderBrowserDialog { ShowNewFolderButton = true }; + if (folderBrowser.ShowDialog() == false) return; + + await _threadWorkerView.Begin(_ => + { + var exportOptions = new ExporterOptions { - p = SelectedModel; - _loadedModels.Add(new ModelAndCam(export) {IsVisible = false}); - } - else - { - p = new ModelAndCam(export); - _loadedModels.Add(p); - } - - switch (export) - { - case UStaticMesh st: - LoadStaticMesh(st, p); - break; - case USkeletalMesh sk: - LoadSkeletalMesh(sk, p); - break; - case UMaterialInstance mi: - LoadMaterialInstance(mi, p); - break; - default: - throw new ArgumentOutOfRangeException(nameof(export)); - } - - if (AppendMode && CanAppend) return; - SelectedModel = p; - Cam.UpDirection = new Vector3D(0, 1, 0); - Cam.Position = p.Position; - Cam.LookDirection = p.LookDirection; - } - - #region PUBLIC METHODS - public void RenderingToggle() - { - if (SelectedModel == null) return; - SelectedModel.RenderingToggle = !SelectedModel.RenderingToggle; - } - - public void WirefreameToggle() - { - if (SelectedModel == null) return; - SelectedModel.WireframeToggle = !SelectedModel.WireframeToggle; - } - - public void MaterialColorToggle() - { - if (SelectedModel == null) return; - SelectedModel.ShowMaterialColor = !SelectedModel.ShowMaterialColor; - } - - public void DiffuseOnlyToggle() - { - if (SelectedModel == null) return; - SelectedModel.DiffuseOnlyToggle = !SelectedModel.DiffuseOnlyToggle; - } - - public void FocusOnSelectedMesh() - { - Cam.AnimateTo(SelectedModel.Position, SelectedModel.LookDirection, new Vector3D(0, 1, 0), 500); - } - - public async Task SaveLoadedModels() - { - if (_loadedModels.Count < 1) return; - - var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = true}; - if (folderBrowser.ShowDialog() == false) return; - - await _threadWorkerView.Begin(_ => - { - var exportOptions = new CUE4Parse_Conversion.ExporterOptions() - { - TextureFormat = UserSettings.Default.TextureExportFormat, - LodFormat = UserSettings.Default.LodExportFormat, - MeshFormat = UserSettings.Default.MeshExportFormat, - Platform = UserSettings.Default.OverridedPlatform - }; - foreach (var model in _loadedModels) - { - var toSave = new CUE4Parse_Conversion.Exporter(model.Export, exportOptions); - if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName)) - { - Log.Information("Successfully saved {FileName}", savedFileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); - } - else - { - Log.Error("{FileName} could not be saved", savedFileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true); - } - } - }); - } - - public void SaveAsScene() - { - if (_loadedModels.Count < 1) return; - - var fileBrowser = new VistaSaveFileDialog - { - Title = "Save Loaded Models As...", - DefaultExt = ".glb", - Filter = "glTF Binary File (*.glb)|*.glb|glTF ASCII File (*.gltf)|*.gltf|All Files(*.*)|*.*", - AddExtension = true, - OverwritePrompt = true, - CheckPathExists = true + TextureFormat = UserSettings.Default.TextureExportFormat, + LodFormat = UserSettings.Default.LodExportFormat, + MeshFormat = UserSettings.Default.MeshExportFormat, + Platform = UserSettings.Default.OverridedPlatform }; - - if (fileBrowser.ShowDialog() == false || string.IsNullOrEmpty(fileBrowser.FileName)) return; - - var sceneBuilder = new SceneBuilder(); - var materialExports = new List(); foreach (var model in _loadedModels) { - switch (model.Export) + var toSave = new Exporter(model.Export, exportOptions); + if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName)) { - case UStaticMesh sm: - { - var mesh = new MeshBuilder(sm.Name); - if (sm.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) - { - var lod = convertedMesh.LODs.First(); - for (var i = 0; i < lod.Sections.Value.Length; i++) - { - Gltf.ExportStaticMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); - } - sceneBuilder.AddRigidMesh(mesh, AffineTransform.Identity); - } - break; - } - case USkeletalMesh sk: - { - var mesh = new MeshBuilder(sk.Name); - - if (sk.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) - { - var lod = convertedMesh.LODs.First(); - for (var i = 0; i < lod.Sections.Value.Length; i++) - { - Gltf.ExportSkelMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); - } - var armatureNodeBuilder = new NodeBuilder(sk.Name+".ao"); - var armature = Gltf.CreateGltfSkeleton(convertedMesh.RefSkeleton, armatureNodeBuilder); - sceneBuilder.AddSkinnedMesh(mesh, Matrix4x4.Identity, armature); - } - break; - } - } - } - - var scene = sceneBuilder.ToGltf2(); - var fileName = fileBrowser.FileName; - if (fileName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)) - scene.SaveGLB(fileName); - else if (fileName.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase)) - scene.SaveGLTF(fileName); - else if (fileName.EndsWith(".obj", StringComparison.OrdinalIgnoreCase)) - scene.SaveAsWavefront(fileName); - else - throw new ArgumentOutOfRangeException(nameof(fileName),$@"Unknown file format {fileName. SubstringAfterWithLast('.')}"); - - if (!CheckIfSaved(fileName)) return; - foreach (var materialExport in materialExports) - { - materialExport.TryWriteToDir(new DirectoryInfo(StringUtils.SubstringBeforeWithLast(fileName, '\\')), out _); - } - } - - public void CopySelectedMaterialName() - { - if (SelectedModel is not { } m || m.SelectedGeometry is null) - return; - - Clipboard.SetText(m.SelectedGeometry.DisplayName.TrimEnd()); - } - #endregion - - public bool TryOverwriteMaterial(UMaterialInstance materialInstance) - { - if (SelectedModel?.SelectedGeometry == null || _loadedModels.Count < 1) return false; - - (PBRMaterial m, _, _) = LoadMaterial(materialInstance); - - var obj = new ResolvedLoadedObject(materialInstance); - switch (_loadedModels[SelectedModel.SelectedGeometry.ExportIndex].Export) - { - case UStaticMesh { Materials: { } } st: - st.Materials[SelectedModel.SelectedGeometry.MaterialIndex] = obj; - break; - case USkeletalMesh sk: - sk.Materials[SelectedModel.SelectedGeometry.MaterialIndex].Material = obj; - break; - case UMaterialInstance: - SelectedModel.SwapExport(materialInstance); - break; - } - - SelectedModel.SelectedGeometry.Material = m; - return m != null; - } - - private void LoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam) - { - var builder = new MeshBuilder(); - builder.AddBox(Vector3.Zero, 10, 10, 10); - cam.TriangleCount = 12; // no need to count - - SetupCameraAndAxis(new FBox(new FVector(-8), new FVector(8)), cam); - var (m, isRendering, isTransparent) = LoadMaterial(materialInstance); - - cam.Group3d.Add(new CustomMeshGeometryModel3D - { - Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0,1,0), -45)), - DisplayName = materialInstance.Name, Geometry = builder.ToMeshGeometry3D(), MaterialIndex = 0, - Material = m, IsTransparent = isTransparent, IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 - }); - } - - private void LoadStaticMesh(UStaticMesh mesh, ModelAndCam cam) - { - if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) - { - return; - } - - SetupCameraAndAxis(convertedMesh.BoundingBox, cam); - foreach (var lod in convertedMesh.LODs) - { - if (lod.SkipLod) continue; - PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); - break; - } - } - - private void LoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam) - { - if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) - { - return; - } - - SetupCameraAndAxis(convertedMesh.BoundingBox, cam); - foreach (var lod in convertedMesh.LODs) - { - if (lod.SkipLod) continue; - PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); - break; - } - } - - private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, ModelAndCam cam) - { - for (int i = 0; i < sections.Length; i++) // each section is a mesh part with its own material - { - var section = sections[i]; - var builder = new MeshBuilder(); - cam.TriangleCount += section.NumFaces; // NumFaces * 3 (triangle) = next section FirstIndex - - for (var j = 0; j < section.NumFaces; j++) // draw a triangle for each face - { - foreach (var t in _facesIndex) // triangle face 1 then 0 then 2 - { - var id = section.FirstIndex + j * 3 + t; - var vert = verts[indices[id]]; - var p = new Vector3(vert.Position.X, vert.Position.Z, vert.Position.Y); // up direction is Y - var n = new Vector3(vert.Normal.X, vert.Normal.Z, vert.Normal.Y); - n.Normalize(); - - builder.AddNode(p, n, new Vector2(vert.UV.U, vert.UV.V)); - builder.TriangleIndices.Add(j * 3 + t); // one mesh part is "j * 3 + t" use "id" if you're building the full mesh - } - } - - if (section.Material == null || !section.Material.TryLoad(out var o) || o is not UMaterialInterface material) - { - cam.Group3d.Add(new CustomMeshGeometryModel3D - { - DisplayName = section.Material?.Name.ToString() ?? $"material_{section.MaterialIndex}", MaterialIndex = section.MaterialIndex, - Geometry = builder.ToMeshGeometry3D(), Material = new PBRMaterial(), IsTransparent = false, - IsRendering = true, ExportIndex = _loadedModels.Count - 1 - }); + Log.Information("Successfully saved {FileName}", savedFileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); } else { - var (m, isRendering, isTransparent) = LoadMaterial(material); - cam.Group3d.Add(new CustomMeshGeometryModel3D + Log.Error("{FileName} could not be saved", savedFileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true); + } + } + }); + } + + public void SaveAsScene() + { + if (_loadedModels.Count < 1) return; + + var fileBrowser = new VistaSaveFileDialog + { + Title = "Save Loaded Models As...", + DefaultExt = ".glb", + Filter = "glTF Binary File (*.glb)|*.glb|glTF ASCII File (*.gltf)|*.gltf|All Files(*.*)|*.*", + AddExtension = true, + OverwritePrompt = true, + CheckPathExists = true + }; + + if (fileBrowser.ShowDialog() == false || string.IsNullOrEmpty(fileBrowser.FileName)) return; + + var sceneBuilder = new SceneBuilder(); + var materialExports = new List(); + foreach (var model in _loadedModels) + { + switch (model.Export) + { + case UStaticMesh sm: + { + var mesh = new MeshBuilder(sm.Name); + if (sm.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) { - DisplayName = material.Name, MaterialIndex = section.MaterialIndex, - Geometry = builder.ToMeshGeometry3D(), Material = m, IsTransparent = isTransparent, - IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 - }); + var lod = convertedMesh.LODs.First(); + for (var i = 0; i < lod.Sections.Value.Length; i++) + { + Gltf.ExportStaticMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); + } + + sceneBuilder.AddRigidMesh(mesh, AffineTransform.Identity); + } + + break; + } + case USkeletalMesh sk: + { + var mesh = new MeshBuilder(sk.Name); + + if (sk.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0) + { + var lod = convertedMesh.LODs.First(); + for (var i = 0; i < lod.Sections.Value.Length; i++) + { + Gltf.ExportSkelMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh); + } + + var armatureNodeBuilder = new NodeBuilder(sk.Name + ".ao"); + var armature = Gltf.CreateGltfSkeleton(convertedMesh.RefSkeleton, armatureNodeBuilder); + sceneBuilder.AddSkinnedMesh(mesh, Matrix4x4.Identity, armature); + } + + break; } } } - private (PBRMaterial material, bool isRendering, bool isTransparent) LoadMaterial(UMaterialInterface unrealMaterial) + var scene = sceneBuilder.ToGltf2(); + var fileName = fileBrowser.FileName; + if (fileName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase)) + scene.SaveGLB(fileName); + else if (fileName.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase)) + scene.SaveGLTF(fileName); + else if (fileName.EndsWith(".obj", StringComparison.OrdinalIgnoreCase)) + scene.SaveAsWavefront(fileName); + else + throw new ArgumentOutOfRangeException(nameof(fileName), $@"Unknown file format {fileName.SubstringAfterWithLast('.')}"); + + if (!CheckIfSaved(fileName)) return; + foreach (var materialExport in materialExports) { - var m = new PBRMaterial {RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true}; - var parameters = new CMaterialParams(); - unrealMaterial.GetParams(parameters); + materialExport.TryWriteToDir(new DirectoryInfo(StringUtils.SubstringBeforeWithLast(fileName, '\\')), out _); + } + } - var isRendering = !parameters.IsNull; - if (isRendering) + public void CopySelectedMaterialName() + { + if (SelectedModel is not { } m || m.SelectedGeometry is null) + return; + + Clipboard.SetText(m.SelectedGeometry.DisplayName.TrimEnd()); + } + + #endregion + + public bool TryOverwriteMaterial(UMaterialInstance materialInstance) + { + if (SelectedModel?.SelectedGeometry == null || _loadedModels.Count < 1) return false; + + var (m, _, _) = LoadMaterial(materialInstance); + + var obj = new ResolvedLoadedObject(materialInstance); + switch (_loadedModels[SelectedModel.SelectedGeometry.ExportIndex].Export) + { + case UStaticMesh { Materials: { } } st: + st.Materials[SelectedModel.SelectedGeometry.MaterialIndex] = obj; + break; + case USkeletalMesh sk: + sk.Materials[SelectedModel.SelectedGeometry.MaterialIndex].Material = obj; + break; + case UMaterialInstance: + SelectedModel.SwapExport(materialInstance); + break; + } + + SelectedModel.SelectedGeometry.Material = m; + return m != null; + } + + private void LoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam) + { + var builder = new MeshBuilder(); + builder.AddBox(Vector3.Zero, 10, 10, 10); + cam.TriangleCount = 12; // no need to count + + SetupCameraAndAxis(new FBox(new FVector(-8), new FVector(8)), cam); + var (m, isRendering, isTransparent) = LoadMaterial(materialInstance); + + cam.Group3d.Add(new CustomMeshGeometryModel3D + { + Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), -45)), + DisplayName = materialInstance.Name, Geometry = builder.ToMeshGeometry3D(), MaterialIndex = 0, + Material = m, IsTransparent = isTransparent, IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 + }); + } + + private void LoadStaticMesh(UStaticMesh mesh, ModelAndCam cam) + { + if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) + { + return; + } + + SetupCameraAndAxis(convertedMesh.BoundingBox, cam); + foreach (var lod in convertedMesh.LODs.Where(lod => !lod.SkipLod)) + { + PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); + break; + } + } + + private void LoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam) + { + if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0) + { + return; + } + + SetupCameraAndAxis(convertedMesh.BoundingBox, cam); + foreach (var lod in convertedMesh.LODs.Where(lod => !lod.SkipLod)) + { + PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam); + break; + } + } + + private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, ModelAndCam cam) + { + for (var i = 0; i < sections.Length; i++) // each section is a mesh part with its own material + { + var section = sections[i]; + var builder = new MeshBuilder(); + cam.TriangleCount += section.NumFaces; // NumFaces * 3 (triangle) = next section FirstIndex + + for (var j = 0; j < section.NumFaces; j++) // draw a triangle for each face { - if (!parameters.HasTopDiffuseTexture && parameters.DiffuseColor is { A: > 0 } diffuseColor) + foreach (var t in _facesIndex) // triangle face 1 then 0 then 2 { - m.AlbedoColor = new Color4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A); + var id = section.FirstIndex + j * 3 + t; + var vert = verts[indices[id]]; + var p = new Vector3(vert.Position.X, vert.Position.Z, vert.Position.Y); // up direction is Y + var n = new Vector3(vert.Normal.X, vert.Normal.Z, vert.Normal.Y); + n.Normalize(); + + builder.AddNode(p, n, new Vector2(vert.UV.U, vert.UV.V)); + builder.TriangleIndices.Add(j * 3 + t); // one mesh part is "j * 3 + t" use "id" if you're building the full mesh } - else if (parameters.Diffuse is UTexture2D diffuse) + } + + if (section.Material == null || !section.Material.TryLoad(out var o) || o is not UMaterialInterface material) + { + cam.Group3d.Add(new CustomMeshGeometryModel3D { - m.AlbedoMap = new TextureModel(diffuse.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); - } - - if (parameters.Normal is UTexture2D normal) - { - m.NormalMap = new TextureModel(normal.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); - } - - if (parameters.Specular is UTexture2D specular) - { - var mip = specular.GetFirstMip(); - byte[] data; - SKColorType colorType; - switch (UserSettings.Default.OverridedPlatform) - { - case ETexturePlatform.Playstation: - PlaystationDecoder.DecodeTexturePlaystation(mip, specular.Format, specular.isNormalMap, - out data, out colorType); - break; - case ETexturePlatform.NintendoSwitch: - NintendoSwitchDecoder.DecodeTextureNSW(mip, specular.Format, specular.isNormalMap, - out data, out colorType); - break; - default: - TextureDecoder.DecodeTexture(mip, specular.Format, specular.isNormalMap, - out data, out colorType); - break; - } - - - switch (_game) - { - case FGame.FortniteGame: - { - // Fortnite's Specular Texture Channels - // R Specular - // G Metallic - // B Roughness - unsafe - { - var offset = 0; - fixed (byte* d = data) - { - for (var i = 0; i < mip.SizeX * mip.SizeY; i++) - { - d[offset] = 0; - (d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // swap G and B - offset += 4; - } - } - } - parameters.RoughnessValue = 1; - parameters.MetallicValue = 1; - break; - } - case FGame.ShooterGame: - { - var packedPBRType = specular.Name[(specular.Name.LastIndexOf('_') + 1)..]; - switch (packedPBRType) - { - case "MRAE": // R: Metallic, G: AO (0-127) & Emissive (128-255), B: Roughness (Character PBR) - unsafe - { - var offset = 0; - fixed (byte* d = data) - { - for (var i = 0; i < mip.SizeX * mip.SizeY; i++) - { - (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B - (d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // swap R and G - offset += 4; - } - } - } - break; - case "MRAS": // R: Metallic, B: Roughness, B: AO, A: Specular (Legacy PBR) - case "MRA": // R: Metallic, B: Roughness, B: AO (Environment PBR) - case "MRS": // R: Metallic, G: Roughness, B: Specular (Weapon PBR) - unsafe - { - var offset = 0; - fixed (byte* d = data) - { - for (var i = 0; i < mip.SizeX * mip.SizeY; i++) - { - (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B - offset += 4; - } - } - } - break; - } - parameters.RoughnessValue = 1; - parameters.MetallicValue = 1; - break; - } - case FGame.Gameface: - { - // GTA's Specular Texture Channels - // R Metallic - // G Roughness - // B Specular - unsafe - { - var offset = 0; - fixed (byte* d = data) - { - for (var i = 0; i < mip.SizeX * mip.SizeY; i++) - { - (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B - offset += 4; - } - } - } - break; - } - } - - using var bitmap = new SKBitmap(new SKImageInfo(mip.SizeX, mip.SizeY, colorType, SKAlphaType.Unpremul)); - unsafe - { - fixed (byte* p = data) - { - bitmap.SetPixels(new IntPtr(p)); - } - } - - // R -> AO G -> Roughness B -> Metallic - m.RoughnessMetallicMap = new TextureModel(bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream()); - m.RoughnessFactor = parameters.RoughnessValue; - m.MetallicFactor = parameters.MetallicValue; - m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0; - } - - if (parameters.HasTopEmissiveTexture && parameters.Emissive is UTexture2D emissive && parameters.EmissiveColor is { A: > 0 } emissiveColor) - { - m.EmissiveColor = new Color4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); - m.EmissiveMap = new TextureModel(emissive.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); - } + DisplayName = section.Material?.Name.ToString() ?? $"material_{section.MaterialIndex}", MaterialIndex = section.MaterialIndex, + Geometry = builder.ToMeshGeometry3D(), Material = new PBRMaterial(), IsTransparent = false, + IsRendering = true, ExportIndex = _loadedModels.Count - 1 + }); } else { - m.AlbedoColor = new Color4(1, 0, 0, 1); - } - - return (m, isRendering, parameters.IsTransparent); - } - - private void SetupCameraAndAxis(FBox box, ModelAndCam cam) - { - if (AppendMode && CanAppend) return; - var center = box.GetCenter(); - - var lineBuilder = new LineBuilder(); - lineBuilder.AddLine(new Vector3(box.Min.X, center.Z, center.Y), new Vector3(box.Max.X, center.Z, center.Y)); - cam.XAxis = lineBuilder.ToLineGeometry3D(); - lineBuilder = new LineBuilder(); - lineBuilder.AddLine(new Vector3(center.X, box.Min.Z, center.Y), new Vector3(center.X, box.Max.Z, center.Y)); - cam.YAxis = lineBuilder.ToLineGeometry3D(); - lineBuilder = new LineBuilder(); - lineBuilder.AddLine(new Vector3(center.X, center.Z, box.Min.Y), new Vector3(center.X, center.Z, box.Max.Y)); - cam.ZAxis = lineBuilder.ToLineGeometry3D(); - - cam.Position = new Point3D(box.Max.X + center.X * 2, center.Z, box.Min.Y + center.Y * 2); - cam.LookDirection = new Vector3D(-cam.Position.X + center.X, 0, -cam.Position.Z + center.Y); - } - - private bool CheckIfSaved(string path) - { - if (File.Exists(path)) - { - Log.Information("Successfully saved {FileName}", path); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved {path}", Constants.WHITE, true); - return true; - } - - Log.Error("{FileName} could not be saved", path); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{path}'", Constants.WHITE, true); - return false; - } - - public void Clear() - { - foreach (var g in _loadedModels.ToList()) - { - g.Dispose(); - _loadedModels.Remove(g); + var (m, isRendering, isTransparent) = LoadMaterial(material); + cam.Group3d.Add(new CustomMeshGeometryModel3D + { + DisplayName = material.Name, MaterialIndex = section.MaterialIndex, + Geometry = builder.ToMeshGeometry3D(), Material = m, IsTransparent = isTransparent, + IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1 + }); } } } - public class ModelAndCam : ViewModel + private (PBRMaterial material, bool isRendering, bool isTransparent) LoadMaterial(UMaterialInterface unrealMaterial) { - public UObject Export { get; private set; } - public Point3D Position { get; set; } - public Vector3D LookDirection { get; set; } - public Geometry3D XAxis { get; set; } - public Geometry3D YAxis { get; set; } - public Geometry3D ZAxis { get; set; } - public int TriangleCount { get; set; } + var m = new PBRMaterial { RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true }; + var parameters = new CMaterialParams(); + unrealMaterial.GetParams(parameters); - private bool _isVisible = true; - public bool IsVisible + var isRendering = !parameters.IsNull; + if (isRendering) { - get => _isVisible; - set => SetProperty(ref _isVisible, value); - } - - private bool _renderingToggle; - public bool RenderingToggle - { - get => _renderingToggle; - set + if (!parameters.HasTopDiffuseTexture && parameters.DiffuseColor is { A: > 0 } diffuseColor) { - SetProperty(ref _renderingToggle, value); - foreach (var g in Group3d) - { - if (g is not CustomMeshGeometryModel3D geometryModel) - continue; - - geometryModel.IsRendering = !geometryModel.IsRendering; - } + m.AlbedoColor = new Color4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A); } - } - - private bool _wireframeToggle; - public bool WireframeToggle - { - get => _wireframeToggle; - set + else if (parameters.Diffuse is UTexture2D diffuse) { - SetProperty(ref _wireframeToggle, value); - foreach (var g in Group3d) - { - if (g is not CustomMeshGeometryModel3D geometryModel) - continue; - - geometryModel.RenderWireframe = !geometryModel.RenderWireframe; - } + m.AlbedoMap = new TextureModel(diffuse.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); } - } - private bool _showMaterialColor; - public bool ShowMaterialColor - { - get => _showMaterialColor; - set + if (parameters.Normal is UTexture2D normal) { - SetProperty(ref _showMaterialColor, value); - for (int i = 0; i < Group3d.Count; i++) + m.NormalMap = new TextureModel(normal.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); + } + + if (parameters.Specular is UTexture2D specular) + { + var mip = specular.GetFirstMip(); + byte[] data; + SKColorType colorType; + switch (UserSettings.Default.OverridedPlatform) { - if (Group3d[i] is not CustomMeshGeometryModel3D { Material: PBRMaterial material } m) - continue; + case ETexturePlatform.Playstation: + PlaystationDecoder.DecodeTexturePlaystation(mip, specular.Format, specular.isNormalMap, + out data, out colorType); + break; + case ETexturePlatform.NintendoSwitch: + NintendoSwitchDecoder.DecodeTextureNSW(mip, specular.Format, specular.isNormalMap, + out data, out colorType); + break; + default: + TextureDecoder.DecodeTexture(mip, specular.Format, specular.isNormalMap, + out data, out colorType); + break; + } - var index = B(i); - material.RenderAlbedoMap = !_showMaterialColor; - if (_showMaterialColor) + switch (_game) + { + case FGame.FortniteGame: { - m.Tag = material.AlbedoColor; - material.AlbedoColor = new Color4(_table[C(index)] / 255, _table[C(index >> 1)] / 255, _table[C(index >> 2)] / 255, 1); + // Fortnite's Specular Texture Channels + // R Specular + // G Metallic + // B Roughness + unsafe + { + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + d[offset] = 0; + (d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // swap G and B + offset += 4; + } + } + } + + parameters.RoughnessValue = 1; + parameters.MetallicValue = 1; + break; } - else material.AlbedoColor = (Color4) m.Tag; - } - } - } + case FGame.ShooterGame: + { + var packedPBRType = specular.Name[(specular.Name.LastIndexOf('_') + 1)..]; + switch (packedPBRType) + { + case "MRAE": // R: Metallic, G: AO (0-127) & Emissive (128-255), B: Roughness (Character PBR) + unsafe + { + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B + (d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // swap R and G + offset += 4; + } + } + } - private bool _diffuseOnlyToggle; - public bool DiffuseOnlyToggle - { - get => _diffuseOnlyToggle; - set - { - SetProperty(ref _diffuseOnlyToggle, value); - foreach (var g in Group3d) + break; + case "MRAS": // R: Metallic, B: Roughness, B: AO, A: Specular (Legacy PBR) + case "MRA": // R: Metallic, B: Roughness, B: AO (Environment PBR) + case "MRS": // R: Metallic, G: Roughness, B: Specular (Weapon PBR) + unsafe + { + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B + offset += 4; + } + } + } + + break; + } + + parameters.RoughnessValue = 1; + parameters.MetallicValue = 1; + break; + } + case FGame.Gameface: + { + // GTA's Specular Texture Channels + // R Metallic + // G Roughness + // B Specular + unsafe + { + var offset = 0; + fixed (byte* d = data) + { + for (var i = 0; i < mip.SizeX * mip.SizeY; i++) + { + (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B + offset += 4; + } + } + } + + break; + } + } + + using var bitmap = new SKBitmap(new SKImageInfo(mip.SizeX, mip.SizeY, colorType, SKAlphaType.Unpremul)); + unsafe { - if (g is not CustomMeshGeometryModel3D { Material: PBRMaterial material }) - continue; - - material.RenderAmbientOcclusionMap = !material.RenderAmbientOcclusionMap; - material.RenderDisplacementMap = !material.RenderDisplacementMap; - // material.RenderEmissiveMap = !material.RenderEmissiveMap; - // material.RenderEnvironmentMap = !material.RenderEnvironmentMap; - material.RenderIrradianceMap = !material.RenderIrradianceMap; - material.RenderRoughnessMetallicMap = !material.RenderRoughnessMetallicMap; - material.RenderShadowMap = !material.RenderShadowMap; - material.RenderNormalMap = !material.RenderNormalMap; + fixed (byte* p = data) + { + bitmap.SetPixels(new IntPtr(p)); + } } + + // R -> AO G -> Roughness B -> Metallic + m.RoughnessMetallicMap = new TextureModel(bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream()); + m.RoughnessFactor = parameters.RoughnessValue; + m.MetallicFactor = parameters.MetallicValue; + m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0; } - } - private CustomMeshGeometryModel3D _selectedGeometry; // selected material - public CustomMeshGeometryModel3D SelectedGeometry - { - get => _selectedGeometry; - set => SetProperty(ref _selectedGeometry, value); - } - - private ObservableElement3DCollection _group3d; // material list - public ObservableElement3DCollection Group3d - { - get => _group3d; - set => SetProperty(ref _group3d, value); - } - - private readonly float[] _table = { 255 * 0.9f, 25 * 3.0f, 255 * 0.6f, 255 * 0.0f }; - private readonly int[] _table2 = { 0, 1, 2, 4, 7, 3, 5, 6 }; - - public ModelAndCam(UObject export) - { - Export = export; - TriangleCount = 0; - Group3d = new ObservableElement3DCollection(); - } - - private int B(int x) => (x & 0xFFF8) | _table2[x & 7] ^ 7; - private int C(int x) => (x & 1) | ((x >> 2) & 2); - - public void SwapExport(UObject e) - { - Export = e; - } - - public void Dispose() - { - TriangleCount = 0; - SelectedGeometry = null; - foreach (var g in Group3d.ToList()) + if (parameters.HasTopEmissiveTexture && parameters.Emissive is UTexture2D emissive && parameters.EmissiveColor is { A: > 0 } emissiveColor) { - g.Dispose(); - Group3d.Remove(g); + m.EmissiveColor = new Color4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); + m.EmissiveMap = new TextureModel(emissive.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream()); } } + else + { + m.AlbedoColor = new Color4(1, 0, 0, 1); + } + + return (m, isRendering, parameters.IsTransparent); } - public class CustomMeshGeometryModel3D : MeshGeometryModel3D + private void SetupCameraAndAxis(FBox box, ModelAndCam cam) { - public string DisplayName { get; set; } - public int MaterialIndex { get; set; } - public int ExportIndex { get; set; } + if (AppendMode && CanAppend) return; + var center = box.GetCenter(); + + var lineBuilder = new LineBuilder(); + lineBuilder.AddLine(new Vector3(box.Min.X, center.Z, center.Y), new Vector3(box.Max.X, center.Z, center.Y)); + cam.XAxis = lineBuilder.ToLineGeometry3D(); + lineBuilder = new LineBuilder(); + lineBuilder.AddLine(new Vector3(center.X, box.Min.Z, center.Y), new Vector3(center.X, box.Max.Z, center.Y)); + cam.YAxis = lineBuilder.ToLineGeometry3D(); + lineBuilder = new LineBuilder(); + lineBuilder.AddLine(new Vector3(center.X, center.Z, box.Min.Y), new Vector3(center.X, center.Z, box.Max.Y)); + cam.ZAxis = lineBuilder.ToLineGeometry3D(); + + cam.Position = new Point3D(box.Max.X + center.X * 2, center.Z, box.Min.Y + center.Y * 2); + cam.LookDirection = new Vector3D(-cam.Position.X + center.X, 0, -cam.Position.Z + center.Y); + } + + private bool CheckIfSaved(string path) + { + if (File.Exists(path)) + { + Log.Information("Successfully saved {FileName}", path); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved {path}", Constants.WHITE, true); + return true; + } + + Log.Error("{FileName} could not be saved", path); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{path}'", Constants.WHITE, true); + return false; + } + + public void Clear() + { + foreach (var g in _loadedModels.ToList()) + { + g.Dispose(); + _loadedModels.Remove(g); + } } } + +public class ModelAndCam : ViewModel +{ + public UObject Export { get; private set; } + public Point3D Position { get; set; } + public Vector3D LookDirection { get; set; } + public Geometry3D XAxis { get; set; } + public Geometry3D YAxis { get; set; } + public Geometry3D ZAxis { get; set; } + public int TriangleCount { get; set; } + + private bool _isVisible = true; + public bool IsVisible + { + get => _isVisible; + set => SetProperty(ref _isVisible, value); + } + + private bool _renderingToggle; + public bool RenderingToggle + { + get => _renderingToggle; + set + { + SetProperty(ref _renderingToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D geometryModel) + continue; + + geometryModel.IsRendering = !geometryModel.IsRendering; + } + } + } + + private bool _wireframeToggle; + public bool WireframeToggle + { + get => _wireframeToggle; + set + { + SetProperty(ref _wireframeToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D geometryModel) + continue; + + geometryModel.RenderWireframe = !geometryModel.RenderWireframe; + } + } + } + + private bool _showMaterialColor; + public bool ShowMaterialColor + { + get => _showMaterialColor; + set + { + SetProperty(ref _showMaterialColor, value); + for (var i = 0; i < Group3d.Count; i++) + { + if (Group3d[i] is not CustomMeshGeometryModel3D { Material: PBRMaterial material } m) + continue; + + var index = B(i); + material.RenderAlbedoMap = !_showMaterialColor; + + if (_showMaterialColor) + { + m.Tag = material.AlbedoColor; + material.AlbedoColor = new Color4(_table[C(index)] / 255, _table[C(index >> 1)] / 255, _table[C(index >> 2)] / 255, 1); + } + else material.AlbedoColor = (Color4) m.Tag; + } + } + } + + private bool _diffuseOnlyToggle; + public bool DiffuseOnlyToggle + { + get => _diffuseOnlyToggle; + set + { + SetProperty(ref _diffuseOnlyToggle, value); + foreach (var g in Group3d) + { + if (g is not CustomMeshGeometryModel3D { Material: PBRMaterial material }) + continue; + + material.RenderAmbientOcclusionMap = !material.RenderAmbientOcclusionMap; + material.RenderDisplacementMap = !material.RenderDisplacementMap; + // material.RenderEmissiveMap = !material.RenderEmissiveMap; + // material.RenderEnvironmentMap = !material.RenderEnvironmentMap; + material.RenderIrradianceMap = !material.RenderIrradianceMap; + material.RenderRoughnessMetallicMap = !material.RenderRoughnessMetallicMap; + material.RenderShadowMap = !material.RenderShadowMap; + material.RenderNormalMap = !material.RenderNormalMap; + } + } + } + + private CustomMeshGeometryModel3D _selectedGeometry; // selected material + public CustomMeshGeometryModel3D SelectedGeometry + { + get => _selectedGeometry; + set => SetProperty(ref _selectedGeometry, value); + } + + private ObservableElement3DCollection _group3d; // material list + public ObservableElement3DCollection Group3d + { + get => _group3d; + set => SetProperty(ref _group3d, value); + } + + private readonly float[] _table = { 255 * 0.9f, 25 * 3.0f, 255 * 0.6f, 255 * 0.0f }; + private readonly int[] _table2 = { 0, 1, 2, 4, 7, 3, 5, 6 }; + + public ModelAndCam(UObject export) + { + Export = export; + TriangleCount = 0; + Group3d = new ObservableElement3DCollection(); + } + + private int B(int x) => (x & 0xFFF8) | _table2[x & 7] ^ 7; + private int C(int x) => (x & 1) | ((x >> 2) & 2); + + public void SwapExport(UObject e) + { + Export = e; + } + + public void Dispose() + { + TriangleCount = 0; + SelectedGeometry = null; + foreach (var g in Group3d.ToList()) + { + g.Dispose(); + Group3d.Remove(g); + } + } +} + +public class CustomMeshGeometryModel3D : MeshGeometryModel3D +{ + public string DisplayName { get; set; } + public int MaterialIndex { get; set; } + public int ExportIndex { get; set; } +} \ No newline at end of file diff --git a/FModel/ViewModels/SearchViewModel.cs b/FModel/ViewModels/SearchViewModel.cs index 2ee03729..301a6067 100644 --- a/FModel/ViewModels/SearchViewModel.cs +++ b/FModel/ViewModels/SearchViewModel.cs @@ -6,61 +6,59 @@ using System.Text.RegularExpressions; using System.Windows.Data; using FModel.Framework; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class SearchViewModel : ViewModel { - public class SearchViewModel : ViewModel + private string _filterText; + public string FilterText { - private string _filterText; - public string FilterText - { - get => _filterText; - set => SetProperty(ref _filterText, value); - } + get => _filterText; + set => SetProperty(ref _filterText, value); + } - private bool _hasRegexEnabled; - public bool HasRegexEnabled - { - get => _hasRegexEnabled; - set => SetProperty(ref _hasRegexEnabled, value); - } + private bool _hasRegexEnabled; + public bool HasRegexEnabled + { + get => _hasRegexEnabled; + set => SetProperty(ref _hasRegexEnabled, value); + } - private bool _hasMatchCaseEnabled; - public bool HasMatchCaseEnabled - { - get => _hasMatchCaseEnabled; - set => SetProperty(ref _hasMatchCaseEnabled, value); - } + private bool _hasMatchCaseEnabled; + public bool HasMatchCaseEnabled + { + get => _hasMatchCaseEnabled; + set => SetProperty(ref _hasMatchCaseEnabled, value); + } - public int ResultsCount => SearchResults?.Count ?? 0; - public RangeObservableCollection SearchResults { get; } - public ICollectionView SearchResultsView { get; } + public int ResultsCount => SearchResults?.Count ?? 0; + public RangeObservableCollection SearchResults { get; } + public ICollectionView SearchResultsView { get; } - public SearchViewModel() - { - SearchResults = new RangeObservableCollection(); - SearchResultsView = new ListCollectionView(SearchResults); - } + public SearchViewModel() + { + SearchResults = new RangeObservableCollection(); + SearchResultsView = new ListCollectionView(SearchResults); + } - public void RefreshFilter() - { - if (SearchResultsView.Filter == null) - SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' ')); - else - SearchResultsView.Refresh(); - } + public void RefreshFilter() + { + if (SearchResultsView.Filter == null) + SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' ')); + else + SearchResultsView.Refresh(); + } - private bool ItemFilter(object item, IEnumerable filters) - { - if (item is not AssetItem assetItem) - return true; + private bool ItemFilter(object item, IEnumerable filters) + { + if (item is not AssetItem assetItem) + return true; - if (!HasRegexEnabled) - return filters.All(x => assetItem.FullPath.IndexOf(x, - HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0); + if (!HasRegexEnabled) + return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); - var o = RegexOptions.None; - if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase; - return new Regex(FilterText, o).Match(assetItem.FullPath).Success; - } + var o = RegexOptions.None; + if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase; + return new Regex(FilterText, o).Match(assetItem.FullPath).Success; } } \ No newline at end of file diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index 85fcca67..50a55c1a 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -14,323 +14,324 @@ using FModel.Services; using FModel.Settings; using FModel.ViewModels.ApiEndpoints.Models; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class SettingsViewModel : ViewModel { - public class SettingsViewModel : ViewModel + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler; + + private EUpdateMode _selectedUpdateMode; + public EUpdateMode SelectedUpdateMode { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; - private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler; - - private EUpdateMode _selectedUpdateMode; - public EUpdateMode SelectedUpdateMode - { - get => _selectedUpdateMode; - set => SetProperty(ref _selectedUpdateMode, value); - } - - private string _selectedPreset; - public string SelectedPreset - { - get => _selectedPreset; - set - { - SetProperty(ref _selectedPreset, value); - RaisePropertyChanged("EnableElements"); - } - } - - private ETexturePlatform _selectedUePlatform; - public ETexturePlatform SelectedUePlatform - { - get => _selectedUePlatform; - set => SetProperty(ref _selectedUePlatform, value); - } - - private EGame _selectedUeGame; - public EGame SelectedUeGame - { - get => _selectedUeGame; - set => SetProperty(ref _selectedUeGame, value); - } - - private List _selectedCustomVersions; - public List SelectedCustomVersions - { - get => _selectedCustomVersions; - set => SetProperty(ref _selectedCustomVersions, value); - } - - private Dictionary _selectedOptions; - public Dictionary SelectedOptions - { - get => _selectedOptions; - set => SetProperty(ref _selectedOptions, value); - } - - private ELanguage _selectedAssetLanguage; - public ELanguage SelectedAssetLanguage - { - get => _selectedAssetLanguage; - set => SetProperty(ref _selectedAssetLanguage, value); - } - - private EAesReload _selectedAesReload; - public EAesReload SelectedAesReload - { - get => _selectedAesReload; - set => SetProperty(ref _selectedAesReload, value); - } - - private EDiscordRpc _selectedDiscordRpc; - public EDiscordRpc SelectedDiscordRpc - { - get => _selectedDiscordRpc; - set => SetProperty(ref _selectedDiscordRpc, value); - } - - private ECompressedAudio _selectedCompressedAudio; - public ECompressedAudio SelectedCompressedAudio - { - get => _selectedCompressedAudio; - set => SetProperty(ref _selectedCompressedAudio, value); - } - - private EIconStyle _selectedCosmeticStyle; - public EIconStyle SelectedCosmeticStyle - { - get => _selectedCosmeticStyle; - set => SetProperty(ref _selectedCosmeticStyle, value); - } - - private EMeshFormat _selectedMeshExportFormat; - public EMeshFormat SelectedMeshExportFormat - { - get => _selectedMeshExportFormat; - set => SetProperty(ref _selectedMeshExportFormat, value); - } - - private ELodFormat _selectedLodExportFormat; - public ELodFormat SelectedLodExportFormat - { - get => _selectedLodExportFormat; - set => SetProperty(ref _selectedLodExportFormat, value); - } - - private ETextureFormat _selectedTextureExportFormat; - public ETextureFormat SelectedTextureExportFormat - { - get => _selectedTextureExportFormat; - set => SetProperty(ref _selectedTextureExportFormat, value); - } - - public ReadOnlyObservableCollection UpdateModes { get; private set; } - public ObservableCollection Presets { get; private set; } - public ReadOnlyObservableCollection UeGames { get; private set; } - public ReadOnlyObservableCollection AssetLanguages { get; private set; } - public ReadOnlyObservableCollection AesReloads { get; private set; } - public ReadOnlyObservableCollection DiscordRpcs { get; private set; } - public ReadOnlyObservableCollection CompressedAudios { get; private set; } - public ReadOnlyObservableCollection CosmeticStyles { get; private set; } - public ReadOnlyObservableCollection MeshExportFormats { get; private set; } - public ReadOnlyObservableCollection LodExportFormats { get; private set; } - public ReadOnlyObservableCollection TextureExportFormats { get; private set; } - public ReadOnlyObservableCollection Platforms { get; private set; } - - public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER; - - private readonly FGame _game; - private Game _gamePreset; - private string _outputSnapshot; - private string _rawDataSnapshot; - private string _propertiesSnapshot; - private string _textureSnapshot; - private string _audioSnapshot; - private string _modelSnapshot; - private string _gameSnapshot; - private EUpdateMode _updateModeSnapshot; - private string _presetSnapshot; - private ETexturePlatform _uePlatformSnapshot; - private EGame _ueGameSnapshot; - private List _customVersionsSnapshot; - private Dictionary _optionsSnapshot; - private ELanguage _assetLanguageSnapshot; - private ECompressedAudio _compressedAudioSnapshot; - private EIconStyle _cosmeticStyleSnapshot; - private EMeshFormat _meshExportFormatSnapshot; - private ELodFormat _lodExportFormatSnapshot; - private ETextureFormat _textureExportFormatSnapshot; - - public SettingsViewModel(FGame game) - { - _game = game; - } - - public void Initialize() - { - _outputSnapshot = UserSettings.Default.OutputDirectory; - _rawDataSnapshot = UserSettings.Default.RawDataDirectory; - _propertiesSnapshot = UserSettings.Default.PropertiesDirectory; - _textureSnapshot = UserSettings.Default.TextureDirectory; - _audioSnapshot = UserSettings.Default.AudioDirectory; - _modelSnapshot = UserSettings.Default.ModelDirectory; - _gameSnapshot = UserSettings.Default.GameDirectory; - _updateModeSnapshot = UserSettings.Default.UpdateMode; - _presetSnapshot = UserSettings.Default.Presets[_game]; - _uePlatformSnapshot = UserSettings.Default.OverridedPlatform; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings)) - { - _ueGameSnapshot = settings.OverridedGame; - _customVersionsSnapshot = settings.OverridedCustomVersions; - _optionsSnapshot = settings.OverridedOptions; - } - else - { - _ueGameSnapshot = UserSettings.Default.OverridedGame[_game]; - _customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game]; - _optionsSnapshot = UserSettings.Default.OverridedOptions[_game]; - } - _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; - _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; - _cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle; - _meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat; - _lodExportFormatSnapshot = UserSettings.Default.LodExportFormat; - _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; - - SelectedUpdateMode = _updateModeSnapshot; - SelectedPreset = _presetSnapshot; - SelectedUePlatform = _uePlatformSnapshot; - SelectedUeGame = _ueGameSnapshot; - SelectedCustomVersions = _customVersionsSnapshot; - SelectedOptions = _optionsSnapshot; - SelectedAssetLanguage = _assetLanguageSnapshot; - SelectedCompressedAudio = _compressedAudioSnapshot; - SelectedCosmeticStyle = _cosmeticStyleSnapshot; - SelectedMeshExportFormat = _meshExportFormatSnapshot; - SelectedLodExportFormat = _lodExportFormatSnapshot; - SelectedTextureExportFormat = _textureExportFormatSnapshot; - SelectedAesReload = UserSettings.Default.AesReload; - SelectedDiscordRpc = UserSettings.Default.DiscordRpc; - - UpdateModes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUpdateModes())); - Presets = new ObservableCollection(EnumeratePresets()); - UeGames = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeGames())); - AssetLanguages = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAssetLanguages())); - AesReloads = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAesReloads())); - DiscordRpcs = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateDiscordRpcs())); - CompressedAudios = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCompressedAudios())); - CosmeticStyles = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCosmeticStyles())); - MeshExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateMeshExportFormat())); - LodExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLodExportFormat())); - TextureExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateTextureExportFormat())); - Platforms = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUePlatforms())); - } - - public async Task InitPresets(string gameName) - { - await _threadWorkerView.Begin(cancellationToken => - { - if (string.IsNullOrEmpty(gameName)) return; - _gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName); - }); - - if (_gamePreset?.Versions == null) return; - foreach (var version in _gamePreset.Versions.Keys) - { - Presets.Add(version); - } - } - - public void SwitchPreset(string key) - { - if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return; - SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST); - - SelectedCustomVersions = new List(); - foreach (var (guid, v) in version.CustomVersions) - { - SelectedCustomVersions.Add(new FCustomVersion {Key = new FGuid(guid), Version = v}); - } - - SelectedOptions = new Dictionary(); - foreach (var (k, v) in version.Options) - { - SelectedOptions[k] = v; - } - } - - public void ResetPreset() - { - SelectedUeGame = _ueGameSnapshot; - SelectedCustomVersions = _customVersionsSnapshot; - SelectedOptions = _optionsSnapshot; - } - - public SettingsOut Save() - { - var ret = SettingsOut.Nothing; - - if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions || - _uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox - _outputSnapshot != UserSettings.Default.OutputDirectory || // textbox - _rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox - _propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox - _textureSnapshot != UserSettings.Default.TextureDirectory || // textbox - _audioSnapshot != UserSettings.Default.AudioDirectory || // textbox - _modelSnapshot != UserSettings.Default.ModelDirectory || // textbox - _gameSnapshot != UserSettings.Default.GameDirectory) // textbox - ret = SettingsOut.Restart; - - if (_assetLanguageSnapshot != SelectedAssetLanguage) - ret = SettingsOut.ReloadLocres; - - if (_updateModeSnapshot != SelectedUpdateMode) - ret = SettingsOut.CheckForUpdates; - - UserSettings.Default.UpdateMode = SelectedUpdateMode; - UserSettings.Default.Presets[_game] = SelectedPreset; - UserSettings.Default.OverridedPlatform = SelectedUePlatform; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) - { - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame; - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions; - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions; - } - else - { - UserSettings.Default.OverridedGame[_game] = SelectedUeGame; - UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions; - UserSettings.Default.OverridedOptions[_game] = SelectedOptions; - } - UserSettings.Default.AssetLanguage = SelectedAssetLanguage; - UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; - UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle; - UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat; - UserSettings.Default.LodExportFormat = SelectedLodExportFormat; - UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat; - UserSettings.Default.AesReload = SelectedAesReload; - UserSettings.Default.DiscordRpc = SelectedDiscordRpc; - - if (SelectedDiscordRpc == EDiscordRpc.Never) - _discordHandler.Shutdown(); - - return ret; - } - - private IEnumerable EnumerateUpdateModes() => Enum.GetValues(); - private IEnumerable EnumeratePresets() - { - yield return Constants._NO_PRESET_TRIGGER; - } - private IEnumerable EnumerateUeGames() => Enum.GetValues(); - private IEnumerable EnumerateAssetLanguages() => Enum.GetValues(); - private IEnumerable EnumerateAesReloads() => Enum.GetValues(); - private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(); - private IEnumerable EnumerateCompressedAudios() => Enum.GetValues(); - private IEnumerable EnumerateCosmeticStyles() => Enum.GetValues(); - private IEnumerable EnumerateMeshExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateLodExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateTextureExportFormat() => Enum.GetValues(); - private IEnumerable EnumerateUePlatforms() => Enum.GetValues(); + get => _selectedUpdateMode; + set => SetProperty(ref _selectedUpdateMode, value); } -} + + private string _selectedPreset; + public string SelectedPreset + { + get => _selectedPreset; + set + { + SetProperty(ref _selectedPreset, value); + RaisePropertyChanged("EnableElements"); + } + } + + private ETexturePlatform _selectedUePlatform; + public ETexturePlatform SelectedUePlatform + { + get => _selectedUePlatform; + set => SetProperty(ref _selectedUePlatform, value); + } + + private EGame _selectedUeGame; + public EGame SelectedUeGame + { + get => _selectedUeGame; + set => SetProperty(ref _selectedUeGame, value); + } + + private List _selectedCustomVersions; + public List SelectedCustomVersions + { + get => _selectedCustomVersions; + set => SetProperty(ref _selectedCustomVersions, value); + } + + private Dictionary _selectedOptions; + public Dictionary SelectedOptions + { + get => _selectedOptions; + set => SetProperty(ref _selectedOptions, value); + } + + private ELanguage _selectedAssetLanguage; + public ELanguage SelectedAssetLanguage + { + get => _selectedAssetLanguage; + set => SetProperty(ref _selectedAssetLanguage, value); + } + + private EAesReload _selectedAesReload; + public EAesReload SelectedAesReload + { + get => _selectedAesReload; + set => SetProperty(ref _selectedAesReload, value); + } + + private EDiscordRpc _selectedDiscordRpc; + public EDiscordRpc SelectedDiscordRpc + { + get => _selectedDiscordRpc; + set => SetProperty(ref _selectedDiscordRpc, value); + } + + private ECompressedAudio _selectedCompressedAudio; + public ECompressedAudio SelectedCompressedAudio + { + get => _selectedCompressedAudio; + set => SetProperty(ref _selectedCompressedAudio, value); + } + + private EIconStyle _selectedCosmeticStyle; + public EIconStyle SelectedCosmeticStyle + { + get => _selectedCosmeticStyle; + set => SetProperty(ref _selectedCosmeticStyle, value); + } + + private EMeshFormat _selectedMeshExportFormat; + public EMeshFormat SelectedMeshExportFormat + { + get => _selectedMeshExportFormat; + set => SetProperty(ref _selectedMeshExportFormat, value); + } + + private ELodFormat _selectedLodExportFormat; + public ELodFormat SelectedLodExportFormat + { + get => _selectedLodExportFormat; + set => SetProperty(ref _selectedLodExportFormat, value); + } + + private ETextureFormat _selectedTextureExportFormat; + public ETextureFormat SelectedTextureExportFormat + { + get => _selectedTextureExportFormat; + set => SetProperty(ref _selectedTextureExportFormat, value); + } + + public ReadOnlyObservableCollection UpdateModes { get; private set; } + public ObservableCollection Presets { get; private set; } + public ReadOnlyObservableCollection UeGames { get; private set; } + public ReadOnlyObservableCollection AssetLanguages { get; private set; } + public ReadOnlyObservableCollection AesReloads { get; private set; } + public ReadOnlyObservableCollection DiscordRpcs { get; private set; } + public ReadOnlyObservableCollection CompressedAudios { get; private set; } + public ReadOnlyObservableCollection CosmeticStyles { get; private set; } + public ReadOnlyObservableCollection MeshExportFormats { get; private set; } + public ReadOnlyObservableCollection LodExportFormats { get; private set; } + public ReadOnlyObservableCollection TextureExportFormats { get; private set; } + public ReadOnlyObservableCollection Platforms { get; private set; } + + public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER; + + private readonly FGame _game; + private Game _gamePreset; + private string _outputSnapshot; + private string _rawDataSnapshot; + private string _propertiesSnapshot; + private string _textureSnapshot; + private string _audioSnapshot; + private string _modelSnapshot; + private string _gameSnapshot; + private EUpdateMode _updateModeSnapshot; + private string _presetSnapshot; + private ETexturePlatform _uePlatformSnapshot; + private EGame _ueGameSnapshot; + private List _customVersionsSnapshot; + private Dictionary _optionsSnapshot; + private ELanguage _assetLanguageSnapshot; + private ECompressedAudio _compressedAudioSnapshot; + private EIconStyle _cosmeticStyleSnapshot; + private EMeshFormat _meshExportFormatSnapshot; + private ELodFormat _lodExportFormatSnapshot; + private ETextureFormat _textureExportFormatSnapshot; + + public SettingsViewModel(FGame game) + { + _game = game; + } + + public void Initialize() + { + _outputSnapshot = UserSettings.Default.OutputDirectory; + _rawDataSnapshot = UserSettings.Default.RawDataDirectory; + _propertiesSnapshot = UserSettings.Default.PropertiesDirectory; + _textureSnapshot = UserSettings.Default.TextureDirectory; + _audioSnapshot = UserSettings.Default.AudioDirectory; + _modelSnapshot = UserSettings.Default.ModelDirectory; + _gameSnapshot = UserSettings.Default.GameDirectory; + _updateModeSnapshot = UserSettings.Default.UpdateMode; + _presetSnapshot = UserSettings.Default.Presets[_game]; + _uePlatformSnapshot = UserSettings.Default.OverridedPlatform; + if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings)) + { + _ueGameSnapshot = settings.OverridedGame; + _customVersionsSnapshot = settings.OverridedCustomVersions; + _optionsSnapshot = settings.OverridedOptions; + } + else + { + _ueGameSnapshot = UserSettings.Default.OverridedGame[_game]; + _customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game]; + _optionsSnapshot = UserSettings.Default.OverridedOptions[_game]; + } + + _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; + _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; + _cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle; + _meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat; + _lodExportFormatSnapshot = UserSettings.Default.LodExportFormat; + _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; + + SelectedUpdateMode = _updateModeSnapshot; + SelectedPreset = _presetSnapshot; + SelectedUePlatform = _uePlatformSnapshot; + SelectedUeGame = _ueGameSnapshot; + SelectedCustomVersions = _customVersionsSnapshot; + SelectedOptions = _optionsSnapshot; + SelectedAssetLanguage = _assetLanguageSnapshot; + SelectedCompressedAudio = _compressedAudioSnapshot; + SelectedCosmeticStyle = _cosmeticStyleSnapshot; + SelectedMeshExportFormat = _meshExportFormatSnapshot; + SelectedLodExportFormat = _lodExportFormatSnapshot; + SelectedTextureExportFormat = _textureExportFormatSnapshot; + SelectedAesReload = UserSettings.Default.AesReload; + SelectedDiscordRpc = UserSettings.Default.DiscordRpc; + + UpdateModes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUpdateModes())); + Presets = new ObservableCollection(EnumeratePresets()); + UeGames = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeGames())); + AssetLanguages = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAssetLanguages())); + AesReloads = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAesReloads())); + DiscordRpcs = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateDiscordRpcs())); + CompressedAudios = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCompressedAudios())); + CosmeticStyles = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateCosmeticStyles())); + MeshExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateMeshExportFormat())); + LodExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateLodExportFormat())); + TextureExportFormats = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateTextureExportFormat())); + Platforms = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUePlatforms())); + } + + public async Task InitPresets(string gameName) + { + await _threadWorkerView.Begin(cancellationToken => + { + if (string.IsNullOrEmpty(gameName)) return; + _gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName); + }); + + if (_gamePreset?.Versions == null) return; + foreach (var version in _gamePreset.Versions.Keys) + { + Presets.Add(version); + } + } + + public void SwitchPreset(string key) + { + if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return; + SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST); + + SelectedCustomVersions = new List(); + foreach (var (guid, v) in version.CustomVersions) + { + SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v }); + } + + SelectedOptions = new Dictionary(); + foreach (var (k, v) in version.Options) + { + SelectedOptions[k] = v; + } + } + + public void ResetPreset() + { + SelectedUeGame = _ueGameSnapshot; + SelectedCustomVersions = _customVersionsSnapshot; + SelectedOptions = _optionsSnapshot; + } + + public SettingsOut Save() + { + var ret = SettingsOut.Nothing; + + if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions || + _uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox + _outputSnapshot != UserSettings.Default.OutputDirectory || // textbox + _rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox + _propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox + _textureSnapshot != UserSettings.Default.TextureDirectory || // textbox + _audioSnapshot != UserSettings.Default.AudioDirectory || // textbox + _modelSnapshot != UserSettings.Default.ModelDirectory || // textbox + _gameSnapshot != UserSettings.Default.GameDirectory) // textbox + ret = SettingsOut.Restart; + + if (_assetLanguageSnapshot != SelectedAssetLanguage) + ret = SettingsOut.ReloadLocres; + + if (_updateModeSnapshot != SelectedUpdateMode) + ret = SettingsOut.CheckForUpdates; + + UserSettings.Default.UpdateMode = SelectedUpdateMode; + UserSettings.Default.Presets[_game] = SelectedPreset; + UserSettings.Default.OverridedPlatform = SelectedUePlatform; + if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) + { + UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame; + UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions; + UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions; + } + else + { + UserSettings.Default.OverridedGame[_game] = SelectedUeGame; + UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions; + UserSettings.Default.OverridedOptions[_game] = SelectedOptions; + } + + UserSettings.Default.AssetLanguage = SelectedAssetLanguage; + UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; + UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle; + UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat; + UserSettings.Default.LodExportFormat = SelectedLodExportFormat; + UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat; + UserSettings.Default.AesReload = SelectedAesReload; + UserSettings.Default.DiscordRpc = SelectedDiscordRpc; + + if (SelectedDiscordRpc == EDiscordRpc.Never) + _discordHandler.Shutdown(); + + return ret; + } + + private IEnumerable EnumerateUpdateModes() => Enum.GetValues(); + private IEnumerable EnumeratePresets() + { + yield return Constants._NO_PRESET_TRIGGER; + } + private IEnumerable EnumerateUeGames() => Enum.GetValues(); + private IEnumerable EnumerateAssetLanguages() => Enum.GetValues(); + private IEnumerable EnumerateAesReloads() => Enum.GetValues(); + private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(); + private IEnumerable EnumerateCompressedAudios() => Enum.GetValues(); + private IEnumerable EnumerateCosmeticStyles() => Enum.GetValues(); + private IEnumerable EnumerateMeshExportFormat() => Enum.GetValues(); + private IEnumerable EnumerateLodExportFormat() => Enum.GetValues(); + private IEnumerable EnumerateTextureExportFormat() => Enum.GetValues(); + private IEnumerable EnumerateUePlatforms() => Enum.GetValues(); +} \ No newline at end of file diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 759dd610..3faf233e 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -18,430 +18,437 @@ using System.Windows.Media.Imaging; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse_Conversion.Textures; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class TabImage : ViewModel { - public class TabImage : ViewModel + public string ExportName { get; } + public byte[] ImageBuffer { get; set; } + + public TabImage(string name, bool rnn, SKBitmap img) { - public string ExportName { get; } - public byte[] ImageBuffer { get; set; } - - public TabImage(string name, bool rnn, SKBitmap img) - { - ExportName = name; - RenderNearestNeighbor = rnn; - SetImage(img); - } - - private BitmapImage _image; - public BitmapImage Image - { - get => _image; - set - { - if (_image == value) return; - SetProperty(ref _image, value); - } - } - - private bool _renderNearestNeighbor; - public bool RenderNearestNeighbor - { - get => _renderNearestNeighbor; - set => SetProperty(ref _renderNearestNeighbor, value); - } - - private bool _noAlpha; - public bool NoAlpha - { - get => _noAlpha; - set - { - SetProperty(ref _noAlpha, value); - ResetImage(); - } - } - - private void SetImage(SKBitmap bitmap) - { - _bmp = bitmap; - - using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); - using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); - Image = image; - } - - private SKBitmap _bmp; - private void ResetImage() => SetImage(_bmp); + ExportName = name; + RenderNearestNeighbor = rnn; + SetImage(img); } - public class TabItem : ViewModel + private BitmapImage _image; + public BitmapImage Image { - private string _header; - public string Header + get => _image; + set { - get => _header; - set => SetProperty(ref _header, value); - } - - private string _directory; - public string Directory - { - get => _directory; - set => SetProperty(ref _directory, value); - } - - private bool _hasSearchOpen; - public bool HasSearchOpen - { - get => _hasSearchOpen; - set => SetProperty(ref _hasSearchOpen, value); - } - - private string _textToFind; - public string TextToFind - { - get => _textToFind; - set => SetProperty(ref _textToFind, value); - } - - private bool _searchUp; - public bool SearchUp - { - get => _searchUp; - set => SetProperty(ref _searchUp, value); - } - - private bool _caseSensitive; - public bool CaseSensitive - { - get => _caseSensitive; - set => SetProperty(ref _caseSensitive, value); - } - - private bool _useRegEx; - public bool UseRegEx - { - get => _useRegEx; - set => SetProperty(ref _useRegEx, value); - } - - private bool _wholeWord; - public bool WholeWord - { - get => _wholeWord; - set => SetProperty(ref _wholeWord, value); - } - - private TextDocument _document; - public TextDocument Document - { - get => _document; - set => SetProperty(ref _document, value); - } - - private double _fontSize = 11.0; - public double FontSize - { - get => _fontSize; - set => SetProperty(ref _fontSize, value); - } - - private double _scrollPosition; - public double ScrollPosition - { - get => _scrollPosition; - set => SetProperty(ref _scrollPosition, value); - } - - private string _scrollTrigger; - public string ScrollTrigger - { - get => _scrollTrigger; - set => SetProperty(ref _scrollTrigger, value); - } - - private IHighlightingDefinition _highlighter; - public IHighlightingDefinition Highlighter - { - get => _highlighter; - set - { - if (_highlighter == value) return; - SetProperty(ref _highlighter, value); - } - } - - private TabImage _selectedImage; - public TabImage SelectedImage - { - get => _selectedImage; - set - { - if (_selectedImage == value) return; - SetProperty(ref _selectedImage, value); - RaisePropertyChanged("HasImage"); - RaisePropertyChanged("Page"); - } - } - - public bool HasImage => SelectedImage != null; - public bool HasMultipleImages => _images.Count > 1; - public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; - - private readonly ObservableCollection _images; - - public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger); - - private TabCommand _tabCommand; - public TabCommand TabCommand => _tabCommand ??= new TabCommand(this); - private ImageCommand _imageCommand; - public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this); - private GoToCommand _goToCommand; - public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null); - - public TabItem(string header, string directory) - { - Header = header; - Directory = directory; - _images = new ObservableCollection(); - } - - public void ClearImages() - { - Application.Current.Dispatcher.Invoke(() => - { - _images.Clear(); - SelectedImage = null; - RaisePropertyChanged("HasMultipleImages"); - }); - } - - public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform)); - public void AddImage(string name, bool rnn, SKBitmap[] img) - { - foreach (var i in img) AddImage(name, rnn, i); - } - public void AddImage(string name, bool rnn, SKBitmap img) - { - Application.Current.Dispatcher.Invoke(() => - { - var t = new TabImage(name, rnn, img); - if (UserSettings.Default.IsAutoSaveTextures) - SaveImage(t, true); - - _images.Add(t); - SelectedImage ??= t; - RaisePropertyChanged("Page"); - RaisePropertyChanged("HasMultipleImages"); - }); - } - - public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage); - public void GoNextImage() => SelectedImage = _images.Next(SelectedImage); - - public void SetDocumentText(string text, bool bulkSave) - { - Application.Current.Dispatcher.Invoke(() => - { - Document ??= new TextDocument(); - Document.Text = text; - - if (UserSettings.Default.IsAutoSaveProps || bulkSave) - SaveProperty(true); - }); - } - public void ResetDocumentText() - { - Application.Current.Dispatcher.Invoke(() => - { - Document ??= new TextDocument(); - Document.Text = string.Empty; - }); - } - - public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave); - private void SaveImage(TabImage image, bool autoSave) - { - if (image == null) return; - var fileName = $"{image.ExportName}.png"; - var directory = Path.Combine(UserSettings.Default.TextureDirectory, - UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/'); - - if (!autoSave) - { - var saveFileDialog = new SaveFileDialog - { - Title = "Save Texture", - FileName = fileName, - InitialDirectory = UserSettings.Default.TextureDirectory, - Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" - }; - var result = saveFileDialog.ShowDialog(); - if (!result.HasValue || !result.Value) return; - directory = saveFileDialog.FileName; - } - else - { - System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); - } - - using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length); - } - - SaveCheck(directory, fileName); - } - - public void SaveProperty(bool autoSave) - { - var fileName = Path.ChangeExtension(Header, ".json"); - var directory = Path.Combine(UserSettings.Default.PropertiesDirectory, - UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/'); - - if (!autoSave) - { - var saveFileDialog = new SaveFileDialog - { - Title = "Save Property", - FileName = fileName, - InitialDirectory = UserSettings.Default.PropertiesDirectory, - Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*" - }; - var result = saveFileDialog.ShowDialog(); - if (!result.HasValue || !result.Value) return; - directory = saveFileDialog.FileName; - } - else - { - System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); - } - - Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text)); - SaveCheck(directory, fileName); - } - - private void SaveCheck(string path, string fileName) - { - if (File.Exists(path)) - { - Log.Information("{FileName} successfully saved", fileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true); - } - else - { - Log.Error("{FileName} could not be saved", fileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true); - } + if (_image == value) return; + SetProperty(ref _image, value); } } - public class TabControlViewModel : ViewModel + private bool _renderNearestNeighbor; + public bool RenderNearestNeighbor { - private TabItem _selectedTab; - public TabItem SelectedTab + get => _renderNearestNeighbor; + set => SetProperty(ref _renderNearestNeighbor, value); + } + + private bool _noAlpha; + public bool NoAlpha + { + get => _noAlpha; + set { - get => _selectedTab; - set => SetProperty(ref _selectedTab, value); + SetProperty(ref _noAlpha, value); + ResetImage(); } + } - private AddTabCommand _addTabCommand; - public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this); + private void SetImage(SKBitmap bitmap) + { + _bmp = bitmap; + using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100); + using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false); + var image = new BitmapImage(); + image.BeginInit(); + image.CacheOption = BitmapCacheOption.OnLoad; + image.StreamSource = stream; + image.EndInit(); + image.Freeze(); + Image = image; + } - private readonly ObservableCollection _tabItems; - public ReadOnlyObservableCollection TabsItems { get; } + private SKBitmap _bmp; + private void ResetImage() => SetImage(_bmp); +} - public bool HasNoTabs => _tabItems.Count == 0; - public bool CanAddTabs => _tabItems.Count < 25; +public class TabItem : ViewModel +{ + private string _header; + public string Header + { + get => _header; + set => SetProperty(ref _header, value); + } - public TabControlViewModel() + private string _directory; + public string Directory + { + get => _directory; + set => SetProperty(ref _directory, value); + } + + private bool _hasSearchOpen; + public bool HasSearchOpen + { + get => _hasSearchOpen; + set => SetProperty(ref _hasSearchOpen, value); + } + + private string _textToFind; + public string TextToFind + { + get => _textToFind; + set => SetProperty(ref _textToFind, value); + } + + private bool _searchUp; + public bool SearchUp + { + get => _searchUp; + set => SetProperty(ref _searchUp, value); + } + + private bool _caseSensitive; + public bool CaseSensitive + { + get => _caseSensitive; + set => SetProperty(ref _caseSensitive, value); + } + + private bool _useRegEx; + public bool UseRegEx + { + get => _useRegEx; + set => SetProperty(ref _useRegEx, value); + } + + private bool _wholeWord; + public bool WholeWord + { + get => _wholeWord; + set => SetProperty(ref _wholeWord, value); + } + + private TextDocument _document; + public TextDocument Document + { + get => _document; + set => SetProperty(ref _document, value); + } + + private double _fontSize = 11.0; + public double FontSize + { + get => _fontSize; + set => SetProperty(ref _fontSize, value); + } + + private double _scrollPosition; + public double ScrollPosition + { + get => _scrollPosition; + set => SetProperty(ref _scrollPosition, value); + } + + private string _scrollTrigger; + public string ScrollTrigger + { + get => _scrollTrigger; + set => SetProperty(ref _scrollTrigger, value); + } + + private IHighlightingDefinition _highlighter; + public IHighlightingDefinition Highlighter + { + get => _highlighter; + set { - _tabItems = new ObservableCollection(EnumerateTabs()); - TabsItems = new ReadOnlyObservableCollection(_tabItems); - SelectedTab = TabsItems.FirstOrDefault(); + if (_highlighter == value) return; + SetProperty(ref _highlighter, value); } + } - public void AddTab(string header = null, string directory = null) + private TabImage _selectedImage; + public TabImage SelectedImage + { + get => _selectedImage; + set { - if (!CanAddTabs) return; + if (_selectedImage == value) return; + SetProperty(ref _selectedImage, value); + RaisePropertyChanged("HasImage"); + RaisePropertyChanged("Page"); + } + } - var h = header ?? "New Tab"; - var d = directory ?? string.Empty; - if (SelectedTab is { Header : "New Tab" }) + public bool HasImage => SelectedImage != null; + public bool HasMultipleImages => _images.Count > 1; + public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}"; + + private readonly ObservableCollection _images; + + public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger); + + private TabCommand _tabCommand; + public TabCommand TabCommand => _tabCommand ??= new TabCommand(this); + private ImageCommand _imageCommand; + public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this); + private GoToCommand _goToCommand; + public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null); + + public TabItem(string header, string directory) + { + Header = header; + Directory = directory; + _images = new ObservableCollection(); + } + + public void ClearImages() + { + Application.Current.Dispatcher.Invoke(() => + { + _images.Clear(); + SelectedImage = null; + RaisePropertyChanged("HasMultipleImages"); + }); + } + + public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform)); + + public void AddImage(string name, bool rnn, SKBitmap[] img) + { + foreach (var i in img) AddImage(name, rnn, i); + } + + public void AddImage(string name, bool rnn, SKBitmap img) + { + Application.Current.Dispatcher.Invoke(() => + { + var t = new TabImage(name, rnn, img); + if (UserSettings.Default.IsAutoSaveTextures) + SaveImage(t, true); + + _images.Add(t); + SelectedImage ??= t; + RaisePropertyChanged("Page"); + RaisePropertyChanged("HasMultipleImages"); + }); + } + + public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage); + public void GoNextImage() => SelectedImage = _images.Next(SelectedImage); + + public void SetDocumentText(string text, bool bulkSave) + { + Application.Current.Dispatcher.Invoke(() => + { + Document ??= new TextDocument(); + Document.Text = text; + + if (UserSettings.Default.IsAutoSaveProps || bulkSave) + SaveProperty(true); + }); + } + + public void ResetDocumentText() + { + Application.Current.Dispatcher.Invoke(() => + { + Document ??= new TextDocument(); + Document.Text = string.Empty; + }); + } + + public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave); + + private void SaveImage(TabImage image, bool autoSave) + { + if (image == null) return; + var fileName = $"{image.ExportName}.png"; + var directory = Path.Combine(UserSettings.Default.TextureDirectory, + UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/'); + + if (!autoSave) + { + var saveFileDialog = new SaveFileDialog { - SelectedTab.Header = h; - SelectedTab.Directory = d; - return; - } + Title = "Save Texture", + FileName = fileName, + InitialDirectory = UserSettings.Default.TextureDirectory, + Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" + }; + var result = saveFileDialog.ShowDialog(); + if (!result.HasValue || !result.Value) return; + directory = saveFileDialog.FileName; + } + else + { + System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); + } - Application.Current.Dispatcher.Invoke(() => + using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length); + } + + SaveCheck(directory, fileName); + } + + public void SaveProperty(bool autoSave) + { + var fileName = Path.ChangeExtension(Header, ".json"); + var directory = Path.Combine(UserSettings.Default.PropertiesDirectory, + UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/'); + + if (!autoSave) + { + var saveFileDialog = new SaveFileDialog { - _tabItems.Add(new TabItem(h, d)); - SelectedTab = _tabItems.Last(); - }); + Title = "Save Property", + FileName = fileName, + InitialDirectory = UserSettings.Default.PropertiesDirectory, + Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*" + }; + var result = saveFileDialog.ShowDialog(); + if (!result.HasValue || !result.Value) return; + directory = saveFileDialog.FileName; + } + else + { + System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/')); } - public void RemoveTab(TabItem tab = null) - { - Application.Current.Dispatcher.Invoke(() => - { - var tabCount = _tabItems.Count; - var tabToDelete = tab ?? SelectedTab; - switch (tabCount) - { - case <= 0: - return; - // select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete - case > 1: - SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0 - break; - } + Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text)); + SaveCheck(directory, fileName); + } - _tabItems.Remove(tabToDelete); - OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete)); - }); + private void SaveCheck(string path, string fileName) + { + if (File.Exists(path)) + { + Log.Information("{FileName} successfully saved", fileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true); } - - public class TabEventArgs : EventArgs + else { - public TabItem TabToRemove { get; set; } - public TabEventArgs(TabItem tab) { TabToRemove = tab; } - } - public event EventHandler OnTabRemove; - public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab); - public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab); - - public void RemoveOtherTabs(TabItem tab) - { - Application.Current.Dispatcher.Invoke(() => - { - foreach (var t in _tabItems.Where(t => t != tab).ToList()) - { - _tabItems.Remove(t); - } - }); - } - - public void RemoveAllTabs() - { - Application.Current.Dispatcher.Invoke(() => - { - SelectedTab = null; - _tabItems.Clear(); - }); - } - - private static IEnumerable EnumerateTabs() - { - yield return new TabItem("New Tab", string.Empty); + Log.Error("{FileName} could not be saved", fileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true); } } } + +public class TabControlViewModel : ViewModel +{ + private TabItem _selectedTab; + public TabItem SelectedTab + { + get => _selectedTab; + set => SetProperty(ref _selectedTab, value); + } + + private AddTabCommand _addTabCommand; + public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this); + + private readonly ObservableCollection _tabItems; + public ReadOnlyObservableCollection TabsItems { get; } + + public bool HasNoTabs => _tabItems.Count == 0; + public bool CanAddTabs => _tabItems.Count < 25; + + public TabControlViewModel() + { + _tabItems = new ObservableCollection(EnumerateTabs()); + TabsItems = new ReadOnlyObservableCollection(_tabItems); + SelectedTab = TabsItems.FirstOrDefault(); + } + + public void AddTab(string header = null, string directory = null) + { + if (!CanAddTabs) return; + + var h = header ?? "New Tab"; + var d = directory ?? string.Empty; + if (SelectedTab is { Header : "New Tab" }) + { + SelectedTab.Header = h; + SelectedTab.Directory = d; + return; + } + + Application.Current.Dispatcher.Invoke(() => + { + _tabItems.Add(new TabItem(h, d)); + SelectedTab = _tabItems.Last(); + }); + } + + public void RemoveTab(TabItem tab = null) + { + Application.Current.Dispatcher.Invoke(() => + { + var tabCount = _tabItems.Count; + var tabToDelete = tab ?? SelectedTab; + switch (tabCount) + { + case <= 0: + return; + // select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete + case > 1: + SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0 + break; + } + + _tabItems.Remove(tabToDelete); + OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete)); + }); + } + + public class TabEventArgs : EventArgs + { + public TabItem TabToRemove { get; set; } + + public TabEventArgs(TabItem tab) + { + TabToRemove = tab; + } + } + + public event EventHandler OnTabRemove; + public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab); + public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab); + + public void RemoveOtherTabs(TabItem tab) + { + Application.Current.Dispatcher.Invoke(() => + { + foreach (var t in _tabItems.Where(t => t != tab).ToList()) + { + _tabItems.Remove(t); + } + }); + } + + public void RemoveAllTabs() + { + Application.Current.Dispatcher.Invoke(() => + { + SelectedTab = null; + _tabItems.Clear(); + }); + } + + private static IEnumerable EnumerateTabs() + { + yield return new TabItem("New Tab", string.Empty); + } +} \ No newline at end of file diff --git a/FModel/ViewModels/ThreadWorkerViewModel.cs b/FModel/ViewModels/ThreadWorkerViewModel.cs index f4a236e5..05f9cfb0 100644 --- a/FModel/ViewModels/ThreadWorkerViewModel.cs +++ b/FModel/ViewModels/ThreadWorkerViewModel.cs @@ -7,113 +7,112 @@ using FModel.Services; using FModel.Views.Resources.Controls; using Serilog; -namespace FModel.ViewModels +namespace FModel.ViewModels; + +public class ThreadWorkerViewModel : ViewModel { - public class ThreadWorkerViewModel : ViewModel + private bool _statusChangeAttempted; + public bool StatusChangeAttempted { - private bool _statusChangeAttempted; - public bool StatusChangeAttempted + get => _statusChangeAttempted; + private set => SetProperty(ref _statusChangeAttempted, value); + } + + private bool _operationCancelled; + public bool OperationCancelled + { + get => _operationCancelled; + private set => SetProperty(ref _operationCancelled, value); + } + + private CancellationTokenSource _currentCancellationTokenSource; + public CancellationTokenSource CurrentCancellationTokenSource + { + get => _currentCancellationTokenSource; + set { - get => _statusChangeAttempted; - private set => SetProperty(ref _statusChangeAttempted, value); - } - - private bool _operationCancelled; - public bool OperationCancelled - { - get => _operationCancelled; - private set => SetProperty(ref _operationCancelled, value); - } - - private CancellationTokenSource _currentCancellationTokenSource; - public CancellationTokenSource CurrentCancellationTokenSource - { - get => _currentCancellationTokenSource; - set - { - if (_currentCancellationTokenSource == value) return; - SetProperty(ref _currentCancellationTokenSource, value); - RaisePropertyChanged("CanBeCanceled"); - } - } - - public bool CanBeCanceled => CurrentCancellationTokenSource != null; - - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - private readonly AsyncQueue> _jobs; - - public ThreadWorkerViewModel() - { - _jobs = new AsyncQueue>(); - } - - public async Task Begin(Action action) - { - if (!_applicationView.IsReady) - { - SignalOperationInProgress(); - return; - } - - CurrentCancellationTokenSource ??= new CancellationTokenSource(); - _jobs.Enqueue(action); - await ProcessQueues(); - } - - public void Cancel() - { - if (!CanBeCanceled) - { - SignalOperationInProgress(); - return; - } - - CurrentCancellationTokenSource.Cancel(); - } - - private async Task ProcessQueues() - { - if (_jobs.Count > 0) - { - _applicationView.Status = EStatusKind.Loading; - await foreach (var job in _jobs) - { - try - { - // will end in "catch" if canceled - await Task.Run(() => job(CurrentCancellationTokenSource.Token)); - } - catch (OperationCanceledException) - { - _applicationView.Status = EStatusKind.Stopped; - CurrentCancellationTokenSource = null; // kill token - OperationCancelled = true; - OperationCancelled = false; - return; - } - catch (Exception e) - { - _applicationView.Status = EStatusKind.Failed; - CurrentCancellationTokenSource = null; // kill token - - Log.Error("{Exception}", e); - - FLogger.AppendError(); - FLogger.AppendText(e.Message, Constants.WHITE, true); - FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true); - return; - } - } - - _applicationView.Status = EStatusKind.Completed; - CurrentCancellationTokenSource = null; // kill token - } - } - - public void SignalOperationInProgress() - { - StatusChangeAttempted = true; - StatusChangeAttempted = false; + if (_currentCancellationTokenSource == value) return; + SetProperty(ref _currentCancellationTokenSource, value); + RaisePropertyChanged("CanBeCanceled"); } } -} + + public bool CanBeCanceled => CurrentCancellationTokenSource != null; + + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private readonly AsyncQueue> _jobs; + + public ThreadWorkerViewModel() + { + _jobs = new AsyncQueue>(); + } + + public async Task Begin(Action action) + { + if (!_applicationView.IsReady) + { + SignalOperationInProgress(); + return; + } + + CurrentCancellationTokenSource ??= new CancellationTokenSource(); + _jobs.Enqueue(action); + await ProcessQueues(); + } + + public void Cancel() + { + if (!CanBeCanceled) + { + SignalOperationInProgress(); + return; + } + + CurrentCancellationTokenSource.Cancel(); + } + + private async Task ProcessQueues() + { + if (_jobs.Count > 0) + { + _applicationView.Status = EStatusKind.Loading; + await foreach (var job in _jobs) + { + try + { + // will end in "catch" if canceled + await Task.Run(() => job(CurrentCancellationTokenSource.Token)); + } + catch (OperationCanceledException) + { + _applicationView.Status = EStatusKind.Stopped; + CurrentCancellationTokenSource = null; // kill token + OperationCancelled = true; + OperationCancelled = false; + return; + } + catch (Exception e) + { + _applicationView.Status = EStatusKind.Failed; + CurrentCancellationTokenSource = null; // kill token + + Log.Error("{Exception}", e); + + FLogger.AppendError(); + FLogger.AppendText(e.Message, Constants.WHITE, true); + FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true); + return; + } + } + + _applicationView.Status = EStatusKind.Completed; + CurrentCancellationTokenSource = null; // kill token + } + } + + public void SignalOperationInProgress() + { + StatusChangeAttempted = true; + StatusChangeAttempted = false; + } +} \ No newline at end of file diff --git a/FModel/Views/About.xaml.cs b/FModel/Views/About.xaml.cs index 6b365d284..a3e31484 100644 --- a/FModel/Views/About.xaml.cs +++ b/FModel/Views/About.xaml.cs @@ -1,10 +1,9 @@ -namespace FModel.Views +namespace FModel.Views; + +public partial class About { - public partial class About + public About() { - public About() - { - InitializeComponent(); - } + InitializeComponent(); } } \ No newline at end of file diff --git a/FModel/Views/AesManager.xaml.cs b/FModel/Views/AesManager.xaml.cs index 8f2b77d0..7abe0a41 100644 --- a/FModel/Views/AesManager.xaml.cs +++ b/FModel/Views/AesManager.xaml.cs @@ -3,33 +3,32 @@ using System.Windows; using FModel.Services; using FModel.ViewModels; -namespace FModel.Views +namespace FModel.Views; + +public partial class AesManager { - public partial class AesManager + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public AesManager() { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + DataContext = _applicationView; + InitializeComponent(); + } - public AesManager() - { - DataContext = _applicationView; - InitializeComponent(); - } + private void OnClick(object sender, RoutedEventArgs e) + { + Close(); + } - private void OnClick(object sender, RoutedEventArgs e) - { - Close(); - } + private async void OnRefreshAes(object sender, RoutedEventArgs e) + { + await _applicationView.CUE4Parse.RefreshAes(); + await _applicationView.AesManager.InitAes(); + _applicationView.AesManager.HasChange = true; // yes even if nothing actually changed + } - private async void OnRefreshAes(object sender, RoutedEventArgs e) - { - await _applicationView.CUE4Parse.RefreshAes(); - await _applicationView.AesManager.InitAes(); - _applicationView.AesManager.HasChange = true; // yes even if nothing actually changed - } - - private async void OnClosing(object sender, CancelEventArgs e) - { - await _applicationView.AesManager.UpdateProvider(false); - } + private async void OnClosing(object sender, CancelEventArgs e) + { + await _applicationView.AesManager.UpdateProvider(false); } } \ No newline at end of file diff --git a/FModel/Views/AudioPlayer.xaml.cs b/FModel/Views/AudioPlayer.xaml.cs index c510408d..56487a1f 100644 --- a/FModel/Views/AudioPlayer.xaml.cs +++ b/FModel/Views/AudioPlayer.xaml.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel; -using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -11,88 +10,84 @@ using FModel.Settings; using FModel.ViewModels; using Microsoft.Win32; -namespace FModel.Views +namespace FModel.Views; + +public partial class AudioPlayer { - public partial class AudioPlayer + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public AudioPlayer() { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - - public AudioPlayer() - { - DataContext = _applicationView; - InitializeComponent(); - } - - public void Load(byte[] data, string filePath) - { - _applicationView.AudioPlayer.AddToPlaylist(data, filePath); - } - - private void OnClosing(object sender, CancelEventArgs e) - { - _applicationView.AudioPlayer.Stop(); - _applicationView.AudioPlayer.Dispose(); - DiscordService.DiscordHandler.UpdateToSavedPresence(); - } - - private void OnDeviceSwap(object sender, SelectionChangedEventArgs e) - { - if (sender is not ComboBox {SelectedItem: MMDevice selectedDevice}) - return; - - UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID; - _applicationView.AudioPlayer.Device(); - } - - private void OnVolumeChange(object sender, RoutedEventArgs e) - { - _applicationView.AudioPlayer.Volume(); - } - - private void OnPreviewKeyDown(object sender, KeyEventArgs e) - { - if (e.OriginalSource is TextBox) - return; - - if (UserSettings.Default.AddAudio.IsTriggered(e.Key)) - { - var openFileDialog = new OpenFileDialog - { - Title = "Select an audio file", - InitialDirectory = UserSettings.Default.AudioDirectory, - Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*", - Multiselect = true - }; - - if (!(bool) openFileDialog.ShowDialog()) return; - foreach (var file in openFileDialog.FileNames) - { - _applicationView.AudioPlayer.AddToPlaylist(file); - } - } - else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key)) - _applicationView.AudioPlayer.PlayPauseOnStart(); - else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key)) - _applicationView.AudioPlayer.Previous(); - else if (UserSettings.Default.NextAudio.IsTriggered(e.Key)) - _applicationView.AudioPlayer.Next(); - } - - private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e) - { - _applicationView.AudioPlayer.PlayPauseOnForce(); - } - - private void OnFilterTextChanged(object sender, TextChangedEventArgs e) - { - if (sender is not TextBox textBox) - return; - - var filters = textBox.Text.Trim().Split(' '); - _applicationView.AudioPlayer.AudioFilesView.Filter = o => - { - return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); - }; - } + DataContext = _applicationView; + InitializeComponent(); } -} + + public void Load(byte[] data, string filePath) + { + _applicationView.AudioPlayer.AddToPlaylist(data, filePath); + } + + private void OnClosing(object sender, CancelEventArgs e) + { + _applicationView.AudioPlayer.Stop(); + _applicationView.AudioPlayer.Dispose(); + DiscordService.DiscordHandler.UpdateToSavedPresence(); + } + + private void OnDeviceSwap(object sender, SelectionChangedEventArgs e) + { + if (sender is not ComboBox { SelectedItem: MMDevice selectedDevice }) + return; + + UserSettings.Default.AudioDeviceId = selectedDevice.DeviceID; + _applicationView.AudioPlayer.Device(); + } + + private void OnVolumeChange(object sender, RoutedEventArgs e) + { + _applicationView.AudioPlayer.Volume(); + } + + private void OnPreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.OriginalSource is TextBox) + return; + + if (UserSettings.Default.AddAudio.IsTriggered(e.Key)) + { + var openFileDialog = new OpenFileDialog + { + Title = "Select an audio file", + InitialDirectory = UserSettings.Default.AudioDirectory, + Filter = "OGG Files (*.ogg)|*.ogg|WAV Files (*.wav)|*.wav|WEM Files (*.wem)|*.wem|ADPCM Files (*.adpcm)|*.adpcm|All Files (*.*)|*.*", + Multiselect = true + }; + + if (!openFileDialog.ShowDialog().GetValueOrDefault()) return; + foreach (var file in openFileDialog.FileNames) + { + _applicationView.AudioPlayer.AddToPlaylist(file); + } + } + else if (UserSettings.Default.PlayPauseAudio.IsTriggered(e.Key)) + _applicationView.AudioPlayer.PlayPauseOnStart(); + else if (UserSettings.Default.PreviousAudio.IsTriggered(e.Key)) + _applicationView.AudioPlayer.Previous(); + else if (UserSettings.Default.NextAudio.IsTriggered(e.Key)) + _applicationView.AudioPlayer.Next(); + } + + private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + _applicationView.AudioPlayer.PlayPauseOnForce(); + } + + private void OnFilterTextChanged(object sender, TextChangedEventArgs e) + { + if (sender is not TextBox textBox) + return; + + var filters = textBox.Text.Trim().Split(' '); + _applicationView.AudioPlayer.AudioFilesView.Filter = o => { return o is AudioFile audio && filters.All(x => audio.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); }; + } +} \ No newline at end of file diff --git a/FModel/Views/BackupManager.xaml.cs b/FModel/Views/BackupManager.xaml.cs index f7442e9b..59b07e6b 100644 --- a/FModel/Views/BackupManager.xaml.cs +++ b/FModel/Views/BackupManager.xaml.cs @@ -1,31 +1,30 @@ using System.Windows; using FModel.ViewModels; -namespace FModel.Views +namespace FModel.Views; + +public partial class BackupManager { - public partial class BackupManager + private readonly BackupManagerViewModel _viewModel; + + public BackupManager(string gameName) { - private readonly BackupManagerViewModel _viewModel; + DataContext = _viewModel = new BackupManagerViewModel(gameName); + InitializeComponent(); + } - public BackupManager(string gameName) - { - DataContext = _viewModel = new BackupManagerViewModel(gameName); - InitializeComponent(); - } + private async void OnLoaded(object sender, RoutedEventArgs e) + { + await _viewModel.Initialize(); + } - private async void OnLoaded(object sender, RoutedEventArgs e) - { - await _viewModel.Initialize(); - } + private async void OnDownloadClick(object sender, RoutedEventArgs e) + { + await _viewModel.Download(); + } - private async void OnDownloadClick(object sender, RoutedEventArgs e) - { - await _viewModel.Download(); - } - - private async void OnCreateBackupClick(object sender, RoutedEventArgs e) - { - await _viewModel.CreateBackup(); - } + private async void OnCreateBackupClick(object sender, RoutedEventArgs e) + { + await _viewModel.CreateBackup(); } } \ No newline at end of file diff --git a/FModel/Views/CustomDir.xaml.cs b/FModel/Views/CustomDir.xaml.cs index d27ee2fe..b5e87f18 100644 --- a/FModel/Views/CustomDir.xaml.cs +++ b/FModel/Views/CustomDir.xaml.cs @@ -1,24 +1,23 @@ using System.Windows; using FModel.ViewModels; -namespace FModel.Views -{ - public partial class CustomDir - { - public CustomDir(CustomDirectory customDir) - { - DataContext = customDir; - InitializeComponent(); - - Activate(); - WpfSuckMyDick.Focus(); - WpfSuckMyDick.SelectAll(); - } +namespace FModel.Views; - private void OnClick(object sender, RoutedEventArgs e) - { - DialogResult = true; - Close(); - } +public partial class CustomDir +{ + public CustomDir(CustomDirectory customDir) + { + DataContext = customDir; + InitializeComponent(); + + Activate(); + WpfSuckMyDick.Focus(); + WpfSuckMyDick.SelectAll(); + } + + private void OnClick(object sender, RoutedEventArgs e) + { + DialogResult = true; + Close(); } } \ No newline at end of file diff --git a/FModel/Views/DirectorySelector.xaml.cs b/FModel/Views/DirectorySelector.xaml.cs index 4103d16e..26815ed8 100644 --- a/FModel/Views/DirectorySelector.xaml.cs +++ b/FModel/Views/DirectorySelector.xaml.cs @@ -2,64 +2,63 @@ using Ookii.Dialogs.Wpf; using System.Windows; -namespace FModel.Views +namespace FModel.Views; + +/// +/// Logique d'interaction pour DirectorySelector.xaml +/// +public partial class DirectorySelector { - /// - /// Logique d'interaction pour DirectorySelector.xaml - /// - public partial class DirectorySelector + public DirectorySelector(GameSelectorViewModel gameSelectorViewModel) { - public DirectorySelector(GameSelectorViewModel gameSelectorViewModel) + DataContext = gameSelectorViewModel; + InitializeComponent(); + } + + private void OnClick(object sender, RoutedEventArgs e) + { + DialogResult = true; + Close(); + } + + private void OnBrowseDirectories(object sender, RoutedEventArgs e) + { + if (DataContext is not GameSelectorViewModel gameLauncherViewModel) + return; + + var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; + if (folderBrowser.ShowDialog() == true) { - DataContext = gameSelectorViewModel; - InitializeComponent(); - } - - private void OnClick(object sender, RoutedEventArgs e) - { - DialogResult = true; - Close(); - } - - private void OnBrowseDirectories(object sender, RoutedEventArgs e) - { - if (DataContext is not GameSelectorViewModel gameLauncherViewModel) - return; - - var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; - if (folderBrowser.ShowDialog() == true) - { - gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath); - } - } - - private void OnBrowseManualDirectories(object sender, RoutedEventArgs e) - { - var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; - if (folderBrowser.ShowDialog() == true) - { - HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath; - } - } - - private void OnAddDirectory(object sender, RoutedEventArgs e) - { - if (DataContext is not GameSelectorViewModel gameLauncherViewModel|| - string.IsNullOrEmpty(HelloMyNameIsGame.Text) || - string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text)) - return; - - gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text); - HelloMyNameIsGame.Clear(); - HelloGameMyNameIsDirectory.Clear(); - } - - private void OnDeleteDirectory(object sender, RoutedEventArgs e) - { - if (DataContext is not GameSelectorViewModel gameLauncherViewModel) - return; - - gameLauncherViewModel.DeleteSelectedGame(); + gameLauncherViewModel.AddUnknownGame(folderBrowser.SelectedPath); } } -} + + private void OnBrowseManualDirectories(object sender, RoutedEventArgs e) + { + var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; + if (folderBrowser.ShowDialog() == true) + { + HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath; + } + } + + private void OnAddDirectory(object sender, RoutedEventArgs e) + { + if (DataContext is not GameSelectorViewModel gameLauncherViewModel|| + string.IsNullOrEmpty(HelloMyNameIsGame.Text) || + string.IsNullOrEmpty(HelloGameMyNameIsDirectory.Text)) + return; + + gameLauncherViewModel.AddUnknownGame(HelloMyNameIsGame.Text, HelloGameMyNameIsDirectory.Text); + HelloMyNameIsGame.Clear(); + HelloGameMyNameIsDirectory.Clear(); + } + + private void OnDeleteDirectory(object sender, RoutedEventArgs e) + { + if (DataContext is not GameSelectorViewModel gameLauncherViewModel) + return; + + gameLauncherViewModel.DeleteSelectedGame(); + } +} \ No newline at end of file diff --git a/FModel/Views/ImageMerger.xaml.cs b/FModel/Views/ImageMerger.xaml.cs index ffc2d6ef..7bc5def0 100644 --- a/FModel/Views/ImageMerger.xaml.cs +++ b/FModel/Views/ImageMerger.xaml.cs @@ -1,15 +1,10 @@ using AdonisUI.Controls; - using FModel.Extensions; using FModel.Settings; using FModel.Views.Resources.Controls; - using Microsoft.Win32; - using Serilog; - using SkiaSharp; - using System; using System.Collections.Generic; using System.Drawing.Imaging; @@ -22,281 +17,281 @@ using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media.Imaging; -namespace FModel.Views +namespace FModel.Views; + +public partial class ImageMerger { - public partial class ImageMerger + private const string FILENAME = "Preview.png"; + private byte[] _imageBuffer; + + public ImageMerger() { - private const string FILENAME = "Preview.png"; - private byte[] _imagebuffer; + InitializeComponent(); + } - public ImageMerger() - { - InitializeComponent(); - } - - private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs) - { - if (ImagePreview.Source != null) - await DrawPreview().ConfigureAwait(false); - } - private async void Click_DrawPreview(object sender, MouseButtonEventArgs e) - { - if (ImagePreview.Source != null) - await DrawPreview().ConfigureAwait(false); - } - - private async Task DrawPreview() - { - AddButton.IsEnabled = false; - UpButton.IsEnabled = false; - DownButton.IsEnabled = false; - DeleteButton.IsEnabled = false; - ClearButton.IsEnabled = false; - SizeSlider.IsEnabled = false; - OpenImageButton.IsEnabled = false; - SaveImageButton.IsEnabled = false; - - var margin = UserSettings.Default.ImageMergerMargin; - int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value); - var positions = new Dictionary(); - var images = new SKBitmap[ImagesListBox.Items.Count]; - for (var i = 0; i < images.Length; i++) - { - var item = (ListBoxItem) ImagesListBox.Items[i]; - var ms = new MemoryStream(); - var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - if (item.ContentStringFormat.EndsWith(".tif")) - { - await using var tmp = new MemoryStream(); - await stream.CopyToAsync(tmp); - System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png); - } - else - { - await stream.CopyToAsync(ms); - } - - var image = SKBitmap.Decode(ms.ToArray()); - positions[i] = new SKPoint(curW, curH); - images[i] = image; - - if (image.Height > lineMaxHeight) - lineMaxHeight = image.Height; - - if (num % imagesPerRow == 0) - { - maxWidth = curW + image.Width + margin; - curH += lineMaxHeight + margin; - - curW = 0; - lineMaxHeight = 0; - } - else - { - maxHeight = curH + lineMaxHeight + margin; - curW += image.Width + margin; - if (curW > maxWidth) - maxWidth = curW; - } - - num++; - } - - await Task.Run(() => - { - using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul); - using var canvas = new SKCanvas(bmp); - - for (var i = 0; i < images.Length; i++) - { - using (images[i]) - { - canvas.DrawBitmap(images[i], positions[i], new SKPaint {FilterQuality = SKFilterQuality.High, IsAntialias = true}); - } - } - - using var data = bmp.Encode(SKEncodedImageFormat.Png, 100); - using var stream = new MemoryStream(_imagebuffer = data.ToArray()); - var photo = new BitmapImage(); - photo.BeginInit(); - photo.CacheOption = BitmapCacheOption.OnLoad; - photo.StreamSource = stream; - photo.EndInit(); - photo.Freeze(); - - Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; }); - }).ContinueWith(t => - { - AddButton.IsEnabled = true; - UpButton.IsEnabled = true; - DownButton.IsEnabled = true; - DeleteButton.IsEnabled = true; - ClearButton.IsEnabled = true; - SizeSlider.IsEnabled = true; - OpenImageButton.IsEnabled = true; - SaveImageButton.IsEnabled = true; - }, TaskScheduler.FromCurrentSynchronizationContext()); - } - - private async void OnImageAdd(object sender, RoutedEventArgs e) - { - var fileBrowser = new OpenFileDialog - { - Title = "Add image(s)", - InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports", - Multiselect = true, - Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*" - }; - var result = fileBrowser.ShowDialog(); - if (!result.HasValue || !result.Value) return; - - foreach (var file in fileBrowser.FileNames) - { - ImagesListBox.Items.Add(new ListBoxItem - { - ContentStringFormat = file, - Content = Path.GetFileNameWithoutExtension(file) - }); - } - - SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count))); + private async void DrawPreview(object sender, DragCompletedEventArgs dragCompletedEventArgs) + { + if (ImagePreview.Source != null) await DrawPreview().ConfigureAwait(false); - } + } - private async void ModifyItemInList(object sender, RoutedEventArgs e) + private async void Click_DrawPreview(object sender, MouseButtonEventArgs e) + { + if (ImagePreview.Source != null) + await DrawPreview().ConfigureAwait(false); + } + + private async Task DrawPreview() + { + AddButton.IsEnabled = false; + UpButton.IsEnabled = false; + DownButton.IsEnabled = false; + DeleteButton.IsEnabled = false; + ClearButton.IsEnabled = false; + SizeSlider.IsEnabled = false; + OpenImageButton.IsEnabled = false; + SaveImageButton.IsEnabled = false; + + var margin = UserSettings.Default.ImageMergerMargin; + int num = 1, curW = 0, curH = 0, maxWidth = 0, maxHeight = 0, lineMaxHeight = 0, imagesPerRow = Convert.ToInt32(SizeSlider.Value); + var positions = new Dictionary(); + var images = new SKBitmap[ImagesListBox.Items.Count]; + for (var i = 0; i < images.Length; i++) { - if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return; - var indices = ImagesListBox.SelectedItems.Cast().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray(); - var reloadImage = false; + var item = (ListBoxItem) ImagesListBox.Items[i]; + var ms = new MemoryStream(); + var stream = new FileStream(item.ContentStringFormat, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - switch (((Button) sender).Name) + if (item.ContentStringFormat.EndsWith(".tif")) { - case "UpButton": - { - if (indices.Length > 0 && indices[0] > 0) - { - for (var i = 0; i < ImagesListBox.Items.Count; i++) - { - if (!indices.Contains(i)) continue; - var item = (ListBoxItem) ImagesListBox.Items[i]; - ImagesListBox.Items.Remove(item); - ImagesListBox.Items.Insert(i - 1, item); - item.IsSelected = true; - reloadImage = true; - } - } - - ImagesListBox.SelectedItems.Add(indices); - if (reloadImage) - { - await DrawPreview().ConfigureAwait(false); - } - - break; - } - case "DownButton": - { - if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1) - { - for (var i = ImagesListBox.Items.Count - 1; i > -1; --i) - { - if (!indices.Contains(i)) continue; - var item = (ListBoxItem) ImagesListBox.Items[i]; - ImagesListBox.Items.Remove(item); - ImagesListBox.Items.Insert(i + 1, item); - item.IsSelected = true; - reloadImage = true; - } - } - - if (reloadImage) - { - await DrawPreview().ConfigureAwait(false); - } - - break; - } - case "DeleteButton": - { - if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0) - { - for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i) - ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]); - } - - await DrawPreview().ConfigureAwait(false); - - break; - } - } - } - - private void OnClear(object sender, RoutedEventArgs e) - { - ImagesListBox.Items.Clear(); - ImagePreview.Source = null; - } - - private void OnOpenImage(object sender, RoutedEventArgs e) - { - if (ImagePreview.Source == null) return; - Helper.OpenWindow("Merged Image", () => - { - new ImagePopout - { - Title = "Merged Image", - Width = ImagePreview.Source.Width, - Height = ImagePreview.Source.Height, - WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal, - ImageCtrl = {Source = ImagePreview.Source} - }.Show(); - }); - } - - private void OnSaveImage(object sender, RoutedEventArgs e) - { - Application.Current.Dispatcher.Invoke(delegate - { - if (ImagePreview.Source == null) return; - var saveFileDialog = new SaveFileDialog - { - Title = "Save Image", - FileName = FILENAME, - InitialDirectory = UserSettings.Default.OutputDirectory, - Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*" - }; - var result = saveFileDialog.ShowDialog(); - if (!result.HasValue || !result.Value) return; - - using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read)) - { - fs.Write(_imagebuffer, 0, _imagebuffer.Length); - } - - SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName)); - }); - } - - private static void SaveCheck(string path, string fileName) - { - if (File.Exists(path)) - { - Log.Information("{FileName} successfully saved", fileName); - FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true); + await using var tmp = new MemoryStream(); + await stream.CopyToAsync(tmp); + System.Drawing.Image.FromStream(tmp).Save(ms, ImageFormat.Png); } else { - Log.Error("{FileName} could not be saved", fileName); - FLogger.AppendError(); - FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true); + await stream.CopyToAsync(ms); } + + var image = SKBitmap.Decode(ms.ToArray()); + positions[i] = new SKPoint(curW, curH); + images[i] = image; + + if (image.Height > lineMaxHeight) + lineMaxHeight = image.Height; + + if (num % imagesPerRow == 0) + { + maxWidth = curW + image.Width + margin; + curH += lineMaxHeight + margin; + + curW = 0; + lineMaxHeight = 0; + } + else + { + maxHeight = curH + lineMaxHeight + margin; + curW += image.Width + margin; + if (curW > maxWidth) + maxWidth = curW; + } + + num++; } - private void OnCopyImage(object sender, RoutedEventArgs e) + await Task.Run(() => { - ClipboardExtensions.SetImage(_imagebuffer, FILENAME); + using var bmp = new SKBitmap(maxWidth - margin, maxHeight - margin, SKColorType.Rgba8888, SKAlphaType.Premul); + using var canvas = new SKCanvas(bmp); + + for (var i = 0; i < images.Length; i++) + { + using (images[i]) + { + canvas.DrawBitmap(images[i], positions[i], new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); + } + } + + using var data = bmp.Encode(SKEncodedImageFormat.Png, 100); + using var stream = new MemoryStream(_imageBuffer = data.ToArray()); + var photo = new BitmapImage(); + photo.BeginInit(); + photo.CacheOption = BitmapCacheOption.OnLoad; + photo.StreamSource = stream; + photo.EndInit(); + photo.Freeze(); + + Application.Current.Dispatcher.Invoke(delegate { ImagePreview.Source = photo; }); + }).ContinueWith(t => + { + AddButton.IsEnabled = true; + UpButton.IsEnabled = true; + DownButton.IsEnabled = true; + DeleteButton.IsEnabled = true; + ClearButton.IsEnabled = true; + SizeSlider.IsEnabled = true; + OpenImageButton.IsEnabled = true; + SaveImageButton.IsEnabled = true; + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + + private async void OnImageAdd(object sender, RoutedEventArgs e) + { + var fileBrowser = new OpenFileDialog + { + Title = "Add image(s)", + InitialDirectory = $"{UserSettings.Default.OutputDirectory}\\Exports", + Multiselect = true, + Filter = "Image Files (*.png,*.bmp,*.jpg,*.jpeg,*.jfif,*.jpe,*.tiff,*.tif)|*.png;*.bmp;*.jpg;*.jpeg;*.jfif;*.jpe;*.tiff;*.tif|All Files (*.*)|*.*" + }; + var result = fileBrowser.ShowDialog(); + if (!result.HasValue || !result.Value) return; + + foreach (var file in fileBrowser.FileNames) + { + ImagesListBox.Items.Add(new ListBoxItem + { + ContentStringFormat = file, + Content = Path.GetFileNameWithoutExtension(file) + }); + } + + SizeSlider.Value = Math.Min(ImagesListBox.Items.Count, Math.Round(Math.Sqrt(ImagesListBox.Items.Count))); + await DrawPreview().ConfigureAwait(false); + } + + private async void ModifyItemInList(object sender, RoutedEventArgs e) + { + if (ImagesListBox.Items.Count <= 0 || ImagesListBox.SelectedItems.Count <= 0) return; + var indices = ImagesListBox.SelectedItems.Cast().Select(i => ImagesListBox.Items.IndexOf(i)).ToArray(); + var reloadImage = false; + + switch (((Button) sender).Name) + { + case "UpButton": + { + if (indices.Length > 0 && indices[0] > 0) + { + for (var i = 0; i < ImagesListBox.Items.Count; i++) + { + if (!indices.Contains(i)) continue; + var item = (ListBoxItem) ImagesListBox.Items[i]; + ImagesListBox.Items.Remove(item); + ImagesListBox.Items.Insert(i - 1, item); + item.IsSelected = true; + reloadImage = true; + } + } + + ImagesListBox.SelectedItems.Add(indices); + if (reloadImage) + { + await DrawPreview().ConfigureAwait(false); + } + + break; + } + case "DownButton": + { + if (indices.Length > 0 && indices[^1] < ImagesListBox.Items.Count - 1) + { + for (var i = ImagesListBox.Items.Count - 1; i > -1; --i) + { + if (!indices.Contains(i)) continue; + var item = (ListBoxItem) ImagesListBox.Items[i]; + ImagesListBox.Items.Remove(item); + ImagesListBox.Items.Insert(i + 1, item); + item.IsSelected = true; + reloadImage = true; + } + } + + if (reloadImage) + { + await DrawPreview().ConfigureAwait(false); + } + + break; + } + case "DeleteButton": + { + if (ImagesListBox.Items.Count > 0 && ImagesListBox.SelectedItems.Count > 0) + { + for (var i = ImagesListBox.SelectedItems.Count - 1; i >= 0; --i) + ImagesListBox.Items.Remove(ImagesListBox.SelectedItems[i]); + } + + await DrawPreview().ConfigureAwait(false); + + break; + } } } -} + + private void OnClear(object sender, RoutedEventArgs e) + { + ImagesListBox.Items.Clear(); + ImagePreview.Source = null; + } + + private void OnOpenImage(object sender, RoutedEventArgs e) + { + if (ImagePreview.Source == null) return; + Helper.OpenWindow("Merged Image", () => + { + new ImagePopout + { + Title = "Merged Image", + Width = ImagePreview.Source.Width, + Height = ImagePreview.Source.Height, + WindowState = ImagePreview.Source.Height > 1000 ? WindowState.Maximized : WindowState.Normal, + ImageCtrl = { Source = ImagePreview.Source } + }.Show(); + }); + } + + private void OnSaveImage(object sender, RoutedEventArgs e) + { + Application.Current.Dispatcher.Invoke(delegate + { + if (ImagePreview.Source == null) return; + var saveFileDialog = new SaveFileDialog + { + Title = "Save Image", + FileName = FILENAME, + InitialDirectory = UserSettings.Default.OutputDirectory, + Filter = "Png Files (*.png)|*.png|All Files (*.*)|*.*" + }; + var result = saveFileDialog.ShowDialog(); + if (!result.HasValue || !result.Value) return; + + using (var fs = new FileStream(saveFileDialog.FileName, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + fs.Write(_imageBuffer, 0, _imageBuffer.Length); + } + + SaveCheck(saveFileDialog.FileName, Path.GetFileName(saveFileDialog.FileName)); + }); + } + + private static void SaveCheck(string path, string fileName) + { + if (File.Exists(path)) + { + Log.Information("{FileName} successfully saved", fileName); + FLogger.AppendInformation(); + FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true); + } + else + { + Log.Error("{FileName} could not be saved", fileName); + FLogger.AppendError(); + FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true); + } + } + + private void OnCopyImage(object sender, RoutedEventArgs e) + { + ClipboardExtensions.SetImage(_imageBuffer, FILENAME); + } +} \ No newline at end of file diff --git a/FModel/Views/MapViewer.xaml.cs b/FModel/Views/MapViewer.xaml.cs index d679f099..a11e143c 100644 --- a/FModel/Views/MapViewer.xaml.cs +++ b/FModel/Views/MapViewer.xaml.cs @@ -11,71 +11,70 @@ using FModel.Views.Resources.Controls; using Microsoft.Win32; using Serilog; -namespace FModel.Views +namespace FModel.Views; + +public partial class MapViewer { - public partial class MapViewer + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public MapViewer() { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + DataContext = _applicationView; + _applicationView.MapViewer.Initialize(); - public MapViewer() + InitializeComponent(); + } + + private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence(); + + private void OnClick(object sender, RoutedEventArgs e) + { + if (_applicationView.MapViewer.MapImage == null) return; + var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png"); + + var saveFileDialog = new SaveFileDialog { - DataContext = _applicationView; - _applicationView.MapViewer.Initialize(); + Title = "Save MiniMap", + FileName = "MiniMap.png", + InitialDirectory = path.SubstringBeforeLast('\\'), + Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" + }; - InitializeComponent(); + if (!saveFileDialog.ShowDialog().GetValueOrDefault()) return; + path = saveFileDialog.FileName; + + using var fileStream = new FileStream(path, FileMode.Create); + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave())); + encoder.Save(fileStream); + + if (File.Exists(path)) + { + Log.Information("MiniMap.png successfully saved"); + FLogger.AppendInformation(); + FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true); } - - private void OnClosing(object sender, CancelEventArgs e) => DiscordService.DiscordHandler.UpdateToSavedPresence(); - - private void OnClick(object sender, RoutedEventArgs e) + else { - if (_applicationView.MapViewer.MapImage == null) return; - var path = Path.Combine(UserSettings.Default.TextureDirectory, "MiniMap.png"); - - var saveFileDialog = new SaveFileDialog - { - Title = "Save MiniMap", - FileName = "MiniMap.png", - InitialDirectory = path.SubstringBeforeLast('\\'), - Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*" - }; - - if (!(bool) saveFileDialog.ShowDialog()) return; - path = saveFileDialog.FileName; - - using var fileStream = new FileStream(path, FileMode.Create); - var encoder = new PngBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(_applicationView.MapViewer.GetImageToSave())); - encoder.Save(fileStream); - - if (File.Exists(path)) - { - Log.Information("MiniMap.png successfully saved"); - FLogger.AppendInformation(); - FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true); - } - else - { - Log.Error("MiniMap.png could not be saved"); - FLogger.AppendError(); - FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true); - } - } - - private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) - { - var i = 0; - foreach (var item in MapTree.Items) - { - if (item is not TreeViewItem {IsSelected: true}) - { - i++; - continue; - } - - _applicationView.MapViewer.MapIndex = i; - break; - } + Log.Error("MiniMap.png could not be saved"); + FLogger.AppendError(); + FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true); } } -} + + private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + var i = 0; + foreach (var item in MapTree.Items) + { + if (item is not TreeViewItem { IsSelected: true }) + { + i++; + continue; + } + + _applicationView.MapViewer.MapIndex = i; + break; + } + } +} \ No newline at end of file diff --git a/FModel/Views/ModelViewer.xaml.cs b/FModel/Views/ModelViewer.xaml.cs index 8619bfa5..07d6a2bd 100644 --- a/FModel/Views/ModelViewer.xaml.cs +++ b/FModel/Views/ModelViewer.xaml.cs @@ -10,107 +10,106 @@ using HelixToolkit.Wpf.SharpDX; using MessageBox = AdonisUI.Controls.MessageBox; using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; -namespace FModel.Views +namespace FModel.Views; + +public partial class ModelViewer { - public partial class ModelViewer + private bool _messageShown; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public ModelViewer() { - private bool _messageShown; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + DataContext = _applicationView; + InitializeComponent(); + } - public ModelViewer() + public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export); + public void Overwrite(UMaterialInstance materialInstance) + { + if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance)) { - DataContext = _applicationView; - InitializeComponent(); - } - - public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export); - public void Overwrite(UMaterialInstance materialInstance) - { - if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance)) - { - _applicationView.CUE4Parse.ModelIsOverwritingMaterial = false; - } - else - { - MessageBox.Show(new MessageBoxModel - { - Text = "An attempt to load a material failed.", - Caption = "Error", - Icon = MessageBoxImage.Error, - Buttons = MessageBoxButtons.OkCancel(), - IsSoundEnabled = false - }); - } - } - - private void OnClosing(object sender, CancelEventArgs e) - { - _applicationView.ModelViewer.Clear(); - _applicationView.ModelViewer.AppendMode = false; _applicationView.CUE4Parse.ModelIsOverwritingMaterial = false; - MyAntiCrashGroup.ItemsSource = null; // <3 } - - private async void OnWindowKeyDown(object sender, KeyEventArgs e) + else { - switch (e.Key) + MessageBox.Show(new MessageBoxModel { - case Key.W: - _applicationView.ModelViewer.WirefreameToggle(); - break; - case Key.H: - _applicationView.ModelViewer.RenderingToggle(); - break; - case Key.D: - _applicationView.ModelViewer.DiffuseOnlyToggle(); - break; - case Key.M: - _applicationView.ModelViewer.MaterialColorToggle(); - break; - case Key.Decimal: - _applicationView.ModelViewer.FocusOnSelectedMesh(); - break; - case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift): - _applicationView.ModelViewer.SaveAsScene(); - break; - case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control): - await _applicationView.ModelViewer.SaveLoadedModels(); - break; - } - } - - private void OnMouse3DDown(object sender, MouseDown3DEventArgs e) - { - if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return; - _applicationView.ModelViewer.SelectedModel.SelectedGeometry = m; - MaterialsListName.ScrollIntoView(m); - } - - private void OnFocusClick(object sender, RoutedEventArgs e) - => _applicationView.ModelViewer.FocusOnSelectedMesh(); - - private void OnCopyClick(object sender, RoutedEventArgs e) - => _applicationView.ModelViewer.CopySelectedMaterialName(); - - private async void Save(object sender, RoutedEventArgs e) - => await _applicationView.ModelViewer.SaveLoadedModels(); - - private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e) - { - _applicationView.CUE4Parse.ModelIsOverwritingMaterial = true; - if (!_messageShown) - { - MessageBox.Show(new MessageBoxModel - { - Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.", - Caption = "How To Overwrite Material?", - Icon = MessageBoxImage.Information, - IsSoundEnabled = false - }); - _messageShown = true; - } - - MainWindow.YesWeCats.Activate(); + Text = "An attempt to load a material failed.", + Caption = "Error", + Icon = MessageBoxImage.Error, + Buttons = MessageBoxButtons.OkCancel(), + IsSoundEnabled = false + }); } } -} + + private void OnClosing(object sender, CancelEventArgs e) + { + _applicationView.ModelViewer.Clear(); + _applicationView.ModelViewer.AppendMode = false; + _applicationView.CUE4Parse.ModelIsOverwritingMaterial = false; + MyAntiCrashGroup.ItemsSource = null; // <3 + } + + private async void OnWindowKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.W: + _applicationView.ModelViewer.WirefreameToggle(); + break; + case Key.H: + _applicationView.ModelViewer.RenderingToggle(); + break; + case Key.D: + _applicationView.ModelViewer.DiffuseOnlyToggle(); + break; + case Key.M: + _applicationView.ModelViewer.MaterialColorToggle(); + break; + case Key.Decimal: + _applicationView.ModelViewer.FocusOnSelectedMesh(); + break; + case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift): + _applicationView.ModelViewer.SaveAsScene(); + break; + case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control): + await _applicationView.ModelViewer.SaveLoadedModels(); + break; + } + } + + private void OnMouse3DDown(object sender, MouseDown3DEventArgs e) + { + if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return; + _applicationView.ModelViewer.SelectedModel.SelectedGeometry = m; + MaterialsListName.ScrollIntoView(m); + } + + private void OnFocusClick(object sender, RoutedEventArgs e) + => _applicationView.ModelViewer.FocusOnSelectedMesh(); + + private void OnCopyClick(object sender, RoutedEventArgs e) + => _applicationView.ModelViewer.CopySelectedMaterialName(); + + private async void Save(object sender, RoutedEventArgs e) + => await _applicationView.ModelViewer.SaveLoadedModels(); + + private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e) + { + _applicationView.CUE4Parse.ModelIsOverwritingMaterial = true; + if (!_messageShown) + { + MessageBox.Show(new MessageBoxModel + { + Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.", + Caption = "How To Overwrite Material?", + Icon = MessageBoxImage.Information, + IsSoundEnabled = false + }); + _messageShown = true; + } + + MainWindow.YesWeCats.Activate(); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aed/BraceFoldingStrategy.cs b/FModel/Views/Resources/Controls/Aed/BraceFoldingStrategy.cs index 68f5b10e..66e6655a 100644 --- a/FModel/Views/Resources/Controls/Aed/BraceFoldingStrategy.cs +++ b/FModel/Views/Resources/Controls/Aed/BraceFoldingStrategy.cs @@ -3,127 +3,128 @@ using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Folding; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class JsonFoldingStrategies { - public class JsonFoldingStrategies - { - private readonly BraceFoldingStrategy _strategy; - private readonly FoldingManager _foldingManager; + private readonly BraceFoldingStrategy _strategy; + private readonly FoldingManager _foldingManager; - public JsonFoldingStrategies(TextEditor avalonEditor) - { - _foldingManager = FoldingManager.Install(avalonEditor.TextArea); - _strategy = new BraceFoldingStrategy(avalonEditor); - } - - public void UpdateFoldings(TextDocument document) - { - _foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1); - } - - public void UnfoldAll() - { - if (_foldingManager.AllFoldings == null) - return; - - foreach (var folding in _foldingManager.AllFoldings) - { - folding.IsFolded = false; - } - } - - public void FoldToggle(int offset) - { - if (_foldingManager.AllFoldings == null) - return; - - var foldSection = _foldingManager.GetFoldingsContaining(offset); - if (foldSection.Count > 0) - foldSection[^1].IsFolded = !foldSection[^1].IsFolded; - } - - public void FoldToggleAtLevel(int level = 0) - { - if (_foldingManager.AllFoldings == null) - return; - - var dowhat = -1; - var foldunfold = false; - foreach (var folding in _foldingManager.AllFoldings) - { - if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue; - - if (dowhat < 0) // determine if we fold or unfold based on the first one - { - dowhat = 1; - foldunfold = !folding.IsFolded; - } - folding.IsFolded = foldunfold; - } - } - } - - public class BraceFoldingStrategy - { - public BraceFoldingStrategy(TextEditor editor) - { - UpdateFoldings(editor.Document); - } - - public IEnumerable UpdateFoldings(TextDocument document) - { - return CreateNewFoldings(document); - } - - public IEnumerable CreateNewFoldings(ITextSource document) - { - var newFoldings = new List(); - var startOffsets = new Stack(); - var lastNewLineOffset = 0; - var level = -1; - - for (var i = 0; i < document.TextLength; i++) - { - var c = document.GetCharAt(i); - switch (c) - { - case '{' or '[': - level++; - startOffsets.Push(i); - break; - case '}' or ']' when startOffsets.Count > 0: - { - var startOffset = startOffsets.Pop(); - if (startOffset < lastNewLineOffset) - { - newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level)); - } - level--; - break; - } - case '\n' or '\r': - lastNewLineOffset = i + 1; - break; - } - } - - newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); - return newFoldings; - } + public JsonFoldingStrategies(TextEditor avalonEditor) + { + _foldingManager = FoldingManager.Install(avalonEditor.TextArea); + _strategy = new BraceFoldingStrategy(avalonEditor); } - public class CustomNewFolding : NewFolding - { - public int Level { get; } - - public CustomNewFolding(int start, int end, int level) : base(start, end) - { - Level = level; - } + public void UpdateFoldings(TextDocument document) + { + _foldingManager.UpdateFoldings(_strategy.UpdateFoldings(document), -1); + } - public override string ToString() - { - return $"[{Level}] {StartOffset} -> {EndOffset}"; - } - } + public void UnfoldAll() + { + if (_foldingManager.AllFoldings == null) + return; + + foreach (var folding in _foldingManager.AllFoldings) + { + folding.IsFolded = false; + } + } + + public void FoldToggle(int offset) + { + if (_foldingManager.AllFoldings == null) + return; + + var foldSection = _foldingManager.GetFoldingsContaining(offset); + if (foldSection.Count > 0) + foldSection[^1].IsFolded = !foldSection[^1].IsFolded; + } + + public void FoldToggleAtLevel(int level = 0) + { + if (_foldingManager.AllFoldings == null) + return; + + var dowhat = -1; + var foldunfold = false; + foreach (var folding in _foldingManager.AllFoldings) + { + if (folding.Tag is not CustomNewFolding realFolding || realFolding.Level != level) continue; + + if (dowhat < 0) // determine if we fold or unfold based on the first one + { + dowhat = 1; + foldunfold = !folding.IsFolded; + } + + folding.IsFolded = foldunfold; + } + } +} + +public class BraceFoldingStrategy +{ + public BraceFoldingStrategy(TextEditor editor) + { + UpdateFoldings(editor.Document); + } + + public IEnumerable UpdateFoldings(TextDocument document) + { + return CreateNewFoldings(document); + } + + public IEnumerable CreateNewFoldings(ITextSource document) + { + var newFoldings = new List(); + var startOffsets = new Stack(); + var lastNewLineOffset = 0; + var level = -1; + + for (var i = 0; i < document.TextLength; i++) + { + var c = document.GetCharAt(i); + switch (c) + { + case '{' or '[': + level++; + startOffsets.Push(i); + break; + case '}' or ']' when startOffsets.Count > 0: + { + var startOffset = startOffsets.Pop(); + if (startOffset < lastNewLineOffset) + { + newFoldings.Add(new CustomNewFolding(startOffset, i + 1, level)); + } + + level--; + break; + } + case '\n' or '\r': + lastNewLineOffset = i + 1; + break; + } + } + + newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); + return newFoldings; + } +} + +public class CustomNewFolding : NewFolding +{ + public int Level { get; } + + public CustomNewFolding(int start, int end, int level) : base(start, end) + { + Level = level; + } + + public override string ToString() + { + return $"[{Level}] {StartOffset} -> {EndOffset}"; + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs index ac2241a7..914309e8 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs @@ -1,39 +1,36 @@ using System.Text.RegularExpressions; using ICSharpCode.AvalonEdit.Rendering; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class GamePathElementGenerator : VisualLineElementGenerator { - public class GamePathElementGenerator : VisualLineElementGenerator + private readonly Regex _gamePathRegex = + new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + public GamePathElementGenerator() { - private readonly Regex _gamePathRegex = - new("\"(?:ObjectPath|AssetPathName|AssetName|ParameterName|CollisionProfileName|TableId)\": \"(?'target'(?!/?Script/)(.*/.*))\",?$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } - public GamePathElementGenerator() - { - } + private Match FindMatch(int startOffset) + { + var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; + var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); + return _gamePathRegex.Match(relevantText); + } - private Match FindMatch(int startOffset) - { - var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; - var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); - return _gamePathRegex.Match(relevantText); - } + public override int GetFirstInterestedOffset(int startOffset) + { + var m = FindMatch(startOffset); + return m.Success ? startOffset + m.Index : -1; + } - public override int GetFirstInterestedOffset(int startOffset) - { - var m = FindMatch(startOffset); - return m.Success ? startOffset + m.Index : -1; - } + public override VisualLineElement ConstructElement(int offset) + { + var m = FindMatch(offset); + if (!m.Success || m.Index != 0) return null; - public override VisualLineElement ConstructElement(int offset) - { - var m = FindMatch(offset); - if (!m.Success || m.Index != 0) return null; - - return m.Groups.TryGetValue("target", out var g) ? - new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : - null; - } + return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index 20555192..ccd348d3 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -7,73 +7,73 @@ using FModel.Services; using FModel.ViewModels; using ICSharpCode.AvalonEdit.Rendering; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class GamePathVisualLineText : VisualLineText { - public class GamePathVisualLineText : VisualLineText + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public delegate void GamePathOnClick(string gamePath); + + public event GamePathOnClick OnGamePathClicked; + private readonly string _gamePath; + + public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - - public delegate void GamePathOnClick(string gamePath); - public event GamePathOnClick OnGamePathClicked; - private readonly string _gamePath; - - public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) - { - _gamePath = gamePath; - } - - public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); - - TextRunProperties.SetForegroundBrush(Brushes.Plum); - return base.CreateTextRun(startVisualColumn, context); - } - - private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None; - - protected override void OnQueryCursor(QueryCursorEventArgs e) - { - if (!GamePathIsClickable()) return; - e.Handled = true; - e.Cursor = Cursors.Hand; - } - - protected override void OnMouseDown(MouseButtonEventArgs e) - { - if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable()) - return; - if (e.Handled || OnGamePathClicked == null) - return; - - OnGamePathClicked(_gamePath); - e.Handled = true; - } - - protected override VisualLineText CreateInstance(int length) - { - var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length); - a.OnGamePathClicked += async gamePath => - { - var obj = gamePath.SubstringAfterLast('.'); - var package = gamePath.SubstringBeforeLast('.'); - var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal); - if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase)) - { - var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj); - var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); - AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); - AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); - } - else - { - await _threadWorkerView.Begin(_ => - _applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj)); - } - }; - return a; - } + _gamePath = gamePath; } -} + + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); + + TextRunProperties.SetForegroundBrush(Brushes.Plum); + return base.CreateTextRun(startVisualColumn, context); + } + + private bool GamePathIsClickable() => !string.IsNullOrEmpty(_gamePath) && Keyboard.Modifiers == ModifierKeys.None; + + protected override void OnQueryCursor(QueryCursorEventArgs e) + { + if (!GamePathIsClickable()) return; + e.Handled = true; + e.Cursor = Cursors.Hand; + } + + protected override void OnMouseDown(MouseButtonEventArgs e) + { + if (e.ChangedButton != MouseButton.Left || !GamePathIsClickable()) + return; + if (e.Handled || OnGamePathClicked == null) + return; + + OnGamePathClicked(_gamePath); + e.Handled = true; + } + + protected override VisualLineText CreateInstance(int length) + { + var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length); + a.OnGamePathClicked += async gamePath => + { + var obj = gamePath.SubstringAfterLast('.'); + var package = gamePath.SubstringBeforeLast('.'); + var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal); + if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase)) + { + var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj); + var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); + AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); + } + else + { + await _threadWorkerView.Begin(_ => + _applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj)); + } + }; + return a; + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aed/HexColorElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/HexColorElementGenerator.cs index e4cae93b..c2b5a293 100644 --- a/FModel/Views/Resources/Controls/Aed/HexColorElementGenerator.cs +++ b/FModel/Views/Resources/Controls/Aed/HexColorElementGenerator.cs @@ -1,39 +1,37 @@ using System.Text.RegularExpressions; using ICSharpCode.AvalonEdit.Rendering; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class HexColorElementGenerator : VisualLineElementGenerator { - public class HexColorElementGenerator : VisualLineElementGenerator + private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + public HexColorElementGenerator() { - private readonly Regex _hexColorRegex = - new Regex("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } - public HexColorElementGenerator() - { - } + private Match FindMatch(int startOffset) + { + var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; + var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); + return _hexColorRegex.Match(relevantText); + } - private Match FindMatch(int startOffset) - { - var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; - var relevantText = CurrentContext.Document.GetText(startOffset, endOffset - startOffset); - return _hexColorRegex.Match(relevantText); - } + public override int GetFirstInterestedOffset(int startOffset) + { + var m = FindMatch(startOffset); + return m.Success ? startOffset + m.Index : -1; + } - public override int GetFirstInterestedOffset(int startOffset) - { - var m = FindMatch(startOffset); - return m.Success ? startOffset + m.Index : -1; - } + public override VisualLineElement ConstructElement(int offset) + { + var m = FindMatch(offset); + if (!m.Success || m.Index != 0) return null; - public override VisualLineElement ConstructElement(int offset) - { - var m = FindMatch(offset); - if (!m.Success || m.Index != 0) return null; - - return m.Groups.TryGetValue("target", out var g) ? - new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : - null; - } + return m.Groups.TryGetValue("target", out var g) ? + new HexColorVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : + null; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aed/HexColorVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/HexColorVisualLineText.cs index 86cde69d..dbef316c 100644 --- a/FModel/Views/Resources/Controls/Aed/HexColorVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/HexColorVisualLineText.cs @@ -3,29 +3,28 @@ using System.Windows.Media; using System.Windows.Media.TextFormatting; using ICSharpCode.AvalonEdit.Rendering; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class HexColorVisualLineText : VisualLineText { - public class HexColorVisualLineText : VisualLineText + private readonly string _hexColor; + + public HexColorVisualLineText(string hexColor, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { - private readonly string _hexColor; + _hexColor = hexColor; + } - public HexColorVisualLineText(string hexColor, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) - { - _hexColor = hexColor; - } + public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) + { + if (context == null) + throw new ArgumentNullException(nameof(context)); - public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) - { - if (context == null) - throw new ArgumentNullException(nameof(context)); + TextRunProperties.SetForegroundBrush(Brushes.PeachPuff); + return base.CreateTextRun(startVisualColumn, context); + } - TextRunProperties.SetForegroundBrush(Brushes.PeachPuff); - return base.CreateTextRun(startVisualColumn, context); - } - - protected override VisualLineText CreateInstance(int length) - { - return new HexColorVisualLineText(_hexColor, ParentVisualLine, length); - } + protected override VisualLineText CreateInstance(int length) + { + return new HexColorVisualLineText(_hexColor, ParentVisualLine, length); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/CustomCodecFactory.cs b/FModel/Views/Resources/Controls/Aup/CustomCodecFactory.cs index bbcd4d6c..6e2bc7e1 100644 --- a/FModel/Views/Resources/Controls/Aup/CustomCodecFactory.cs +++ b/FModel/Views/Resources/Controls/Aup/CustomCodecFactory.cs @@ -14,78 +14,77 @@ using CSCore.Codecs.WAV; using CSCore.Codecs.WMA; using CSCore.MediaFoundation; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public class CustomCodecFactory { - public class CustomCodecFactory + private readonly Dictionary _codecs; + + /// + /// TODO add MSADPCM, OPUS, WEM decoders + /// + public CustomCodecFactory() { - private readonly Dictionary _codecs; - - /// - /// TODO add MSADPCM, OPUS, WEM decoders - /// - public CustomCodecFactory() + _codecs = new Dictionary { - _codecs = new Dictionary + ["mp3"] = new(s => { - ["mp3"] = new(s => + try { - try - { - return new DmoMp3Decoder(s); - } - catch (Exception) - { - if (Mp3MediafoundationDecoder.IsSupported) - return new Mp3MediafoundationDecoder(s); - throw; - } - }, "mp3", "mpeg3"), - ["wav"] = new(s => + return new DmoMp3Decoder(s); + } + catch (Exception) { - IWaveSource res = new WaveFileReader(s); - if (res.WaveFormat.WaveFormatTag is AudioEncoding.Pcm or AudioEncoding.IeeeFloat or AudioEncoding.Extensible) return res; - res.Dispose(); - res = new MediaFoundationDecoder(s); - - return res; - }, "wav", "wave"), - ["ogg"] = new(s => new NVorbisSource(s).ToWaveSource(), "ogg"), - ["flac"] = new(s => new FlacFile(s), "flac", "fla"), - ["aiff"] = new(s => new AiffReader(s), "aiff", "aif", "aifc") - }; - - if (AacDecoder.IsSupported) + if (Mp3MediafoundationDecoder.IsSupported) + return new Mp3MediafoundationDecoder(s); + throw; + } + }, "mp3", "mpeg3"), + ["wav"] = new(s => { - _codecs["aac"] = new CodecFactoryEntry(s => new AacDecoder(s), - "aac", "adt", "adts", "m2ts", "mp2", "3g2", "3gp2", "3gp", "3gpp", "m4a", "m4v", "mp4v", "mp4", "mov"); - } + IWaveSource res = new WaveFileReader(s); + if (res.WaveFormat.WaveFormatTag is AudioEncoding.Pcm or AudioEncoding.IeeeFloat or AudioEncoding.Extensible) return res; + res.Dispose(); + res = new MediaFoundationDecoder(s); - if (WmaDecoder.IsSupported) - { - _codecs["wma"] = new CodecFactoryEntry(s => new WmaDecoder(s), "asf", "wm", "wmv", "wma"); - } + return res; + }, "wav", "wave"), + ["ogg"] = new(s => new NVorbisSource(s).ToWaveSource(), "ogg"), + ["flac"] = new(s => new FlacFile(s), "flac", "fla"), + ["aiff"] = new(s => new AiffReader(s), "aiff", "aif", "aifc") + }; - if (Mp1Decoder.IsSupported) - { - _codecs["mp1"] = new CodecFactoryEntry(s => new Mp1Decoder(s), "mp1", "m2ts"); - } - - if (Mp2Decoder.IsSupported) - { - _codecs["mp2"] = new CodecFactoryEntry(s => new Mp2Decoder(s), "mp2", "m2ts"); - } - - if (DDPDecoder.IsSupported) - { - _codecs["ddp"] = new CodecFactoryEntry(s => new DDPDecoder(s), - "mp2", "m2ts", "m4a", "m4v", "mp4v", "mp4", "mov", "asf", "wm", "wmv", "wma", "avi", "ac3", "ec3"); - } + if (AacDecoder.IsSupported) + { + _codecs["aac"] = new CodecFactoryEntry(s => new AacDecoder(s), + "aac", "adt", "adts", "m2ts", "mp2", "3g2", "3gp2", "3gp", "3gpp", "m4a", "m4v", "mp4v", "mp4", "mov"); } - public IWaveSource GetCodec(byte[] data, string ext) + if (WmaDecoder.IsSupported) { - var stream = new MemoryStream(data) {Position = 0}; - return _codecs.TryGetValue(ext, out var codecFactoryEntry) ? codecFactoryEntry.GetCodecAction(stream) : new MediaFoundationDecoder(stream); + _codecs["wma"] = new CodecFactoryEntry(s => new WmaDecoder(s), "asf", "wm", "wmv", "wma"); + } + + if (Mp1Decoder.IsSupported) + { + _codecs["mp1"] = new CodecFactoryEntry(s => new Mp1Decoder(s), "mp1", "m2ts"); + } + + if (Mp2Decoder.IsSupported) + { + _codecs["mp2"] = new CodecFactoryEntry(s => new Mp2Decoder(s), "mp2", "m2ts"); + } + + if (DDPDecoder.IsSupported) + { + _codecs["ddp"] = new CodecFactoryEntry(s => new DDPDecoder(s), + "mp2", "m2ts", "m4a", "m4v", "mp4v", "mp4", "mov", "asf", "wm", "wmv", "wma", "avi", "ac3", "ec3"); } } + + public IWaveSource GetCodec(byte[] data, string ext) + { + var stream = new MemoryStream(data) { Position = 0 }; + return _codecs.TryGetValue(ext, out var codecFactoryEntry) ? codecFactoryEntry.GetCodecAction(stream) : new MediaFoundationDecoder(stream); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/ISource.cs b/FModel/Views/Resources/Controls/Aup/ISource.cs index f270ce05..e8ee30f9 100644 --- a/FModel/Views/Resources/Controls/Aup/ISource.cs +++ b/FModel/Views/Resources/Controls/Aup/ISource.cs @@ -1,22 +1,21 @@ using System; using FModel.ViewModels; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public interface ISource { - public interface ISource - { - AudioFile PlayedFile { get; } - float[] FftData { get; } - SpectrumProvider Spectrum { get; } + AudioFile PlayedFile { get; } + float[] FftData { get; } + SpectrumProvider Spectrum { get; } - void Play(); - void Pause(); - void Resume(); - void Stop(); - void SkipTo(double percentage); - void Dispose(); + void Play(); + void Pause(); + void Resume(); + void Stop(); + void SkipTo(double percentage); + void Dispose(); - event EventHandler SourceEvent; - event EventHandler SourcePropertyChangedEvent; - } + event EventHandler SourceEvent; + event EventHandler SourcePropertyChangedEvent; } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/NVorbisSource.cs b/FModel/Views/Resources/Controls/Aup/NVorbisSource.cs index 07241477..7753bf5c 100644 --- a/FModel/Views/Resources/Controls/Aup/NVorbisSource.cs +++ b/FModel/Views/Resources/Controls/Aup/NVorbisSource.cs @@ -3,58 +3,57 @@ using System.IO; using CSCore; using NVorbis; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public sealed class NVorbisSource : ISampleSource { - public sealed class NVorbisSource : ISampleSource + private readonly Stream _stream; + private readonly VorbisReader _vorbisReader; + private bool _disposed; + + public WaveFormat WaveFormat { get; } + + public NVorbisSource(Stream stream) { - private readonly Stream _stream; - private readonly VorbisReader _vorbisReader; - private bool _disposed; + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + if (!stream.CanRead) + throw new ArgumentException(@"Stream is not readable.", nameof(stream)); - public WaveFormat WaveFormat { get; } + _stream = stream; + _vorbisReader = new VorbisReader(stream, false); + WaveFormat = new WaveFormat(_vorbisReader.SampleRate, 32, _vorbisReader.Channels, AudioEncoding.IeeeFloat); + } - public NVorbisSource(Stream stream) + public bool CanSeek => _stream.CanSeek; + public long Length => CanSeek ? (long) (_vorbisReader.TotalTime.TotalSeconds * WaveFormat.SampleRate * WaveFormat.Channels) : 0; + + public long Position + { + get => CanSeek ? (long) (_vorbisReader.TimePosition.TotalSeconds * _vorbisReader.SampleRate * _vorbisReader.Channels) : 0; + set { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (!stream.CanRead) - throw new ArgumentException(@"Stream is not readable.", nameof(stream)); + if (!CanSeek) + throw new InvalidOperationException("NVorbisSource is not seekable."); + if (value < 0 || value > Length) + throw new ArgumentOutOfRangeException(nameof(value)); - _stream = stream; - _vorbisReader = new VorbisReader(stream, false); - WaveFormat = new WaveFormat(_vorbisReader.SampleRate, 32, _vorbisReader.Channels, AudioEncoding.IeeeFloat); - } - - public bool CanSeek => _stream.CanSeek; - public long Length => CanSeek ? (long) (_vorbisReader.TotalTime.TotalSeconds * WaveFormat.SampleRate * WaveFormat.Channels) : 0; - - public long Position - { - get => CanSeek ? (long) (_vorbisReader.TimePosition.TotalSeconds * _vorbisReader.SampleRate * _vorbisReader.Channels) : 0; - set - { - if (!CanSeek) - throw new InvalidOperationException("NVorbisSource is not seekable."); - if (value < 0 || value > Length) - throw new ArgumentOutOfRangeException(nameof(value)); - - _vorbisReader.TimePosition = TimeSpan.FromSeconds((double) value / _vorbisReader.SampleRate / _vorbisReader.Channels); - } - } - - public int Read(float[] buffer, int offset, int count) - { - return _vorbisReader.ReadSamples(buffer, offset, count); - } - - public void Dispose() - { - if (!_disposed) - _vorbisReader.Dispose(); - else - throw new ObjectDisposedException("NVorbisSource"); - - _disposed = true; + _vorbisReader.TimePosition = TimeSpan.FromSeconds((double) value / _vorbisReader.SampleRate / _vorbisReader.Channels); } } + + public int Read(float[] buffer, int offset, int count) + { + return _vorbisReader.ReadSamples(buffer, offset, count); + } + + public void Dispose() + { + if (!_disposed) + _vorbisReader.Dispose(); + else + throw new ObjectDisposedException("NVorbisSource"); + + _disposed = true; + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs b/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs index 09294766..aeca57f0 100644 --- a/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs +++ b/FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs @@ -1,19 +1,18 @@ using System; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public enum ESourceEventType { - public enum ESourceEventType - { - Loading - } + Loading +} - public class SourceEventArgs : EventArgs - { - public ESourceEventType Event { get; } +public class SourceEventArgs : EventArgs +{ + public ESourceEventType Event { get; } - public SourceEventArgs(ESourceEventType e) - { - Event = e; - } + public SourceEventArgs(ESourceEventType e) + { + Event = e; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/SourcePropertyChangedEventArgs.cs b/FModel/Views/Resources/Controls/Aup/SourcePropertyChangedEventArgs.cs index 84d5bcb9..9fc1b620 100644 --- a/FModel/Views/Resources/Controls/Aup/SourcePropertyChangedEventArgs.cs +++ b/FModel/Views/Resources/Controls/Aup/SourcePropertyChangedEventArgs.cs @@ -1,26 +1,25 @@ using System; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public enum ESourceProperty { - public enum ESourceProperty - { - FftData, - PlaybackState, - Length, - Position, - WaveformData, - RecordingState - } + FftData, + PlaybackState, + Length, + Position, + WaveformData, + RecordingState +} - public class SourcePropertyChangedEventArgs : EventArgs - { - public ESourceProperty Property { get; } - public object Value { get; } +public class SourcePropertyChangedEventArgs : EventArgs +{ + public ESourceProperty Property { get; } + public object Value { get; } - public SourcePropertyChangedEventArgs(ESourceProperty property, object value) - { - Property = property; - Value = value; - } + public SourcePropertyChangedEventArgs(ESourceProperty property, object value) + { + Property = property; + Value = value; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs b/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs index a7e7d732..5d7da0d0 100644 --- a/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs +++ b/FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs @@ -8,419 +8,436 @@ using CSCore; using CSCore.SoundIn; using CSCore.SoundOut; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +[TemplatePart(Name = "PART_Spectrum", Type = typeof(Grid))] +public sealed class SpectrumAnalyzer : UserControl { - [TemplatePart(Name = "PART_Spectrum", Type = typeof(Grid))] - public sealed class SpectrumAnalyzer : UserControl + public enum ScalingStrategy { - public enum ScalingStrategy + Decibel, + Linear, + Sqrt + } + + private Grid _spectrumGrid; + private Border[] _bars; + + static SpectrumAnalyzer() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(SpectrumAnalyzer), new FrameworkPropertyMetadata(typeof(SpectrumAnalyzer))); + } + + private ISource _source; + + public ISource Source + { + get => (ISource) GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register("Source", typeof(ISource), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); + + public ScalingStrategy SpectrumScalingStrategy + { + get => (ScalingStrategy) GetValue(ScalingStrategyProperty); + set => SetValue(ScalingStrategyProperty, value); + } + + public static readonly DependencyProperty ScalingStrategyProperty = + DependencyProperty.Register("ScalingStrategy", typeof(ScalingStrategy), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(ScalingStrategy.Linear, OnScalingStrategyChanged, OnCoerceScalingStrategy)); + + public int FrequencyBarCount + { + get => (int) GetValue(FrequencyBarCountProperty); + set => SetValue(FrequencyBarCountProperty, value); + } + + public static readonly DependencyProperty FrequencyBarCountProperty = + DependencyProperty.Register("FrequencyBarCount", typeof(int), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(25, OnFrequencyBarCountChanged, OnCoerceFrequencyBarCount)); + + public int FrequencyBarSpacing + { + get => (int) GetValue(FrequencyBarSpacingProperty); + set => SetValue(FrequencyBarSpacingProperty, value); + } + + public static readonly DependencyProperty FrequencyBarSpacingProperty = + DependencyProperty.Register("FrequencyBarSpacing", typeof(int), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(2, OnFrequencyBarSpacingChanged, OnCoerceFrequencyBarSpacing)); + + public Brush FrequencyBarBrush + { + get => (Brush) GetValue(FrequencyBarBrushProperty); + set => SetValue(FrequencyBarBrushProperty, value); + } + + public static readonly DependencyProperty FrequencyBarBrushProperty = + DependencyProperty.Register("FrequencyBarBrush", typeof(Brush), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(Brushes.LightGreen, OnFrequencyBarBrushChanged, OnCoerceFrequencyBarBrush)); + + public Brush FrequencyBarBorderBrush + { + get => (Brush) GetValue(FrequencyBarBorderBrushProperty); + set => SetValue(FrequencyBarBorderBrushProperty, value); + } + + public static readonly DependencyProperty FrequencyBarBorderBrushProperty = + DependencyProperty.Register("FrequencyBarBorderBrush", typeof(Brush), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(Brushes.Transparent, OnFrequencyBarBorderBrushChanged, OnCoerceFrequencyBarBorderBrush)); + + public CornerRadius FrequencyBarCornerRadius + { + get => (CornerRadius) GetValue(FrequencyBarCornerRadiusProperty); + set => SetValue(FrequencyBarCornerRadiusProperty, value); + } + + public static readonly DependencyProperty FrequencyBarCornerRadiusProperty = + DependencyProperty.Register("FrequencyBarCornerRadius", typeof(CornerRadius), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(new CornerRadius(1, 1, 0, 0), OnFrequencyBarCornerRadiusChanged, OnCoerceFrequencyBarCornerRadius)); + + public Thickness FrequencyBarBorderThickness + { + get => (Thickness) GetValue(FrequencyBarBorderThicknessProperty); + set => SetValue(FrequencyBarBorderThicknessProperty, value); + } + + public static readonly DependencyProperty FrequencyBarBorderThicknessProperty = + DependencyProperty.Register("FrequencyBarBorderThickness", typeof(Thickness), typeof(SpectrumAnalyzer), + new UIPropertyMetadata(new Thickness(0), OnFrequencyBarBorderThicknessChanged, OnCoerceFrequencyBarBorderThickness)); + + private void OnSourceChanged(ISource oldValue, ISource newValue) + { + _source = Source; + _source.SourceEvent += OnSourceEvent; + _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; + } + + private SpectrumProvider _spectrumProvider; + + private void OnSourceEvent(object sender, SourceEventArgs e) + { + if (e.Event != ESourceEventType.Loading) return; + _spectrumProvider = Source.Spectrum; + UpdateFrequencyMapping(); + } + + private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) + { + switch (e.Property) { - Decibel, - Linear, - Sqrt - } - - private Grid _spectrumGrid; - private Border[] _bars; - - static SpectrumAnalyzer() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(SpectrumAnalyzer), new FrameworkPropertyMetadata(typeof(SpectrumAnalyzer))); - } - - private ISource _source; - public ISource Source - { - get => (ISource) GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - public static readonly DependencyProperty SourceProperty = - DependencyProperty.Register("Source", typeof(ISource), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); - - public ScalingStrategy SpectrumScalingStrategy - { - get => (ScalingStrategy) GetValue(ScalingStrategyProperty); - set => SetValue(ScalingStrategyProperty, value); - } - public static readonly DependencyProperty ScalingStrategyProperty = - DependencyProperty.Register("ScalingStrategy", typeof(ScalingStrategy), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(ScalingStrategy.Linear, OnScalingStrategyChanged, OnCoerceScalingStrategy)); - - public int FrequencyBarCount - { - get => (int) GetValue(FrequencyBarCountProperty); - set => SetValue(FrequencyBarCountProperty, value); - } - public static readonly DependencyProperty FrequencyBarCountProperty = - DependencyProperty.Register("FrequencyBarCount", typeof(int), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(25, OnFrequencyBarCountChanged, OnCoerceFrequencyBarCount)); - - public int FrequencyBarSpacing - { - get => (int) GetValue(FrequencyBarSpacingProperty); - set => SetValue(FrequencyBarSpacingProperty, value); - } - public static readonly DependencyProperty FrequencyBarSpacingProperty = - DependencyProperty.Register("FrequencyBarSpacing", typeof(int), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(2, OnFrequencyBarSpacingChanged, OnCoerceFrequencyBarSpacing)); - - public Brush FrequencyBarBrush - { - get => (Brush) GetValue(FrequencyBarBrushProperty); - set => SetValue(FrequencyBarBrushProperty, value); - } - public static readonly DependencyProperty FrequencyBarBrushProperty = - DependencyProperty.Register("FrequencyBarBrush", typeof(Brush), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(Brushes.LightGreen, OnFrequencyBarBrushChanged, OnCoerceFrequencyBarBrush)); - - public Brush FrequencyBarBorderBrush - { - get => (Brush) GetValue(FrequencyBarBorderBrushProperty); - set => SetValue(FrequencyBarBorderBrushProperty, value); - } - public static readonly DependencyProperty FrequencyBarBorderBrushProperty = - DependencyProperty.Register("FrequencyBarBorderBrush", typeof(Brush), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(Brushes.Transparent, OnFrequencyBarBorderBrushChanged, OnCoerceFrequencyBarBorderBrush)); - - public CornerRadius FrequencyBarCornerRadius - { - get => (CornerRadius) GetValue(FrequencyBarCornerRadiusProperty); - set => SetValue(FrequencyBarCornerRadiusProperty, value); - } - public static readonly DependencyProperty FrequencyBarCornerRadiusProperty = - DependencyProperty.Register("FrequencyBarCornerRadius", typeof(CornerRadius), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(new CornerRadius(1, 1, 0, 0), OnFrequencyBarCornerRadiusChanged, OnCoerceFrequencyBarCornerRadius)); - - public Thickness FrequencyBarBorderThickness - { - get => (Thickness) GetValue(FrequencyBarBorderThicknessProperty); - set => SetValue(FrequencyBarBorderThicknessProperty, value); - } - public static readonly DependencyProperty FrequencyBarBorderThicknessProperty = - DependencyProperty.Register("FrequencyBarBorderThickness", typeof(Thickness), typeof(SpectrumAnalyzer), - new UIPropertyMetadata(new Thickness(0), OnFrequencyBarBorderThicknessChanged, OnCoerceFrequencyBarBorderThickness)); - - private void OnSourceChanged(ISource oldValue, ISource newValue) - { - _source = Source; - _source.SourceEvent += OnSourceEvent; - _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; - } - - private SpectrumProvider _spectrumProvider; - private void OnSourceEvent(object sender, SourceEventArgs e) - { - if (e.Event != ESourceEventType.Loading) return; - _spectrumProvider = Source.Spectrum; - UpdateFrequencyMapping(); - } - - private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) - { - switch (e.Property) - { - case ESourceProperty.FftData: - UpdateSpectrum(SpectrumResolution, _source.FftData); - break; - case ESourceProperty.PlaybackState when (PlaybackState) e.Value == PlaybackState.Playing: - case ESourceProperty.RecordingState when (RecordingState) e.Value == RecordingState.Recording: - CreateBars(); - break; - case ESourceProperty.RecordingState when (RecordingState) e.Value == RecordingState.Stopped: - SilenceBars(); - break; - } - } - - private ISource OnCoerceSource(ISource value) => value; - private static object OnCoerceSource(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceSource((ISource) value); - return value; - } - - private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); - } - - private ScalingStrategy OnCoerceScalingStrategy(ScalingStrategy value) => value; - private static object OnCoerceScalingStrategy(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceScalingStrategy((ScalingStrategy) value); - return value; - } - - private void OnScalingStrategyChanged(ScalingStrategy oldValue, ScalingStrategy newValue) - { - } - - private static void OnScalingStrategyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnScalingStrategyChanged((ScalingStrategy) e.OldValue, (ScalingStrategy) e.NewValue); - } - - private int OnCoerceFrequencyBarCount(int value) => value; - private static object OnCoerceFrequencyBarCount(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarCount((int) value); - return value; - } - - private static void OnFrequencyBarCountChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarCountChanged((int) e.OldValue, (int) e.NewValue); - } - - private void OnFrequencyBarCountChanged(int oldValue, int newValue) - { - FrequencyBarCount = newValue switch - { - < 1 => 1, - > 100 => 100, - _ => FrequencyBarCount - }; - - CreateBars(); - } - - private int OnCoerceFrequencyBarSpacing(int value) => value; - private static object OnCoerceFrequencyBarSpacing(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarSpacing((int) value); - return value; - } - - private void OnFrequencyBarSpacingChanged(int oldValue, int newValue) - { - } - - private static void OnFrequencyBarSpacingChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarSpacingChanged((int) e.OldValue, (int) e.NewValue); - } - - private Brush OnCoerceFrequencyBarBrush(Brush value) => value; - private static object OnCoerceFrequencyBarBrush(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarBrush((Brush) value); - return value; - } - - private void OnFrequencyBarBrushChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnFrequencyBarBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private Brush OnCoerceFrequencyBarBorderBrush(Brush value) => value; - private static object OnCoerceFrequencyBarBorderBrush(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarBorderBrush((Brush) value); - return value; - } - - private void OnFrequencyBarBorderBrushChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnFrequencyBarBorderBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarBorderBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private CornerRadius OnCoerceFrequencyBarCornerRadius(CornerRadius value) => value; - private static object OnCoerceFrequencyBarCornerRadius(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarCornerRadius((CornerRadius) value); - return value; - } - - private void OnFrequencyBarCornerRadiusChanged(CornerRadius oldValue, CornerRadius newValue) - { - } - - private static void OnFrequencyBarCornerRadiusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarCornerRadiusChanged((CornerRadius) e.OldValue, (CornerRadius) e.NewValue); - } - - private Thickness OnCoerceFrequencyBarBorderThickness(Thickness value) => value; - private static object OnCoerceFrequencyBarBorderThickness(DependencyObject o, object value) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - return spectrumAnalyzer.OnCoerceFrequencyBarBorderThickness((Thickness) value); - return value; - } - - private void OnFrequencyBarBorderThicknessChanged(Thickness oldValue, Thickness newValue) - { - } - - private static void OnFrequencyBarBorderThicknessChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is SpectrumAnalyzer spectrumAnalyzer) - spectrumAnalyzer.OnFrequencyBarBorderThicknessChanged((Thickness) e.OldValue, (Thickness) e.NewValue); - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - _spectrumGrid = GetTemplateChild("PART_Spectrum") as Grid; - - CreateBars(); - UpdateFrequencyMapping(); - } - - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) - { - UpdateFrequencyMapping(); - } - - private const int ScaleFactorLinear = 50; - private const int ScaleFactorSqr = 2; - private const int MaxFftIndex = 4096 / 2 - 1; - private const int MaximumFrequency = 48000; - private const int MinimumFrequency = 20; - private const int SpectrumResolution = 100; - private const double MinDbValue = -90; - private const double DbScale = 0 - MinDbValue; - private int[] _spectrumIndexMax = new int[100]; - private int _maximumFrequencyIndex; - private int _minimumFrequencyIndex; - - private void CreateBars() - { - Dispatcher.BeginInvoke((Action) delegate - { - if (_spectrumGrid == null) return; - - _spectrumGrid.Children.Clear(); - _bars = new Border[FrequencyBarCount]; - for (var i = 0; i < _bars.Length; i++) - { - var borderBrush = FrequencyBarBorderBrush.Clone(); - var barBrush = FrequencyBarBrush.Clone(); - borderBrush.Freeze(); - barBrush.Freeze(); - - _bars[i] = new Border - { - CornerRadius = FrequencyBarCornerRadius, - BorderThickness = FrequencyBarBorderThickness, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Bottom, - Height = 0, - BorderBrush = borderBrush, - Background = barBrush - }; - _spectrumGrid.Children.Add(_bars[i]); - } - }, DispatcherPriority.Render); - } - - private void SilenceBars() - { - Dispatcher.BeginInvoke((Action) delegate - { - if (_spectrumGrid == null) return; - - _spectrumGrid.Children.Clear(); - _spectrumGrid.CacheMode = new BitmapCache(); - }, DispatcherPriority.Render); - } - - private void UpdateFrequencyMapping() - { - if (_spectrumProvider == null) return; - - _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, MaxFftIndex); - _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), MaxFftIndex); - _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(SpectrumResolution, true); - - var indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex; - var linearIndexBucketSize = Math.Round(indexCount / (double) SpectrumResolution, 3); - for (var i = 1; i < SpectrumResolution; i++) - { - _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize); - } - - _spectrumIndexMax[^1] = _maximumFrequencyIndex; - } - - private void UpdateSpectrum(double maxValue, IReadOnlyList fftBuffer) - { - var spectrumScalingStrategy = ScalingStrategy.Decibel; - Dispatcher.Invoke(delegate { spectrumScalingStrategy = SpectrumScalingStrategy; }); - - var lastValue = 0D; - var spectrumPointIndex = 0; - var dataPoints = new List(); - for (var i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++) - { - var tempVal = spectrumScalingStrategy switch - { - ScalingStrategy.Decibel => (20 * Math.Log10(fftBuffer[i]) - MinDbValue) / DbScale * maxValue, - ScalingStrategy.Linear => fftBuffer[i] * ScaleFactorLinear * maxValue, - ScalingStrategy.Sqrt => Math.Sqrt(fftBuffer[i]) * ScaleFactorSqr * maxValue, - _ => 0D - }; - - var bAgain = true; - var value = Math.Max(0, Math.Max(tempVal, 0)); - while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 && i == _spectrumIndexMax[spectrumPointIndex]) - { - if (!bAgain) - value = lastValue; - - if (value > maxValue) - value = maxValue; - - if (spectrumPointIndex > 0) - value = (lastValue + value) / 1.5; - - dataPoints.Add(value); - lastValue = value; - value = 0.0; - spectrumPointIndex++; - bAgain = false; - } - } - - Dispatcher.Invoke(delegate - { - for (var i = 0; i < FrequencyBarCount; i++) - { - var barSpacing = RenderSize.Width / FrequencyBarCount; - var barWidth = barSpacing - FrequencyBarSpacing; - if (barWidth < .5) - barWidth = .5; - - var b = _bars[i]; - b.Height = dataPoints[i] / 100 * RenderSize.Height; - b.Width = barWidth; - b.Margin = new Thickness(i * barSpacing, 0, 0, 0); - } - }, DispatcherPriority.ApplicationIdle); + case ESourceProperty.FftData: + UpdateSpectrum(SpectrumResolution, _source.FftData); + break; + case ESourceProperty.PlaybackState when (PlaybackState) e.Value == PlaybackState.Playing: + case ESourceProperty.RecordingState when (RecordingState) e.Value == RecordingState.Recording: + CreateBars(); + break; + case ESourceProperty.RecordingState when (RecordingState) e.Value == RecordingState.Stopped: + SilenceBars(); + break; } } + + private ISource OnCoerceSource(ISource value) => value; + + private static object OnCoerceSource(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceSource((ISource) value); + return value; + } + + private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); + } + + private ScalingStrategy OnCoerceScalingStrategy(ScalingStrategy value) => value; + + private static object OnCoerceScalingStrategy(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceScalingStrategy((ScalingStrategy) value); + return value; + } + + private void OnScalingStrategyChanged(ScalingStrategy oldValue, ScalingStrategy newValue) + { + } + + private static void OnScalingStrategyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnScalingStrategyChanged((ScalingStrategy) e.OldValue, (ScalingStrategy) e.NewValue); + } + + private int OnCoerceFrequencyBarCount(int value) => value; + + private static object OnCoerceFrequencyBarCount(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarCount((int) value); + return value; + } + + private static void OnFrequencyBarCountChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarCountChanged((int) e.OldValue, (int) e.NewValue); + } + + private void OnFrequencyBarCountChanged(int oldValue, int newValue) + { + FrequencyBarCount = newValue switch + { + < 1 => 1, + > 100 => 100, + _ => FrequencyBarCount + }; + + CreateBars(); + } + + private int OnCoerceFrequencyBarSpacing(int value) => value; + + private static object OnCoerceFrequencyBarSpacing(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarSpacing((int) value); + return value; + } + + private void OnFrequencyBarSpacingChanged(int oldValue, int newValue) + { + } + + private static void OnFrequencyBarSpacingChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarSpacingChanged((int) e.OldValue, (int) e.NewValue); + } + + private Brush OnCoerceFrequencyBarBrush(Brush value) => value; + + private static object OnCoerceFrequencyBarBrush(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarBrush((Brush) value); + return value; + } + + private void OnFrequencyBarBrushChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnFrequencyBarBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private Brush OnCoerceFrequencyBarBorderBrush(Brush value) => value; + + private static object OnCoerceFrequencyBarBorderBrush(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarBorderBrush((Brush) value); + return value; + } + + private void OnFrequencyBarBorderBrushChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnFrequencyBarBorderBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarBorderBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private CornerRadius OnCoerceFrequencyBarCornerRadius(CornerRadius value) => value; + + private static object OnCoerceFrequencyBarCornerRadius(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarCornerRadius((CornerRadius) value); + return value; + } + + private void OnFrequencyBarCornerRadiusChanged(CornerRadius oldValue, CornerRadius newValue) + { + } + + private static void OnFrequencyBarCornerRadiusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarCornerRadiusChanged((CornerRadius) e.OldValue, (CornerRadius) e.NewValue); + } + + private Thickness OnCoerceFrequencyBarBorderThickness(Thickness value) => value; + + private static object OnCoerceFrequencyBarBorderThickness(DependencyObject o, object value) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + return spectrumAnalyzer.OnCoerceFrequencyBarBorderThickness((Thickness) value); + return value; + } + + private void OnFrequencyBarBorderThicknessChanged(Thickness oldValue, Thickness newValue) + { + } + + private static void OnFrequencyBarBorderThicknessChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is SpectrumAnalyzer spectrumAnalyzer) + spectrumAnalyzer.OnFrequencyBarBorderThicknessChanged((Thickness) e.OldValue, (Thickness) e.NewValue); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _spectrumGrid = GetTemplateChild("PART_Spectrum") as Grid; + + CreateBars(); + UpdateFrequencyMapping(); + } + + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + UpdateFrequencyMapping(); + } + + private const int ScaleFactorLinear = 50; + private const int ScaleFactorSqr = 2; + private const int MaxFftIndex = 4096 / 2 - 1; + private const int MaximumFrequency = 48000; + private const int MinimumFrequency = 20; + private const int SpectrumResolution = 100; + private const double MinDbValue = -90; + private const double DbScale = 0 - MinDbValue; + private int[] _spectrumIndexMax = new int[100]; + private int _maximumFrequencyIndex; + private int _minimumFrequencyIndex; + + private void CreateBars() + { + Dispatcher.BeginInvoke((Action) delegate + { + if (_spectrumGrid == null) return; + + _spectrumGrid.Children.Clear(); + _bars = new Border[FrequencyBarCount]; + for (var i = 0; i < _bars.Length; i++) + { + var borderBrush = FrequencyBarBorderBrush.Clone(); + var barBrush = FrequencyBarBrush.Clone(); + borderBrush.Freeze(); + barBrush.Freeze(); + + _bars[i] = new Border + { + CornerRadius = FrequencyBarCornerRadius, + BorderThickness = FrequencyBarBorderThickness, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Bottom, + Height = 0, + BorderBrush = borderBrush, + Background = barBrush + }; + _spectrumGrid.Children.Add(_bars[i]); + } + }, DispatcherPriority.Render); + } + + private void SilenceBars() + { + Dispatcher.BeginInvoke((Action) delegate + { + if (_spectrumGrid == null) return; + + _spectrumGrid.Children.Clear(); + _spectrumGrid.CacheMode = new BitmapCache(); + }, DispatcherPriority.Render); + } + + private void UpdateFrequencyMapping() + { + if (_spectrumProvider == null) return; + + _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, MaxFftIndex); + _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), MaxFftIndex); + _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(SpectrumResolution, true); + + var indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex; + var linearIndexBucketSize = Math.Round(indexCount / (double) SpectrumResolution, 3); + for (var i = 1; i < SpectrumResolution; i++) + { + _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize); + } + + _spectrumIndexMax[^1] = _maximumFrequencyIndex; + } + + private void UpdateSpectrum(double maxValue, IReadOnlyList fftBuffer) + { + var spectrumScalingStrategy = ScalingStrategy.Decibel; + Dispatcher.Invoke(delegate { spectrumScalingStrategy = SpectrumScalingStrategy; }); + + var lastValue = 0D; + var spectrumPointIndex = 0; + var dataPoints = new List(); + for (var i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++) + { + var tempVal = spectrumScalingStrategy switch + { + ScalingStrategy.Decibel => (20 * Math.Log10(fftBuffer[i]) - MinDbValue) / DbScale * maxValue, + ScalingStrategy.Linear => fftBuffer[i] * ScaleFactorLinear * maxValue, + ScalingStrategy.Sqrt => Math.Sqrt(fftBuffer[i]) * ScaleFactorSqr * maxValue, + _ => 0D + }; + + var bAgain = true; + var value = Math.Max(0, Math.Max(tempVal, 0)); + while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 && i == _spectrumIndexMax[spectrumPointIndex]) + { + if (!bAgain) + value = lastValue; + + if (value > maxValue) + value = maxValue; + + if (spectrumPointIndex > 0) + value = (lastValue + value) / 1.5; + + dataPoints.Add(value); + lastValue = value; + value = 0.0; + spectrumPointIndex++; + bAgain = false; + } + } + + Dispatcher.Invoke(delegate + { + for (var i = 0; i < FrequencyBarCount; i++) + { + var barSpacing = RenderSize.Width / FrequencyBarCount; + var barWidth = barSpacing - FrequencyBarSpacing; + if (barWidth < .5) + barWidth = .5; + + var b = _bars[i]; + b.Height = dataPoints[i] / 100 * RenderSize.Height; + b.Width = barWidth; + b.Margin = new Thickness(i * barSpacing, 0, 0, 0); + } + }, DispatcherPriority.ApplicationIdle); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/SpectrumProvider.cs b/FModel/Views/Resources/Controls/Aup/SpectrumProvider.cs index 1e9a3869..28bcc248 100644 --- a/FModel/Views/Resources/Controls/Aup/SpectrumProvider.cs +++ b/FModel/Views/Resources/Controls/Aup/SpectrumProvider.cs @@ -2,48 +2,47 @@ using System.Collections.Generic; using CSCore.DSP; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +public class SpectrumProvider : FftProvider { - public class SpectrumProvider : FftProvider + private readonly int _sampleRate; + private readonly List _contexts = new(); + + public SpectrumProvider(int channels, int sampleRate, FftSize fftSize) : base(channels, fftSize) { - private readonly int _sampleRate; - private readonly List _contexts = new(); + if (sampleRate <= 0) + throw new ArgumentOutOfRangeException(nameof(sampleRate)); - public SpectrumProvider(int channels, int sampleRate, FftSize fftSize) : base(channels, fftSize) - { - if (sampleRate <= 0) - throw new ArgumentOutOfRangeException(nameof(sampleRate)); + _sampleRate = sampleRate; + } - _sampleRate = sampleRate; - } + public int GetFftBandIndex(float frequency) + { + var f = _sampleRate / 2.0; + return (int) (frequency / f * ((int) FftSize / 2)); + } - public int GetFftBandIndex(float frequency) - { - var f = _sampleRate / 2.0; - return (int) (frequency / f * ((int) FftSize / 2)); - } + public bool GetFftData(float[] fftResultBuffer, object context) + { + if (_contexts.Contains(context)) + return false; - public bool GetFftData(float[] fftResultBuffer, object context) - { - if (_contexts.Contains(context)) - return false; + _contexts.Add(context); + GetFftData(fftResultBuffer); + return true; + } - _contexts.Add(context); - GetFftData(fftResultBuffer); - return true; - } - - public override void Add(float[] samples, int count) - { - base.Add(samples, count); - if (count > 0) - _contexts.Clear(); - } - - public override void Add(float left, float right) - { - base.Add(left, right); + public override void Add(float[] samples, int count) + { + base.Add(samples, count); + if (count > 0) _contexts.Clear(); - } + } + + public override void Add(float left, float right) + { + base.Add(left, right); + _contexts.Clear(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/Timeclock.cs b/FModel/Views/Resources/Controls/Aup/Timeclock.cs index 2c7e7828..f6582880 100644 --- a/FModel/Views/Resources/Controls/Aup/Timeclock.cs +++ b/FModel/Views/Resources/Controls/Aup/Timeclock.cs @@ -3,326 +3,326 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Media; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +[TemplatePart(Name = "PART_Timeclock", Type = typeof(Grid))] +[TemplatePart(Name = "PART_Time", Type = typeof(TextBlock))] +public sealed class Timeclock : UserControl { - [TemplatePart(Name = "PART_Timeclock", Type = typeof(Grid))] - [TemplatePart(Name = "PART_Time", Type = typeof(TextBlock))] - public sealed class Timeclock : UserControl + public enum EClockType { - public enum EClockType + TimeElapsed, + TimeRemaining + } + + private Grid _timeclockGrid; + private TextBlock _timeText; + private const string DefaultTimeFormat = "hh\\:mm\\:ss\\.ff"; + + static Timeclock() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Timeclock), new FrameworkPropertyMetadata(typeof(Timeclock))); + } + + private ISource _source; + public ISource Source + { + get => (ISource) GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register("Source", typeof(ISource), typeof(Timeclock), + new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); + + public EClockType ClockType + { + get => (EClockType) GetValue(ClockTypeProperty); + set => SetValue(ClockTypeProperty, value); + } + public static readonly DependencyProperty ClockTypeProperty = + DependencyProperty.Register("ClockType", typeof(EClockType), typeof(Timeclock), + new UIPropertyMetadata(EClockType.TimeElapsed, OnClockTypeChanged, OnCoerceClockType)); + + public FontFamily LabelFont + { + get => (FontFamily) GetValue(LabelFontProperty); + set => SetValue(LabelFontProperty, value); + } + public static readonly DependencyProperty LabelFontProperty = + DependencyProperty.Register("LabelFont", typeof(FontFamily), typeof(Timeclock), + new UIPropertyMetadata(new FontFamily("Segoe UI"), OnLabelFontChanged, OnCoerceLabelFont)); + + public Brush LabelForeground + { + get => (Brush) GetValue(LabelForegroundProperty); + set => SetValue(LabelForegroundProperty, value); + } + public static readonly DependencyProperty LabelForegroundProperty = + DependencyProperty.Register("LabelForeground", typeof(Brush), typeof(Timeclock), + new UIPropertyMetadata(Brushes.Coral, OnLabelForegroundChanged, OnCoerceLabelForeground)); + + public FontFamily TimeFont + { + get => (FontFamily) GetValue(TimeFontProperty); + set => SetValue(TimeFontProperty, value); + } + public static readonly DependencyProperty TimeFontProperty = + DependencyProperty.Register("TimeFont", typeof(FontFamily), typeof(Timeclock), + new UIPropertyMetadata(new FontFamily("Ebrima"), OnTimeFontChanged, OnCoerceTimeFont)); + + public Brush TimeForeground + { + get => (Brush) GetValue(TimeForegroundProperty); + set => SetValue(TimeForegroundProperty, value); + } + public static readonly DependencyProperty TimeForegroundProperty = + DependencyProperty.Register("TimeForeground", typeof(Brush), typeof(Timeclock), + new UIPropertyMetadata(Brushes.Silver, OnTimeForegroundChanged, OnCoerceTimeForeground)); + + public CornerRadius CornerRadius + { + get => (CornerRadius) GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + public static readonly DependencyProperty CornerRadiusProperty = + DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(Timeclock), + new UIPropertyMetadata(new CornerRadius(3), OnCornerRadiusChanged, OnCoerceCornerRadius)); + + public string Label + { + get => (string) GetValue(LabelProperty); + set => SetValue(LabelProperty, value); + } + public static readonly DependencyProperty LabelProperty = + DependencyProperty.Register("Label", typeof(string), typeof(Timeclock), + new UIPropertyMetadata(string.Empty, OnLabelChanged, OnCoerceLabel)); + + public string TimeFormat + { + get => (string) GetValue(TimeFormatProperty); + set => SetValue(TimeFormatProperty, value); + } + public static readonly DependencyProperty TimeFormatProperty = + DependencyProperty.Register("TimeFormat", typeof(string), typeof(Timeclock), + new UIPropertyMetadata(DefaultTimeFormat, OnTimeFormatChanged, OnCoerceTimeFormat)); + + private void OnSourceChanged(ISource oldValue, ISource newValue) + { + _source = Source; + _source.SourceEvent += OnSourceEvent; + _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; + OnSourceEvent(this, null); + } + + private void OnSourceEvent(object sender, SourceEventArgs e) + { + if (Source == null) return; + Label = Source.PlayedFile.FileName; + Dispatcher.BeginInvoke((Action) CalculateTime); + } + + private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) + { + if (_timeText == null || e.Property != ESourceProperty.Position) return; + + CalculateTime(); + } + + private ISource OnCoerceSource(ISource value) => value; + private static object OnCoerceSource(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceSource((ISource) value); + return value; + } + + private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); + } + + private EClockType OnCoerceClockType(EClockType value) => value; + private static object OnCoerceClockType(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceClockType((EClockType) value); + return value; + } + + private void OnClockTypeChanged(EClockType oldValue, EClockType newValue) => CalculateTime(); + private static void OnClockTypeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnClockTypeChanged((EClockType) e.OldValue, (EClockType) e.NewValue); + } + + private FontFamily OnCoerceLabelFont(FontFamily value) => value; + private static object OnCoerceLabelFont(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceLabelFont((FontFamily) value); + return value; + } + + private void OnLabelFontChanged(FontFamily oldValue, FontFamily newValue) + { + } + + private static void OnLabelFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnLabelFontChanged((FontFamily) e.OldValue, (FontFamily) e.NewValue); + } + + private Brush OnCoerceLabelForeground(Brush value) => value; + private static object OnCoerceLabelForeground(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceLabelForeground((Brush) value); + return value; + } + + private void OnLabelForegroundChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnLabelForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnLabelForegroundChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private FontFamily OnCoerceTimeFont(FontFamily value) => value; + private static object OnCoerceTimeFont(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceTimeFont((FontFamily) value); + return value; + } + + private void OnTimeFontChanged(FontFamily oldValue, FontFamily newValue) + { + } + + private static void OnTimeFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnTimeFontChanged((FontFamily) e.OldValue, (FontFamily) e.NewValue); + } + + private Brush OnCoerceTimeForeground(Brush value) => value; + private static object OnCoerceTimeForeground(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceTimeForeground((Brush) value); + return value; + } + + private void OnTimeForegroundChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnTimeForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnTimeForegroundChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private CornerRadius OnCoerceCornerRadius(CornerRadius value) => value; + private static object OnCoerceCornerRadius(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceCornerRadius((CornerRadius) value); + return value; + } + + private void OnCornerRadiusChanged(CornerRadius oldValue, CornerRadius newValue) + { + } + + private static void OnCornerRadiusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnCornerRadiusChanged((CornerRadius) e.OldValue, (CornerRadius) e.NewValue); + } + + private string OnCoerceLabel(string value) => value; + private static object OnCoerceLabel(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceLabel((string) value); + return value; + } + + private void OnLabelChanged(string oldValue, string newValue) + { + } + + private static void OnLabelChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnLabelChanged((string) e.OldValue, (string) e.NewValue); + } + + private string OnCoerceTimeFormat(string value) => value; + private static object OnCoerceTimeFormat(DependencyObject o, object value) + { + if (o is Timeclock timeclock) + return timeclock.OnCoerceTimeFormat((string) value); + return value; + } + + private void OnTimeFormatChanged(string oldValue, string newValue) + { + try { - TimeElapsed, - TimeRemaining + TimeSpan.Zero.ToString(newValue); + } + catch + { + TimeFormat = DefaultTimeFormat; } - private Grid _timeclockGrid; - private TextBlock _timeText; - private const string DefaultTimeFormat = "hh\\:mm\\:ss\\.ff"; + CalculateTime(); + } - static Timeclock() + private static void OnTimeFormatChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeclock timeclock) + timeclock.OnTimeFormatChanged((string) e.OldValue, (string) e.NewValue); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _timeclockGrid = GetTemplateChild("PART_Timeclock") as Grid; + _timeText = GetTemplateChild("PART_Time") as TextBlock; + _timeclockGrid.CacheMode = new BitmapCache(); + + OnSourceEvent(this, null); + } + + private void CalculateTime() + { + if (_source != null) { - DefaultStyleKeyProperty.OverrideMetadata(typeof(Timeclock), new FrameworkPropertyMetadata(typeof(Timeclock))); - } - - private ISource _source; - public ISource Source - { - get => (ISource) GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - public static readonly DependencyProperty SourceProperty = - DependencyProperty.Register("Source", typeof(ISource), typeof(Timeclock), - new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); - - public EClockType ClockType - { - get => (EClockType) GetValue(ClockTypeProperty); - set => SetValue(ClockTypeProperty, value); - } - public static readonly DependencyProperty ClockTypeProperty = - DependencyProperty.Register("ClockType", typeof(EClockType), typeof(Timeclock), - new UIPropertyMetadata(EClockType.TimeElapsed, OnClockTypeChanged, OnCoerceClockType)); - - public FontFamily LabelFont - { - get => (FontFamily) GetValue(LabelFontProperty); - set => SetValue(LabelFontProperty, value); - } - public static readonly DependencyProperty LabelFontProperty = - DependencyProperty.Register("LabelFont", typeof(FontFamily), typeof(Timeclock), - new UIPropertyMetadata(new FontFamily("Segoe UI"), OnLabelFontChanged, OnCoerceLabelFont)); - - public Brush LabelForeground - { - get => (Brush) GetValue(LabelForegroundProperty); - set => SetValue(LabelForegroundProperty, value); - } - public static readonly DependencyProperty LabelForegroundProperty = - DependencyProperty.Register("LabelForeground", typeof(Brush), typeof(Timeclock), - new UIPropertyMetadata(Brushes.Coral, OnLabelForegroundChanged, OnCoerceLabelForeground)); - - public FontFamily TimeFont - { - get => (FontFamily) GetValue(TimeFontProperty); - set => SetValue(TimeFontProperty, value); - } - public static readonly DependencyProperty TimeFontProperty = - DependencyProperty.Register("TimeFont", typeof(FontFamily), typeof(Timeclock), - new UIPropertyMetadata(new FontFamily("Ebrima"), OnTimeFontChanged, OnCoerceTimeFont)); - - public Brush TimeForeground - { - get => (Brush) GetValue(TimeForegroundProperty); - set => SetValue(TimeForegroundProperty, value); - } - public static readonly DependencyProperty TimeForegroundProperty = - DependencyProperty.Register("TimeForeground", typeof(Brush), typeof(Timeclock), - new UIPropertyMetadata(Brushes.Silver, OnTimeForegroundChanged, OnCoerceTimeForeground)); - - public CornerRadius CornerRadius - { - get => (CornerRadius) GetValue(CornerRadiusProperty); - set => SetValue(CornerRadiusProperty, value); - } - public static readonly DependencyProperty CornerRadiusProperty = - DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(Timeclock), - new UIPropertyMetadata(new CornerRadius(3), OnCornerRadiusChanged, OnCoerceCornerRadius)); - - public string Label - { - get => (string) GetValue(LabelProperty); - set => SetValue(LabelProperty, value); - } - public static readonly DependencyProperty LabelProperty = - DependencyProperty.Register("Label", typeof(string), typeof(Timeclock), - new UIPropertyMetadata(string.Empty, OnLabelChanged, OnCoerceLabel)); - - public string TimeFormat - { - get => (string) GetValue(TimeFormatProperty); - set => SetValue(TimeFormatProperty, value); - } - public static readonly DependencyProperty TimeFormatProperty = - DependencyProperty.Register("TimeFormat", typeof(string), typeof(Timeclock), - new UIPropertyMetadata(DefaultTimeFormat, OnTimeFormatChanged, OnCoerceTimeFormat)); - - private void OnSourceChanged(ISource oldValue, ISource newValue) - { - _source = Source; - _source.SourceEvent += OnSourceEvent; - _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; - OnSourceEvent(this, null); - } - - private void OnSourceEvent(object sender, SourceEventArgs e) - { - if (Source == null) return; - Label = Source.PlayedFile.FileName; - Dispatcher.BeginInvoke((Action) CalculateTime); - } - - private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) - { - if (_timeText == null || e.Property != ESourceProperty.Position) return; - - CalculateTime(); - } - - private ISource OnCoerceSource(ISource value) => value; - private static object OnCoerceSource(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceSource((ISource) value); - return value; - } - - private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); - } - - private EClockType OnCoerceClockType(EClockType value) => value; - private static object OnCoerceClockType(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceClockType((EClockType) value); - return value; - } - - private void OnClockTypeChanged(EClockType oldValue, EClockType newValue) => CalculateTime(); - private static void OnClockTypeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnClockTypeChanged((EClockType) e.OldValue, (EClockType) e.NewValue); - } - - private FontFamily OnCoerceLabelFont(FontFamily value) => value; - private static object OnCoerceLabelFont(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceLabelFont((FontFamily) value); - return value; - } - - private void OnLabelFontChanged(FontFamily oldValue, FontFamily newValue) - { - } - - private static void OnLabelFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnLabelFontChanged((FontFamily) e.OldValue, (FontFamily) e.NewValue); - } - - private Brush OnCoerceLabelForeground(Brush value) => value; - private static object OnCoerceLabelForeground(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceLabelForeground((Brush) value); - return value; - } - - private void OnLabelForegroundChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnLabelForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnLabelForegroundChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private FontFamily OnCoerceTimeFont(FontFamily value) => value; - private static object OnCoerceTimeFont(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceTimeFont((FontFamily) value); - return value; - } - - private void OnTimeFontChanged(FontFamily oldValue, FontFamily newValue) - { - } - - private static void OnTimeFontChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnTimeFontChanged((FontFamily) e.OldValue, (FontFamily) e.NewValue); - } - - private Brush OnCoerceTimeForeground(Brush value) => value; - private static object OnCoerceTimeForeground(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceTimeForeground((Brush) value); - return value; - } - - private void OnTimeForegroundChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnTimeForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnTimeForegroundChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private CornerRadius OnCoerceCornerRadius(CornerRadius value) => value; - private static object OnCoerceCornerRadius(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceCornerRadius((CornerRadius) value); - return value; - } - - private void OnCornerRadiusChanged(CornerRadius oldValue, CornerRadius newValue) - { - } - - private static void OnCornerRadiusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnCornerRadiusChanged((CornerRadius) e.OldValue, (CornerRadius) e.NewValue); - } - - private string OnCoerceLabel(string value) => value; - private static object OnCoerceLabel(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceLabel((string) value); - return value; - } - - private void OnLabelChanged(string oldValue, string newValue) - { - } - - private static void OnLabelChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnLabelChanged((string) e.OldValue, (string) e.NewValue); - } - - private string OnCoerceTimeFormat(string value) => value; - private static object OnCoerceTimeFormat(DependencyObject o, object value) - { - if (o is Timeclock timeclock) - return timeclock.OnCoerceTimeFormat((string) value); - return value; - } - - private void OnTimeFormatChanged(string oldValue, string newValue) - { - try + var position = _source.PlayedFile.Position; + var length = _source.PlayedFile.Duration; + Dispatcher.BeginInvoke((Action) delegate { - TimeSpan.Zero.ToString(newValue); - } - catch - { - TimeFormat = DefaultTimeFormat; - } - - CalculateTime(); - } - private static void OnTimeFormatChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeclock timeclock) - timeclock.OnTimeFormatChanged((string) e.OldValue, (string) e.NewValue); - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - _timeclockGrid = GetTemplateChild("PART_Timeclock") as Grid; - _timeText = GetTemplateChild("PART_Time") as TextBlock; - _timeclockGrid.CacheMode = new BitmapCache(); - - OnSourceEvent(this, null); - } - - private void CalculateTime() - { - if (_source != null) - { - var position = _source.PlayedFile.Position; - var length = _source.PlayedFile.Duration; - Dispatcher.BeginInvoke((Action) delegate + _timeText.Text = ClockType switch { - _timeText.Text = ClockType switch - { - EClockType.TimeElapsed => position.ToString(TimeFormat), - EClockType.TimeRemaining => (length - position).ToString(TimeFormat), - _ => _timeText.Text - }; - }); - } - else - { - Dispatcher.BeginInvoke((Action) delegate { _timeText.Text = TimeSpan.Zero.ToString(TimeFormat); }); - } + EClockType.TimeElapsed => position.ToString(TimeFormat), + EClockType.TimeRemaining => (length - position).ToString(TimeFormat), + _ => _timeText.Text + }; + }); + } + else + { + Dispatcher.BeginInvoke((Action) delegate { _timeText.Text = TimeSpan.Zero.ToString(TimeFormat); }); } } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Aup/Timeline.cs b/FModel/Views/Resources/Controls/Aup/Timeline.cs index 7709b4bc..d4953cb2 100644 --- a/FModel/Views/Resources/Controls/Aup/Timeline.cs +++ b/FModel/Views/Resources/Controls/Aup/Timeline.cs @@ -4,346 +4,346 @@ using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; -namespace FModel.Views.Resources.Controls.Aup +namespace FModel.Views.Resources.Controls.Aup; + +[TemplatePart(Name = "PART_ControlContainer")] +[TemplatePart(Name = "PART_TimelineArea", Type = typeof(Border))] +[TemplatePart(Name = "PART_MousePosition", Type = typeof(Rectangle))] +[TemplatePart(Name = "PART_Timeline", Type = typeof(Grid))] +[TemplatePart(Name = "PART_Length", Type = typeof(Canvas))] +[TemplatePart(Name = "PART_ProgressLine", Type = typeof(Border))] +public sealed class Timeline : UserControl { - [TemplatePart(Name = "PART_ControlContainer")] - [TemplatePart(Name = "PART_TimelineArea", Type = typeof(Border))] - [TemplatePart(Name = "PART_MousePosition", Type = typeof(Rectangle))] - [TemplatePart(Name = "PART_Timeline", Type = typeof(Grid))] - [TemplatePart(Name = "PART_Length", Type = typeof(Canvas))] - [TemplatePart(Name = "PART_ProgressLine", Type = typeof(Border))] - public sealed class Timeline : UserControl + private Grid _controlContainer; + private Border _timelineArea; + private Rectangle _position; + private Grid _timelineGrid; + private Grid _lengthGrid; + private Border _progressLine; + + static Timeline() { - private Grid _controlContainer; - private Border _timelineArea; - private Rectangle _position; - private Grid _timelineGrid; - private Grid _lengthGrid; - private Border _progressLine; + DefaultStyleKeyProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata(typeof(Timeline))); + } - static Timeline() + private ISource _source; + public ISource Source + { + get => (ISource) GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register("Source", typeof(ISource), typeof(Timeline), + new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); + + public TimeSpan Position + { + get => (TimeSpan) GetValue(PositionProperty); + set => SetValue(PositionProperty, value); + } + public static readonly DependencyProperty PositionProperty = + DependencyProperty.Register("Position", typeof(TimeSpan), typeof(Timeline), + new UIPropertyMetadata(TimeSpan.Zero, OnPositionChanged, OnCoercePosition)); + + public Brush TickBrush + { + get => (Brush) GetValue(TickBrushProperty); + set => SetValue(TickBrushProperty, value); + } + public static readonly DependencyProperty TickBrushProperty = + DependencyProperty.Register("TickBrush", typeof(Brush), typeof(Timeline), + new UIPropertyMetadata(Brushes.Red, OnTickBrushChanged, OnCoerceTickBrush)); + + public Brush TimeBrush + { + get => (Brush) GetValue(TimeBrushProperty); + set => SetValue(TimeBrushProperty, value); + } + public static readonly DependencyProperty TimeBrushProperty = + DependencyProperty.Register("TimeBrush", typeof(Brush), typeof(Timeline), + new UIPropertyMetadata(Brushes.Blue, OnTimeBrushChanged, OnCoerceTimeBrush)); + + public Brush ProgressLineBrush + { + get => (Brush) GetValue(ProgressLineBrushProperty); + set => SetValue(ProgressLineBrushProperty, value); + } + public static readonly DependencyProperty ProgressLineBrushProperty = + DependencyProperty.Register("ProgressLineBrush", typeof(Brush), typeof(Timeline), + new UIPropertyMetadata(Brushes.Violet, OnProgressLineBrushChanged, OnCoerceProgressLineBrush)); + + public Brush ProgressBrush + { + get => (Brush) GetValue(ProgressBrushProperty); + set => SetValue(ProgressBrushProperty, value); + } + public static readonly DependencyProperty ProgressBrushProperty = + DependencyProperty.Register("ProgressBrush", typeof(Brush), typeof(Timeline), + new UIPropertyMetadata(Brushes.DarkGreen, OnProgressBrushChanged, OnCoerceProgressBrush)); + + public Brush MousePositionBrush + { + get => (Brush) GetValue(MousePositionBrushProperty); + set => SetValue(MousePositionBrushProperty, value); + } + public static readonly DependencyProperty MousePositionBrushProperty = + DependencyProperty.Register("MousePositionBrush", typeof(Brush), typeof(Timeline), + new UIPropertyMetadata(Brushes.Brown, OnMousePositionBrushChanged, OnCoerceMousePositionBrush)); + + private void OnSourceChanged(ISource oldValue, ISource newValue) + { + _source = Source; + _source.SourceEvent += OnSourceEvent; + _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; + OnSourceEvent(this, null); + } + + private void OnSourceEvent(object sender, SourceEventArgs e) + { + if (Source == null) return; + Dispatcher.BeginInvoke((Action) UpdateTimeline); + } + + private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) + { + if (e.Property != ESourceProperty.Position) return; + + var position = (TimeSpan) e.Value; + Dispatcher.BeginInvoke((Action) delegate { - DefaultStyleKeyProperty.OverrideMetadata(typeof(Timeline), new FrameworkPropertyMetadata(typeof(Timeline))); - } + Position = position; + if (_lengthGrid == null) return; - private ISource _source; - public ISource Source - { - get => (ISource) GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - public static readonly DependencyProperty SourceProperty = - DependencyProperty.Register("Source", typeof(ISource), typeof(Timeline), - new UIPropertyMetadata(null, OnSourceChanged, OnCoerceSource)); - - public TimeSpan Position - { - get => (TimeSpan) GetValue(PositionProperty); - set => SetValue(PositionProperty, value); - } - public static readonly DependencyProperty PositionProperty = - DependencyProperty.Register("Position", typeof(TimeSpan), typeof(Timeline), - new UIPropertyMetadata(TimeSpan.Zero, OnPositionChanged, OnCoercePosition)); - - public Brush TickBrush - { - get => (Brush) GetValue(TickBrushProperty); - set => SetValue(TickBrushProperty, value); - } - public static readonly DependencyProperty TickBrushProperty = - DependencyProperty.Register("TickBrush", typeof(Brush), typeof(Timeline), - new UIPropertyMetadata(Brushes.Red, OnTickBrushChanged, OnCoerceTickBrush)); - - public Brush TimeBrush - { - get => (Brush) GetValue(TimeBrushProperty); - set => SetValue(TimeBrushProperty, value); - } - public static readonly DependencyProperty TimeBrushProperty = - DependencyProperty.Register("TimeBrush", typeof(Brush), typeof(Timeline), - new UIPropertyMetadata(Brushes.Blue, OnTimeBrushChanged, OnCoerceTimeBrush)); - - public Brush ProgressLineBrush - { - get => (Brush) GetValue(ProgressLineBrushProperty); - set => SetValue(ProgressLineBrushProperty, value); - } - public static readonly DependencyProperty ProgressLineBrushProperty = - DependencyProperty.Register("ProgressLineBrush", typeof(Brush), typeof(Timeline), - new UIPropertyMetadata(Brushes.Violet, OnProgressLineBrushChanged, OnCoerceProgressLineBrush)); - - public Brush ProgressBrush - { - get => (Brush) GetValue(ProgressBrushProperty); - set => SetValue(ProgressBrushProperty, value); - } - public static readonly DependencyProperty ProgressBrushProperty = - DependencyProperty.Register("ProgressBrush", typeof(Brush), typeof(Timeline), - new UIPropertyMetadata(Brushes.DarkGreen, OnProgressBrushChanged, OnCoerceProgressBrush)); - - public Brush MousePositionBrush - { - get => (Brush) GetValue(MousePositionBrushProperty); - set => SetValue(MousePositionBrushProperty, value); - } - public static readonly DependencyProperty MousePositionBrushProperty = - DependencyProperty.Register("MousePositionBrush", typeof(Brush), typeof(Timeline), - new UIPropertyMetadata(Brushes.Brown, OnMousePositionBrushChanged, OnCoerceMousePositionBrush)); - - private void OnSourceChanged(ISource oldValue, ISource newValue) - { - _source = Source; - _source.SourceEvent += OnSourceEvent; - _source.SourcePropertyChangedEvent += OnSourcePropertyChangedEvent; - OnSourceEvent(this, null); - } - - private void OnSourceEvent(object sender, SourceEventArgs e) - { - if (Source == null) return; - Dispatcher.BeginInvoke((Action) UpdateTimeline); - } - - private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e) - { - if (e.Property != ESourceProperty.Position) return; - - var position = (TimeSpan) e.Value; - Dispatcher.BeginInvoke((Action) delegate + var x = 0d; + if (_source.PlayedFile.Duration.TotalMilliseconds != 0) { - Position = position; - if (_lengthGrid == null) return; + x = position.TotalMilliseconds / _source.PlayedFile.Duration.TotalMilliseconds * _lengthGrid.RenderSize.Width; + } - var x = 0d; - if (_source.PlayedFile.Duration.TotalMilliseconds != 0) - { - x = position.TotalMilliseconds / _source.PlayedFile.Duration.TotalMilliseconds * _lengthGrid.RenderSize.Width; - } + _progressLine.Width = x; + }); + } - _progressLine.Width = x; - }); - } + private ISource OnCoerceSource(ISource value) => value; + private static object OnCoerceSource(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceSource((ISource) value); + return value; + } - private ISource OnCoerceSource(ISource value) => value; - private static object OnCoerceSource(DependencyObject o, object value) + private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); + } + + private TimeSpan OnCoercePosition(TimeSpan value) => value; + private static object OnCoercePosition(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoercePosition((TimeSpan) value); + return value; + } + + private void OnPositionChanged(TimeSpan oldValue, TimeSpan newValue) + { + } + + private static void OnPositionChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnPositionChanged((TimeSpan) e.OldValue, (TimeSpan) e.NewValue); + } + + private Brush OnCoerceTickBrush(Brush value) => value; + private static object OnCoerceTickBrush(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceTickBrush((Brush) value); + return value; + } + + private void OnTickBrushChanged(Brush oldValue, Brush newValue) => UpdateTimeline(); + private static void OnTickBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnTickBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private Brush OnCoerceTimeBrush(Brush value) => value; + private static object OnCoerceTimeBrush(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceTimeBrush((Brush) value); + return value; + } + + private void OnTimeBrushChanged(Brush oldValue, Brush newValue) => UpdateTimeline(); + private static void OnTimeBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnTimeBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private Brush OnCoerceProgressLineBrush(Brush value) => value; + private static object OnCoerceProgressLineBrush(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceProgressLineBrush((Brush) value); + return value; + } + + private void OnProgressLineBrushChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnProgressLineBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnProgressLineBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private Brush OnCoerceProgressBrush(Brush value) => value; + private static object OnCoerceProgressBrush(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceProgressBrush((Brush) value); + return value; + } + + private void OnProgressBrushChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnProgressBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnProgressBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + private Brush OnCoerceMousePositionBrush(Brush value) => value; + private static object OnCoerceMousePositionBrush(DependencyObject o, object value) + { + if (o is Timeline timeline) + return timeline.OnCoerceMousePositionBrush((Brush) value); + return value; + } + + private void OnMousePositionBrushChanged(Brush oldValue, Brush newValue) + { + } + + private static void OnMousePositionBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + if (o is Timeline timeline) + timeline.OnMousePositionBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _controlContainer = GetTemplateChild("PART_ControlContainer") as Grid; + _timelineGrid = GetTemplateChild("PART_Timeline") as Grid; + _lengthGrid = GetTemplateChild("PART_Length") as Grid; + _progressLine = GetTemplateChild("PART_ProgressLine") as Border; + _timelineGrid.CacheMode = new BitmapCache(); + + _timelineArea = GetTemplateChild("PART_TimelineArea") as Border; + _position = GetTemplateChild("PART_MousePosition") as Rectangle; + _timelineArea.MouseEnter += (_, _) => _position.Visibility = Visibility.Visible; + _timelineArea.MouseLeave += (_, _) => _position.Visibility = Visibility.Collapsed; + _timelineArea.MouseMove += (_, args) + => _position.Margin = new Thickness(args.GetPosition(_timelineArea).X, 0, _position.ActualWidth, 0); + _timelineArea.MouseLeftButtonDown += (_, args) + => Source.SkipTo(args.GetPosition(_timelineArea).X / _timelineArea.ActualWidth); + + OnSourceEvent(this, null); + } + + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) + { + base.OnRenderSizeChanged(sizeInfo); + UpdateTimeline(); + } + + private readonly Border _bottomBorder = new() + { + Height = 1, + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Stretch + }; + + private void UpdateTimeline() + { + if (_source == null || _source.PlayedFile.Duration == TimeSpan.Zero || _lengthGrid == null || + _lengthGrid.RenderSize.Width < 1 || _lengthGrid.RenderSize.Height < 1) return; + + _lengthGrid.Children.Clear(); + + // freeze brushes + var tickBrush = TickBrush.Clone(); + var timeBrush = TimeBrush.Clone(); + tickBrush.Freeze(); + timeBrush.Freeze(); + + // Draw the bottom border + _bottomBorder.Background = tickBrush; + _lengthGrid.Children.Add(_bottomBorder); + + // Determine the number of major ticks that we should display. + // This depends on the width of the timeline. + var width = _lengthGrid.RenderSize.Width; + var majorTickCount = Math.Floor(width / 100); + var totalSeconds = _source.PlayedFile.Duration.TotalSeconds; + var majorTickSecondInterval = Math.Floor(totalSeconds / majorTickCount); + majorTickSecondInterval = Math.Ceiling(majorTickSecondInterval / 10) * 10; + var minorTickInterval = majorTickSecondInterval / 5 == 0 ? 1 : majorTickSecondInterval / 5; + var minorTickCount = totalSeconds / minorTickInterval; + + for (var i = 0; i < minorTickCount; i++) { - if (o is Timeline timeline) - return timeline.OnCoerceSource((ISource) value); - return value; - } + var interval = i * minorTickInterval; + var positionPercent = interval / totalSeconds; + var x = positionPercent * width; - private static void OnSourceChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnSourceChanged((ISource) e.OldValue, (ISource) e.NewValue); - } - - private TimeSpan OnCoercePosition(TimeSpan value) => value; - private static object OnCoercePosition(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoercePosition((TimeSpan) value); - return value; - } - - private void OnPositionChanged(TimeSpan oldValue, TimeSpan newValue) - { - } - - private static void OnPositionChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnPositionChanged((TimeSpan) e.OldValue, (TimeSpan) e.NewValue); - } - - private Brush OnCoerceTickBrush(Brush value) => value; - private static object OnCoerceTickBrush(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoerceTickBrush((Brush) value); - return value; - } - - private void OnTickBrushChanged(Brush oldValue, Brush newValue) => UpdateTimeline(); - private static void OnTickBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnTickBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private Brush OnCoerceTimeBrush(Brush value) => value; - private static object OnCoerceTimeBrush(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoerceTimeBrush((Brush) value); - return value; - } - - private void OnTimeBrushChanged(Brush oldValue, Brush newValue) => UpdateTimeline(); - private static void OnTimeBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnTimeBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private Brush OnCoerceProgressLineBrush(Brush value) => value; - private static object OnCoerceProgressLineBrush(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoerceProgressLineBrush((Brush) value); - return value; - } - - private void OnProgressLineBrushChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnProgressLineBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnProgressLineBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private Brush OnCoerceProgressBrush(Brush value) => value; - private static object OnCoerceProgressBrush(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoerceProgressBrush((Brush) value); - return value; - } - - private void OnProgressBrushChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnProgressBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnProgressBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - private Brush OnCoerceMousePositionBrush(Brush value) => value; - private static object OnCoerceMousePositionBrush(DependencyObject o, object value) - { - if (o is Timeline timeline) - return timeline.OnCoerceMousePositionBrush((Brush) value); - return value; - } - - private void OnMousePositionBrushChanged(Brush oldValue, Brush newValue) - { - } - - private static void OnMousePositionBrushChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) - { - if (o is Timeline timeline) - timeline.OnMousePositionBrushChanged((Brush) e.OldValue, (Brush) e.NewValue); - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - _controlContainer = GetTemplateChild("PART_ControlContainer") as Grid; - _timelineGrid = GetTemplateChild("PART_Timeline") as Grid; - _lengthGrid = GetTemplateChild("PART_Length") as Grid; - _progressLine = GetTemplateChild("PART_ProgressLine") as Border; - _timelineGrid.CacheMode = new BitmapCache(); - - _timelineArea = GetTemplateChild("PART_TimelineArea") as Border; - _position = GetTemplateChild("PART_MousePosition") as Rectangle; - _timelineArea.MouseEnter += (_, _) => _position.Visibility = Visibility.Visible; - _timelineArea.MouseLeave += (_, _) => _position.Visibility = Visibility.Collapsed; - _timelineArea.MouseMove += (_, args) - => _position.Margin = new Thickness(args.GetPosition(_timelineArea).X, 0, _position.ActualWidth, 0); - _timelineArea.MouseLeftButtonDown += (_, args) - => Source.SkipTo(args.GetPosition(_timelineArea).X / _timelineArea.ActualWidth); - - OnSourceEvent(this, null); - } - - protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) - { - base.OnRenderSizeChanged(sizeInfo); - UpdateTimeline(); - } - - private readonly Border _bottomBorder = new() - { - Height = 1, - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Stretch - }; - private void UpdateTimeline() - { - if (_source == null || _source.PlayedFile.Duration == TimeSpan.Zero || _lengthGrid == null || - _lengthGrid.RenderSize.Width < 1 || _lengthGrid.RenderSize.Height < 1) return; - - _lengthGrid.Children.Clear(); - - // freeze brushes - var tickBrush = TickBrush.Clone(); - var timeBrush = TimeBrush.Clone(); - tickBrush.Freeze(); - timeBrush.Freeze(); - - // Draw the bottom border - _bottomBorder.Background = tickBrush; - _lengthGrid.Children.Add(_bottomBorder); - - // Determine the number of major ticks that we should display. - // This depends on the width of the timeline. - var width = _lengthGrid.RenderSize.Width; - var majorTickCount = Math.Floor(width / 100); - var totalSeconds = _source.PlayedFile.Duration.TotalSeconds; - var majorTickSecondInterval = Math.Floor(totalSeconds / majorTickCount); - majorTickSecondInterval = Math.Ceiling(majorTickSecondInterval / 10) * 10; - var minorTickInterval = majorTickSecondInterval / 5 == 0 ? 1 : majorTickSecondInterval / 5; - var minorTickCount = totalSeconds / minorTickInterval; - - for (var i = 0; i < minorTickCount; i++) + if (interval % majorTickSecondInterval != 0) { - var interval = i * minorTickInterval; - var positionPercent = interval / totalSeconds; - var x = positionPercent * width; - - if (interval % majorTickSecondInterval != 0) + // Minor tick + _lengthGrid.Children.Add(new Border { - // Minor tick - _lengthGrid.Children.Add(new Border - { - Width = 1, - Height = 7, - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, - Background = tickBrush, - Margin = new Thickness(x, 0, 0, 0) - }); - } - else + Width = 1, + Height = 7, + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + Background = tickBrush, + Margin = new Thickness(x, 0, 0, 0) + }); + } + else + { + // Major tick + _lengthGrid.Children.Add(new Border { - // Major tick - _lengthGrid.Children.Add(new Border - { - Width = 1, - VerticalAlignment = VerticalAlignment.Stretch, - HorizontalAlignment = HorizontalAlignment.Left, - Background = tickBrush, - Margin = new Thickness(x, 0, 0, 0) - }); + Width = 1, + VerticalAlignment = VerticalAlignment.Stretch, + HorizontalAlignment = HorizontalAlignment.Left, + Background = tickBrush, + Margin = new Thickness(x, 0, 0, 0) + }); - // Add time label - var time = new TextBlock - { - VerticalAlignment = VerticalAlignment.Bottom, - HorizontalAlignment = HorizontalAlignment.Left, - Foreground = timeBrush - }; - var ts = TimeSpan.FromSeconds(interval); - time.Text = ts.TotalHours >= 1 ? ts.ToString(@"h\:mm\:ss") : ts.ToString(@"mm\:ss"); - time.Margin = new Thickness(x + 5, 0, 0, 7); - _lengthGrid.Children.Add(time); - } + // Add time label + var time = new TextBlock + { + VerticalAlignment = VerticalAlignment.Bottom, + HorizontalAlignment = HorizontalAlignment.Left, + Foreground = timeBrush + }; + var ts = TimeSpan.FromSeconds(interval); + time.Text = ts.TotalHours >= 1 ? ts.ToString(@"h\:mm\:ss") : ts.ToString(@"mm\:ss"); + time.Margin = new Thickness(x + 5, 0, 0, 7); + _lengthGrid.Children.Add(time); } } } diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index b05eb17d..4833d57e 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -12,236 +12,242 @@ using FModel.ViewModels; using ICSharpCode.AvalonEdit; using SkiaSharp; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +/// +/// Logique d'interaction pour AvalonEditor.xaml +/// +public partial class AvalonEditor { - /// - /// Logique d'interaction pour AvalonEditor.xaml - /// - public partial class AvalonEditor + public static TextEditor YesWeEditor; + public static System.Windows.Controls.TextBox YesWeSearch; + private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private readonly System.Windows.Controls.ToolTip _toolTip = new(); + private readonly Dictionary> _savedCarets = new(); + private NavigationList _caretsOffsets { - public static TextEditor YesWeEditor; - public static System.Windows.Controls.TextBox YesWeSearch; - private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private readonly System.Windows.Controls.ToolTip _toolTip = new(); - private readonly Dictionary> _savedCarets = new(); - private NavigationList _caretsOffsets + get => MyAvalonEditor.Document != null + ? _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList()) + : new NavigationList(); + } + private bool _ignoreCaret = true; + + public AvalonEditor() + { + CommandBindings.Add(new CommandBinding(NavigationCommands.Search, (_, e) => FindNext(e.Parameter != null))); + InitializeComponent(); + + YesWeEditor = MyAvalonEditor; + YesWeSearch = WpfSuckMyDick; + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); + + ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose; + } + + private void OnPreviewKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { - get => MyAvalonEditor.Document != null - ? _savedCarets.GetOrAdd(MyAvalonEditor.Document.FileName, () => new NavigationList()) - : new NavigationList(); - } - private bool _ignoreCaret = true; - - public AvalonEditor() - { - CommandBindings.Add(new CommandBinding(NavigationCommands.Search, (_, e) => FindNext(e.Parameter != null))); - InitializeComponent(); - - YesWeEditor = MyAvalonEditor; - YesWeSearch = WpfSuckMyDick; - MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); - MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); - - ApplicationService.ApplicationView.CUE4Parse.TabControl.OnTabRemove += OnTabClose; - } - - private void OnPreviewKeyDown(object sender, KeyEventArgs e) - { - switch (e.Key) - { - case Key.Escape: - ((TabItem) DataContext).HasSearchOpen = false; - break; - case Key.Enter when !Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) && ((TabItem) DataContext).HasSearchOpen: - FindNext(); - break; - case Key.Enter when Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) && ((TabItem) DataContext).HasSearchOpen: - var dc = (TabItem)DataContext; - var old = dc.SearchUp; - dc.SearchUp = true; - FindNext(); - dc.SearchUp = old; - break; - case Key.System: // Alt - if (Keyboard.IsKeyDown(Key.Left)) - { - if (_caretsOffsets.Count == 0) return; - MyAvalonEditor.CaretOffset = _caretsOffsets.MovePrevious; - MyAvalonEditor.TextArea.Caret.BringCaretToView(); - } else if ((Keyboard.IsKeyDown(Key.Right))) - { - if (_caretsOffsets.Count == 0) return; - MyAvalonEditor.CaretOffset = _caretsOffsets.MoveNext; - MyAvalonEditor.TextArea.Caret.BringCaretToView(); - } - break; - } - } - - private void OnMouseHover(object sender, MouseEventArgs e) - { - var pos = MyAvalonEditor.GetPositionFromPoint(e.GetPosition(MyAvalonEditor)); - if (pos == null) return; - - var line = MyAvalonEditor.Document.GetLineByNumber(pos.Value.Line); - var m = _hexColorRegex.Match(MyAvalonEditor.Document.GetText(line.Offset, line.Length)); - if (!m.Success || !m.Groups.TryGetValue("target", out var g)) return; - - var color = SKColor.Parse(g.Value); - _toolTip.PlacementTarget = this; // required for property inheritance - _toolTip.Background = new SolidColorBrush(Color.FromArgb(color.Alpha, color.Red, color.Green, color.Blue)); - _toolTip.Foreground = _toolTip.BorderBrush = PerceivedBrightness(color) > 130 ? Brushes.Black : Brushes.White; - _toolTip.Content = $"#{g.Value}"; - _toolTip.IsOpen = true; - e.Handled = true; - } - - private void OnMouseHoverStopped(object sender, MouseEventArgs e) - { - _toolTip.IsOpen = false; - } - - private int PerceivedBrightness(SKColor c) - { - return (int) Math.Sqrt( - c.Red * c.Red * .299 + - c.Green * c.Green * .587 + - c.Blue * c.Blue * .114); - } - - private void OnTextChanged(object sender, EventArgs e) - { - if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem || - avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text)) - return; - avalonEditor.Document.FileName = tabItem.Directory + '/' + StringExtensions.SubstringBeforeLast(tabItem.Header, '.'); - - if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName)) - _ignoreCaret = true; - - if (!tabItem.ShouldScroll) return; - - var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger); - var line = avalonEditor.Document.GetLineByNumber(lineNumber); - avalonEditor.Select(line.Offset, line.Length); - avalonEditor.ScrollToLine(lineNumber); - } - - private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - if (DataContext is not TabItem tabItem || Keyboard.Modifiers != ModifierKeys.Control) - return; - - var fontSize = tabItem.FontSize + e.Delta / 50.0; - tabItem.FontSize = fontSize switch - { - < 6 => 6, - > 200 => 200, - _ => fontSize - }; - } - - private void OnDeleteSearchClick(object sender, RoutedEventArgs e) - { - ((TabItem) DataContext).TextToFind = string.Empty; - } - - private void FindNext(bool invertLeftRight = false) - { - var viewModel = (TabItem) DataContext; - if (viewModel.Document == null || string.IsNullOrEmpty(viewModel.TextToFind)) - return; - - Regex r; - if (invertLeftRight) - { - viewModel.SearchUp = !viewModel.SearchUp; - r = GetRegEx(); - viewModel.SearchUp = !viewModel.SearchUp; - } - else r = GetRegEx(); - - var rightToLeft = r.Options.HasFlag(RegexOptions.RightToLeft); - var m = r.Match(MyAvalonEditor.Text, rightToLeft ? MyAvalonEditor.SelectionStart : MyAvalonEditor.SelectionStart + MyAvalonEditor.SelectionLength); - if (m.Success) - { - MyAvalonEditor.Select(m.Index, m.Length); - MyAvalonEditor.TextArea.Caret.BringCaretToView(); - } - else - { - // we have reached the end of the document - // start again from the beginning/end, - var oldEditor = MyAvalonEditor; - do + case Key.Escape: + ((TabItem) DataContext).HasSearchOpen = false; + break; + case Key.Enter when !Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) && ((TabItem) DataContext).HasSearchOpen: + FindNext(); + break; + case Key.Enter when Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) && ((TabItem) DataContext).HasSearchOpen: + var dc = (TabItem) DataContext; + var old = dc.SearchUp; + dc.SearchUp = true; + FindNext(); + dc.SearchUp = old; + break; + case Key.System: // Alt + if (Keyboard.IsKeyDown(Key.Left)) { - m = rightToLeft ? r.Match(MyAvalonEditor.Text, MyAvalonEditor.Text.Length - 1) : r.Match(MyAvalonEditor.Text, 0); - if (!m.Success) continue; - MyAvalonEditor.Select(m.Index, m.Length); + if (_caretsOffsets.Count == 0) return; + MyAvalonEditor.CaretOffset = _caretsOffsets.MovePrevious; MyAvalonEditor.TextArea.Caret.BringCaretToView(); - break; - } while (MyAvalonEditor != oldEditor); - } - } + } + else if (Keyboard.IsKeyDown(Key.Right)) + { + if (_caretsOffsets.Count == 0) return; + MyAvalonEditor.CaretOffset = _caretsOffsets.MoveNext; + MyAvalonEditor.TextArea.Caret.BringCaretToView(); + } - private Regex GetRegEx(bool forceLeftToRight = false) - { - Regex r; - var o = RegexOptions.None; - var viewModel = (TabItem) DataContext; - - if (viewModel.SearchUp && !forceLeftToRight) - o |= RegexOptions.RightToLeft; - if (!viewModel.CaseSensitive) - o |= RegexOptions.IgnoreCase; - - if (viewModel.UseRegEx) - { - r = new Regex(viewModel.TextToFind, o); - } - else - { - var s = Regex.Escape(viewModel.TextToFind); - if (viewModel.WholeWord) - s = "\\W" + s + "\\W"; - - r = new Regex(s, o); - } - - return r; - } - - private void OnCloseClick(object sender, RoutedEventArgs e) - { - ((TabItem) DataContext).HasSearchOpen = false; - } - - private void OnTabClose(object sender, EventArgs eventArgs) - { - if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document == null) - return; - - var fileName = e.TabToRemove.Document.FileName; - if (_savedCarets.ContainsKey(fileName)) - _savedCarets.Remove(fileName); - } - - private void SaveCaretLoc(int offset) - { - if (_ignoreCaret) { _ignoreCaret = false; return;} // first always point to the end of the file for some reason - if (_caretsOffsets.Count >= 10) - _caretsOffsets.RemoveAt(0); - if (!_caretsOffsets.Contains(offset)) - { - _caretsOffsets.Add(offset); - _caretsOffsets.CurrentIndex = _caretsOffsets.Count-1; - } - } - - private void OnMouseRelease(object sender, MouseButtonEventArgs e) - { - SaveCaretLoc(MyAvalonEditor.CaretOffset); + break; } } -} + + private void OnMouseHover(object sender, MouseEventArgs e) + { + var pos = MyAvalonEditor.GetPositionFromPoint(e.GetPosition(MyAvalonEditor)); + if (pos == null) return; + + var line = MyAvalonEditor.Document.GetLineByNumber(pos.Value.Line); + var m = _hexColorRegex.Match(MyAvalonEditor.Document.GetText(line.Offset, line.Length)); + if (!m.Success || !m.Groups.TryGetValue("target", out var g)) return; + + var color = SKColor.Parse(g.Value); + _toolTip.PlacementTarget = this; // required for property inheritance + _toolTip.Background = new SolidColorBrush(Color.FromArgb(color.Alpha, color.Red, color.Green, color.Blue)); + _toolTip.Foreground = _toolTip.BorderBrush = PerceivedBrightness(color) > 130 ? Brushes.Black : Brushes.White; + _toolTip.Content = $"#{g.Value}"; + _toolTip.IsOpen = true; + e.Handled = true; + } + + private void OnMouseHoverStopped(object sender, MouseEventArgs e) + { + _toolTip.IsOpen = false; + } + + private int PerceivedBrightness(SKColor c) + { + return (int) Math.Sqrt( + c.Red * c.Red * .299 + + c.Green * c.Green * .587 + + c.Blue * c.Blue * .114); + } + + private void OnTextChanged(object sender, EventArgs e) + { + if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem || + avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text)) + return; + avalonEditor.Document.FileName = tabItem.Directory + '/' + StringExtensions.SubstringBeforeLast(tabItem.Header, '.'); + + if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName)) + _ignoreCaret = true; + + if (!tabItem.ShouldScroll) return; + + var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger); + var line = avalonEditor.Document.GetLineByNumber(lineNumber); + avalonEditor.Select(line.Offset, line.Length); + avalonEditor.ScrollToLine(lineNumber); + } + + private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (DataContext is not TabItem tabItem || Keyboard.Modifiers != ModifierKeys.Control) + return; + + var fontSize = tabItem.FontSize + e.Delta / 50.0; + tabItem.FontSize = fontSize switch + { + < 6 => 6, + > 200 => 200, + _ => fontSize + }; + } + + private void OnDeleteSearchClick(object sender, RoutedEventArgs e) + { + ((TabItem) DataContext).TextToFind = string.Empty; + } + + private void FindNext(bool invertLeftRight = false) + { + var viewModel = (TabItem) DataContext; + if (viewModel.Document == null || string.IsNullOrEmpty(viewModel.TextToFind)) + return; + + Regex r; + if (invertLeftRight) + { + viewModel.SearchUp = !viewModel.SearchUp; + r = GetRegEx(); + viewModel.SearchUp = !viewModel.SearchUp; + } + else r = GetRegEx(); + + var rightToLeft = r.Options.HasFlag(RegexOptions.RightToLeft); + var m = r.Match(MyAvalonEditor.Text, rightToLeft ? MyAvalonEditor.SelectionStart : MyAvalonEditor.SelectionStart + MyAvalonEditor.SelectionLength); + if (m.Success) + { + MyAvalonEditor.Select(m.Index, m.Length); + MyAvalonEditor.TextArea.Caret.BringCaretToView(); + } + else + { + // we have reached the end of the document + // start again from the beginning/end, + var oldEditor = MyAvalonEditor; + do + { + m = rightToLeft ? r.Match(MyAvalonEditor.Text, MyAvalonEditor.Text.Length - 1) : r.Match(MyAvalonEditor.Text, 0); + if (!m.Success) continue; + MyAvalonEditor.Select(m.Index, m.Length); + MyAvalonEditor.TextArea.Caret.BringCaretToView(); + break; + } while (MyAvalonEditor != oldEditor); + } + } + + private Regex GetRegEx(bool forceLeftToRight = false) + { + Regex r; + var o = RegexOptions.None; + var viewModel = (TabItem) DataContext; + + if (viewModel.SearchUp && !forceLeftToRight) + o |= RegexOptions.RightToLeft; + if (!viewModel.CaseSensitive) + o |= RegexOptions.IgnoreCase; + + if (viewModel.UseRegEx) + { + r = new Regex(viewModel.TextToFind, o); + } + else + { + var s = Regex.Escape(viewModel.TextToFind); + if (viewModel.WholeWord) + s = "\\W" + s + "\\W"; + + r = new Regex(s, o); + } + + return r; + } + + private void OnCloseClick(object sender, RoutedEventArgs e) + { + ((TabItem) DataContext).HasSearchOpen = false; + } + + private void OnTabClose(object sender, EventArgs eventArgs) + { + if (eventArgs is not TabControlViewModel.TabEventArgs e || e.TabToRemove.Document == null) + return; + + var fileName = e.TabToRemove.Document.FileName; + if (_savedCarets.ContainsKey(fileName)) + _savedCarets.Remove(fileName); + } + + private void SaveCaretLoc(int offset) + { + if (_ignoreCaret) + { + _ignoreCaret = false; + return; + } // first always point to the end of the file for some reason + + if (_caretsOffsets.Count >= 10) + _caretsOffsets.RemoveAt(0); + if (!_caretsOffsets.Contains(offset)) + { + _caretsOffsets.Add(offset); + _caretsOffsets.CurrentIndex = _caretsOffsets.Count - 1; + } + } + + private void OnMouseRelease(object sender, MouseButtonEventArgs e) + { + SaveCaretLoc(MyAvalonEditor.CaretOffset); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Breadcrumb.xaml.cs b/FModel/Views/Resources/Controls/Breadcrumb.xaml.cs index 0ddf764c..8be8e3ea 100644 --- a/FModel/Views/Resources/Controls/Breadcrumb.xaml.cs +++ b/FModel/Views/Resources/Controls/Breadcrumb.xaml.cs @@ -6,65 +6,67 @@ using System.Windows.Media; using System.Windows.Shapes; using FModel.Services; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public partial class Breadcrumb { - public partial class Breadcrumb + private const string _NAVIGATE_NEXT = "M9.31 6.71c-.39.39-.39 1.02 0 1.41L13.19 12l-3.88 3.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41L10.72 6.7c-.38-.38-1.02-.38-1.41.01z"; + + public Breadcrumb() { - private const string _NAVIGATE_NEXT = "M9.31 6.71c-.39.39-.39 1.02 0 1.41L13.19 12l-3.88 3.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41L10.72 6.7c-.38-.38-1.02-.38-1.41.01z"; - - public Breadcrumb() - { - InitializeComponent(); - } + InitializeComponent(); + } - private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue is not string pathAtThisPoint) return; - InMeDaddy.Children.Clear(); + private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (e.NewValue is not string pathAtThisPoint) return; + InMeDaddy.Children.Clear(); - var folders = pathAtThisPoint.Split('/'); - for (var i = 0; i < folders.Length; i++) + var folders = pathAtThisPoint.Split('/'); + for (var i = 0; i < folders.Length; i++) + { + var textBlock = new TextBlock { - var textBlock = new TextBlock - { - Text = folders[i], - Background = Brushes.Transparent, - Cursor = Cursors.Hand, - Tag = i + 1, - Margin = new Thickness(0, 3, 0, 0) - }; - textBlock.MouseUp += OnMouseClick; - - InMeDaddy.Children.Add(textBlock); - if (i >= folders.Length - 1) continue; + Text = folders[i], + Background = Brushes.Transparent, + Cursor = Cursors.Hand, + Tag = i + 1, + Margin = new Thickness(0, 3, 0, 0) + }; + textBlock.MouseUp += OnMouseClick; - InMeDaddy.Children.Add(new Viewbox + InMeDaddy.Children.Add(textBlock); + if (i >= folders.Length - 1) continue; + + InMeDaddy.Children.Add(new Viewbox + { + Width = 16, + Height = 16, + HorizontalAlignment = HorizontalAlignment.Center, + Child = new Canvas { - Width = 16, - Height = 16, - HorizontalAlignment = HorizontalAlignment.Center, - Child = new Canvas + Width = 24, + Height = 24, + Children = { - Width = 24, - Height = 24, - Children = { new Path + new Path { Fill = Brushes.White, Data = Geometry.Parse(_NAVIGATE_NEXT) - }} + } } - }); - } - } - - private void OnMouseClick(object sender, MouseButtonEventArgs e) - { - if (sender is not TextBlock {DataContext: string pathAtThisPoint, Tag: int index}) return; - - var directory = string.Join('/', pathAtThisPoint.Split('/').Take(index)); - if (pathAtThisPoint.Equals(directory)) return; - - ApplicationService.ApplicationView.CustomDirectories.GoToCommand.JumpTo(directory); + } + }); } } + + private void OnMouseClick(object sender, MouseButtonEventArgs e) + { + if (sender is not TextBlock { DataContext: string pathAtThisPoint, Tag: int index }) return; + + var directory = string.Join('/', pathAtThisPoint.Split('/').Take(index)); + if (pathAtThisPoint.Equals(directory)) return; + + ApplicationService.ApplicationView.CustomDirectories.GoToCommand.JumpTo(directory); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/DictionaryEditor.xaml.cs b/FModel/Views/Resources/Controls/DictionaryEditor.xaml.cs index 8e7ed5e9..d8373588 100644 --- a/FModel/Views/Resources/Controls/DictionaryEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/DictionaryEditor.xaml.cs @@ -9,78 +9,77 @@ using FModel.Extensions; using ICSharpCode.AvalonEdit.Document; using Newtonsoft.Json; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public partial class DictionaryEditor { - public partial class DictionaryEditor + private readonly bool _enableElements; + private readonly List _defaultCustomVersions; + private readonly Dictionary _defaultOptions; + + public List CustomVersions { get; private set; } + public Dictionary Options { get; private set; } + + public DictionaryEditor(string title, bool enableElements) { - private readonly bool _enableElements; - private readonly List _defaultCustomVersions; - private readonly Dictionary _defaultOptions; - - public List CustomVersions { get; private set; } - public Dictionary Options { get; private set; } + _enableElements = enableElements; + _defaultCustomVersions = new List { new() { Key = new FGuid(), Version = 0 } }; + _defaultOptions = new Dictionary { { "key1", true }, { "key2", false } }; - public DictionaryEditor(string title, bool enableElements) + InitializeComponent(); + + Title = title; + MyAvalonEditor.IsReadOnly = !_enableElements; + MyAvalonEditor.SyntaxHighlighting = AvalonExtensions.HighlighterSelector(""); + } + + public DictionaryEditor(List customVersions, string title, bool enableElements) : this(title, enableElements) + { + MyAvalonEditor.Document = new TextDocument { - _enableElements = enableElements; - _defaultCustomVersions = new List {new() { Key = new FGuid(), Version = 0 }}; - _defaultOptions = new Dictionary {{ "key1", true }, { "key2", false }}; - - InitializeComponent(); + Text = JsonConvert.SerializeObject(customVersions ?? _defaultCustomVersions, Formatting.Indented) + }; + } - Title = title; - MyAvalonEditor.IsReadOnly = !_enableElements; - MyAvalonEditor.SyntaxHighlighting = AvalonExtensions.HighlighterSelector(""); + public DictionaryEditor(Dictionary options, string title, bool enableElements) : this(title, enableElements) + { + MyAvalonEditor.Document = new TextDocument + { + Text = JsonConvert.SerializeObject(options ?? _defaultOptions, Formatting.Indented) + }; + } + + private void OnClick(object sender, RoutedEventArgs e) + { + if (!_enableElements) + { + DialogResult = false; + Close(); + return; } - public DictionaryEditor(List customVersions, string title, bool enableElements) : this(title, enableElements) + try { - MyAvalonEditor.Document = new TextDocument + switch (Title) { - Text = JsonConvert.SerializeObject(customVersions ?? _defaultCustomVersions, Formatting.Indented) - }; + case "Versioning Configuration (Custom Versions)": + CustomVersions = JsonConvert.DeserializeObject>(MyAvalonEditor.Document.Text); + DialogResult = !CustomVersions.SequenceEqual(_defaultCustomVersions); + Close(); + break; + case "Versioning Configuration (Options)": + Options = JsonConvert.DeserializeObject>(MyAvalonEditor.Document.Text); + DialogResult = !Options.SequenceEqual(_defaultOptions); + Close(); + break; + default: + throw new NotImplementedException(); + } } - - public DictionaryEditor(Dictionary options, string title, bool enableElements) : this(title, enableElements) + catch { - MyAvalonEditor.Document = new TextDocument - { - Text = JsonConvert.SerializeObject(options ?? _defaultOptions, Formatting.Indented) - }; - } - - private void OnClick(object sender, RoutedEventArgs e) - { - if (!_enableElements) - { - DialogResult = false; - Close(); - return; - } - - try - { - switch (Title) - { - case "Versioning Configuration (Custom Versions)": - CustomVersions = JsonConvert.DeserializeObject>(MyAvalonEditor.Document.Text); - DialogResult = !CustomVersions.SequenceEqual(_defaultCustomVersions); - Close(); - break; - case "Versioning Configuration (Options)": - Options = JsonConvert.DeserializeObject>(MyAvalonEditor.Document.Text); - DialogResult = !Options.SequenceEqual(_defaultOptions); - Close(); - break; - default: - throw new NotImplementedException(); - } - } - catch - { - HeBrokeIt.Text = "GG YOU BROKE THE FORMAT, FIX THE JSON OR CANCEL THE CHANGES!"; - HeBrokeIt.Foreground = new SolidColorBrush((Color)ColorConverter.ConvertFromString(Constants.RED)); - } + HeBrokeIt.Text = "GG YOU BROKE THE FORMAT, FIX THE JSON OR CANCEL THE CHANGES!"; + HeBrokeIt.Foreground = new SolidColorBrush((Color) ColorConverter.ConvertFromString(Constants.RED)); } } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/HotkeyTextBox.cs b/FModel/Views/Resources/Controls/HotkeyTextBox.cs index 8b3072f7..c15cf12a 100644 --- a/FModel/Views/Resources/Controls/HotkeyTextBox.cs +++ b/FModel/Views/Resources/Controls/HotkeyTextBox.cs @@ -3,42 +3,43 @@ using System.Windows.Controls; using System.Windows.Input; using FModel.Framework; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +/// +/// https://tyrrrz.me/blog/hotkey-editor-control-in-wpf +/// +public class HotkeyTextBox : TextBox { - /// - /// https://tyrrrz.me/blog/hotkey-editor-control-in-wpf - /// - public class HotkeyTextBox : TextBox + public Hotkey HotKey { - public Hotkey HotKey - { - get => (Hotkey) GetValue(HotKeyProperty); - set => SetValue(HotKeyProperty, value); - } - public static readonly DependencyProperty HotKeyProperty = DependencyProperty.Register("HotKey", typeof(Hotkey), - typeof(HotkeyTextBox), new FrameworkPropertyMetadata(new Hotkey(Key.None), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HotKeyChanged)); + get => (Hotkey) GetValue(HotKeyProperty); + set => SetValue(HotKeyProperty, value); + } - private static void HotKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) - { - if (sender is not HotkeyTextBox control) return; - control.Text = control.HotKey.ToString(); - } + public static readonly DependencyProperty HotKeyProperty = DependencyProperty.Register("HotKey", typeof(Hotkey), + typeof(HotkeyTextBox), new FrameworkPropertyMetadata(new Hotkey(Key.None), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, HotKeyChanged)); - public HotkeyTextBox() - { - IsReadOnly = true; - IsReadOnlyCaretVisible = false; - IsUndoEnabled = false; + private static void HotKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (sender is not HotkeyTextBox control) return; + control.Text = control.HotKey.ToString(); + } - if (ContextMenu != null) - ContextMenu.Visibility = Visibility.Collapsed; + public HotkeyTextBox() + { + IsReadOnly = true; + IsReadOnlyCaretVisible = false; + IsUndoEnabled = false; - Text = HotKey.ToString(); - } + if (ContextMenu != null) + ContextMenu.Visibility = Visibility.Collapsed; - private static bool HasKeyChar(Key key) => - // A - Z - key is >= Key.A and <= Key.Z or + Text = HotKey.ToString(); + } + + private static bool HasKeyChar(Key key) => + // A - Z + key is >= Key.A and <= Key.Z or // 0 - 9 Key.D0 and <= Key.D9 or // Numpad 0 - 9 @@ -46,47 +47,46 @@ namespace FModel.Views.Resources.Controls // The rest Key.OemQuestion or Key.OemQuotes or Key.OemPlus or Key.OemOpenBrackets or Key.OemCloseBrackets or Key.OemMinus or Key.DeadCharProcessed or Key.Oem1 or Key.Oem5 or Key.Oem7 or Key.OemPeriod or Key.OemComma or Key.Add or Key.Divide or Key.Multiply or Key.Subtract or Key.Oem102 or Key.Decimal; - protected override void OnPreviewKeyDown(KeyEventArgs e) + protected override void OnPreviewKeyDown(KeyEventArgs e) + { + e.Handled = true; + + // Get modifiers and key data + var modifiers = Keyboard.Modifiers; + var key = e.Key; + + switch (key) { - e.Handled = true; - - // Get modifiers and key data - var modifiers = Keyboard.Modifiers; - var key = e.Key; - - switch (key) - { - // If nothing was pressed - return - case Key.None: - return; - // If Alt is used as modifier - the key needs to be extracted from SystemKey - case Key.System: - key = e.SystemKey; - break; - // If Delete/Backspace/Escape is pressed without modifiers - clear current value and return - case Key.Delete or Key.Back or Key.Escape when modifiers == ModifierKeys.None: - HotKey = new Hotkey(Key.None); - return; - // If the only key pressed is one of the modifier keys - return - case Key.LeftCtrl: - case Key.RightCtrl: - case Key.LeftAlt: - case Key.RightAlt: - case Key.LeftShift: - case Key.RightShift: - case Key.LWin: - case Key.RWin: - case Key.Clear: - case Key.OemClear: - case Key.Apps: - // If Enter/Space/Tab is pressed without modifiers - return - case Key.Enter or Key.Space or Key.Tab when modifiers == ModifierKeys.None: - return; - default: - // Set value - HotKey = new Hotkey(key, modifiers); - break; - } + // If nothing was pressed - return + case Key.None: + return; + // If Alt is used as modifier - the key needs to be extracted from SystemKey + case Key.System: + key = e.SystemKey; + break; + // If Delete/Backspace/Escape is pressed without modifiers - clear current value and return + case Key.Delete or Key.Back or Key.Escape when modifiers == ModifierKeys.None: + HotKey = new Hotkey(Key.None); + return; + // If the only key pressed is one of the modifier keys - return + case Key.LeftCtrl: + case Key.RightCtrl: + case Key.LeftAlt: + case Key.RightAlt: + case Key.LeftShift: + case Key.RightShift: + case Key.LWin: + case Key.RWin: + case Key.Clear: + case Key.OemClear: + case Key.Apps: + // If Enter/Space/Tab is pressed without modifiers - return + case Key.Enter or Key.Space or Key.Tab when modifiers == ModifierKeys.None: + return; + default: + // Set value + HotKey = new Hotkey(key, modifiers); + break; } } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/ImagePopout.xaml.cs b/FModel/Views/Resources/Controls/ImagePopout.xaml.cs index 11891e70..4ff4f967 100644 --- a/FModel/Views/Resources/Controls/ImagePopout.xaml.cs +++ b/FModel/Views/Resources/Controls/ImagePopout.xaml.cs @@ -1,10 +1,9 @@ -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public partial class ImagePopout { - public partial class ImagePopout + public ImagePopout() { - public ImagePopout() - { - InitializeComponent(); - } + InitializeComponent(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Mgn/EFrameType.cs b/FModel/Views/Resources/Controls/Mgn/EFrameType.cs index ed125508..6620b8de 100644 --- a/FModel/Views/Resources/Controls/Mgn/EFrameType.cs +++ b/FModel/Views/Resources/Controls/Mgn/EFrameType.cs @@ -1,8 +1,7 @@ -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public enum EFrameType { - public enum EFrameType - { - Circle, - Rectangle - } + Circle, + Rectangle } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Mgn/Magnifier.cs b/FModel/Views/Resources/Controls/Mgn/Magnifier.cs index 9eae0887..d5d29d41 100644 --- a/FModel/Views/Resources/Controls/Mgn/Magnifier.cs +++ b/FModel/Views/Resources/Controls/Mgn/Magnifier.cs @@ -2,176 +2,175 @@ using System.Windows.Controls; using System.Windows.Media; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +[TemplatePart(Name = PART_VisualBrush, Type = typeof(VisualBrush))] +public class Magnifier : Control { - [TemplatePart(Name = PART_VisualBrush, Type = typeof(VisualBrush))] - public class Magnifier : Control + private const double DEFAULT_SIZE = 100d; + private const string PART_VisualBrush = "PART_VisualBrush"; + private VisualBrush _visualBrush = new(); + + public static readonly DependencyProperty FrameTypeProperty = + DependencyProperty.Register("FrameType", typeof(EFrameType), typeof(Magnifier), new UIPropertyMetadata(EFrameType.Circle, OnFrameTypeChanged)); + public EFrameType FrameType { - private const double DEFAULT_SIZE = 100d; - private const string PART_VisualBrush = "PART_VisualBrush"; - private VisualBrush _visualBrush = new(); + get => (EFrameType) GetValue(FrameTypeProperty); + set => SetValue(FrameTypeProperty, value); + } - public static readonly DependencyProperty FrameTypeProperty = - DependencyProperty.Register("FrameType", typeof(EFrameType), typeof(Magnifier), new UIPropertyMetadata(EFrameType.Circle, OnFrameTypeChanged)); - public EFrameType FrameType + private static void OnFrameTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var m = (Magnifier) d; + m.OnFrameTypeChanged((EFrameType) e.OldValue, (EFrameType) e.NewValue); + } + + protected virtual void OnFrameTypeChanged(EFrameType oldValue, EFrameType newValue) + { + UpdateSizeFromRadius(); + } + + public static readonly DependencyProperty IsUsingZoomOnMouseWheelProperty = + DependencyProperty.Register("IsUsingZoomOnMouseWheel", typeof(bool), typeof(Magnifier), new UIPropertyMetadata(true)); + public bool IsUsingZoomOnMouseWheel + { + get => (bool) GetValue(IsUsingZoomOnMouseWheelProperty); + set => SetValue(IsUsingZoomOnMouseWheelProperty, value); + } + + public bool IsFrozen { get; private set; } + + public static readonly DependencyProperty RadiusProperty = + DependencyProperty.Register("Radius", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE / 2, OnRadiusPropertyChanged)); + public double Radius + { + get => (double) GetValue(RadiusProperty); + set => SetValue(RadiusProperty, value); + } + + private static void OnRadiusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var m = (Magnifier) d; + m.OnRadiusChanged(e); + } + + protected virtual void OnRadiusChanged(DependencyPropertyChangedEventArgs e) + { + UpdateSizeFromRadius(); + } + + public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(UIElement), typeof(Magnifier)); + public UIElement Target + { + get => (UIElement) GetValue(TargetProperty); + set => SetValue(TargetProperty, value); + } + + public Rect ViewBox + { + get => _visualBrush.Viewbox; + set => _visualBrush.Viewbox = value; + } + + public static readonly DependencyProperty ZoomFactorProperty = + DependencyProperty.Register("ZoomFactor", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(0.5, OnZoomFactorPropertyChanged), OnValidationCallback); + public double ZoomFactor + { + get => (double) GetValue(ZoomFactorProperty); + set => SetValue(ZoomFactorProperty, value); + } + + private static bool OnValidationCallback(object baseValue) + { + return (double) baseValue >= 0; + } + + private static void OnZoomFactorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var m = (Magnifier) d; + m.OnZoomFactorChanged(e); + } + + protected virtual void OnZoomFactorChanged(DependencyPropertyChangedEventArgs e) + { + UpdateViewBox(); + } + + public static readonly DependencyProperty ZoomFactorOnMouseWheelProperty = + DependencyProperty.Register("ZoomFactorOnMouseWheel", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(0.1d, OnZoomFactorOnMouseWheelPropertyChanged), OnZoomFactorOnMouseWheelValidationCallback); + public double ZoomFactorOnMouseWheel + { + get => (double) GetValue(ZoomFactorOnMouseWheelProperty); + set => SetValue(ZoomFactorOnMouseWheelProperty, value); + } + + private static bool OnZoomFactorOnMouseWheelValidationCallback(object baseValue) + { + return (double) baseValue >= 0; + } + + private static void OnZoomFactorOnMouseWheelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var m = (Magnifier) d; + m.OnZoomFactorOnMouseWheelChanged(e); + } + + protected virtual void OnZoomFactorOnMouseWheelChanged(DependencyPropertyChangedEventArgs e) + { + } + + static Magnifier() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(typeof(Magnifier))); + HeightProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE)); + WidthProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE)); + } + + public Magnifier() + { + SizeChanged += OnSizeChangedEvent; + } + + private void OnSizeChangedEvent(object sender, SizeChangedEventArgs e) + { + UpdateViewBox(); + } + + private void UpdateSizeFromRadius() + { + if (FrameType != EFrameType.Circle) return; + + var newSize = Radius * 2; + if (!Helper.AreVirtuallyEqual(Width, newSize)) { - get => (EFrameType) GetValue(FrameTypeProperty); - set => SetValue(FrameTypeProperty, value); + Width = newSize; } - private static void OnFrameTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + if (!Helper.AreVirtuallyEqual(Height, newSize)) { - var m = (Magnifier) d; - m.OnFrameTypeChanged((EFrameType) e.OldValue, (EFrameType) e.NewValue); - } - - protected virtual void OnFrameTypeChanged(EFrameType oldValue, EFrameType newValue) - { - UpdateSizeFromRadius(); - } - - public static readonly DependencyProperty IsUsingZoomOnMouseWheelProperty = - DependencyProperty.Register("IsUsingZoomOnMouseWheel", typeof(bool), typeof(Magnifier), new UIPropertyMetadata(true)); - public bool IsUsingZoomOnMouseWheel - { - get => (bool) GetValue(IsUsingZoomOnMouseWheelProperty); - set => SetValue(IsUsingZoomOnMouseWheelProperty, value); - } - - public bool IsFrozen { get; private set; } - - public static readonly DependencyProperty RadiusProperty = - DependencyProperty.Register("Radius", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE / 2, OnRadiusPropertyChanged)); - public double Radius - { - get => (double) GetValue(RadiusProperty); - set => SetValue(RadiusProperty, value); - } - - private static void OnRadiusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var m = (Magnifier) d; - m.OnRadiusChanged(e); - } - - protected virtual void OnRadiusChanged(DependencyPropertyChangedEventArgs e) - { - UpdateSizeFromRadius(); - } - - public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(UIElement), typeof(Magnifier)); - public UIElement Target - { - get => (UIElement) GetValue(TargetProperty); - set => SetValue(TargetProperty, value); - } - - public Rect ViewBox - { - get => _visualBrush.Viewbox; - set => _visualBrush.Viewbox = value; - } - - public static readonly DependencyProperty ZoomFactorProperty = - DependencyProperty.Register("ZoomFactor", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(0.5, OnZoomFactorPropertyChanged), OnValidationCallback); - public double ZoomFactor - { - get => (double) GetValue(ZoomFactorProperty); - set => SetValue(ZoomFactorProperty, value); - } - - private static bool OnValidationCallback(object baseValue) - { - return (double) baseValue >= 0; - } - - private static void OnZoomFactorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var m = (Magnifier) d; - m.OnZoomFactorChanged(e); - } - - protected virtual void OnZoomFactorChanged(DependencyPropertyChangedEventArgs e) - { - UpdateViewBox(); - } - - public static readonly DependencyProperty ZoomFactorOnMouseWheelProperty = - DependencyProperty.Register("ZoomFactorOnMouseWheel", typeof(double), typeof(Magnifier), new FrameworkPropertyMetadata(0.1d, OnZoomFactorOnMouseWheelPropertyChanged), OnZoomFactorOnMouseWheelValidationCallback); - public double ZoomFactorOnMouseWheel - { - get => (double) GetValue(ZoomFactorOnMouseWheelProperty); - set => SetValue(ZoomFactorOnMouseWheelProperty, value); - } - - private static bool OnZoomFactorOnMouseWheelValidationCallback(object baseValue) - { - return (double) baseValue >= 0; - } - - private static void OnZoomFactorOnMouseWheelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var m = (Magnifier) d; - m.OnZoomFactorOnMouseWheelChanged(e); - } - - protected virtual void OnZoomFactorOnMouseWheelChanged(DependencyPropertyChangedEventArgs e) - { - } - - static Magnifier() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(typeof(Magnifier))); - HeightProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE)); - WidthProperty.OverrideMetadata(typeof(Magnifier), new FrameworkPropertyMetadata(DEFAULT_SIZE)); - } - - public Magnifier() - { - SizeChanged += OnSizeChangedEvent; - } - - private void OnSizeChangedEvent(object sender, SizeChangedEventArgs e) - { - UpdateViewBox(); - } - - private void UpdateSizeFromRadius() - { - if (FrameType != EFrameType.Circle) return; - - var newSize = Radius * 2; - if (!Helper.AreVirtuallyEqual(Width, newSize)) - { - Width = newSize; - } - - if (!Helper.AreVirtuallyEqual(Height, newSize)) - { - Height = newSize; - } - } - - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - var newBrush = GetTemplateChild(PART_VisualBrush) as VisualBrush ?? new VisualBrush(); - newBrush.Viewbox = _visualBrush.Viewbox; - _visualBrush = newBrush; - } - - public void Freeze(bool freeze) - { - IsFrozen = freeze; - } - - private void UpdateViewBox() - { - if (!IsInitialized) - return; - - ViewBox = new Rect(ViewBox.Location, new Size(ActualWidth * ZoomFactor, ActualHeight * ZoomFactor)); + Height = newSize; } } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + var newBrush = GetTemplateChild(PART_VisualBrush) as VisualBrush ?? new VisualBrush(); + newBrush.Viewbox = _visualBrush.Viewbox; + _visualBrush = newBrush; + } + + public void Freeze(bool freeze) + { + IsFrozen = freeze; + } + + private void UpdateViewBox() + { + if (!IsInitialized) + return; + + ViewBox = new Rect(ViewBox.Location, new Size(ActualWidth * ZoomFactor, ActualHeight * ZoomFactor)); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Mgn/MagnifierAdorner.cs b/FModel/Views/Resources/Controls/Mgn/MagnifierAdorner.cs index d5639e7b..0c752404 100644 --- a/FModel/Views/Resources/Controls/Mgn/MagnifierAdorner.cs +++ b/FModel/Views/Resources/Controls/Mgn/MagnifierAdorner.cs @@ -3,84 +3,83 @@ using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class MagnifierAdorner : Adorner { - public class MagnifierAdorner : Adorner + private Magnifier _magnifier; + private Point _currentMousePosition; + private double _currentZoomFactor; + + public MagnifierAdorner(UIElement element, Magnifier magnifier) : base(element) { - private Magnifier _magnifier; - private Point _currentMousePosition; - private double _currentZoomFactor; + _magnifier = magnifier; + _currentZoomFactor = _magnifier.ZoomFactor; - public MagnifierAdorner(UIElement element, Magnifier magnifier) : base(element) - { - _magnifier = magnifier; - _currentZoomFactor = _magnifier.ZoomFactor; + UpdateViewBox(); + AddVisualChild(_magnifier); - UpdateViewBox(); - AddVisualChild(_magnifier); + Loaded += (_, _) => InputManager.Current.PostProcessInput += OnProcessInput; + Unloaded += (_, _) => InputManager.Current.PostProcessInput -= OnProcessInput; + } - Loaded += (s, e) => InputManager.Current.PostProcessInput += OnProcessInput; - Unloaded += (s, e) => InputManager.Current.PostProcessInput -= OnProcessInput; - } + private void OnProcessInput(object sender, ProcessInputEventArgs e) + { + var pt = Mouse.GetPosition(this); + if (_currentMousePosition == pt && _magnifier.ZoomFactor == _currentZoomFactor) + return; - private void OnProcessInput(object sender, ProcessInputEventArgs e) - { - var pt = Mouse.GetPosition(this); - if (_currentMousePosition == pt && _magnifier.ZoomFactor == _currentZoomFactor) - return; + if (_magnifier.IsFrozen) + return; - if (_magnifier.IsFrozen) - return; + _currentMousePosition = pt; + _currentZoomFactor = _magnifier.ZoomFactor; - _currentMousePosition = pt; - _currentZoomFactor = _magnifier.ZoomFactor; + UpdateViewBox(); + InvalidateArrange(); + } - UpdateViewBox(); - InvalidateArrange(); - } + public void UpdateViewBox() + { + var viewBoxLocation = CalculateViewBoxLocation(); + _magnifier.ViewBox = new Rect(viewBoxLocation, _magnifier.ViewBox.Size); + } - public void UpdateViewBox() - { - var viewBoxLocation = CalculateViewBoxLocation(); - _magnifier.ViewBox = new Rect(viewBoxLocation, _magnifier.ViewBox.Size); - } + private Point CalculateViewBoxLocation() + { + double offsetX, offsetY; + var adorner = Mouse.GetPosition(this); + var element = Mouse.GetPosition(AdornedElement); - private Point CalculateViewBoxLocation() - { - double offsetX, offsetY; - var adorner = Mouse.GetPosition(this); - var element = Mouse.GetPosition(AdornedElement); + offsetX = element.X - adorner.X; + offsetY = element.Y - adorner.Y; - offsetX = element.X - adorner.X; - offsetY = element.Y - adorner.Y; + var parentOffsetVector = VisualTreeHelper.GetOffset(_magnifier.Target); + var parentOffset = new Point(parentOffsetVector.X, parentOffsetVector.Y); - var parentOffsetVector = VisualTreeHelper.GetOffset(_magnifier.Target); - var parentOffset = new Point(parentOffsetVector.X, parentOffsetVector.Y); + var left = _currentMousePosition.X - (_magnifier.ViewBox.Width / 2 + offsetX) + parentOffset.X; + var top = _currentMousePosition.Y - (_magnifier.ViewBox.Height / 2 + offsetY) + parentOffset.Y; + return new Point(left, top); + } - var left = _currentMousePosition.X - (_magnifier.ViewBox.Width / 2 + offsetX) + parentOffset.X; - var top = _currentMousePosition.Y - (_magnifier.ViewBox.Height / 2 + offsetY) + parentOffset.Y; - return new Point(left, top); - } + protected override Visual GetVisualChild(int index) + { + return _magnifier; + } - protected override Visual GetVisualChild(int index) - { - return _magnifier; - } + protected override int VisualChildrenCount => 1; - protected override int VisualChildrenCount => 1; + protected override Size MeasureOverride(Size constraint) + { + _magnifier.Measure(constraint); + return base.MeasureOverride(constraint); + } - protected override Size MeasureOverride(Size constraint) - { - _magnifier.Measure(constraint); - return base.MeasureOverride(constraint); - } - - protected override Size ArrangeOverride(Size finalSize) - { - var x = _currentMousePosition.X - _magnifier.Width / 2; - var y = _currentMousePosition.Y - _magnifier.Height / 2; - _magnifier.Arrange(new Rect(x, y, _magnifier.Width, _magnifier.Height)); - return base.ArrangeOverride(finalSize); - } + protected override Size ArrangeOverride(Size finalSize) + { + var x = _currentMousePosition.X - _magnifier.Width / 2; + var y = _currentMousePosition.Y - _magnifier.Height / 2; + _magnifier.Arrange(new Rect(x, y, _magnifier.Width, _magnifier.Height)); + return base.ArrangeOverride(finalSize); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Mgn/MagnifierManager.cs b/FModel/Views/Resources/Controls/Mgn/MagnifierManager.cs index ba5ef15a..c18ce92a 100644 --- a/FModel/Views/Resources/Controls/Mgn/MagnifierManager.cs +++ b/FModel/Views/Resources/Controls/Mgn/MagnifierManager.cs @@ -3,101 +3,100 @@ using System.Windows; using System.Windows.Documents; using System.Windows.Input; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class MagnifierManager : DependencyObject { - public class MagnifierManager : DependencyObject + private MagnifierAdorner _adorner; + private UIElement _element; + + public static readonly DependencyProperty CurrentProperty = + DependencyProperty.RegisterAttached("Magnifier", typeof(Magnifier), typeof(UIElement), new FrameworkPropertyMetadata(null, OnMagnifierChanged)); + + public static void SetMagnifier(UIElement element, Magnifier value) { - private MagnifierAdorner _adorner; - private UIElement _element; + element.SetValue(CurrentProperty, value); + } - public static readonly DependencyProperty CurrentProperty = - DependencyProperty.RegisterAttached("Magnifier", typeof(Magnifier), typeof(UIElement), new FrameworkPropertyMetadata(null, OnMagnifierChanged)); + public static Magnifier GetMagnifier(UIElement element) + { + return (Magnifier) element.GetValue(CurrentProperty); + } - public static void SetMagnifier(UIElement element, Magnifier value) + private static void OnMagnifierChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not UIElement target) + throw new ArgumentException("Magnifier can only be attached to a UIElement."); + + new MagnifierManager().AttachToMagnifier(target, e.NewValue as Magnifier); + } + + private void ElementOnMouseLeftButtonUp(object sender, MouseEventArgs e) + { + if (GetMagnifier(_element) is { IsFrozen: true }) + return; + + HideAdorner(); + } + + private void ElementOnMouseLeftButtonDown(object sender, MouseEventArgs e) + { + ShowAdorner(); + } + + private void ElementOnMouseWheel(object sender, MouseWheelEventArgs e) + { + if (GetMagnifier(_element) is not { IsUsingZoomOnMouseWheel: true } magnifier) return; + + switch (e.Delta) { - element.SetValue(CurrentProperty, value); - } - - public static Magnifier GetMagnifier(UIElement element) - { - return (Magnifier) element.GetValue(CurrentProperty); - } - - private static void OnMagnifierChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is not UIElement target) - throw new ArgumentException("Magnifier can only be attached to a UIElement."); - - new MagnifierManager().AttachToMagnifier(target, e.NewValue as Magnifier); - } - - private void ElementOnMouseLeftButtonUp(object sender, MouseEventArgs e) - { - if (GetMagnifier(_element) is {IsFrozen: true}) - return; - - HideAdorner(); - } - - private void ElementOnMouseLeftButtonDown(object sender, MouseEventArgs e) - { - ShowAdorner(); - } - - private void ElementOnMouseWheel(object sender, MouseWheelEventArgs e) - { - if (GetMagnifier(_element) is not {IsUsingZoomOnMouseWheel: true} magnifier) return; - - switch (e.Delta) + case < 0: { - case < 0: - { - var newValue = magnifier.ZoomFactor + magnifier.ZoomFactorOnMouseWheel; - magnifier.SetCurrentValue(Magnifier.ZoomFactorProperty, newValue); - break; - } - case > 0: - { - var newValue = magnifier.ZoomFactor >= magnifier.ZoomFactorOnMouseWheel ? magnifier.ZoomFactor - magnifier.ZoomFactorOnMouseWheel : 0d; - magnifier.SetCurrentValue(Magnifier.ZoomFactorProperty, newValue); - break; - } + var newValue = magnifier.ZoomFactor + magnifier.ZoomFactorOnMouseWheel; + magnifier.SetCurrentValue(Magnifier.ZoomFactorProperty, newValue); + break; } - - _adorner.UpdateViewBox(); - } - - private void AttachToMagnifier(UIElement element, Magnifier magnifier) - { - _element = element; - _element.MouseLeftButtonDown += ElementOnMouseLeftButtonDown; - _element.MouseLeftButtonUp += ElementOnMouseLeftButtonUp; - _element.MouseWheel += ElementOnMouseWheel; - - magnifier.Target = _element; - - _adorner = new MagnifierAdorner(_element, magnifier); - } - - private void ShowAdorner() - { - VerifyAdornerLayer(); - _adorner.Visibility = Visibility.Visible; - } - - private void VerifyAdornerLayer() - { - if (_adorner.Parent != null) return; - var layer = AdornerLayer.GetAdornerLayer(_element); - layer?.Add(_adorner); - } - - private void HideAdorner() - { - if (_adorner.Visibility == Visibility.Visible) + case > 0: { - _adorner.Visibility = Visibility.Collapsed; + var newValue = magnifier.ZoomFactor >= magnifier.ZoomFactorOnMouseWheel ? magnifier.ZoomFactor - magnifier.ZoomFactorOnMouseWheel : 0d; + magnifier.SetCurrentValue(Magnifier.ZoomFactorProperty, newValue); + break; } } + + _adorner.UpdateViewBox(); + } + + private void AttachToMagnifier(UIElement element, Magnifier magnifier) + { + _element = element; + _element.MouseLeftButtonDown += ElementOnMouseLeftButtonDown; + _element.MouseLeftButtonUp += ElementOnMouseLeftButtonUp; + _element.MouseWheel += ElementOnMouseWheel; + + magnifier.Target = _element; + + _adorner = new MagnifierAdorner(_element, magnifier); + } + + private void ShowAdorner() + { + VerifyAdornerLayer(); + _adorner.Visibility = Visibility.Visible; + } + + private void VerifyAdornerLayer() + { + if (_adorner.Parent != null) return; + var layer = AdornerLayer.GetAdornerLayer(_element); + layer?.Add(_adorner); + } + + private void HideAdorner() + { + if (_adorner.Visibility == Visibility.Visible) + { + _adorner.Visibility = Visibility.Collapsed; + } } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/OnTagDataTemplateSelector.cs b/FModel/Views/Resources/Controls/OnTagDataTemplateSelector.cs index 8f7b6e7a..e106125a 100644 --- a/FModel/Views/Resources/Controls/OnTagDataTemplateSelector.cs +++ b/FModel/Views/Resources/Controls/OnTagDataTemplateSelector.cs @@ -1,14 +1,13 @@ using System.Windows; using System.Windows.Controls; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class OnTagDataTemplateSelector : DataTemplateSelector { - public class OnTagDataTemplateSelector : DataTemplateSelector + public override DataTemplate SelectTemplate(object item, DependencyObject container) { - public override DataTemplate SelectTemplate(object item, DependencyObject container) - { - if (item is not string s || container is not FrameworkElement f) return null; - return f.FindResource(s) as DataTemplate; - } + if (item is not string s || container is not FrameworkElement f) return null; + return f.FindResource(s) as DataTemplate; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs index 5eec8649..ea6d95b5 100644 --- a/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs +++ b/FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs @@ -8,93 +8,92 @@ using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; using SkiaSharp; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public partial class PropertiesPopout { - public partial class PropertiesPopout + private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", + RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + private readonly System.Windows.Controls.ToolTip _toolTip = new(); + private JsonFoldingStrategies _manager; + + public PropertiesPopout(TabItem contextViewModel) { - private readonly Regex _hexColorRegex = new("\"Hex\": \"(?'target'[0-9A-Fa-f]{3,8})\"$", - RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private readonly System.Windows.Controls.ToolTip _toolTip = new(); - private JsonFoldingStrategies _manager; + InitializeComponent(); - public PropertiesPopout(TabItem contextViewModel) + MyAvalonEditor.Document = new TextDocument { - InitializeComponent(); + Text = contextViewModel.Document.Text, + FileName = contextViewModel.Directory + '/' + contextViewModel.Header.SubstringBeforeLast('.') + }; + MyAvalonEditor.FontSize = contextViewModel.FontSize; + MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter; + MyAvalonEditor.ScrollToVerticalOffset(contextViewModel.ScrollPosition); + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); + MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); + _manager = new JsonFoldingStrategies(MyAvalonEditor); + _manager.UpdateFoldings(MyAvalonEditor.Document); + } - MyAvalonEditor.Document = new TextDocument - { - Text = contextViewModel.Document.Text, - FileName = contextViewModel.Directory + '/' + contextViewModel.Header.SubstringBeforeLast('.') - }; - MyAvalonEditor.FontSize = contextViewModel.FontSize; - MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter; - MyAvalonEditor.ScrollToVerticalOffset(contextViewModel.ScrollPosition); - MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new GamePathElementGenerator()); - MyAvalonEditor.TextArea.TextView.ElementGenerators.Add(new HexColorElementGenerator()); - _manager = new JsonFoldingStrategies(MyAvalonEditor); - _manager.UpdateFoldings(MyAvalonEditor.Document); - } + private void OnMouseHover(object sender, MouseEventArgs e) + { + var pos = MyAvalonEditor.GetPositionFromPoint(e.GetPosition(MyAvalonEditor)); + if (pos == null) return; - private void OnMouseHover(object sender, MouseEventArgs e) + var line = MyAvalonEditor.Document.GetLineByNumber(pos.Value.Line); + var m = _hexColorRegex.Match(MyAvalonEditor.Document.GetText(line.Offset, line.Length)); + if (!m.Success || !m.Groups.TryGetValue("target", out var g)) return; + + var color = SKColor.Parse(g.Value); + _toolTip.PlacementTarget = this; // required for property inheritance + _toolTip.Background = new SolidColorBrush(Color.FromArgb(color.Alpha, color.Red, color.Green, color.Blue)); + _toolTip.Foreground = _toolTip.BorderBrush = PerceivedBrightness(color) > 130 ? Brushes.Black : Brushes.White; + _toolTip.Content = $"#{g.Value}"; + _toolTip.IsOpen = true; + e.Handled = true; + } + + private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (sender is not TextEditor avalonEditor || Keyboard.Modifiers != ModifierKeys.Control) + return; + + var fontSize = avalonEditor.FontSize + e.Delta / 50.0; + + avalonEditor.FontSize = fontSize switch { - var pos = MyAvalonEditor.GetPositionFromPoint(e.GetPosition(MyAvalonEditor)); - if (pos == null) return; - - var line = MyAvalonEditor.Document.GetLineByNumber(pos.Value.Line); - var m = _hexColorRegex.Match(MyAvalonEditor.Document.GetText(line.Offset, line.Length)); - if (!m.Success || !m.Groups.TryGetValue("target", out var g)) return; - - var color = SKColor.Parse(g.Value); - _toolTip.PlacementTarget = this; // required for property inheritance - _toolTip.Background = new SolidColorBrush(Color.FromArgb(color.Alpha, color.Red, color.Green, color.Blue)); - _toolTip.Foreground = _toolTip.BorderBrush = PerceivedBrightness(color) > 130 ? Brushes.Black : Brushes.White; - _toolTip.Content = $"#{g.Value}"; - _toolTip.IsOpen = true; - e.Handled = true; - } - - private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) - { - if (sender is not TextEditor avalonEditor || Keyboard.Modifiers != ModifierKeys.Control) - return; - - var fontSize = avalonEditor.FontSize + e.Delta / 50.0; - - avalonEditor.FontSize = fontSize switch - { - < 6 => 6, - > 200 => 200, - _ => fontSize - }; - } + < 6 => 6, + > 200 => 200, + _ => fontSize + }; + } - private void OnPreviewKeyDown(object sender, KeyEventArgs e) + private void OnPreviewKeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) { - switch (e.Key) - { - case Key.J when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): - _manager.UnfoldAll(); - break; - case Key.L when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): - _manager.FoldToggle(MyAvalonEditor.CaretOffset); - break; - case >= Key.D0 and <= Key.D9 when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): - _manager.FoldToggleAtLevel(int.Parse(e.Key.ToString()[1].ToString())); - break; - } - } - - private void OnMouseHoverStopped(object sender, MouseEventArgs e) - { - _toolTip.IsOpen = false; - } - - private int PerceivedBrightness(SKColor c) - { - return (int) Math.Sqrt( - c.Red * c.Red * .299 + - c.Green * c.Green * .587 + - c.Blue * c.Blue * .114); + case Key.J when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): + _manager.UnfoldAll(); + break; + case Key.L when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): + _manager.FoldToggle(MyAvalonEditor.CaretOffset); + break; + case >= Key.D0 and <= Key.D9 when Keyboard.IsKeyDown(Key.K) && Keyboard.Modifiers.HasFlag(ModifierKeys.Control): + _manager.FoldToggleAtLevel(int.Parse(e.Key.ToString()[1].ToString())); + break; } } + + private void OnMouseHoverStopped(object sender, MouseEventArgs e) + { + _toolTip.IsOpen = false; + } + + private int PerceivedBrightness(SKColor c) + { + return (int) Math.Sqrt( + c.Red * c.Red * .299 + + c.Green * c.Green * .587 + + c.Blue * c.Blue * .114); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs index d5b902ca..cc5faaa1 100644 --- a/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs +++ b/FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs @@ -5,168 +5,167 @@ using System.Windows.Data; using System.Windows.Documents; using System.Windows.Media; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +/// +/// https://github.com/xceedsoftware/wpftoolkit/tree/master/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/RichTextBox +/// +public interface ITextFormatter { - /// - /// https://github.com/xceedsoftware/wpftoolkit/tree/master/ExtendedWPFToolkitSolution/Src/Xceed.Wpf.Toolkit/RichTextBox - /// - public interface ITextFormatter + string GetText(FlowDocument document); + void SetText(FlowDocument document, string text); +} + +public class FLogger : ITextFormatter +{ + public static CustomRichTextBox Logger; + private static readonly BrushConverter _brushConverter = new(); + + public static void AppendInformation() => AppendText("[INF] ", Constants.BLUE); + public static void AppendWarning() => AppendText("[WRN] ", Constants.YELLOW); + public static void AppendError() => AppendText("[ERR] ", Constants.RED); + public static void AppendDebug() => AppendText("[DBG] ", Constants.GREEN); + + public static void AppendText(string message, string color, bool newLine = false) { - string GetText(FlowDocument document); - void SetText(FlowDocument document, string text); + Application.Current.Dispatcher.Invoke(delegate + { + var textRange = new TextRange(Logger.Document.ContentEnd, Logger.Document.ContentEnd) + { + Text = newLine ? $"{message}{Environment.NewLine}" : message + }; + + try + { + textRange.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color)); + } + finally + { + Logger.ScrollToEnd(); + } + }); } - public class FLogger : ITextFormatter + public string GetText(FlowDocument document) { - public static CustomRichTextBox Logger; - private static readonly BrushConverter _brushConverter = new(); + return new TextRange(document.ContentStart, document.ContentEnd).Text; + } - public static void AppendInformation() => AppendText("[INF] ", Constants.BLUE); - public static void AppendWarning() => AppendText("[WRN] ", Constants.YELLOW); - public static void AppendError() => AppendText("[ERR] ", Constants.RED); - public static void AppendDebug() => AppendText("[DBG] ", Constants.GREEN); + public void SetText(FlowDocument document, string text) + { + new TextRange(document.ContentStart, document.ContentEnd).Text = text; + } +} - public static void AppendText(string message, string color, bool newLine = false) +public class CustomRichTextBox : RichTextBox +{ + private bool _preventDocumentUpdate; + private bool _preventTextUpdate; + + public CustomRichTextBox() + { + } + + public CustomRichTextBox(FlowDocument document) : base(document) + { + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", typeof(string), typeof(CustomRichTextBox), + new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, + OnTextPropertyChanged, CoerceTextProperty, true, UpdateSourceTrigger.LostFocus)); + + public string Text + { + get => (string) GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((CustomRichTextBox) d).UpdateDocumentFromText(); + } + + private static object CoerceTextProperty(DependencyObject d, object value) + { + return value ?? ""; + } + + public static readonly DependencyProperty TextFormatterProperty = + DependencyProperty.Register( + "TextFormatter", typeof(ITextFormatter), typeof(CustomRichTextBox), + new FrameworkPropertyMetadata(new FLogger(), OnTextFormatterPropertyChanged)); + + public ITextFormatter TextFormatter + { + get => (ITextFormatter) GetValue(TextFormatterProperty); + set => SetValue(TextFormatterProperty, value); + } + + private static void OnTextFormatterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is CustomRichTextBox richTextBox) { - Application.Current.Dispatcher.Invoke(delegate - { - var textRange = new TextRange(Logger.Document.ContentEnd, Logger.Document.ContentEnd) - { - Text = newLine ? $"{message}{Environment.NewLine}" : message - }; - - try - { - textRange.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color)); - } - finally - { - Logger.ScrollToEnd(); - } - }); - } - - public string GetText(FlowDocument document) - { - return new TextRange(document.ContentStart, document.ContentEnd).Text; - } - - public void SetText(FlowDocument document, string text) - { - new TextRange(document.ContentStart, document.ContentEnd).Text = text; + richTextBox.OnTextFormatterPropertyChanged((ITextFormatter) e.OldValue, (ITextFormatter) e.NewValue); } } - public class CustomRichTextBox : RichTextBox + protected virtual void OnTextFormatterPropertyChanged(ITextFormatter oldValue, ITextFormatter newValue) { - private bool _preventDocumentUpdate; - private bool _preventTextUpdate; + UpdateTextFromDocument(); + } - public CustomRichTextBox() + protected override void OnTextChanged(TextChangedEventArgs e) + { + UpdateTextFromDocument(); + base.OnTextChanged(e); + } + + private void UpdateTextFromDocument() + { + if (_preventTextUpdate) + return; + + _preventDocumentUpdate = true; + SetCurrentValue(TextProperty, TextFormatter.GetText(Document)); + _preventDocumentUpdate = false; + } + + private void UpdateDocumentFromText() + { + if (_preventDocumentUpdate) + return; + + _preventTextUpdate = true; + TextFormatter.SetText(Document, Text); + _preventTextUpdate = false; + } + + public void Clear() + { + Document.Blocks.Clear(); + } + + public override void BeginInit() + { + base.BeginInit(); + _preventTextUpdate = true; + _preventDocumentUpdate = true; + } + + public override void EndInit() + { + base.EndInit(); + _preventTextUpdate = false; + _preventDocumentUpdate = false; + + if (!string.IsNullOrEmpty(Text)) { + UpdateDocumentFromText(); } - - public CustomRichTextBox(FlowDocument document) : base(document) - { - } - - public static readonly DependencyProperty TextProperty = DependencyProperty.Register( - "Text", typeof(string), typeof(CustomRichTextBox), - new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, - OnTextPropertyChanged, CoerceTextProperty, true, UpdateSourceTrigger.LostFocus)); - - public string Text - { - get => (string) GetValue(TextProperty); - set => SetValue(TextProperty, value); - } - - private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((CustomRichTextBox) d).UpdateDocumentFromText(); - } - - private static object CoerceTextProperty(DependencyObject d, object value) - { - return value ?? ""; - } - - public static readonly DependencyProperty TextFormatterProperty = - DependencyProperty.Register( - "TextFormatter", typeof(ITextFormatter), typeof(CustomRichTextBox), - new FrameworkPropertyMetadata(new FLogger(), OnTextFormatterPropertyChanged)); - - public ITextFormatter TextFormatter - { - get => (ITextFormatter) GetValue(TextFormatterProperty); - set => SetValue(TextFormatterProperty, value); - } - - private static void OnTextFormatterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (d is CustomRichTextBox richTextBox) - { - richTextBox.OnTextFormatterPropertyChanged((ITextFormatter) e.OldValue, (ITextFormatter) e.NewValue); - } - } - - protected virtual void OnTextFormatterPropertyChanged(ITextFormatter oldValue, ITextFormatter newValue) + else { UpdateTextFromDocument(); } - - protected override void OnTextChanged(TextChangedEventArgs e) - { - UpdateTextFromDocument(); - base.OnTextChanged(e); - } - - private void UpdateTextFromDocument() - { - if (_preventTextUpdate) - return; - - _preventDocumentUpdate = true; - SetCurrentValue(TextProperty, TextFormatter.GetText(Document)); - _preventDocumentUpdate = false; - } - - private void UpdateDocumentFromText() - { - if (_preventDocumentUpdate) - return; - - _preventTextUpdate = true; - TextFormatter.SetText(Document, Text); - _preventTextUpdate = false; - } - - public void Clear() - { - Document.Blocks.Clear(); - } - - public override void BeginInit() - { - base.BeginInit(); - _preventTextUpdate = true; - _preventDocumentUpdate = true; - } - - public override void EndInit() - { - base.EndInit(); - _preventTextUpdate = false; - _preventDocumentUpdate = false; - - if (!string.IsNullOrEmpty(Text)) - { - UpdateDocumentFromText(); - } - else - { - UpdateTextFromDocument(); - } - } } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/Rtb/CustomScrollViewer.cs b/FModel/Views/Resources/Controls/Rtb/CustomScrollViewer.cs index 9c5627d4..e8acc848 100644 --- a/FModel/Views/Resources/Controls/Rtb/CustomScrollViewer.cs +++ b/FModel/Views/Resources/Controls/Rtb/CustomScrollViewer.cs @@ -1,56 +1,55 @@ using System.Windows; using System.Windows.Controls; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public class CustomScrollViewer : ScrollViewer { - public class CustomScrollViewer : ScrollViewer + /// + /// VerticalOffset attached property + /// + public new static readonly DependencyProperty VerticalOffsetProperty = + DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), + typeof(CustomScrollViewer), new FrameworkPropertyMetadata(double.NaN, + FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnVerticalOffsetPropertyChanged)); + + /// + /// Just a flag that the binding has been applied. + /// + private static readonly DependencyProperty _verticalScrollBindingProperty = + DependencyProperty.RegisterAttached("_verticalScrollBinding", typeof(bool?), typeof(CustomScrollViewer)); + + public static double GetVerticalOffset(DependencyObject depObj) { - /// - /// VerticalOffset attached property - /// - public new static readonly DependencyProperty VerticalOffsetProperty = - DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), - typeof(CustomScrollViewer), new FrameworkPropertyMetadata(double.NaN, - FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnVerticalOffsetPropertyChanged)); + return (double) depObj.GetValue(VerticalOffsetProperty); + } - /// - /// Just a flag that the binding has been applied. - /// - private static readonly DependencyProperty _verticalScrollBindingProperty = - DependencyProperty.RegisterAttached("_verticalScrollBinding", typeof(bool?), typeof(CustomScrollViewer)); + public static void SetVerticalOffset(DependencyObject depObj, double value) + { + depObj.SetValue(VerticalOffsetProperty, value); + } - public static double GetVerticalOffset(DependencyObject depObj) + private static void OnVerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var v = (double) e.NewValue; + if (d is not ScrollViewer scrollViewer || double.IsNaN(v)) + return; + + BindVerticalOffset(scrollViewer); + scrollViewer.ScrollToVerticalOffset(v); + } + + private static void BindVerticalOffset(ScrollViewer scrollViewer) + { + if (scrollViewer.GetValue(_verticalScrollBindingProperty) != null) + return; + + scrollViewer.SetValue(_verticalScrollBindingProperty, true); + scrollViewer.ScrollChanged += (_, se) => { - return (double) depObj.GetValue(VerticalOffsetProperty); - } - - public static void SetVerticalOffset(DependencyObject depObj, double value) - { - depObj.SetValue(VerticalOffsetProperty, value); - } - - private static void OnVerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var v = (double) e.NewValue; - if (d is not ScrollViewer scrollViewer || double.IsNaN(v)) + if (se.VerticalChange == 0) return; - - BindVerticalOffset(scrollViewer); - scrollViewer.ScrollToVerticalOffset(v); - } - - private static void BindVerticalOffset(ScrollViewer scrollViewer) - { - if (scrollViewer.GetValue(_verticalScrollBindingProperty) != null) - return; - - scrollViewer.SetValue(_verticalScrollBindingProperty, true); - scrollViewer.ScrollChanged += (s, se) => - { - if (se.VerticalChange == 0) - return; - SetVerticalOffset(scrollViewer, se.VerticalOffset); - }; - } + SetVerticalOffset(scrollViewer, se.VerticalOffset); + }; } } \ No newline at end of file diff --git a/FModel/Views/Resources/Controls/TreeViewItemBehavior.cs b/FModel/Views/Resources/Controls/TreeViewItemBehavior.cs index 37bb6f3c..e4cd12a7 100644 --- a/FModel/Views/Resources/Controls/TreeViewItemBehavior.cs +++ b/FModel/Views/Resources/Controls/TreeViewItemBehavior.cs @@ -1,42 +1,41 @@ using System.Windows; using System.Windows.Controls; -namespace FModel.Views.Resources.Controls +namespace FModel.Views.Resources.Controls; + +public sealed class TreeViewItemBehavior { - public sealed class TreeViewItemBehavior + public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem) { - public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem) - { - return (bool) treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty); - } + return (bool) treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty); + } - public static void SetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem, bool value) - { - treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value); - } + public static void SetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem, bool value) + { + treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value); + } - public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty = - DependencyProperty.RegisterAttached("IsBroughtIntoViewWhenSelected", typeof(bool), typeof(TreeViewItemBehavior), - new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged)); + public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty = + DependencyProperty.RegisterAttached("IsBroughtIntoViewWhenSelected", typeof(bool), typeof(TreeViewItemBehavior), + new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged)); - private static void OnIsBroughtIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) - { - if (depObj is not TreeViewItem item) - return; + private static void OnIsBroughtIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) + { + if (depObj is not TreeViewItem item) + return; - if (e.NewValue is not bool value) - return; + if (e.NewValue is not bool value) + return; - if (value) - item.Selected += OnTreeViewItemSelected; - else - item.Selected -= OnTreeViewItemSelected; - } + if (value) + item.Selected += OnTreeViewItemSelected; + else + item.Selected -= OnTreeViewItemSelected; + } - private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e) - { - if (e.OriginalSource is TreeViewItem item) - item.BringIntoView(); - } + private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e) + { + if (e.OriginalSource is TreeViewItem item) + item.BringIntoView(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/BoolToFillModeConverter.cs b/FModel/Views/Resources/Converters/BoolToFillModeConverter.cs index 6b133140..3a85a8c7 100644 --- a/FModel/Views/Resources/Converters/BoolToFillModeConverter.cs +++ b/FModel/Views/Resources/Converters/BoolToFillModeConverter.cs @@ -3,28 +3,27 @@ using System.Globalization; using System.Windows.Data; using SharpDX.Direct3D11; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class BoolToFillModeConverter : IValueConverter { - public class BoolToFillModeConverter : IValueConverter + public static readonly BoolToFillModeConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly BoolToFillModeConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + return value switch { - return value switch - { - FillMode.Solid => true, - _ => false - }; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value switch - { - true => FillMode.Solid, - _ => FillMode.Wireframe - }; - } + FillMode.Solid => true, + _ => false + }; } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value switch + { + true => FillMode.Solid, + _ => FillMode.Wireframe + }; + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/BoolToRenderModeConverter.cs b/FModel/Views/Resources/Converters/BoolToRenderModeConverter.cs index 1fe28556..22ccbc55 100644 --- a/FModel/Views/Resources/Converters/BoolToRenderModeConverter.cs +++ b/FModel/Views/Resources/Converters/BoolToRenderModeConverter.cs @@ -3,25 +3,25 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Media; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class BoolToRenderModeConverter : IValueConverter { - public class BoolToRenderModeConverter : IValueConverter + public static readonly BoolToRenderModeConverter Instance = new(); + + public BitmapScalingMode Convert(bool value) => (BitmapScalingMode) Convert(value, null, null, null); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly BoolToRenderModeConverter Instance = new(); - - public BitmapScalingMode Convert(bool value) => (BitmapScalingMode) Convert(value, null, null, null); - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + return value switch { - return value switch - { - true => BitmapScalingMode.NearestNeighbor, - _ => BitmapScalingMode.Linear - }; - } + true => BitmapScalingMode.NearestNeighbor, + _ => BitmapScalingMode.Linear + }; + } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/BoolToToggleConverter.cs b/FModel/Views/Resources/Converters/BoolToToggleConverter.cs index 88d1811a..535cc4c0 100644 --- a/FModel/Views/Resources/Converters/BoolToToggleConverter.cs +++ b/FModel/Views/Resources/Converters/BoolToToggleConverter.cs @@ -2,24 +2,23 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class BoolToToggleConverter : IValueConverter { - public class BoolToToggleConverter : IValueConverter + public static readonly BoolToToggleConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly BoolToToggleConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + return value switch { - return value switch - { - true => "Enabled", - _ => "Disabled" - }; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + true => "Enabled", + _ => "Disabled" + }; } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/BorderThicknessToStrokeThicknessConverter.cs b/FModel/Views/Resources/Converters/BorderThicknessToStrokeThicknessConverter.cs index 8c3d8b31..d246a9d6 100644 --- a/FModel/Views/Resources/Converters/BorderThicknessToStrokeThicknessConverter.cs +++ b/FModel/Views/Resources/Converters/BorderThicknessToStrokeThicknessConverter.cs @@ -3,21 +3,20 @@ using System.Globalization; using System.Windows; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class BorderThicknessToStrokeThicknessConverter : IValueConverter { - public class BorderThicknessToStrokeThicknessConverter : IValueConverter + public static readonly BorderThicknessToStrokeThicknessConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly BorderThicknessToStrokeThicknessConverter Instance = new(); + var thickness = (Thickness) value; + return (thickness.Bottom + thickness.Left + thickness.Right + thickness.Top) / 4; + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var thickness = (Thickness) value; - return (thickness.Bottom + thickness.Left + thickness.Right + thickness.Top) / 4; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/CaseInsensitiveStringEqualsConverter.cs b/FModel/Views/Resources/Converters/CaseInsensitiveStringEqualsConverter.cs index bcd4a670..60419fee 100644 --- a/FModel/Views/Resources/Converters/CaseInsensitiveStringEqualsConverter.cs +++ b/FModel/Views/Resources/Converters/CaseInsensitiveStringEqualsConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class CaseInsensitiveStringEqualsConverter : IValueConverter { - public class CaseInsensitiveStringEqualsConverter : IValueConverter + public static readonly CaseInsensitiveStringEqualsConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly CaseInsensitiveStringEqualsConverter Instance = new(); + return value.ToString().Equals(parameter.ToString(), StringComparison.OrdinalIgnoreCase); + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString().Equals(parameter.ToString(), StringComparison.OrdinalIgnoreCase); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/DateTimeToStringConverter.cs b/FModel/Views/Resources/Converters/DateTimeToStringConverter.cs index 3857d99c..1d525940 100644 --- a/FModel/Views/Resources/Converters/DateTimeToStringConverter.cs +++ b/FModel/Views/Resources/Converters/DateTimeToStringConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class DateTimeToStringConverter : IValueConverter { - public class DateTimeToStringConverter : IValueConverter + public static readonly DateTimeToStringConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly DateTimeToStringConverter Instance = new(); + return value is not DateTime dateTime ? value : $"{dateTime.ToLongDateString()}, {dateTime.ToShortTimeString()}"; + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value is not DateTime dateTime ? value : $"{dateTime.ToLongDateString()}, {dateTime.ToShortTimeString()}"; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/EnumToStringConverter.cs b/FModel/Views/Resources/Converters/EnumToStringConverter.cs index 75bbe75c..034918ab 100644 --- a/FModel/Views/Resources/Converters/EnumToStringConverter.cs +++ b/FModel/Views/Resources/Converters/EnumToStringConverter.cs @@ -3,30 +3,28 @@ using System; using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class EnumToStringConverter : IValueConverter { - public class EnumToStringConverter : IValueConverter + public static readonly EnumToStringConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly EnumToStringConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + switch (value) { - switch (value) - { - case null: - return null; - case Enum e: - return e.GetDescription(); - default: - Type t; - t = value.GetType(); - return t.IsValueType ? ((Enum) Activator.CreateInstance(t)).GetDescription() : value; - } - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); + case null: + return null; + case Enum e: + return e.GetDescription(); + default: + var t = value.GetType(); + return t.IsValueType ? ((Enum) Activator.CreateInstance(t)).GetDescription() : value; } } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/FileExtensionEqualsConverter.cs b/FModel/Views/Resources/Converters/FileExtensionEqualsConverter.cs index cc09ac5a..114e136e 100644 --- a/FModel/Views/Resources/Converters/FileExtensionEqualsConverter.cs +++ b/FModel/Views/Resources/Converters/FileExtensionEqualsConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class FileExtensionEqualsConverter : IValueConverter { - public class FileExtensionEqualsConverter : IValueConverter + public static readonly FileExtensionEqualsConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly FileExtensionEqualsConverter Instance = new(); + return value.ToString().EndsWith(parameter.ToString()); + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString().EndsWith(parameter.ToString()); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs b/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs index d36f66d3..ee0c275e 100644 --- a/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs +++ b/FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class FolderToSeparatorTagConverter : IValueConverter { - public class FolderToSeparatorTagConverter : IValueConverter + public static readonly FolderToSeparatorTagConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly FolderToSeparatorTagConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value != null ? $"{value.ToString()?.ToUpper()} PACKAGES" : null; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + return value != null ? $"{value.ToString()?.ToUpper()} PACKAGES" : null; } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/FullPathToFileConverter.cs b/FModel/Views/Resources/Converters/FullPathToFileConverter.cs index 9f11d0a1..fe87d462 100644 --- a/FModel/Views/Resources/Converters/FullPathToFileConverter.cs +++ b/FModel/Views/Resources/Converters/FullPathToFileConverter.cs @@ -3,20 +3,19 @@ using System.Globalization; using System.Windows.Data; using FModel.Extensions; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class FullPathToFileConverter : IValueConverter { - public class FullPathToFileConverter : IValueConverter + public static readonly FullPathToFileConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly FullPathToFileConverter Instance = new(); + return value.ToString().SubstringAfterLast('/'); + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString().SubstringAfterLast('/'); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/IsNullToBoolReversedConverter.cs b/FModel/Views/Resources/Converters/IsNullToBoolReversedConverter.cs index eb690d4a..9bd7bc4f 100644 --- a/FModel/Views/Resources/Converters/IsNullToBoolReversedConverter.cs +++ b/FModel/Views/Resources/Converters/IsNullToBoolReversedConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class IsNullToBoolReversedConverter : IValueConverter { - public class IsNullToBoolReversedConverter : IValueConverter + public static readonly IsNullToBoolReversedConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly IsNullToBoolReversedConverter Instance = new(); + return value != null; + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value != null; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/MultiParameterConverter.cs b/FModel/Views/Resources/Converters/MultiParameterConverter.cs index 7fbdb7de..1b185fce 100644 --- a/FModel/Views/Resources/Converters/MultiParameterConverter.cs +++ b/FModel/Views/Resources/Converters/MultiParameterConverter.cs @@ -2,20 +2,19 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class MultiParameterConverter : IMultiValueConverter { - public class MultiParameterConverter : IMultiValueConverter + public static readonly MultiParameterConverter Instance = new(); + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - public static readonly MultiParameterConverter Instance = new(); + return values.Clone(); + } - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - return values.Clone(); - } - - public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/RatioConverter.cs b/FModel/Views/Resources/Converters/RatioConverter.cs index 049fcc90..21778629 100644 --- a/FModel/Views/Resources/Converters/RatioConverter.cs +++ b/FModel/Views/Resources/Converters/RatioConverter.cs @@ -3,26 +3,25 @@ using System.Globalization; using System.Windows.Data; using System.Windows.Markup; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class RatioConverter : MarkupExtension, IValueConverter { - public class RatioConverter : MarkupExtension, IValueConverter + private static readonly RatioConverter _instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - private static readonly RatioConverter _instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - var size = System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, culture); - return size.ToString("G0", culture); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - - public override object ProvideValue(IServiceProvider serviceProvider) - { - return _instance; - } + var size = System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, culture); + return size.ToString("G0", culture); } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + return _instance; + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/SizeToStringConverter.cs b/FModel/Views/Resources/Converters/SizeToStringConverter.cs index 484c84c5..751933f7 100644 --- a/FModel/Views/Resources/Converters/SizeToStringConverter.cs +++ b/FModel/Views/Resources/Converters/SizeToStringConverter.cs @@ -3,20 +3,19 @@ using System; using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class SizeToStringConverter : IValueConverter { - public class SizeToStringConverter : IValueConverter + public static readonly SizeToStringConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly SizeToStringConverter Instance = new(); + return StringExtensions.GetReadableSize(System.Convert.ToDouble(value)); + } - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return StringExtensions.GetReadableSize(System.Convert.ToDouble(value)); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/StringToGameConverter.cs b/FModel/Views/Resources/Converters/StringToGameConverter.cs index c426fdcf..05fac32f 100644 --- a/FModel/Views/Resources/Converters/StringToGameConverter.cs +++ b/FModel/Views/Resources/Converters/StringToGameConverter.cs @@ -2,40 +2,39 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class StringToGameConverter : IValueConverter { - public class StringToGameConverter : IValueConverter + public static readonly StringToGameConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly StringToGameConverter Instance = new(); - - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + return value switch { - return value switch - { - "Newt" => "Spellbreak", - "Nebula" => "Days Gone", - "Fortnite" => "Fortnite", - "VALORANT" => "Valorant", - "Pewee" => "Rogue Company", - "Catnip" => "Borderlands 3", - "AzaleaAlpha" => "The Cycle", - "Snoek" => "State of Decay 2", - "Rosemallow" => "The Outer Worlds", - "WorldExplorersLive" => "Battle Breakers", - "MinecraftDungeons" => "Minecraft Dungeons", - "shoebill" => "Star Wars: Jedi Fallen Order", - "a99769d95d8f400baad1f67ab5dfe508" => "Core", - 381210 => "Dead By Daylight", - 578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS", - 677620 => "Splitgate", - 1172620 => "Sea of Thieves", - _ => value, - }; - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + "Newt" => "Spellbreak", + "Nebula" => "Days Gone", + "Fortnite" => "Fortnite", + "VALORANT" => "Valorant", + "Pewee" => "Rogue Company", + "Catnip" => "Borderlands 3", + "AzaleaAlpha" => "The Cycle", + "Snoek" => "State of Decay 2", + "Rosemallow" => "The Outer Worlds", + "WorldExplorersLive" => "Battle Breakers", + "MinecraftDungeons" => "Minecraft Dungeons", + "shoebill" => "Star Wars: Jedi Fallen Order", + "a99769d95d8f400baad1f67ab5dfe508" => "Core", + 381210 => "Dead By Daylight", + 578080 => "PLAYERUNKNOWN'S BATTLEGROUNDS", + 677620 => "Splitgate", + 1172620 => "Sea of Thieves", + _ => value, + }; } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/TabSizeConverter.cs b/FModel/Views/Resources/Converters/TabSizeConverter.cs index 236ea69a..147f4bd3 100644 --- a/FModel/Views/Resources/Converters/TabSizeConverter.cs +++ b/FModel/Views/Resources/Converters/TabSizeConverter.cs @@ -3,25 +3,24 @@ using System.Globalization; using System.Windows.Controls; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class TabSizeConverter : IMultiValueConverter { - public class TabSizeConverter : IMultiValueConverter + public static readonly TabSizeConverter Instance = new(); + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { - public static readonly TabSizeConverter Instance = new(); + if (values[0] is not TabControl tabControl) + return 0; - public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) - { - if (values[0] is not TabControl tabControl) - return 0; + var hasDivider = parameter is string; + var width = tabControl.ActualWidth / (hasDivider ? double.Parse(parameter.ToString() ?? "6") : tabControl.Items.Count); + return width <= 1 ? 0 : width - (hasDivider ? 8 : 0); + } - var hasDivider = parameter is string; - var width = tabControl.ActualWidth / (hasDivider ? double.Parse(parameter.ToString() ?? "6") : tabControl.Items.Count); - return width <= 1 ? 0 : width - (hasDivider ? 8 : 0); - } - - public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + public object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/TagToColorConverter.cs b/FModel/Views/Resources/Converters/TagToColorConverter.cs index 9b7012ea..ae0d4cfd 100644 --- a/FModel/Views/Resources/Converters/TagToColorConverter.cs +++ b/FModel/Views/Resources/Converters/TagToColorConverter.cs @@ -4,25 +4,24 @@ using System.Windows.Data; using System.Windows.Media; using HelixToolkit.Wpf.SharpDX; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class TagToColorConverter : IValueConverter { - public class TagToColorConverter : IValueConverter + public static readonly TagToColorConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly TagToColorConverter Instance = new(); + if (value is not PBRMaterial material) + return new SolidColorBrush(Colors.Red); - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - if (value is not PBRMaterial material) - return new SolidColorBrush(Colors.Red); - - return new SolidColorBrush(Color.FromScRgb( - material.AlbedoColor.Alpha, material.AlbedoColor.Red, - material.AlbedoColor.Green, material.AlbedoColor.Blue)); - } - - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + return new SolidColorBrush(Color.FromScRgb( + material.AlbedoColor.Alpha, material.AlbedoColor.Red, + material.AlbedoColor.Green, material.AlbedoColor.Blue)); } -} + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/FModel/Views/Resources/Converters/TrimRightToLeftConverter.cs b/FModel/Views/Resources/Converters/TrimRightToLeftConverter.cs index 2be992b1..314a0f34 100644 --- a/FModel/Views/Resources/Converters/TrimRightToLeftConverter.cs +++ b/FModel/Views/Resources/Converters/TrimRightToLeftConverter.cs @@ -2,32 +2,31 @@ using System.Globalization; using System.Windows.Data; -namespace FModel.Views.Resources.Converters +namespace FModel.Views.Resources.Converters; + +public class TrimRightToLeftConverter : IValueConverter { - public class TrimRightToLeftConverter : IValueConverter + public static readonly TrimRightToLeftConverter Instance = new(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public static readonly TrimRightToLeftConverter Instance = new(); + if (value == null) return null; - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + var path = value.ToString(); + var maxWidth = System.Convert.ToDouble(parameter, culture); + + var did = false; + while (path != null && path.Length > maxWidth / 7) { - if (value == null) return null; - - var path = value.ToString(); - var maxWidth = System.Convert.ToDouble(parameter, culture); - - var did = false; - while (path != null && path.Length > maxWidth / 7) - { - did = true; - path = path[2..]; - } - - return did ? $"...{path}" : path; + did = true; + path = path[2..]; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + return did ? $"...{path}" : path; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/FModel/Views/SearchView.xaml.cs b/FModel/Views/SearchView.xaml.cs index a367536d..8757fc1d 100644 --- a/FModel/Views/SearchView.xaml.cs +++ b/FModel/Views/SearchView.xaml.cs @@ -5,80 +5,79 @@ using System.Windows.Input; using FModel.Extensions; using FModel.Services; using FModel.ViewModels; -using FModel.ViewModels.Commands; -namespace FModel.Views +namespace FModel.Views; + +public partial class SearchView { - public partial class SearchView + private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public SearchView() { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + DataContext = _applicationView; + InitializeComponent(); - public SearchView() + Activate(); + WpfSuckMyDick.Focus(); + WpfSuckMyDick.SelectAll(); + } + + private void OnDeleteSearchClick(object sender, RoutedEventArgs e) + { + _applicationView.CUE4Parse.SearchVm.FilterText = string.Empty; + _applicationView.CUE4Parse.SearchVm.RefreshFilter(); + } + + private async void OnAssetDoubleClick(object sender, RoutedEventArgs e) + { + if (SearchListView.SelectedItem is not AssetItem assetItem) + return; + + WindowState = WindowState.Minimized; + MainWindow.YesWeCats.AssetsListName.ItemsSource = null; + var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(assetItem.FullPath.SubstringBeforeLast('/')); + if (folder == null) return; + + MainWindow.YesWeCats.Activate(); + + do { await Task.Delay(100); } while (MainWindow.YesWeCats.AssetsListName.Items.Count < folder.AssetsList.Assets.Count); + + MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 2; // assets tab + do { - DataContext = _applicationView; - InitializeComponent(); + await Task.Delay(100); + MainWindow.YesWeCats.AssetsListName.SelectedItem = assetItem; + MainWindow.YesWeCats.AssetsListName.ScrollIntoView(assetItem); + } while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null); + } - Activate(); - WpfSuckMyDick.Focus(); - WpfSuckMyDick.SelectAll(); - } + private async void OnAssetExtract(object sender, RoutedEventArgs e) + { + if (SearchListView.SelectedItem is not AssetItem assetItem) + return; - private void OnDeleteSearchClick(object sender, RoutedEventArgs e) + WindowState = WindowState.Minimized; + await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.Extract(assetItem.FullPath, true)); + + MainWindow.YesWeCats.Activate(); + } + + private void OnWindowKeyDown(object sender, KeyEventArgs e) + { + if (e.Key != Key.Enter) return; + _applicationView.CUE4Parse.SearchVm.RefreshFilter(); + } + + private void OnStateChanged(object sender, EventArgs e) + { + switch (WindowState) { - _applicationView.CUE4Parse.SearchVm.FilterText = string.Empty; - _applicationView.CUE4Parse.SearchVm.RefreshFilter(); - } - - private async void OnAssetDoubleClick(object sender, RoutedEventArgs e) - { - if (SearchListView.SelectedItem is not AssetItem assetItem) + case WindowState.Normal: + Activate(); + WpfSuckMyDick.Focus(); + WpfSuckMyDick.SelectAll(); return; - - WindowState = WindowState.Minimized; - MainWindow.YesWeCats.AssetsListName.ItemsSource = null; - var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(assetItem.FullPath.SubstringBeforeLast('/')); - if (folder == null) return; - - MainWindow.YesWeCats.Activate(); - - do { await Task.Delay(100); } while (MainWindow.YesWeCats.AssetsListName.Items.Count < folder.AssetsList.Assets.Count); - MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 2; // assets tab - do - { - await Task.Delay(100); - MainWindow.YesWeCats.AssetsListName.SelectedItem = assetItem; - MainWindow.YesWeCats.AssetsListName.ScrollIntoView(assetItem); - } while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null); - } - - private async void OnAssetExtract(object sender, RoutedEventArgs e) - { - if (SearchListView.SelectedItem is not AssetItem assetItem) - return; - - WindowState = WindowState.Minimized; - await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.Extract(assetItem.FullPath, true)); - - MainWindow.YesWeCats.Activate(); - } - - private void OnWindowKeyDown(object sender, KeyEventArgs e) - { - if (e.Key != Key.Enter) return; - _applicationView.CUE4Parse.SearchVm.RefreshFilter(); - } - - private void OnStateChanged(object sender, EventArgs e) - { - switch (WindowState) - { - case WindowState.Normal: - Activate(); - WpfSuckMyDick.Focus(); - WpfSuckMyDick.SelectAll(); - return; - } } } -} +} \ No newline at end of file diff --git a/FModel/Views/SettingsView.xaml.cs b/FModel/Views/SettingsView.xaml.cs index 015de7c5..04fe274c 100644 --- a/FModel/Views/SettingsView.xaml.cs +++ b/FModel/Views/SettingsView.xaml.cs @@ -8,158 +8,163 @@ using FModel.Views.Resources.Controls; using Microsoft.Win32; using Ookii.Dialogs.Wpf; -namespace FModel.Views +namespace FModel.Views; + +public partial class SettingsView { - public partial class SettingsView + private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + + public SettingsView() { - private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + DataContext = _applicationView; + _applicationView.SettingsView.Initialize(); - public SettingsView() + InitializeComponent(); + + var i = 0; + foreach (var item in SettingsTree.Items) { - DataContext = _applicationView; - _applicationView.SettingsView.Initialize(); - - InitializeComponent(); - - var i = 0; - foreach (var item in SettingsTree.Items) - { - if (item is not TreeViewItem {Visibility: Visibility.Visible} treeItem) continue; - treeItem.IsSelected = i == UserSettings.Default.LastOpenedSettingTab; - i++; - } - } - - private async void OnLoaded(object sender, RoutedEventArgs e) - { - await _applicationView.SettingsView.InitPresets(_applicationView.CUE4Parse.Provider.GameName); - } - - private async void OnClick(object sender, RoutedEventArgs e) - { - var whatShouldIDo = _applicationView.SettingsView.Save(); - if (whatShouldIDo == SettingsOut.Restart) - _applicationView.RestartWithWarning(); - - Close(); - - switch (whatShouldIDo) - { - case SettingsOut.ReloadLocres: - _applicationView.CUE4Parse.LocalizedResourcesCount = 0; - await _applicationView.CUE4Parse.LoadLocalizedResources(); - break; - case SettingsOut.CheckForUpdates: - ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); - break; - } - } - - private void OnBrowseOutput(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.OutputDirectory = path; - } - private void OnBrowseDirectories(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.GameDirectory = path; - } - private void OnBrowseRawData(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.RawDataDirectory = path; - } - private void OnBrowseProperties(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.PropertiesDirectory = path; - } - private void OnBrowseTexture(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.TextureDirectory = path; - } - private void OnBrowseAudio(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.AudioDirectory = path; - } - private void OnBrowseModels(object sender, RoutedEventArgs e) - { - if (TryBrowse(out var path)) UserSettings.Default.ModelDirectory = path; - } - private async void OnBrowseMappings(object sender, RoutedEventArgs e) - { - var openFileDialog = new OpenFileDialog - { - Title = "Select a mapping file", - InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, ".data"), - Filter = "USMAP Files (*.usmap)|*.usmap|All Files (*.*)|*.*" - }; - - if (!(bool) openFileDialog.ShowDialog()) return; - - UserSettings.Default.MappingFilePath = openFileDialog.FileName; - await _applicationView.CUE4Parse.InitBenMappings(); - } - - private bool TryBrowse(out string path) - { - var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false}; - if (folderBrowser.ShowDialog() == true) - { - path = folderBrowser.SelectedPath; - return true; - } - - path = string.Empty; - return false; - } - - private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) - { - var i = 0; - foreach (var item in SettingsTree.Items) - { - if (item is not TreeViewItem {Visibility: Visibility.Visible} treeItem) - continue; - if (!treeItem.IsSelected) - { - i++; - continue; - } - - UserSettings.Default.LastOpenedSettingTab = i; - break; - } - } - - private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - if (sender is not ComboBox {SelectedItem: string s}) return; - if (s == Constants._NO_PRESET_TRIGGER) _applicationView.SettingsView.ResetPreset(); - else _applicationView.SettingsView.SwitchPreset(s); - } - - private void OpenCustomVersions(object sender, RoutedEventArgs e) - { - var dictionary = new DictionaryEditor( - _applicationView.SettingsView.SelectedCustomVersions, - "Versioning Configuration (Custom Versions)", - _applicationView.SettingsView.EnableElements); - var result = dictionary.ShowDialog(); - if (!result.HasValue || !result.Value) - return; - - _applicationView.SettingsView.SelectedCustomVersions = dictionary.CustomVersions; - } - - private void OpenOptions(object sender, RoutedEventArgs e) - { - var dictionary = new DictionaryEditor( - _applicationView.SettingsView.SelectedOptions, - "Versioning Configuration (Options)", - _applicationView.SettingsView.EnableElements); - var result = dictionary.ShowDialog(); - if (!result.HasValue || !result.Value) - return; - - _applicationView.SettingsView.SelectedOptions = dictionary.Options; + if (item is not TreeViewItem { Visibility: Visibility.Visible } treeItem) continue; + treeItem.IsSelected = i == UserSettings.Default.LastOpenedSettingTab; + i++; } } -} + + private async void OnLoaded(object sender, RoutedEventArgs e) + { + await _applicationView.SettingsView.InitPresets(_applicationView.CUE4Parse.Provider.GameName); + } + + private async void OnClick(object sender, RoutedEventArgs e) + { + var whatShouldIDo = _applicationView.SettingsView.Save(); + if (whatShouldIDo == SettingsOut.Restart) + _applicationView.RestartWithWarning(); + + Close(); + + switch (whatShouldIDo) + { + case SettingsOut.ReloadLocres: + _applicationView.CUE4Parse.LocalizedResourcesCount = 0; + await _applicationView.CUE4Parse.LoadLocalizedResources(); + break; + case SettingsOut.CheckForUpdates: + ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode); + break; + } + } + + private void OnBrowseOutput(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.OutputDirectory = path; + } + + private void OnBrowseDirectories(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.GameDirectory = path; + } + + private void OnBrowseRawData(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.RawDataDirectory = path; + } + + private void OnBrowseProperties(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.PropertiesDirectory = path; + } + + private void OnBrowseTexture(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.TextureDirectory = path; + } + + private void OnBrowseAudio(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.AudioDirectory = path; + } + + private void OnBrowseModels(object sender, RoutedEventArgs e) + { + if (TryBrowse(out var path)) UserSettings.Default.ModelDirectory = path; + } + + private async void OnBrowseMappings(object sender, RoutedEventArgs e) + { + var openFileDialog = new OpenFileDialog + { + Title = "Select a mapping file", + InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, ".data"), + Filter = "USMAP Files (*.usmap)|*.usmap|All Files (*.*)|*.*" + }; + + if (!openFileDialog.ShowDialog().GetValueOrDefault()) return; + UserSettings.Default.MappingFilePath = openFileDialog.FileName; + await _applicationView.CUE4Parse.InitBenMappings(); + } + + private bool TryBrowse(out string path) + { + var folderBrowser = new VistaFolderBrowserDialog { ShowNewFolderButton = false }; + if (folderBrowser.ShowDialog() == true) + { + path = folderBrowser.SelectedPath; + return true; + } + + path = string.Empty; + return false; + } + + private void OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) + { + var i = 0; + foreach (var item in SettingsTree.Items) + { + if (item is not TreeViewItem { Visibility: Visibility.Visible } treeItem) + continue; + if (!treeItem.IsSelected) + { + i++; + continue; + } + + UserSettings.Default.LastOpenedSettingTab = i; + break; + } + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is not ComboBox { SelectedItem: string s }) return; + if (s == Constants._NO_PRESET_TRIGGER) _applicationView.SettingsView.ResetPreset(); + else _applicationView.SettingsView.SwitchPreset(s); + } + + private void OpenCustomVersions(object sender, RoutedEventArgs e) + { + var dictionary = new DictionaryEditor( + _applicationView.SettingsView.SelectedCustomVersions, + "Versioning Configuration (Custom Versions)", + _applicationView.SettingsView.EnableElements); + var result = dictionary.ShowDialog(); + if (!result.HasValue || !result.Value) + return; + + _applicationView.SettingsView.SelectedCustomVersions = dictionary.CustomVersions; + } + + private void OpenOptions(object sender, RoutedEventArgs e) + { + var dictionary = new DictionaryEditor( + _applicationView.SettingsView.SelectedOptions, + "Versioning Configuration (Options)", + _applicationView.SettingsView.EnableElements); + var result = dictionary.ShowDialog(); + if (!result.HasValue || !result.Value) + return; + + _applicationView.SettingsView.SelectedOptions = dictionary.Options; + } +} \ No newline at end of file