mirror of
https://github.com/4sval/FModel.git
synced 2026-09-20 07:24:45 -05:00
Merge remote-tracking branch 'origin/dev' into feature/new-viewer
# Conflicts: # CUE4Parse # FModel/MainWindow.xaml # FModel/ViewModels/CUE4ParseViewModel.cs # FModel/Views/Snooper/Animations/Skeleton.cs # FModel/Views/Snooper/Models/SkeletalModel.cs # FModel/Views/Snooper/Models/StaticModel.cs # FModel/Views/Snooper/Renderer.cs
This commit is contained in:
Submodule CUE4Parse updated: e69ae89739...ffd48e7719
@@ -32,6 +32,7 @@ public class BaseIcon : UCreator
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
|
||||
else if (Object.TryGetValue(out EFortRarity hackRarity, "HackRarity")) GetRarity(hackRarity);
|
||||
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
|
||||
|
||||
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||
@@ -48,7 +49,7 @@ public class BaseIcon : UCreator
|
||||
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", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon", "CartridgeIcon", "EffectIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
@@ -66,9 +67,9 @@ public class BaseIcon : UCreator
|
||||
}
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle", "KeyName", "HackDisplayName"))
|
||||
DisplayName = displayName.Text;
|
||||
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "SetDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription"))
|
||||
if (Object.TryGetValue(out FText description, "Description", "ItemDescription", "SetDescription", "BundleDescription", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription", "EventCalloutDescription", "KeyDescription", "HackDescription"))
|
||||
Description = description.Text;
|
||||
else if (Object.TryGetValue(out FText[] descriptions, "Description"))
|
||||
Description = string.Join('\n', descriptions.Select(x => x.Text));
|
||||
|
||||
@@ -65,6 +65,7 @@ public class CreatorPackage : IDisposable
|
||||
case "AthenaEmojiItemDefinition":
|
||||
case "AthenaItemWrapDefinition":
|
||||
case "AthenaToyItemDefinition":
|
||||
case "FortHackData":
|
||||
case "FortHeroType":
|
||||
case "FortTokenType":
|
||||
case "FortAbilityKit":
|
||||
@@ -72,7 +73,9 @@ public class CreatorPackage : IDisposable
|
||||
case "FortMissionInfo":
|
||||
case "RewardGraphToken":
|
||||
case "JunoKnowledgeBundle":
|
||||
case "FortHackDataMinimal":
|
||||
case "FortBannerTokenType":
|
||||
case "FortHackKeyCoreData":
|
||||
case "FortVariantTokenType":
|
||||
case "FortDecoItemDefinition":
|
||||
case "FortStatItemDefinition":
|
||||
@@ -88,6 +91,7 @@ public class CreatorPackage : IDisposable
|
||||
case "SparksBassItemDefinition":
|
||||
case "FortGadgetItemDefinition":
|
||||
case "AthenaCharmItemDefinition":
|
||||
case "FortHackKeyItemDefinition":
|
||||
case "FortPlaysetItemDefinition":
|
||||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
@@ -124,6 +128,7 @@ public class CreatorPackage : IDisposable
|
||||
case "JunoRecipeBundleItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "FortHackCartridgeItemDefinition":
|
||||
case "FortPlayerAugmentItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
case "FortGiftBoxUnlockItemDefinition":
|
||||
|
||||
@@ -131,6 +131,7 @@ public enum EAssetCategory : uint
|
||||
CustomizableObject = Mesh + 3,
|
||||
NaniteDisplacedMesh = Mesh + 4,
|
||||
GeometryCollection = Mesh + 5,
|
||||
ChaosClothAsset = Mesh + 6,
|
||||
Texture = AssetCategoryExtensions.CategoryBase + (3 << 16),
|
||||
Materials = AssetCategoryExtensions.CategoryBase + (4 << 16),
|
||||
Material = Materials + 1,
|
||||
@@ -167,6 +168,15 @@ public enum EAssetCategory : uint
|
||||
DeltaForce = GameSpecific + 4,
|
||||
LegoBatman = GameSpecific + 5,
|
||||
ArcSys = GameSpecific + 6,
|
||||
GothamKnights = GameSpecific + 7,
|
||||
}
|
||||
|
||||
public enum EAssetFamily
|
||||
{
|
||||
None,
|
||||
Criware,
|
||||
Wwise,
|
||||
FMod,
|
||||
}
|
||||
|
||||
public enum EUnluacMode
|
||||
|
||||
@@ -11,7 +11,9 @@ public static class VisualTreeExtensions
|
||||
{
|
||||
if (current is T t)
|
||||
return t;
|
||||
current = VisualTreeHelper.GetParent(current);
|
||||
current = current is FrameworkContentElement contentElement
|
||||
? contentElement.Parent
|
||||
: VisualTreeHelper.GetParent(current);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -820,6 +820,33 @@
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding PendingRequest.DisplayText, Source={x:Static viewModels:SnooperViewModel.Instance}, Mode=OneWay, StringFormat={}{0}: double-click one}" />
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Margin="0 0 10 0"
|
||||
Padding="0"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
Visibility="{Binding IsUpdateAvailable, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Button HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
Style="{StaticResource StatusBarButton}"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}"
|
||||
ToolTip="A new version of FModel is available. Click to view it."
|
||||
Command="{Binding MenuCommand}"
|
||||
CommandParameter="Help_Releases">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
VerticalAlignment="Center">
|
||||
<Viewbox Width="16"
|
||||
Height="16">
|
||||
<Canvas Width="24"
|
||||
Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
|
||||
Data="{StaticResource UpdateIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<TextBlock Margin="10 0 0 0"
|
||||
Text="Update Available" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StatusBarItem>
|
||||
<StatusBarItem Margin="0 0 10 0" Padding="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
|
||||
<Button x:Name="ExportSessionButton"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
|
||||
@@ -131,6 +131,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
||||
UserSettings.Default.LastUpdateCheck = DateTime.Now;
|
||||
|
||||
var targetHash = ((CustomMandatory) args.Mandatory).CommitHash;
|
||||
_applicationView.IsUpdateAvailable = targetHash != Constants.APP_COMMIT_ID;
|
||||
if (targetHash == Constants.APP_COMMIT_ID)
|
||||
{
|
||||
if (UserSettings.Default.ShowChangelog)
|
||||
|
||||
@@ -61,6 +61,13 @@ public class ApplicationViewModel : ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isUpdateAvailable;
|
||||
public bool IsUpdateAvailable
|
||||
{
|
||||
get => _isUpdateAvailable;
|
||||
internal set => SetProperty(ref _isUpdateAvailable, value);
|
||||
}
|
||||
|
||||
private int _selectedLeftTabIndex;
|
||||
public int SelectedLeftTabIndex
|
||||
{
|
||||
@@ -139,7 +146,7 @@ public class ApplicationViewModel : ViewModel
|
||||
public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
|
||||
{
|
||||
var gameDirectory = UserSettings.Default.GameDirectory;
|
||||
if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
|
||||
if (!bAlreadyLaunched && GameSelectorViewModel.IsGameDirectoryAvailable(gameDirectory) && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
|
||||
return currentDir;
|
||||
|
||||
Status.SetStatus(EStatusKind.Configuring);
|
||||
|
||||
@@ -14,9 +14,9 @@ using CSCore.CoreAudioAPI;
|
||||
using CSCore.DSP;
|
||||
using CSCore.SoundOut;
|
||||
using CSCore.Streams;
|
||||
using CUE4Parse.UE4.CriWare.Decoders;
|
||||
using CUE4Parse.UE4.CriWare.Decoders.ADX;
|
||||
using CUE4Parse.UE4.CriWare.Decoders.HCA;
|
||||
using CUE4Parse.UE4.Criware.Decoders;
|
||||
using CUE4Parse.UE4.Criware.Decoders.ADX;
|
||||
using CUE4Parse.UE4.Criware.Decoders.HCA;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
|
||||
@@ -10,6 +10,8 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AdonisUI.Controls;
|
||||
using CUE4Parse_Conversion.Exporters;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
using CUE4Parse;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Encryption.Aes;
|
||||
@@ -22,15 +24,17 @@ using CUE4Parse.GameTypes.AshEchoes.FileProvider;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Wwise;
|
||||
using CUE4Parse.GameTypes.Dawnwalker.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.DFHO.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.HonorOfKings.FileProvider;
|
||||
using CUE4Parse.GameTypes.KRD.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.LegoBatman.Assets;
|
||||
using CUE4Parse.GameTypes.LordOfMysteries.FileProvider;
|
||||
using CUE4Parse.GameTypes.RocoKingdomWorld.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.Tencent.RocoKingdomWorld.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Theia.FileProvider;
|
||||
using CUE4Parse.GameTypes.WarnerBros.GothamKnights.Assets.Exports.Wwise;
|
||||
using CUE4Parse.MappingsProvider;
|
||||
using CUE4Parse.MappingsProvider.Jmap;
|
||||
using CUE4Parse.MappingsProvider.Usmap;
|
||||
@@ -38,10 +42,11 @@ using CUE4Parse.UE4.AssetRegistry;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.CriWare;
|
||||
using CUE4Parse.UE4.Assets.Exports.Criware;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Fmod;
|
||||
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Sound;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
@@ -49,8 +54,8 @@ using CUE4Parse.UE4.Assets.Exports.Verse;
|
||||
using CUE4Parse.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using CUE4Parse.UE4.BinaryConfig;
|
||||
using CUE4Parse.UE4.CriWare;
|
||||
using CUE4Parse.UE4.CriWare.Readers;
|
||||
using CUE4Parse.UE4.Criware;
|
||||
using CUE4Parse.UE4.Criware.Readers;
|
||||
using CUE4Parse.UE4.FMod;
|
||||
using CUE4Parse.UE4.GameFeatures;
|
||||
using CUE4Parse.UE4.IO;
|
||||
@@ -1300,6 +1305,11 @@ public class CUE4ParseViewModel : ViewModel
|
||||
TabControl.SelectedTab.AddImage(sourceFile.SubstringAfterLast('/'), false, bitmap, false, updateUi);
|
||||
return false;
|
||||
}
|
||||
case UBiomesMaskTextureData when (isNone || saveTextures) && pointer.Object.Value is UBiomesMaskTextureData biomes && biomes.MaskData is { } maskData:
|
||||
{
|
||||
TabControl.SelectedTab.AddImage(biomes.Name, false, SKBitmap.Decode(maskData.Value), false, updateUi);
|
||||
return false;
|
||||
}
|
||||
// Supermassive Games (for example - The Dark Pictures Anthology: House of Ashes etc.)
|
||||
case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource:
|
||||
{
|
||||
@@ -1506,12 +1516,37 @@ public class CUE4ParseViewModel : ViewModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Gotham Knights
|
||||
case UOrpheusBank when (isNone || saveAudio) && pointer.Object.Value is UOrpheusBank orpheusBank:
|
||||
{
|
||||
if (orpheusBank.SoundBank is null)
|
||||
return false;
|
||||
|
||||
var extractedSounds = WwiseProvider.ExtractBankSounds(orpheusBank.SoundBank);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// Gotham Knights
|
||||
case UOrpheusEvent when (isNone || saveAudio) && pointer.Object.Value is UOrpheusEvent orpheusEvent:
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractGothamKnightsAudioEventSounds(orpheusEvent);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
||||
case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds:
|
||||
// case UPaperSprite when isNone && UserSettings.Default.PreviewMaterials:
|
||||
case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes:
|
||||
case UGeometryCollection when isNone && UserSettings.Default.PreviewStaticMeshes:
|
||||
case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes:
|
||||
case USkinnedAsset when isNone && UserSettings.Default.PreviewSkeletalMeshes:
|
||||
case USkeleton when isNone && UserSettings.Default.SaveSkeletonAsMesh:
|
||||
case UAnimationAsset when isNone && UserSettings.Default.PreviewAnimations:
|
||||
// case UMaterialInstance when isNone && UserSettings.Default.PreviewMaterials &&
|
||||
@@ -1524,7 +1559,8 @@ public class CUE4ParseViewModel : ViewModel
|
||||
return true;
|
||||
}
|
||||
case UStaticMesh when HasFlag(bulk, EBulkType.Meshes):
|
||||
case USkeletalMesh when HasFlag(bulk, EBulkType.Meshes):
|
||||
case UGeometryCollection when HasFlag(bulk, EBulkType.Meshes):
|
||||
case USkinnedAsset when HasFlag(bulk, EBulkType.Meshes):
|
||||
case USkeleton when UserSettings.Default.SaveSkeletonAsMesh && HasFlag(bulk, EBulkType.Meshes):
|
||||
// case UMaterialInterface when HasFlag(bulk, EBulkType.Materials):
|
||||
case UAnimationAsset when HasFlag(bulk, EBulkType.Animations):
|
||||
|
||||
@@ -148,7 +148,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||
{
|
||||
entries.Add(asset);
|
||||
}
|
||||
else if (includeLooseFiles && asset is OsGameFile)
|
||||
else if (includeLooseFiles && asset is not VfsEntry)
|
||||
{
|
||||
entries.Add(asset);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.GameTypes.Borderlands3.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.Borderlands4.Assets.Exports;
|
||||
@@ -15,12 +15,15 @@ using CUE4Parse.GameTypes.RED.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.GameTypes.SMG.UE4.Assets.Objects;
|
||||
using CUE4Parse.GameTypes.SquareEnix.UE4.Assets.Exports;
|
||||
using CUE4Parse.GameTypes.WarnerBros.GothamKnights.Assets.Exports.Wwise;
|
||||
using CUE4Parse.UE4.Assets;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.BuildData;
|
||||
using CUE4Parse.UE4.Assets.Exports.ChaosClothAsset;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component;
|
||||
using CUE4Parse.UE4.Assets.Exports.CriWare;
|
||||
using CUE4Parse.UE4.Assets.Exports.Criware;
|
||||
using CUE4Parse.UE4.Assets.Exports.Criware.Media;
|
||||
using CUE4Parse.UE4.Assets.Exports.CustomizableObject;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine;
|
||||
using CUE4Parse.UE4.Assets.Exports.Engine.Font;
|
||||
@@ -51,17 +54,11 @@ using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Objects.UObject.Editor;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using Svg.Skia;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
@@ -101,6 +98,13 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private EAssetFamily _assetFamily = EAssetFamily.None;
|
||||
public EAssetFamily AssetFamily
|
||||
{
|
||||
get => _assetFamily;
|
||||
private set => SetProperty(ref _assetFamily, value);
|
||||
}
|
||||
|
||||
private EBulkType _assetActions = EBulkType.None;
|
||||
public EBulkType AssetActions
|
||||
{
|
||||
@@ -204,6 +208,20 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
var dummy = ((AbstractUePackage) pkg).ConstructObject(pointer.Class, pkg);
|
||||
ResolvedAssetType = dummy.ExportType;
|
||||
|
||||
AssetFamily = dummy switch
|
||||
{
|
||||
UAtomSoundBase or UAtomSoundBank or USoundAtomCue or USoundAtomCueSheet
|
||||
or USoundAtomConfig or UAtomDspBusSetting or UAtomRackBase => EAssetFamily.Criware,
|
||||
|
||||
UAkAudioType or UAkAssetData or UAkAssetPlatformData or UAkMediaAssetData
|
||||
or UAkMediaAsset or UWwiseAssetLibrary or UExternalSource or UExternalSourceBank => EAssetFamily.Wwise,
|
||||
|
||||
UFMODEvent or UFMODBank or UFMODBankLookup or UFMODBus
|
||||
or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA => EAssetFamily.FMod,
|
||||
|
||||
_ => EAssetFamily.None,
|
||||
};
|
||||
|
||||
(AssetCategory, AssetActions) = dummy switch
|
||||
{
|
||||
URigVMBlueprintGeneratedClass => (EAssetCategory.RigVMBlueprintGeneratedClass, EBulkType.Code),
|
||||
@@ -217,9 +235,10 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
|
||||
UStaticMesh => (EAssetCategory.StaticMesh, EBulkType.Meshes),
|
||||
USkeletalMesh => (EAssetCategory.SkeletalMesh, EBulkType.Meshes),
|
||||
UChaosClothAsset => (EAssetCategory.ChaosClothAsset, EBulkType.Meshes),
|
||||
UCustomizableObject => (EAssetCategory.CustomizableObject, EBulkType.None),
|
||||
UNaniteDisplacedMesh => (EAssetCategory.NaniteDisplacedMesh, EBulkType.None),
|
||||
UGeometryCollection => (EAssetCategory.GeometryCollection, EBulkType.None),
|
||||
UGeometryCollection => (EAssetCategory.GeometryCollection, EBulkType.Meshes),
|
||||
|
||||
UTexture => (EAssetCategory.Texture, EBulkType.Textures),
|
||||
|
||||
@@ -245,18 +264,23 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
UObjectRedirector => (EAssetCategory.ObjectRedirector, EBulkType.None),
|
||||
UPhysicalMaterial => (EAssetCategory.PhysicalMaterial, EBulkType.None),
|
||||
|
||||
USoundAtomCue or UAkAudioEvent or USoundCue or UFMODEvent
|
||||
USoundAtomCue or UAtomSoundCue or UAkAudioEvent or USoundCue or UFMODEvent
|
||||
or UAkAssetData or UAkAssetPlatformData => (EAssetCategory.AudioEvent, EBulkType.Audio),
|
||||
|
||||
UFMODBankLookup => (EAssetCategory.Data, EBulkType.None),
|
||||
UFileManaMovie => (EAssetCategory.Video, EBulkType.None),
|
||||
|
||||
UFMODBus or UFMODSnapshot or UFMODSnapshotReverb or UFMODVCA or USQEXSEADSoundAttenuation => (EAssetCategory.Audio, EBulkType.None),
|
||||
UFMODBankLookup or USoundAtomConfig or UFMODSnapshotReverb or UFMODBus or UFMODSnapshot
|
||||
or UFMODVCA or UAtomDspBusSetting => (EAssetCategory.Data, EBulkType.None),
|
||||
|
||||
UFMODBank or UAkAudioBank or UAtomWaveBank or UAkInitBank or USQEXSEADSoundBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
USQEXSEADSoundAttenuation => (EAssetCategory.Audio, EBulkType.None),
|
||||
|
||||
UWwiseAssetLibrary or USoundBase or UAkMediaAssetData or UAtomCueSheet
|
||||
or USoundAtomCueSheet or UAkAudioType or UExternalSource or UExternalSourceBank
|
||||
or UAkMediaAsset => (EAssetCategory.Audio, EBulkType.Audio),
|
||||
UFMODBank or UAkAudioBank or UAtomSoundBank or USoundAtomCueSheet
|
||||
or UAkInitBank or USQEXSEADSoundBank => (EAssetCategory.SoundBank, EBulkType.Audio),
|
||||
|
||||
UWwiseAssetLibrary or UAtomSoundBase or USoundBase or UAkMediaAssetData
|
||||
or UExternalSource or UExternalSourceBank or UAkMediaAsset => (EAssetCategory.Audio, EBulkType.Audio),
|
||||
|
||||
UAkAudioType => (EAssetCategory.Data, EBulkType.None),
|
||||
|
||||
UFileMediaSource => (EAssetCategory.Video, EBulkType.None),
|
||||
UFont or UFontFace or USMGLocaleFontUMG => (EAssetCategory.Font, EBulkType.None),
|
||||
@@ -269,6 +293,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
|
||||
UWubAudioEvent or UWubDialogueEvent when GameVersion is EGame.GAME_LEGOBatmanLegacyoftheDarkKnight => (EAssetCategory.LegoBatman, EBulkType.Audio), // Lego Batman: Legacy of the Dark Knight;
|
||||
UREDBinaryObject => (EAssetCategory.ArcSys, EBulkType.None), // Arc System Works games;
|
||||
UOrpheusBank or UOrpheusEvent => (EAssetCategory.GothamKnights, EBulkType.Audio), // Gotham Knights;
|
||||
|
||||
_ => (EAssetCategory.All, EBulkType.None),
|
||||
};
|
||||
@@ -336,6 +361,15 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
{
|
||||
Resolved |= EResolveCompute.Preview;
|
||||
var lowercaseExtension = Asset.Extension.ToLowerInvariant();
|
||||
|
||||
AssetFamily = lowercaseExtension switch
|
||||
{
|
||||
"acb" or "acf" or "adx" or "awb" or "hca" or "usm" => EAssetFamily.Criware,
|
||||
"bnk" or "pck" or "wem" => EAssetFamily.Wwise,
|
||||
"bank" or "fsb" => EAssetFamily.FMod,
|
||||
_ => EAssetFamily.None,
|
||||
};
|
||||
|
||||
switch (lowercaseExtension)
|
||||
{
|
||||
case "uproject":
|
||||
@@ -365,6 +399,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
case "sql":
|
||||
case "py":
|
||||
case "cs":
|
||||
case "acf":
|
||||
AssetCategory = EAssetCategory.Data;
|
||||
break;
|
||||
case "stinfo":
|
||||
@@ -375,6 +410,8 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
break;
|
||||
case "wav":
|
||||
case "awb": // This is technically soundbank and should be below but I want it to be distinguishable from "acb"
|
||||
case "adx":
|
||||
case "hca":
|
||||
case "xvag":
|
||||
case "flac":
|
||||
case "at9":
|
||||
@@ -385,6 +422,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
break;
|
||||
case "acb":
|
||||
case "bank":
|
||||
case "fsb":
|
||||
case "bnk":
|
||||
case "pck":
|
||||
AssetCategory = EAssetCategory.SoundBank;
|
||||
@@ -395,7 +433,9 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
||||
case "ttf":
|
||||
AssetCategory = EAssetCategory.Font;
|
||||
break;
|
||||
case "webm":
|
||||
case "mp4":
|
||||
case "usm":
|
||||
AssetCategory = EAssetCategory.Video;
|
||||
break;
|
||||
case "jpg":
|
||||
|
||||
@@ -58,7 +58,7 @@ public class GameSelectorViewModel : ViewModel
|
||||
|
||||
if (DetectedDirectories.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
|
||||
SelectedDirectory = detectedGame;
|
||||
else if (!string.IsNullOrEmpty(gameDirectory))
|
||||
else if (IsGameDirectoryAvailable(gameDirectory))
|
||||
AddUndetectedDir(gameDirectory);
|
||||
else
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
@@ -189,6 +189,39 @@ public class GameSelectorViewModel : ViewModel
|
||||
SelectedDirectory = DetectedDirectories.Last();
|
||||
}
|
||||
|
||||
public int ClearMissingDirectories()
|
||||
{
|
||||
var removedDirectories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var missingDirectories = UserSettings.Default.PerDirectory
|
||||
.Where(x => !IsGameDirectoryAvailable(x.Value.GameDirectory))
|
||||
.Select(x => new KeyValuePair<string, string>(x.Key, x.Value.GameDirectory))
|
||||
.ToArray();
|
||||
|
||||
foreach (var (key, gameDirectory) in missingDirectories)
|
||||
{
|
||||
UserSettings.Default.PerDirectory.Remove(key);
|
||||
removedDirectories.Add(gameDirectory);
|
||||
}
|
||||
|
||||
var missingDetectedDirectories = _detectedDirectories
|
||||
.Where(x => !IsGameDirectoryAvailable(x.GameDirectory))
|
||||
.ToArray();
|
||||
|
||||
foreach (var directory in missingDetectedDirectories)
|
||||
{
|
||||
_detectedDirectories.Remove(directory);
|
||||
removedDirectories.Add(directory.GameDirectory);
|
||||
}
|
||||
|
||||
if (SelectedDirectory is null || !_detectedDirectories.Contains(SelectedDirectory))
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
|
||||
return removedDirectories.Count;
|
||||
}
|
||||
|
||||
public static bool IsGameDirectoryAvailable(string gameDirectory)
|
||||
=> gameDirectory is Constants._FN_LIVE_TRIGGER or Constants._VAL_LIVE_TRIGGER || Directory.Exists(gameDirectory);
|
||||
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
@@ -196,8 +229,8 @@ public class GameSelectorViewModel : ViewModel
|
||||
.OrderBy(value => ((int)value & 0xFF) == 0);
|
||||
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_8);
|
||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_8);
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE6_0);
|
||||
yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE6_0);
|
||||
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany);
|
||||
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21);
|
||||
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3);
|
||||
|
||||
@@ -130,14 +130,18 @@
|
||||
adonisExtensions:LayerExtension.IncreaseLayer="True">
|
||||
<Grid Margin="30, 12, 6, 12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
|
||||
<Button Grid.Column="0" MinWidth="78" Margin="0 0 12 0"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Bottom" Content="Clear"
|
||||
ToolTip="Remove game directories that no longer exist" Click="OnClearDirectories" />
|
||||
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="True" IsCancel="False"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
|
||||
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
|
||||
<Button Grid.Column="3" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
using FModel.ViewModels;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using Serilog;
|
||||
using System.Windows;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButtons = AdonisUI.Controls.MessageBoxButtons;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxModel = AdonisUI.Controls.MessageBoxModel;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
@@ -63,6 +70,37 @@ public partial class DirectorySelector
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
|
||||
private void OnClearDirectories(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not GameSelectorViewModel gameSelectorViewModel)
|
||||
return;
|
||||
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = "Remove all saved game directories that no longer exist?",
|
||||
Caption = "Clear Empty Directories",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes)
|
||||
return;
|
||||
|
||||
var removedCount = gameSelectorViewModel.ClearMissingDirectories();
|
||||
var message = removedCount switch
|
||||
{
|
||||
0 => "No empty directories were found",
|
||||
1 => "Removed 1 empty game directory",
|
||||
_ => $"Removed {removedCount} empty game directories"
|
||||
};
|
||||
|
||||
Log.Information("{Message}", message);
|
||||
if (FLogger.Logger is not null) // When only directory selector is open, FLogger.Logger is null
|
||||
FLogger.Append(ELog.Information, () => FLogger.Text(message, Constants.WHITE, true));
|
||||
}
|
||||
|
||||
public void AddManualGame(string directory)
|
||||
{
|
||||
ManualGameExpander.IsExpanded = true;
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
<SolidColorBrush x:Key="UserDefinedEnumBrush" Color="DarkGoldenrod" />
|
||||
<SolidColorBrush x:Key="UserDefinedStructBrush" Color="Tan" />
|
||||
|
||||
<SolidColorBrush x:Key="GroomAssetBrush" Color="RosyBrown " />
|
||||
<SolidColorBrush x:Key="ChaosClothBrush" Color="LightSlateGray" />
|
||||
<SolidColorBrush x:Key="CustomizableObjectBrush" Color="DarkRed" />
|
||||
<SolidColorBrush x:Key="NaniteDisplacedMeshBrush" Color="ForestGreen" />
|
||||
|
||||
@@ -22,7 +24,7 @@
|
||||
<SolidColorBrush x:Key="BinaryBrush" Color="Yellow" />
|
||||
<SolidColorBrush x:Key="TextureBrush" Color="MediumPurple" />
|
||||
<SolidColorBrush x:Key="ConfigBrush" Color="LightSlateGray" />
|
||||
|
||||
|
||||
<SolidColorBrush x:Key="AudioBrush" Color="MediumSeaGreen" />
|
||||
<SolidColorBrush x:Key="SoundBankBrush" Color="DarkSeaGreen" />
|
||||
<SolidColorBrush x:Key="AudioEventBrush" Color="LightSeaGreen" />
|
||||
@@ -52,6 +54,11 @@
|
||||
<SolidColorBrush x:Key="PythonBrush" Color="#3d77a8" />
|
||||
<SolidColorBrush x:Key="CSharpBrush" Color="#9e559a" />
|
||||
|
||||
<!-- Asset family brushes -->
|
||||
<SolidColorBrush x:Key="CriwareBrush" Color="#005bab" />
|
||||
<SolidColorBrush x:Key="WwiseBrush" Color="#0152a1" />
|
||||
<SolidColorBrush x:Key="FModBrush" Color="#00758f" />
|
||||
|
||||
<!-- For specific games -->
|
||||
<SolidColorBrush x:Key="BorderlandsBrush" Color="Yellow"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AionBrush" Color="DeepSkyBlue"></SolidColorBrush>
|
||||
|
||||
@@ -44,12 +44,14 @@
|
||||
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||
<Binding Path="AssetCategory" />
|
||||
<Binding Path="ResolvedAssetType" />
|
||||
<Binding Path="AssetFamily" />
|
||||
</MultiBinding>
|
||||
</Path.Data>
|
||||
<Path.Fill>
|
||||
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||
<Binding Path="AssetCategory" />
|
||||
<Binding Path="ResolvedAssetType" />
|
||||
<Binding Path="AssetFamily" />
|
||||
</MultiBinding>
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
@@ -35,12 +35,14 @@
|
||||
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||
<Binding Path="AssetCategory" />
|
||||
<Binding Path="ResolvedAssetType" />
|
||||
<Binding Path="AssetFamily" />
|
||||
</MultiBinding>
|
||||
</Path.Data>
|
||||
<Path.Fill>
|
||||
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
|
||||
<Binding Path="AssetCategory" />
|
||||
<Binding Path="ResolvedAssetType" />
|
||||
<Binding Path="AssetFamily" />
|
||||
</MultiBinding>
|
||||
</Path.Fill>
|
||||
</Path>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
@@ -17,8 +18,9 @@ public class FilterableComboBox : ComboBox
|
||||
/// If true, on lost focus or enter key pressed, checks the text in the combobox. If the text is not present
|
||||
/// in the list, it leaves it blank.
|
||||
/// </summary>
|
||||
public bool OnlyValuesInList {
|
||||
get => (bool)GetValue(OnlyValuesInListProperty);
|
||||
public bool OnlyValuesInList
|
||||
{
|
||||
get => (bool) GetValue(OnlyValuesInListProperty);
|
||||
set => SetValue(OnlyValuesInListProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty OnlyValuesInListProperty =
|
||||
@@ -27,8 +29,9 @@ public class FilterableComboBox : ComboBox
|
||||
/// <summary>
|
||||
/// Selected item, changes only on lost focus or enter key pressed
|
||||
/// </summary>
|
||||
public object EffectivelySelectedItem {
|
||||
get => (bool)GetValue(EffectivelySelectedItemProperty);
|
||||
public object EffectivelySelectedItem
|
||||
{
|
||||
get => (bool) GetValue(EffectivelySelectedItemProperty);
|
||||
set => SetValue(EffectivelySelectedItemProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty EffectivelySelectedItemProperty =
|
||||
@@ -38,6 +41,7 @@ public class FilterableComboBox : ComboBox
|
||||
private bool TextBoxFreezed;
|
||||
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
|
||||
private UserChange<bool> IsDropDownOpenUC;
|
||||
private readonly DispatcherTimer FilterTimer;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers on lost focus or enter key pressed, if the selected item changed since the last time focus was lost or enter was pressed.
|
||||
@@ -47,6 +51,15 @@ public class FilterableComboBox : ComboBox
|
||||
public FilterableComboBox()
|
||||
{
|
||||
IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);
|
||||
FilterTimer = new DispatcherTimer(DispatcherPriority.Background, Dispatcher)
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(125)
|
||||
};
|
||||
FilterTimer.Tick += (_, _) =>
|
||||
{
|
||||
FilterTimer.Stop();
|
||||
RefreshFilter();
|
||||
};
|
||||
DropDownOpened += FilteredComboBox_DropDownOpened;
|
||||
|
||||
Focusable = true;
|
||||
@@ -55,7 +68,8 @@ public class FilterableComboBox : ComboBox
|
||||
StaysOpenOnEdit = true;
|
||||
IsReadOnly = false;
|
||||
|
||||
Loaded += (s, e) => {
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (EditableTextBox != null)
|
||||
new TextBoxBaseUserChangeTracker(EditableTextBox).UserTextChanged += FilteredComboBox_UserTextChange;
|
||||
};
|
||||
@@ -67,25 +81,53 @@ public class FilterableComboBox : ComboBox
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (IsDropDownOpen && Keyboard.Modifiers == ModifierKeys.None && e.Key is Key.Up or Key.Down)
|
||||
{
|
||||
MoveSelection(e.Key);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnPreviewKeyDown(e);
|
||||
if (e.Key == Key.Down && !IsDropDownOpen) {
|
||||
if (e.Key == Key.Down && !IsDropDownOpen)
|
||||
{
|
||||
ApplyPendingFilter();
|
||||
IsDropDownOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Escape) {
|
||||
else if (e.Key == Key.Escape)
|
||||
{
|
||||
ClearFilter();
|
||||
Text = "";
|
||||
IsDropDownOpen = true;
|
||||
}
|
||||
else if (e.Key == Key.Enter || e.Key == Key.Tab) {
|
||||
else if (e.Key == Key.Enter || e.Key == Key.Tab)
|
||||
{
|
||||
ApplyPendingFilter();
|
||||
CheckSelectedItem();
|
||||
TriggerSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveSelection(Key key)
|
||||
{
|
||||
ApplyPendingFilter();
|
||||
if (Items.Count == 0)
|
||||
return;
|
||||
|
||||
var index = SelectedItem == null ? -1 : Items.IndexOf(SelectedItem);
|
||||
index = index < 0
|
||||
? key == Key.Up ? Items.Count - 1 : 0
|
||||
: Math.Clamp(index + (key == Key.Up ? -1 : 1), 0, Items.Count - 1);
|
||||
|
||||
SelectedItem = Items[index];
|
||||
(ItemContainerGenerator.ContainerFromIndex(index) as ComboBoxItem)?.BringIntoView();
|
||||
}
|
||||
|
||||
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
base.OnPreviewLostKeyboardFocus(e);
|
||||
ApplyPendingFilter();
|
||||
CheckSelectedItem();
|
||||
if ((e.OldFocus == this || e.OldFocus == EditableTextBox) && e.NewFocus != this && e.NewFocus != EditableTextBox)
|
||||
TriggerSelectedItemChanged();
|
||||
@@ -100,7 +142,8 @@ public class FilterableComboBox : ComboBox
|
||||
private bool shouldTriggerSelectedItemChanged = false;
|
||||
private void TriggerSelectedItemChanged()
|
||||
{
|
||||
if (shouldTriggerSelectedItemChanged) {
|
||||
if (shouldTriggerSelectedItemChanged)
|
||||
{
|
||||
SelectionEffectivelyChanged?.Invoke(this, SelectedItem);
|
||||
shouldTriggerSelectedItemChanged = false;
|
||||
}
|
||||
@@ -108,9 +151,12 @@ public class FilterableComboBox : ComboBox
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentFilter)) return;
|
||||
FilterTimer.Stop();
|
||||
if (string.IsNullOrEmpty(CurrentFilter))
|
||||
return;
|
||||
CurrentFilter = "";
|
||||
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
|
||||
if (ItemsSource != null)
|
||||
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
|
||||
}
|
||||
|
||||
private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)
|
||||
@@ -121,47 +167,64 @@ public class FilterableComboBox : ComboBox
|
||||
|
||||
private void FilteredComboBox_UserTextChange(object sender, EventArgs e)
|
||||
{
|
||||
if (TextBoxFreezed) return;
|
||||
if (TextBoxFreezed)
|
||||
return;
|
||||
var tb = EditableTextBox;
|
||||
if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)
|
||||
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();
|
||||
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart);
|
||||
else
|
||||
CurrentFilter = tb.Text.ToLower();
|
||||
RefreshFilter();
|
||||
CurrentFilter = tb.Text;
|
||||
|
||||
FilterTimer.Stop();
|
||||
FilterTimer.Start();
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
if (newValue != null) {
|
||||
FilterTimer.Stop();
|
||||
|
||||
if (newValue != null)
|
||||
{
|
||||
var view = CollectionViewSource.GetDefaultView(newValue);
|
||||
view.Filter += FilterItem;
|
||||
}
|
||||
|
||||
if (oldValue != null) {
|
||||
if (oldValue != null)
|
||||
{
|
||||
var view = CollectionViewSource.GetDefaultView(oldValue);
|
||||
if (view != null) view.Filter -= FilterItem;
|
||||
if (view != null)
|
||||
view.Filter -= FilterItem;
|
||||
}
|
||||
|
||||
base.OnItemsSourceChanged(oldValue, newValue);
|
||||
}
|
||||
|
||||
private void ApplyPendingFilter()
|
||||
{
|
||||
if (!FilterTimer.IsEnabled)
|
||||
return;
|
||||
FilterTimer.Stop();
|
||||
RefreshFilter();
|
||||
}
|
||||
|
||||
private void RefreshFilter()
|
||||
{
|
||||
if (ItemsSource == null) return;
|
||||
if (ItemsSource == null)
|
||||
return;
|
||||
|
||||
var view = CollectionViewSource.GetDefaultView(ItemsSource);
|
||||
FreezTextBoxState(() => {
|
||||
var isDropDownOpen = IsDropDownOpen;
|
||||
//always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()
|
||||
IsDropDownOpenUC.Set(false);
|
||||
FreezTextBoxState(() =>
|
||||
{
|
||||
view.Refresh();
|
||||
|
||||
if (!string.IsNullOrEmpty(CurrentFilter) || isDropDownOpen)
|
||||
if (!IsDropDownOpen && !string.IsNullOrEmpty(CurrentFilter))
|
||||
IsDropDownOpenUC.Set(true);
|
||||
|
||||
if (SelectedItem == null) {
|
||||
if (SelectedItem == null)
|
||||
{
|
||||
foreach (var itm in ItemsSource)
|
||||
if (itm.ToString() == Text) {
|
||||
if (itm.ToString() == Text)
|
||||
{
|
||||
SelectedItem = itm;
|
||||
break;
|
||||
}
|
||||
@@ -185,10 +248,16 @@ public class FilterableComboBox : ComboBox
|
||||
|
||||
private bool FilterItem(object value)
|
||||
{
|
||||
if (value == null) return false;
|
||||
if (CurrentFilter.Length == 0) return true;
|
||||
if (value == null)
|
||||
return false;
|
||||
if (CurrentFilter.Length == 0)
|
||||
return true;
|
||||
|
||||
return value.ToString().ToLower().Contains(CurrentFilter);
|
||||
var text = value.ToString();
|
||||
if (text?.StartsWith("GAME_", StringComparison.OrdinalIgnoreCase) == true)
|
||||
text = text[5..];
|
||||
|
||||
return text?.Contains(CurrentFilter, StringComparison.OrdinalIgnoreCase) == true;
|
||||
}
|
||||
|
||||
private class TextBoxBaseUserChangeTracker
|
||||
@@ -205,11 +274,13 @@ public class FilterableComboBox : ComboBox
|
||||
TextBoxBase = textBoxBase;
|
||||
LastText = TextBoxBase.ToString();
|
||||
|
||||
textBoxBase.PreviewTextInput += (s, e) => {
|
||||
textBoxBase.PreviewTextInput += (s, e) =>
|
||||
{
|
||||
IsTextInput = true;
|
||||
};
|
||||
|
||||
textBoxBase.TextChanged += (s, e) => {
|
||||
textBoxBase.TextChanged += (s, e) =>
|
||||
{
|
||||
var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBoxBase.ToString();
|
||||
IsTextInput = false;
|
||||
LastText = TextBoxBase.ToString();
|
||||
@@ -217,17 +288,21 @@ public class FilterableComboBox : ComboBox
|
||||
UserTextChanged?.Invoke(this, e);
|
||||
};
|
||||
|
||||
textBoxBase.PreviewKeyDown += (s, e) => {
|
||||
switch (e.Key) {
|
||||
textBoxBase.PreviewKeyDown += (s, e) =>
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Back:
|
||||
case Key.Space:
|
||||
if (!PressedKeys.Contains(e.Key))
|
||||
PressedKeys.Add(e.Key);
|
||||
break;
|
||||
}
|
||||
if (e.Key == Key.Back) {
|
||||
if (e.Key == Key.Back)
|
||||
{
|
||||
var textBox = textBoxBase as TextBox;
|
||||
if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length) {
|
||||
if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length)
|
||||
{
|
||||
textBox.SelectionStart--;
|
||||
textBox.SelectionLength++;
|
||||
e.Handled = true;
|
||||
@@ -236,12 +311,14 @@ public class FilterableComboBox : ComboBox
|
||||
}
|
||||
};
|
||||
|
||||
textBoxBase.PreviewKeyUp += (s, e) => {
|
||||
textBoxBase.PreviewKeyUp += (s, e) =>
|
||||
{
|
||||
if (PressedKeys.Contains(e.Key))
|
||||
PressedKeys.Remove(e.Key);
|
||||
};
|
||||
|
||||
textBoxBase.LostFocus += (s, e) => {
|
||||
textBoxBase.LostFocus += (s, e) =>
|
||||
{
|
||||
PressedKeys.Clear();
|
||||
IsTextInput = false;
|
||||
};
|
||||
@@ -261,11 +338,13 @@ public class FilterableComboBox : ComboBox
|
||||
|
||||
public void Set(T val)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
IsUserChange = false;
|
||||
action(val);
|
||||
}
|
||||
finally {
|
||||
finally
|
||||
{
|
||||
IsUserChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class FileToGeometryConverter : IMultiValueConverter
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length < 2 || values[0] is not EAssetCategory category || values[1] is not string resolvedAssetType)
|
||||
if (values.Length < 3 || values[0] is not EAssetCategory category || values[1] is not string resolvedAssetType || values[2] is not EAssetFamily family)
|
||||
return null;
|
||||
|
||||
resolvedAssetType = resolvedAssetType.ToLowerInvariant();
|
||||
@@ -32,6 +32,7 @@ public class FileToGeometryConverter : IMultiValueConverter
|
||||
|
||||
EAssetCategory.StaticMesh => ("StaticMeshIconAlt", "NeutralBrush"),
|
||||
EAssetCategory.SkeletalMesh => ("SkeletalMeshIconAlt", "NeutralBrush"),
|
||||
EAssetCategory.ChaosClothAsset => ("ChaosClothIcon", "ChaosClothBrush"),
|
||||
EAssetCategory.CustomizableObject => ("StaticMeshIconAlt", "CustomizableObjectBrush"),
|
||||
EAssetCategory.NaniteDisplacedMesh => ("StaticMeshIconAlt", "NaniteDisplacedMeshBrush"),
|
||||
EAssetCategory.GeometryCollection => ("GeometryCollectionIcon", "NeutralBrush"),
|
||||
@@ -99,12 +100,26 @@ public class FileToGeometryConverter : IMultiValueConverter
|
||||
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
|
||||
EAssetCategory.RocoKingdomWorld => ("RocoKingdomWorldIcon", "RocoKingdomWorldBrush"),
|
||||
EAssetCategory.DeltaForce => ("DeltaForceIcon", "DeltaForceBrush"),
|
||||
EAssetCategory.LegoBatman => ("BatmanIcon", "BatmanBrush"),
|
||||
EAssetCategory.LegoBatman or EAssetCategory.GothamKnights => ("BatmanIcon", "BatmanBrush"),
|
||||
EAssetCategory.ArcSys => ("ArcSysIcon", "ArcSysBrush"),
|
||||
|
||||
_ => ("AssetIcon", "NeutralBrush")
|
||||
};
|
||||
|
||||
(geometry, brush) = (family, category) switch
|
||||
{
|
||||
(EAssetFamily.Criware, EAssetCategory.Data) => ("CriwareIcon", "CriwareBrush"),
|
||||
(EAssetFamily.Criware, _) => ("CriwareIcon", brush),
|
||||
|
||||
(EAssetFamily.Wwise, EAssetCategory.Data) => ("WwiseIcon", "WwiseBrush"),
|
||||
(EAssetFamily.Wwise, _) => ("WwiseIcon", brush),
|
||||
|
||||
(EAssetFamily.FMod, EAssetCategory.Data) => ("FModIcon", "FModBrush"),
|
||||
(EAssetFamily.FMod, _) => ("FModIcon", brush),
|
||||
|
||||
_ => (geometry, brush),
|
||||
};
|
||||
|
||||
if (targetType == typeof(Geometry))
|
||||
return Application.Current.FindResource(geometry) as Geometry;
|
||||
if (targetType == typeof(Brush))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1682,6 +1682,16 @@
|
||||
|
||||
<Style x:Key="UComboBox" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="adonisExtensions:WatermarkExtension.Watermark" Value="UE5 / UE4 / GameName..." />
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
|
||||
<Setter Property="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
|
||||
Reference in New Issue
Block a user