New export pipeline + World & USD support (#684)
Some checks failed
FModel QA Builder / build (push) Has been cancelled

Co-authored-by: NotOfficer <marlon.john@hotmail.de>
Co-authored-by: Krowe-moh <27891447+Krowe-moh@users.noreply.github.com>
Co-authored-by: Chompster86 <chompster86@gmail.com>
Co-authored-by: ScrubN <72096833+ScrubN@users.noreply.github.com>
Co-authored-by: Masusder <59669685+Masusder@users.noreply.github.com>
Co-authored-by: Tectfy <73559984+Tectfy@users.noreply.github.com>
Co-authored-by: yarik0chka <64433879+yarik0chka@users.noreply.github.com>
This commit is contained in:
Valentin 2026-08-02 17:38:18 +02:00 committed by GitHub
parent 56369c3cb7
commit 7c86ee47ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
90 changed files with 5477 additions and 2077 deletions

@ -1 +1 @@
Subproject commit a098f0b6f87372e95d42701216159961eb691948
Subproject commit 7afcbb323c9fd9445d5452856b18b1d732d2dccd

View File

@ -20,7 +20,7 @@
<ResourceDictionary Source="Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml" />
<ResourceDictionary Source="Views/Resources/Resources.xaml" />
<ResourceDictionary Source="Views/Resources/Controls/TiledExplorer/Resources.xaml" />
<ResourceDictionary Source="Views/Resources/Controls/Explorer/Resources.xaml" />
</ResourceDictionary.MergedDictionaries>
<Color x:Key="{x:Static adonisUi:Colors.AccentColor}">#206BD4</Color>

View File

@ -11,7 +11,9 @@ using CUE4Parse;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.Views.Snooper;
using Newtonsoft.Json;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
@ -110,19 +112,29 @@ public partial class App
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs"));
CacheManager.EnsureDirectories();
const string template = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
#if DEBUG
var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log");
#else
var filePath = Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log");
#endif
const string template1 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Enriched}: {Message:lj}{NewLine}{Exception}";
const string template2 = "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{ClassName}] {ObjectPath}: {Message:lj}{NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
#if DEBUG
.Enrich.With<SourceEnricher>()
.MinimumLevel.Verbose()
.WriteTo.Console(outputTemplate: template, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template,
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.log"))
#else
.Enrich.With<CallerEnricher>()
.WriteTo.File(outputTemplate: template,
path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.log"))
#endif
.WriteTo.Logger(lc => lc
.Filter.ByExcluding(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template1, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template1, path: filePath))
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(IsConversionLibrary)
.WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate)
.WriteTo.File(outputTemplate: template2, path: filePath))
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
.CreateLogger();
CacheManager.MigrateLegacyFiles();
@ -130,6 +142,10 @@ public partial class App
Log.Information("{OS}", GetOperatingSystemProductName());
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
static bool IsConversionLibrary(LogEvent e) =>
e.Properties.TryGetValue("SourceContext", out var sc) &&
sc.ToString().Contains("CUE4Parse_Conversion");
}
private void AppExit(object sender, ExitEventArgs e)

View File

@ -56,8 +56,13 @@ public class BaseIcon : UCreator
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"))
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush", "BuildingSymbolNormal") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
Preview = Utils.GetBitmap(res);
else if (Object.TryGetValue(out FStructFallback mission, "MissionIcons", "PopupWidgetData"))
{
if (mission.TryGetValue(out FStructFallback brushsize, "Brush_XL", "Brush_L", "Brush_M", "Brush_S", "Brush_XS", "Brush_XXS", "AvailableIcon", "UnavailableIcon") && brushsize.TryGetValue(out UTexture2D res2, "ResourceObject"))
Preview = Utils.GetBitmap(res2);
}
}
// text

View File

@ -51,6 +51,11 @@ public class CreatorPackage : IDisposable
case "CosmeticShoesItemDefinition":
case "CosmeticCompanionItemDefinition":
case "CosmeticCompanionReactFXItemDefinition":
case "MagpieEntitlementRewardDefinition":
case "FortDeferredItemGrantDefinition":
case "BattleLabDeviceItemDefinition":
case "PiggybackDanceItemDefinition":
case "MyTownBuildingDefinitionData":
case "AthenaPickaxeItemDefinition":
case "AthenaGadgetItemDefinition":
case "AthenaGliderItemDefinition":
@ -64,6 +69,7 @@ public class CreatorPackage : IDisposable
case "FortTokenType":
case "FortAbilityKit":
case "FortWorkerType":
case "FortMissionInfo":
case "RewardGraphToken":
case "JunoKnowledgeBundle":
case "FortBannerTokenType":

View File

@ -109,6 +109,7 @@ public enum EBulkType
Audio = 1 << 5,
Code = 1 << 6,
Raw = 1 << 7,
Worlds = 1 << 8,
}
public enum EAssetCategory : uint
@ -171,3 +172,9 @@ public enum EUnluacMode
Decompile,
Disassemble,
}
public enum EExplorerViewMode
{
Grid,
List
}

View File

@ -0,0 +1,11 @@
using Serilog.Events;
namespace FModel.Extensions;
public static class LogEventExtensions
{
public static string GetContext(this LogEvent log, string propertyName)
{
return log.Properties.TryGetValue(propertyName, out var value) ? value.ToString().Trim('"') : string.Empty;
}
}

View File

@ -121,6 +121,9 @@
<None Remove="Resources\bone.frag" />
<None Remove="Resources\bone.vert" />
<None Remove="Resources\collision.vert" />
<None Remove="Resources\fa-brands-400.otf" />
<None Remove="Resources\fa-solid-900.otf" />
<None Remove="Resources\fa-regular-400.otf" />
</ItemGroup>
<ItemGroup>
@ -149,6 +152,9 @@
<EmbeddedResource Include="Resources\bone.frag" />
<EmbeddedResource Include="Resources\bone.vert" />
<EmbeddedResource Include="Resources\collision.vert" />
<EmbeddedResource Include="Resources\fa-brands-400.otf" />
<EmbeddedResource Include="Resources\fa-solid-900.otf" />
<EmbeddedResource Include="Resources\fa-regular-400.otf" />
</ItemGroup>
<ItemGroup>

View File

@ -66,18 +66,69 @@ public class ImGuiController : IDisposable
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
}
// If not found, Fallback to default ImGui Font
var normalPath = @"C:\Windows\Fonts\segoeui.ttf";
var boldPath = @"C:\Windows\Fonts\segoeuib.ttf";
var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf";
if (File.Exists(normalPath))
FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale);
if (File.Exists(boldPath))
FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale);
if (File.Exists(semiBoldPath))
FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale);
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
var assemblyName = assembly.GetName().Name;
byte[] LoadFont(string name)
{
using var stream = assembly.GetManifestResourceStream($"{assemblyName}.Resources.{name}")
?? throw new FileNotFoundException($"Embedded font '{name}' not found.");
using var ms = new MemoryStream();
stream.CopyTo(ms);
return ms.ToArray();
}
var faSolid = LoadFont("fa-solid-900.otf");
var faRegular = LoadFont("fa-regular-400.otf");
var faBrands = LoadFont("fa-brands-400.otf");
unsafe
{
// FA5 icons live in E000F8FF (brands/regular/solid)
var iconRanges = stackalloc ushort[] { 0xe000, 0xf8ff, 0 };
var cfg = ImGuiNative.ImFontConfig_ImFontConfig();
cfg->MergeMode = 1;
cfg->PixelSnapH = 1;
cfg->GlyphMinAdvanceX = 16f;
// FontDataOwnedByAtlas = 0 because we manage the GCHandle lifetime ourselves
cfg->FontDataOwnedByAtlas = 0;
void MergeFontAwesome(byte[] solid, byte[] regular, byte[] brands)
{
fixed (byte* pSolid = solid)
fixed (byte* pRegular = regular)
fixed (byte* pBrands = brands)
{
io.Fonts.AddFontFromMemoryTTF((IntPtr)pSolid, solid.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
io.Fonts.AddFontFromMemoryTTF((IntPtr)pRegular, regular.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
io.Fonts.AddFontFromMemoryTTF((IntPtr)pBrands, brands.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
}
}
// If not found, Fallback to default ImGui Font
var normalPath = @"C:\Windows\Fonts\segoeui.ttf";
var boldPath = @"C:\Windows\Fonts\segoeuib.ttf";
var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf";
if (File.Exists(normalPath))
{
FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale);
MergeFontAwesome(faSolid, faRegular, faBrands);
}
if (File.Exists(boldPath))
{
FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale);
MergeFontAwesome(faSolid, faRegular, faBrands);
}
if (File.Exists(semiBoldPath))
{
FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale);
MergeFontAwesome(faSolid, faRegular, faBrands);
}
ImGuiNative.ImFontConfig_destroy(cfg);
}
io.Fonts.AddFontDefault();
io.Fonts.Build(); // Build font atlas

View File

@ -19,12 +19,12 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
if (string.IsNullOrEmpty(propertyName))
return Error;
return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty;
return _validationErrors.TryGetValue(propertyName, out IList<string> validationError) ? string.Join(Environment.NewLine, validationError) : string.Empty;
}
}
[JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors());
[JsonIgnore] public bool HasErrors => _validationErrors.Any();
[JsonIgnore] public virtual bool HasErrors => _validationErrors.Count != 0;
public IEnumerable GetErrors(string propertyName)
{
@ -49,8 +49,7 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
public void ClearValidationErrors(string propertyName)
{
if (_validationErrors.ContainsKey(propertyName))
_validationErrors.Remove(propertyName);
_validationErrors.Remove(propertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
@ -72,4 +71,4 @@ public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErro
RaisePropertyChanged(propertyName);
return true;
}
}
}

View File

@ -39,7 +39,7 @@ public static class Helper
else
{
var w = GetOpenedWindow<T>(windowName);
if (windowName == "Search For Packages") w.WindowState = WindowState.Normal;
if (w.WindowState == WindowState.Minimized) w.WindowState = WindowState.Normal;
w.Focus();
}
}

View File

@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FModel"
xmlns:viewModels="clr-namespace:FModel.ViewModels"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:inputs="clr-namespace:FModel.Views.Resources.Controls.Inputs"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
@ -24,6 +25,28 @@
</TaskbarItemInfo.ProgressState>
</TaskbarItemInfo>
</Window.TaskbarItemInfo>
<Window.Resources>
<Canvas x:Key="ExportSessionIcon" x:Shared="False" Width="24" Height="24">
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 21l-8 -4.5v-9l8 -4.5l8 4.5v4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12l8 -4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12v9" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M12 12l-8 -4.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M15 18h7" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 15l3 3l-3 3" />
</Canvas>
</Window.Resources>
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
@ -134,6 +157,13 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Export Session" Command="{Binding MenuCommand}" CommandParameter="Views_ExportSession">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<StaticResource ResourceKey="ExportSessionIcon" />
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -211,7 +241,7 @@
<TextBlock Grid.Column="0" Text="Preview New Explorer System" VerticalAlignment="Center" />
<CheckBox Grid.Column="1" Margin="5 2 5 0" Unchecked="FeaturePreviewOnUnchecked" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.ControlTabNavigation="None"
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
IsChecked="{Binding FeaturePreviewNewAssetExplorer, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"/>
</Grid>
</Grid>
@ -253,7 +283,7 @@
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding Status.IsReady}"
CommandParameter="{Binding SelectedItems, ElementName=DirectoryFilesListBox}" />
</Grid>
<Grid DockPanel.Dock="Top">
<Grid DockPanel.Dock="Top" Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
@ -279,7 +309,7 @@
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=DirectoryFilesListBox, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FileCount, ElementName=DirectoryFilesListBox, FallbackValue='0', StringFormat={}{0} Files}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FileCount, ElementName=DirectoryFilesListBox, FallbackValue='0', StringFormat={}{0:N0} Files}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="File Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=DirectoryFilesListBox, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
@ -371,9 +401,9 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SelectedItem.AssetsList.Assets.Count, ElementName=AssetsFolderName, FallbackValue=0, StringFormat={}{0:N0}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="0" Grid.Column="1" Text="Packages Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.FoldersView.Count, ElementName=AssetsFolderName, FallbackValue=0, StringFormat={}{0:N0}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="1" Grid.Column="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsFolderName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
@ -473,6 +503,8 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" MinWidth="150" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<inputs:SearchTextBox x:Name="AssetsExplorerSearch" Grid.Column="0"
@ -482,17 +514,88 @@
<ComboBox x:Name="CategoriesSelector" Grid.Column="2"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedItem.SelectedCategory, ElementName=AssetsFolderName, Mode=TwoWay}" />
<StackPanel Grid.Column="4"
Orientation="Horizontal"
HorizontalAlignment="Right">
<ToggleButton Style="{StaticResource ExplorerViewToggleButton}"
Width="32"
Height="28"
Padding="7"
ToolTip="Grid View"
IsChecked="{Binding ExplorerViewMode,
Source={x:Static local:Settings.UserSettings.Default},
Converter={x:Static converters:EnumToBoolConverter.Instance},
ConverterParameter={x:Static local:EExplorerViewMode.Grid},
Mode=TwoWay}">
<Path Data="{StaticResource GridIcon}"
Width="14"
Height="14"
Stretch="Uniform"
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</ToggleButton>
<ToggleButton Style="{StaticResource ExplorerViewToggleButton}"
Width="32"
Height="28"
Padding="7"
ToolTip="List View"
IsChecked="{Binding ExplorerViewMode,
Source={x:Static local:Settings.UserSettings.Default},
Converter={x:Static converters:EnumToBoolConverter.Instance},
ConverterParameter={x:Static local:EExplorerViewMode.List},
Mode=TwoWay}">
<Path Data="{StaticResource ListIcon}"
Width="14"
Height="14"
Stretch="Uniform"
Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</ToggleButton>
</StackPanel>
</Grid>
<controls:Breadcrumb Grid.Row="1" Margin="0 5 0 0"
HorizontalAlignment="Left"
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName}" />
<ListBox x:Name="AssetsExplorer" Grid.Row="2"
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
Style="{StaticResource TiledExplorer}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
PreviewKeyDown="OnPreviewKeyDown" />
<Grid Grid.Row="2">
<ListBox x:Name="AssetsExplorer"
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
PreviewKeyDown="OnPreviewKeyDown">
<ListBox.Style>
<Style TargetType="ListBox"
BasedOn="{StaticResource TiledExplorer}">
<Style.Triggers>
<DataTrigger Binding="{Binding ExplorerViewMode, Source={x:Static local:Settings.UserSettings.Default}}"
Value="{x:Static local:EExplorerViewMode.List}">
<Setter Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
<!-- once we need sorting, convert this to a listview (see search window) and add the header back (customize the template to re-add GridViewHeaderRowPresenter) -->
<ListBox x:Name="AssetsListExplorer"
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
PreviewKeyDown="OnPreviewKeyDown">
<ListBox.Style>
<Style TargetType="ListBox"
BasedOn="{StaticResource ListExplorer}">
<Setter Property="Visibility"
Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ExplorerViewMode, Source={x:Static local:Settings.UserSettings.Default}}"
Value="{x:Static local:EExplorerViewMode.List}">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
</Grid>
</Grid>
</Border>
@ -588,7 +691,9 @@
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Open_Output_Directory" Focusable="False">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource FolderIcon}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M11 13l9 -9" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15 4h5v5" />
</Canvas>
</Viewbox>
</Button>
@ -698,23 +803,102 @@
</TextBlock>
</StatusBarItem>
<StatusBarItem Margin="0 0 5 0" HorizontalAlignment="Right">
<StatusBarItem Margin="0 0 5 0" Padding="0"
HorizontalAlignment="Right" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<StatusBarItem Margin="0 0 10 0" HorizontalContentAlignment="Stretch">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource StatusBarIcon}" />
</Canvas>
</Viewbox>
<StatusBarItem Margin="0 0 10 0" Padding="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<Button x:Name="ExportSessionButton"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Command="{Binding MenuCommand}" CommandParameter="Views_ExportSession">
<Button.Style>
<Style TargetType="Button"
BasedOn="{StaticResource StatusBarButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Session.HasQueuedItems, Source={x:Static viewModels:ExportSessionViewModel.Instance}}"
Value="False">
<Setter Property="Background"
Value="{Binding Background, RelativeSource={RelativeSource AncestorType=StatusBar}}" />
<Setter Property="ToolTip"
Value="No items are currently queued for export." />
</DataTrigger>
<DataTrigger Binding="{Binding Session.HasQueuedItems, Source={x:Static viewModels:ExportSessionViewModel.Instance}}"
Value="True">
<Setter Property="Background"
Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
<Setter Property="ToolTip"
Value="There are items waiting in the export queue. Click to open the export session and process them!" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<StackPanel Orientation="Horizontal"
VerticalAlignment="Center">
<Viewbox Width="16" Height="16">
<StaticResource ResourceKey="ExportSessionIcon" />
</Viewbox>
<TextBlock Margin="10 0 0 0"
Text="{Binding Session.TotalQueued, Source={x:Static viewModels:ExportSessionViewModel.Instance}, Mode=OneWay, StringFormat=Queued Items: {0}}"
Visibility="{Binding Session.TotalQueued, Source={x:Static viewModels:ExportSessionViewModel.Instance}, Converter={x:Static converters:IntGreaterThanZeroToVisibilityConverter.Instance}}" />
</StackPanel>
</Button>
</StatusBarItem>
<StatusBarItem Margin="10 0 0 0">
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
<StatusBarItem Margin="0">
<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 StatusBarIcon}" />
</Canvas>
</Viewbox>
<TextBlock Margin="10 0 0 0" Text="{Binding LastUpdateCheck, Source={x:Static settings:UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
</StackPanel>
</StatusBarItem>
</StackPanel>
</StatusBarItem>
</StatusBar>
<controls:DropOverlay Grid.RowSpan="99"
Panel.ZIndex="1000" />
<controls:DropOverlay Grid.Row="0" Grid.RowSpan="99" Panel.ZIndex="1000" />
<Popup Grid.Row="0" Grid.RowSpan="99" Panel.ZIndex="999"
PlacementTarget="{Binding ElementName=ExportSessionButton}" Placement="Custom"
CustomPopupPlacementCallback="OnQueueToastCustomPopupPlacement"
IsOpen="{Binding ShowQueueToast, Source={x:Static viewModels:ExportSessionViewModel.Instance}}"
AllowsTransparency="True" IsHitTestVisible="False">
<Border CornerRadius="5" Padding="10"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer2BackgroundBrush}}"
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer1BorderBrush}}"
BorderThickness="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Viewbox Width="28" Height="28" VerticalAlignment="Center" Margin="0 0 10 0">
<Canvas Width="24" Height="24">
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M8 9h8" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M8 13h6" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M15 18h-2l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v5.5" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 16v3" />
<Path Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
StrokeThickness="2" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="M19 22v.01" />
</Canvas>
</Viewbox>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="Export Session" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Text="{Binding ToolTip, ElementName=ExportSessionButton}"
MaxWidth="300" TextWrapping="Wrap" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</StackPanel>
</StackPanel>
</Border>
</Popup>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -54,8 +54,10 @@ public partial class MainWindow
InitializeComponent();
AssetsExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
AssetsListExplorer.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
AssetsListName.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
AssetsExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e);
AssetsListExplorer.SelectionChanged += (_, e) => SyncSelection(AssetsListName, e);
AssetsListName.SelectionChanged += (_, e) => SyncSelection(AssetsExplorer, e);
FLogger.Logger = LogRtbName;
@ -376,4 +378,15 @@ public partial class MainWindow
childFolder.IsExpanded = true;
childFolder.IsSelected = true;
}
private CustomPopupPlacement[] OnQueueToastCustomPopupPlacement(Size popupSize, Size targetSize, Point offset)
{
return
[
new CustomPopupPlacement(
new Point((targetSize.Width - popupSize.Width) / 2, -popupSize.Height - 10),
PopupPrimaryAxis.Horizontal
)
];
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using FModel.Framework;
using FModel.ViewModels.ApiEndpoints.Models;
@ -26,7 +27,7 @@ public class GitHubApiEndpoint(RestClient client) : AbstractApiProvider(client)
public async Task<Author> GetUserAsync(string username)
{
var request = new FRestRequest($"https://api.github.com/users/{username}");
var request = new FRestRequest($"https://api.github.com/users/{Uri.EscapeDataString(username)}");
var response = await _client.ExecuteAsync<Author>(request).ConfigureAwait(false);
return response.Data;
}

View File

@ -230,17 +230,12 @@ public class AssetsFolderViewModel
return null;
}
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData).Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
foreach (var entry in entries)
{
TreeItem lastNode = null;
TreeItem parentItem = null;
var path = entry.Path;
if (path.StartsWith(localAppData, StringComparison.OrdinalIgnoreCase))
path = path[localAppData.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var folders = path.Split('/', StringSplitOptions.RemoveEmptyEntries);
var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;

View File

@ -239,6 +239,20 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
});
}
public void Unload()
{
Application.Current.Dispatcher.Invoke(() =>
{
_waveSource = null;
PlayedFile = new AudioFile(-1, "No audio file");
Spectrum = null;
RaiseSourceEvent(ESourceEventType.Clearing);
ClearSoundOut();
});
}
public void AddToPlaylist(byte[] data, string filePath)
{
Application.Current.Dispatcher.Invoke(() =>
@ -270,11 +284,30 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
if (_audioFiles.Count < 1) return;
Application.Current.Dispatcher.Invoke(() =>
{
var removedPlaying = false;
if (PlayedFile.Id == SelectedAudioFile.Id)
{
removedPlaying = true;
Stop();
}
_audioFiles.RemoveAt(SelectedAudioFile.Id);
for (var i = 0; i < _audioFiles.Count; i++)
{
_audioFiles[i].Id = i;
}
if (_audioFiles.Count < 1)
{
Unload();
return;
}
SelectedAudioFile = SelectedAudioFile.Id >= _audioFiles.Count ? _audioFiles.Last() : _audioFiles[SelectedAudioFile.Id];
if (!removedPlaying) return;
Load();
Play();
});
}
@ -526,6 +559,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
_soundOut.Volume = UserSettings.Default.AudioPlayerVolume / 100;
}
private void ClearSoundOut()
{
_soundOut = null;
}
private IEnumerable<MMDevice> EnumerateDevices()
{
using var deviceEnumerator = new MMDeviceEnumerator();

View File

@ -66,7 +66,7 @@ using CUE4Parse.UE4.Shaders;
using CUE4Parse.UE4.Versions;
using CUE4Parse.UE4.Wwise;
using CUE4Parse.Utils;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Exporters;
using CUE4Parse_Conversion.Sounds;
using EpicManifestParser;
using EpicManifestParser.UE;
@ -190,27 +190,26 @@ public class CUE4ParseViewModel : ViewModel
}
default:
{
var project = gameDirectory.SubstringBeforeLast(gameDirectory.Contains("eFootball") ? "\\pak" : "\\Content").SubstringAfterLast("\\");
Provider = project switch
Provider = versionContainer.Game switch
{
"StateOfDecay2" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
EGame.GAME_StateOfDecay2 => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"),
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks")
], SearchOption.AllDirectories, versionContainer, pathComparer),
"eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
EGame.GAME_eFootball => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
], SearchOption.AllDirectories, versionContainer, pathComparer),
"DeadByDaylight" => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
EGame.GAME_DeadByDaylight => new DefaultFileProvider(new DirectoryInfo(gameDirectory),
[
new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\DeadByDaylight\\Saved\\PersistentDownloadDir\\DynamicContent")
], SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
_ when versionContainer.Game is EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ when versionContainer.Game is EGame.GAME_ArcRaiders => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_AshEchoes => new AEDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_BlackStigma => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, StringComparer.Ordinal),
EGame.GAME_HonorofKingsWorld => new HoKWDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_LordOfMysteries => new LoMDefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
EGame.GAME_ArcRaiders => new TheiaFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer),
_ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, versionContainer, pathComparer)
};
@ -221,6 +220,7 @@ public class CUE4ParseViewModel : ViewModel
Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps;
Provider.ReadNaniteData = true;
PropertyUtil.SearchPropertyInTemplate = true; // search template properties when looking for a prop via GetOrDefault and cie
GameDirectory = new GameDirectoryViewModel();
AssetsFolder = new AssetsFolderViewModel();
@ -645,7 +645,7 @@ public class CUE4ParseViewModel : ViewModel
Parallel.ForEach(folder.AssetsList.Assets, entry =>
{
cancellationToken.ThrowIfCancellationRequested();
ExportData(entry.Asset, false);
ExportData(entry.Asset);
});
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
@ -654,27 +654,6 @@ public class CUE4ParseViewModel : ViewModel
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs));
public void SaveFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Properties | EBulkType.Auto));
public void TextureFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Textures | EBulkType.Auto));
public void ModelFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Meshes | EBulkType.Auto));
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto));
public void AudioFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto));
public void CodeFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto));
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
{
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
@ -699,7 +678,34 @@ public class CUE4ParseViewModel : ViewModel
if (saveProperties || updateUi)
{
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(result.GetDisplayData(saveProperties), Formatting.Indented), saveProperties, updateUi);
var displayData = result.GetDisplayData(saveProperties);
if (UserSettings.Default.MergeEditorOnlyDataExports && Provider.TryLoadPackage(entry.Path.SubstringBefore('.') + ".o.uasset", out var editorAsset))
{
var pkg = Provider.LoadPackage(entry.Path);
var exports = pkg.GetExports().ToArray();
var finalExports = new List<UObject>(exports);
var editorOnlyDataExports = new HashSet<UObject>();
foreach (var export in exports)
{
var editorData = editorAsset.GetExportOrNull(export.Name + "EditorOnlyData");
if (editorData == null)
continue;
export.Properties.AddRange(editorData.Properties);
editorOnlyDataExports.Add(editorData);
}
if (editorOnlyDataExports.Count > 0)
{
finalExports.AddRange(editorAsset.GetExports().Where(editorExport => !editorOnlyDataExports.Contains(editorExport)));
}
displayData = finalExports;
}
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(displayData, Formatting.Indented), saveProperties, updateUi);
if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons
}
@ -837,6 +843,7 @@ public class CUE4ParseViewModel : ViewModel
}
case "ebd" when Provider.Versions.Game is EGame.GAME_ArcRaiders:
case "json":
case "Json":
{
var data = Provider.SaveAsset(entry);
using var stream = new MemoryStream(data) { Position = 0 };
@ -1133,7 +1140,7 @@ public class CUE4ParseViewModel : ViewModel
}
else if (entry.NameWithoutExtension.Equals("key_manifest"))
{
var keymanifest = new FAion2KeyManifestFile(entry, Provider);
var keymanifest = new FAion2KeyManifestFile(entry);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(keymanifest, Formatting.Indented), saveProperties, updateUi);
}
else
@ -1222,12 +1229,12 @@ public class CUE4ParseViewModel : ViewModel
return decompiled;
}
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName)
{
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
var entry = Provider[fullPath];
TabControl.AddTab(entry, parentExportType);
TabControl.AddTab(entry);
TabControl.SelectedTab.ScrollTrigger = objectName;
var result = Provider.GetLoadPackageResult(entry, objectName);
@ -1267,7 +1274,16 @@ public class CUE4ParseViewModel : ViewModel
}
case UTexture when (isNone || saveTextures) && pointer.Object.Value is UTexture texture:
{
TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi);
if (saveTextures)
{
SaveExport(texture);
}
if (updateUi)
{
TabControl.SelectedTab.AddImage(texture, false, true);
}
return false;
}
case USvgAsset when (isNone || saveTextures) && pointer.Object.Value is USvgAsset svgasset:
@ -1523,13 +1539,13 @@ public class CUE4ParseViewModel : ViewModel
return false;
}
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch
{
"JunoBuildInstructionsItemDefinition" => true,
"JunoBuildingSetAccountItemDefinition" => true,
"JunoBuildingPropAccountItemDefinition" => true,
_ => false
}:
// case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch
// {
// "JunoBuildInstructionsItemDefinition" => true,
// "JunoBuildingSetAccountItemDefinition" => true,
// "JunoBuildingPropAccountItemDefinition" => true,
// _ => false
// }:
case UPaperSprite when isNone && UserSettings.Default.PreviewMaterials:
case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes:
case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes:
@ -1560,10 +1576,11 @@ public class CUE4ParseViewModel : ViewModel
case UStaticMesh when HasFlag(bulk, EBulkType.Meshes):
case USkeletalMesh when HasFlag(bulk, EBulkType.Meshes):
case USkeleton when UserSettings.Default.SaveSkeletonAsMesh && HasFlag(bulk, EBulkType.Meshes):
// case UMaterialInstance when HasFlag(bulk, EBulkType.Materials): // read the fucking json
case UAnimSequenceBase when HasFlag(bulk, EBulkType.Animations):
// case UMaterialInterface when HasFlag(bulk, EBulkType.Materials):
case UAnimationAsset when HasFlag(bulk, EBulkType.Animations):
case UWorld when HasFlag(bulk, EBulkType.Worlds):
{
SaveExport(pointer.Object.Value, updateUi);
SaveExport(pointer.Object.Value);
return true;
}
default:
@ -1720,64 +1737,27 @@ public class CUE4ParseViewModel : ViewModel
});
}
private void SaveExport(UObject export, bool updateUi = true)
private void SaveExport(UObject export)
{
var toSave = new Exporter(export, UserSettings.Default.ExportOptions);
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
try
{
Interlocked.Increment(ref ExportedCount);
Log.Information("Successfully saved {FilePath}", savedFilePath);
if (updateUi)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(label, savedFilePath, true);
});
}
ExportSessionViewModel.Instance.Session.Add(export);
}
else
catch (Exception e)
{
Interlocked.Increment(ref FailedExportCount);
Log.Error("{FileName} could not be saved", export.Name);
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
Log.Error(e, "Could not add to export session");
}
}
private readonly object _rawData = new ();
public void ExportData(GameFile entry, bool updateUi = true)
public void ExportData(GameFile entry)
{
if (Provider.TrySavePackage(entry, out var assets))
try
{
string path = UserSettings.Default.RawDataDirectory;
Parallel.ForEach(assets, kvp =>
{
lock (_rawData)
{
path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
File.WriteAllBytes(path, kvp.Value);
}
});
Interlocked.Increment(ref ExportedCount);
Log.Information("{FileName} successfully exported", entry.Name);
if (updateUi)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(entry.Name, path, true);
});
}
ExportSessionViewModel.Instance.Session.Add(new RawDataExporter(entry, Provider));
}
else
catch (Exception e)
{
Interlocked.Increment(ref FailedExportCount);
Log.Error("{FileName} could not be exported", entry.Name);
if (updateUi)
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
Log.Error(e, "Could not add to export session");
}
}

View File

@ -40,6 +40,9 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
case "Views_3dViewer":
contextViewModel.CUE4Parse.SnooperViewer.Run();
break;
case "Views_ExportSession":
Helper.OpenWindow<AdonisWindow>("Export Session", () => new ExportSessionWindow().Show());
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;

View File

@ -67,6 +67,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
"Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties),
"Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures),
"Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes),
"Save_Worlds" => (EAction.Export, EShowAssetType.None, EBulkType.Worlds),
"Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations),
"Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio),
"Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code),
@ -108,6 +109,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"),
EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"),
EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"),
EBulkType.Worlds => (UserSettings.Default.ModelDirectory, "worlds"),
EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"),
EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"),
EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"),
@ -126,16 +128,17 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
foreach (var folder in folders)
{
cancellationToken.ThrowIfCancellationRequested();
var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued;
folderAction(folder);
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/');
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype);
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype, queuedBefore);
}
Action<GameFile, EBulkType, bool> fileAction = bulktype switch
Action<GameFile, EBulkType> fileAction = bulktype switch
{
EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update),
_ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
EBulkType.Raw => (entry, _) => contextViewModel.CUE4Parse.ExportData(entry),
_ => (entry, bulk) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
};
foreach (var group in assetsGroups)
@ -144,24 +147,31 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
var list = group.ToArray();
var update = list.Length > 1;
var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None);
var queuedBefore = ExportSessionViewModel.Instance.Session.TotalQueued;
foreach (var entry in list)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
fileAction(entry, bulk, update);
fileAction(entry, bulk);
}
if (update)
{
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/');
LogExport(contextViewModel, directory, path, dirType, filetype);
LogExport(contextViewModel, directory, path, dirType, filetype, queuedBefore);
}
}
});
if (action is EAction.Export)
{
await ExportSessionViewModel.Instance.ExportAutomaticallyAsync();
}
}
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType)
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType, int queuedBefore = 0)
{
var queuedDelta = ExportSessionViewModel.Instance.Session.TotalQueued - queuedBefore;
if (contextViewModel.CUE4Parse.ExportedCount > 0)
{
FLogger.Append(ELog.Information, () =>
@ -170,6 +180,13 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
FLogger.Link(directory, Path.Exists(path) ? path : basePath, true);
});
}
else if (queuedDelta > 0)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text($"Queued {queuedDelta} {fileType} for export from {directory}{(UserSettings.Default.ExportImmediately ? ", exporting automatically..." : "")}", Constants.WHITE, true);
});
}
else if (contextViewModel.CUE4Parse.FailedExportCount == 0)
{
// Not an error because folder simply might not contain type of asset user is trying to save

View File

@ -1,3 +1,4 @@
using System;
using System.Windows;
using AdonisUI.Controls;
using FModel.Framework;
@ -31,9 +32,15 @@ public class TabCommand : ViewModelCommand<TabItem>
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(tabViewModel);
break;
case "Assets_Show_Metadata":
_applicationView.CUE4Parse.ShowMetadata(tabViewModel.Entry);
break;
case "Find_References":
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
break;
case "Assets_Decompile":
_applicationView.CUE4Parse.Decompile(tabViewModel.Entry);
break;
case "Save_Data":
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
break;
@ -55,6 +62,12 @@ public class TabCommand : ViewModelCommand<TabItem>
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes);
});
break;
case "Save_Worlds":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Worlds);
});
break;
case "Save_Animations":
await _threadWorkerView.Begin(cancellationToken =>
{
@ -77,9 +90,26 @@ public class TabCommand : ViewModelCommand<TabItem>
}.Show();
});
break;
case "Copy_Asset_Path":
case "File_Path":
Clipboard.SetText(tabViewModel.Entry.Path);
break;
case "File_Name":
Clipboard.SetText(tabViewModel.Entry.Name);
break;
case "Directory_Path":
Clipboard.SetText(tabViewModel.Entry.Directory);
break;
case "File_Path_No_Extension":
Clipboard.SetText(tabViewModel.Entry.PathWithoutExtension);
break;
case "File_Name_No_Extension":
Clipboard.SetText(tabViewModel.Entry.NameWithoutExtension);
break;
}
if (parameter is string command && command.StartsWith("Save_", StringComparison.Ordinal)) // This is kinda bad
{
await ExportSessionViewModel.Instance.ExportAutomaticallyAsync();
}
}
}

View File

@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Windows.Threading;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Options;
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
using FModel.Framework;
using FModel.Settings;
namespace FModel.ViewModels;
public class ExportOptionsViewModel : ViewModel
{
public bool OverrideOptions
{
get;
set => SetProperty(ref field, value);
}
private DispatcherTimer? _feedbackTimer;
public string? FeedbackMessage
{
get;
private set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(HasFeedback));
_feedbackTimer?.Stop();
if (string.IsNullOrWhiteSpace(value)) return;
if (_feedbackTimer == null)
{
_feedbackTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
#pragma warning disable CA2011
_feedbackTimer.Tick += (_, _) => FeedbackMessage = null;
#pragma warning restore CA2011
}
_feedbackTimer.Start();
}
}
public bool HasFeedback => FeedbackMessage != null;
public string OutputDirectory
{
get;
set => SetProperty(ref field, value);
}
public bool ShowExportImmediatelyOption { get; }
public bool ExportImmediately
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<EMeshFormat> MeshFormats { get; } = Enum.GetValues<EMeshFormat>();
public EMeshFormat SelectedMeshFormat
{
get;
set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
RaisePropertyChanged(nameof(TextureFormatsEnabled));
if (value == EMeshFormat.USD)
{
_preUsdTextureFormat = SelectedTextureFormat;
SelectedTextureFormat = ETextureFormat.Png;
}
else if (_preUsdTextureFormat.HasValue)
{
SelectedTextureFormat = _preUsdTextureFormat.Value;
_preUsdTextureFormat = null;
}
}
}
public IEnumerable<ENaniteMeshFormat> NaniteMeshFormats { get; } = Enum.GetValues<ENaniteMeshFormat>();
public ENaniteMeshFormat SelectedNaniteMeshFormat
{
get;
set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(ShowNaniteWarning));
}
}
public bool ShowNaniteWarning => SelectedNaniteMeshFormat != ENaniteMeshFormat.NoNanite;
public IEnumerable<EMeshQuality> MeshQualities { get; } = Enum.GetValues<EMeshQuality>();
public EMeshQuality SelectedMeshQuality
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ESocketFormat> SocketFormats { get; } = Enum.GetValues<ESocketFormat>();
public ESocketFormat SelectedSocketFormat
{
get;
set => SetProperty(ref field, value);
}
public bool SocketSettingsEnabled => SelectedMeshFormat == EMeshFormat.ActorX;
public IEnumerable<EFileCompressionFormat> CompressionFormats { get; } = Enum.GetValues<EFileCompressionFormat>();
public EFileCompressionFormat SelectedCompressionFormat
{
get;
set => SetProperty(ref field, value);
}
public bool CompressionSettingsEnabled => SelectedMeshFormat == EMeshFormat.UEFormat;
public IEnumerable<EMaterialDepth> MaterialDepths { get; } = Enum.GetValues<EMaterialDepth>();
public EMaterialDepth SelectedMaterialDepth
{
get;
set => SetProperty(ref field, value);
}
public bool ExportMaterials
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ETexturePlatform> TexturePlatforms { get; } = Enum.GetValues<ETexturePlatform>();
public ETexturePlatform SelectedTexturePlatform
{
get;
set => SetProperty(ref field, value);
}
public IEnumerable<ETextureFormat> TextureFormats { get; } = Enum.GetValues<ETextureFormat>();
public ETextureFormat SelectedTextureFormat
{
get;
set => SetProperty(ref field, value);
}
private ETextureFormat? _preUsdTextureFormat;
public bool TextureFormatsEnabled => SelectedMeshFormat != EMeshFormat.USD;
public bool ExportHdrTexturesAsHdr
{
get;
set => SetProperty(ref field, value);
}
public int TextureQuality
{
get;
set => SetProperty(ref field, value);
}
public bool ExportMorphTargets
{
get;
set => SetProperty(ref field, value);
}
public bool ExportAllTextureMips
{
get;
set => SetProperty(ref field, value);
}
public ExportOptionsViewModel(bool showExportImmediatelyOption = false)
{
ShowExportImmediatelyOption = showExportImmediatelyOption;
ResetToUserDefaults();
}
public void ResetToUserDefaults()
{
OutputDirectory = UserSettings.Default.ModelDirectory;
SelectedMeshFormat = UserSettings.Default.MeshExportFormat;
SelectedNaniteMeshFormat = UserSettings.Default.NaniteMeshExportFormat;
SelectedMeshQuality = UserSettings.Default.MeshQuality;
SelectedSocketFormat = UserSettings.Default.SocketExportFormat;
SelectedCompressionFormat = UserSettings.Default.CompressionFormat;
SelectedMaterialDepth = UserSettings.Default.MaterialExportFormat;
ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials;
SelectedTexturePlatform = UserSettings.Default.CurrentDir.TexturePlatform;
SelectedTextureFormat = UserSettings.Default.TextureExportFormat;
ExportHdrTexturesAsHdr = UserSettings.Default.SaveHdrTexturesAsHdr;
ExportMorphTargets = UserSettings.Default.SaveMorphTargets;
TextureQuality = UserSettings.Default.TextureQuality;
ExportAllTextureMips = UserSettings.Default.ExportAllTextureMips;
ExportImmediately = UserSettings.Default.ExportImmediately;
OverrideOptions = false;
FeedbackMessage = "Reset to defaults";
}
public void SaveAsUserDefaults()
{
UserSettings.Default.ModelDirectory = OutputDirectory;
UserSettings.Default.MeshExportFormat = SelectedMeshFormat;
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshFormat;
UserSettings.Default.MeshQuality = SelectedMeshQuality;
UserSettings.Default.SocketExportFormat = SelectedSocketFormat;
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
UserSettings.Default.MaterialExportFormat = SelectedMaterialDepth;
UserSettings.Default.SaveEmbeddedMaterials = ExportMaterials;
UserSettings.Default.CurrentDir.TexturePlatform = SelectedTexturePlatform;
UserSettings.Default.TextureExportFormat = SelectedTextureFormat;
UserSettings.Default.SaveHdrTexturesAsHdr = ExportHdrTexturesAsHdr;
UserSettings.Default.SaveMorphTargets = ExportMorphTargets;
UserSettings.Default.TextureQuality = TextureQuality;
UserSettings.Default.ExportAllTextureMips = ExportAllTextureMips;
UserSettings.Default.ExportImmediately = ExportImmediately;
UserSettings.Save();
OverrideOptions = false;
FeedbackMessage = "Saved as default";
}
public ExportOptions BuildOptions() => new(
SelectedMeshFormat,
SelectedNaniteMeshFormat,
SelectedMeshQuality,
SelectedTexturePlatform,
SelectedTextureFormat,
TextureQuality,
ExportHdrTexturesAsHdr,
SelectedMaterialDepth,
ExportMaterials,
ExportMorphTargets,
SelectedSocketFormat,
SelectedCompressionFormat,
ExportAllTextureMips
);
}

View File

@ -0,0 +1,429 @@
using System;
using System.Collections.Concurrent;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Options;
using CUE4Parse.Utils;
using FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.Views;
using FModel.Views.Snooper;
using Serilog.Events;
namespace FModel.ViewModels;
public class ExportSessionViewModel : ViewModel
{
public static ExportSessionViewModel Instance { get; } = new();
private DispatcherTimer? _toastTimer;
public bool ShowQueueToast
{
get;
set
{
if (!SetProperty(ref field, value)) return;
if (!value)
{
_toastTimer?.Stop();
return;
}
if (_toastTimer == null)
{
_toastTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
_toastTimer.Tick += (_, _) =>
{
field = false;
RaisePropertyChanged(nameof(ShowQueueToast));
_toastTimer.Stop();
};
}
_toastTimer.Stop();
_toastTimer.Start();
}
}
private ExportSession? _session;
public ExportSession Session
{
get
{
if (_session != null) return _session;
_session = new ExportSession((args, ct) =>
{
Application.Current.Dispatcher.Invoke(() =>
{
var window = new StreamingLevelFilterWindow(new StreamingLevelFilterViewModel(args));
_stopwatch.Stop();
window.ShowDialog();
_stopwatch.Start();
}, DispatcherPriority.Normal, ct);
});
_session.PropertyChanged += OnSessionPropertyChanged;
return _session;
}
}
public ExportOptionsViewModel Options { get; } = new();
public bool IsRunning
{
get;
private set
{
if (!SetProperty(ref field, value)) return;
RaisePropertyChanged(nameof(CanExport));
}
}
public bool IsFinished
{
get;
private set => SetProperty(ref field, value);
}
public bool CanExport => !IsRunning && Session.TotalQueued > 0;
public int CompletedCount
{
get;
private set => SetProperty(ref field, value);
}
public int SucceededCount
{
get;
private set => SetProperty(ref field, value);
}
public int FailedCount
{
get;
private set => SetProperty(ref field, value);
}
public string? CurrentItemName
{
get;
private set => SetProperty(ref field, value);
}
public TimeSpan ElapsedTime
{
get;
private set => SetProperty(ref field, value);
}
public TimeSpan? EtaTime
{
get;
private set => SetProperty(ref field, value);
}
public bool IsCanceled
{
get;
private set => SetProperty(ref field, value);
}
public double ProgressValue
{
get;
private set => SetProperty(ref field, value);
}
public ObservableCollection<ClassGroupViewModel> ClassGroups { get; } = [];
private CancellationTokenSource? _cts;
private readonly Stopwatch _stopwatch = new();
private readonly ConcurrentQueue<LogEvent> _pendingLogs = new();
private DispatcherTimer? _uiTimer;
private ExportSessionViewModel()
{
ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
}
private void OnLogEvent(LogEvent log)
{
_pendingLogs.Enqueue(log);
Application.Current?.Dispatcher.InvokeAsync(DrainLogs);
}
private int _previousCount;
private void OnSessionPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(ExportSession.TotalQueued)) return;
var count = _session?.TotalQueued ?? 0;
Application.Current?.Dispatcher.InvokeAsync(() =>
{
if (count > 0 && _previousCount == 0)
{
ClearExportHistory();
}
ShowQueueToast = count switch
{
> 0 when _previousCount == 0 => true,
0 => false,
_ => ShowQueueToast
};
_previousCount = count;
RaisePropertyChanged(nameof(CanExport));
});
}
public async Task ExportAsync()
{
if (IsRunning || Session.TotalQueued == 0) return;
IsRunning = true;
IsFinished = false;
IsCanceled = false;
CompletedCount = 0;
SucceededCount = 0;
FailedCount = 0;
_stopwatch.Restart();
_cts = new CancellationTokenSource();
StartUiTimer();
string exportDirectory;
ExportOptions exportOptions;
if (Options.OverrideOptions)
{
exportDirectory = Options.OutputDirectory;
exportOptions = Options.BuildOptions();
}
else
{
exportDirectory = UserSettings.Default.ModelDirectory;
exportOptions = UserSettings.GetExportOptions();
}
var progress = new Progress<ExportProgress>(p =>
{
Application.Current?.Dispatcher.InvokeAsync(() =>
{
CompletedCount = p.Completed;
CurrentItemName = p.LastResult?.ObjectPath;
if (p.LastResult != null)
{
if (p.LastResult.Success) SucceededCount++;
else FailedCount++;
}
ProgressValue = p.Total > 0 ? (double)p.Completed / p.Total : 0;
});
});
try
{
await Session.RunAsync(exportDirectory, exportOptions, progress, _cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Application.Current?.Dispatcher.InvokeAsync(() => IsCanceled = true);
}
finally
{
_stopwatch.Stop();
Application.Current?.Dispatcher.InvokeAsync(() =>
{
StopUiTimer();
IsRunning = false;
IsFinished = !IsCanceled;
UpdateElapsedAndEta();
});
}
}
public async Task ExportAutomaticallyAsync()
{
if (UserSettings.Default.ExportImmediately)
{
await ExportAsync();
}
}
public void CancelExport()
{
_cts?.Cancel();
}
public void ClearQueue()
{
_session?.Clear();
ClearExportHistory();
}
public void RemoveFromQueue(ObjectGroupViewModel item)
{
if (IsRunning || _session?.Remove(item.Path) != true)
return;
var group = ClassGroups.FirstOrDefault(x => x.Objects.Contains(item));
if (group == null)
return;
group.Objects.Remove(item);
if (group.Objects.Count == 0)
{
ClassGroups.Remove(group);
}
}
private void StartUiTimer()
{
_uiTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
_uiTimer.Tick += (_, _) => UpdateElapsedAndEta();
_uiTimer.Start();
}
private void StopUiTimer()
{
_uiTimer?.Stop();
_uiTimer = null;
}
private void UpdateElapsedAndEta()
{
ElapsedTime = _stopwatch.Elapsed;
var remaining = Session.TotalQueued;
if (IsRunning && remaining > 0 && CompletedCount > 1 && ElapsedTime.TotalSeconds > 0)
{
var rate = CompletedCount / ElapsedTime.TotalSeconds;
if (rate > 0)
{
EtaTime = TimeSpan.FromSeconds(remaining / rate);
return;
}
}
EtaTime = null;
}
private void ClearExportHistory()
{
CompletedCount = 0;
SucceededCount = 0;
FailedCount = 0;
ProgressValue = 0;
ElapsedTime = TimeSpan.Zero;
EtaTime = null;
CurrentItemName = null;
IsFinished = false;
IsCanceled = false;
ClassGroups.Clear();
}
private void DrainLogs()
{
while (_pendingLogs.TryDequeue(out var log))
{
var className = log.GetContext("ClassName");
var objectPath = log.GetContext("ObjectPath");
var filePath = log.GetContext("FilePath");
var cg = FindOrCreateClass(className);
var og = FindOrCreateObject(cg, objectPath);
if (log.Level >= LogEventLevel.Error)
{
og.ErrorCount++;
cg.ErrorCount++;
}
if (og.FirstFilePath == null && !string.IsNullOrEmpty(filePath))
og.FirstFilePath = filePath;
og.Entries.Add(new LogEntryViewModel(log));
}
}
private ClassGroupViewModel FindOrCreateClass(string name)
{
var cg = ClassGroups.FirstOrDefault(c => c.Name == name);
if (cg != null) return cg;
cg = new ClassGroupViewModel(name);
ClassGroups.Add(cg);
return cg;
}
private static ObjectGroupViewModel FindOrCreateObject(ClassGroupViewModel cg, string path)
{
var og = cg.Objects.FirstOrDefault(o => o.Path == path);
if (og != null) return og;
og = new ObjectGroupViewModel(path);
cg.Objects.Add(og);
return og;
}
}
public class ClassGroupViewModel(string name) : ViewModel
{
public string Name { get; } = name;
public ObservableCollection<ObjectGroupViewModel> Objects { get; } = [];
public bool IsExpanded
{
get;
set => SetProperty(ref field, value);
}
public int ErrorCount
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasErrors));
}
}
public override bool HasErrors => ErrorCount > 0;
}
public class ObjectGroupViewModel(string path) : ViewModel
{
public string Path { get; } = path;
public string Name { get; } = path.SubstringAfterLast('.');
public ObservableCollection<LogEntryViewModel> Entries { get; } = [];
public bool IsExpanded
{
get;
set => SetProperty(ref field, value);
}
public int ErrorCount
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasErrors));
}
}
public override bool HasErrors => ErrorCount > 0;
public string? FirstFilePath
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasFilePath));
}
}
public bool HasFilePath => FirstFilePath != null;
}
public class LogEntryViewModel(LogEvent log)
{
public LogEventLevel Level { get; } = log.Level;
public DateTimeOffset Timestamp { get; } = log.Timestamp;
public string Message { get; } = log.Exception switch
{
NullReferenceException or ArgumentException => log.RenderMessage(),
_ => log.Exception?.Message ?? log.RenderMessage()
};
public Exception? Exception { get; } = log.Exception;
}

View File

@ -170,7 +170,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
if (Asset.Extension is "umap")
{
AssetCategory = EAssetCategory.World;
AssetActions = EBulkType.Meshes | EBulkType.Textures | EBulkType.Audio | EBulkType.Code;
AssetActions = EBulkType.Worlds | EBulkType.Textures | EBulkType.Audio | EBulkType.Code;
ResolvedAssetType = "World";
Resolved |= EResolveCompute.Preview;
return Task.CompletedTask;

View File

@ -134,11 +134,27 @@ public class GameSelectorViewModel : ViewModel
}
}
var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe");
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
var projectEngineBinariesDir = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64");
if (Directory.Exists(projectEngineBinariesDir))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
return true;
var crashReportClientExe = Path.Combine(projectEngineBinariesDir, "CrashReportClient.exe");
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
return true;
}
if (Directory.GetFiles(projectEngineBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping)
{
foreach (var exe in shipping)
{
if (TryGetUeVersionFromExe(exe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
return true;
}
}
}
}
ueVersion = EGame.GAME_UE4_LATEST;

View File

@ -2,20 +2,16 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Nanite;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Serialization;
using CUE4Parse.UE4.Versions;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Textures;
using CUE4Parse_Conversion.UEFormat.Enums;
using FModel.Extensions;
using CUE4Parse_Conversion.Options;
using CUE4Parse_Conversion.Writers.UEFormat.Enums;
using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Extensions.Themes;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using ICSharpCode.AvalonEdit.Highlighting;
namespace FModel.ViewModels;
@ -30,13 +26,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _useCustomOutputFolders, value);
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
@ -114,60 +103,6 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set
{
SetProperty(ref _selectedMeshExportFormat, value);
RaisePropertyChanged(nameof(SocketSettingsEnabled));
RaisePropertyChanged(nameof(CompressionSettingsEnabled));
}
}
private ESocketFormat _selectedSocketExportFormat;
public ESocketFormat SelectedSocketExportFormat
{
get => _selectedSocketExportFormat;
set => SetProperty(ref _selectedSocketExportFormat, value);
}
private EFileCompressionFormat _selectedCompressionFormat;
public EFileCompressionFormat SelectedCompressionFormat
{
get => _selectedCompressionFormat;
set => SetProperty(ref _selectedCompressionFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ENaniteMeshFormat _selectedNaniteMeshExportFormat;
public ENaniteMeshFormat SelectedNaniteMeshExportFormat
{
get => _selectedNaniteMeshExportFormat;
set => SetProperty(ref _selectedNaniteMeshExportFormat, value);
}
private EMaterialFormat _selectedMaterialExportFormat;
public EMaterialFormat SelectedMaterialExportFormat
{
get => _selectedMaterialExportFormat;
set => SetProperty(ref _selectedMaterialExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
private EJsonHighlightTheme _selectedJsonHighlightTheme;
public EJsonHighlightTheme SelectedJsonHighlightTheme
{
@ -189,8 +124,7 @@ public class SettingsViewModel : ViewModel
set => SetProperty(ref _unluacOpcodeMap, value);
}
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
public ExportOptionsViewModel Options { get; } = new(showExportImmediatelyOption: true);
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
@ -198,14 +132,6 @@ public class SettingsViewModel : ViewModel
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ESocketFormat> SocketExportFormats { get; private set; }
public ReadOnlyObservableCollection<EFileCompressionFormat> CompressionFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ENaniteMeshFormat> NaniteMeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<EMaterialFormat> MaterialExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public ReadOnlyObservableCollection<EJsonHighlightTheme> JsonHighlightThemes { get; private set; }
private string _outputSnapshot;
@ -216,7 +142,6 @@ public class SettingsViewModel : ViewModel
private string _codeSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private IList<FCustomVersion> _customVersionsSnapshot;
private IDictionary<string, bool> _optionsSnapshot;
@ -224,13 +149,6 @@ public class SettingsViewModel : ViewModel
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ESocketFormat _socketExportFormatSnapshot;
private EFileCompressionFormat _compressionFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ENaniteMeshFormat _naniteMeshExportFormatSnapshot;
private EMaterialFormat _materialExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
private EJsonHighlightTheme _jsonHighlightThemeSnapshot;
private bool _mappingsUpdate = false;
@ -250,7 +168,6 @@ public class SettingsViewModel : ViewModel
_codeSnapshot = UserSettings.Default.CodeDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
_optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options;
@ -269,16 +186,8 @@ public class SettingsViewModel : ViewModel
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_socketExportFormatSnapshot = UserSettings.Default.SocketExportFormat;
_compressionFormatSnapshot = UserSettings.Default.CompressionFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_naniteMeshExportFormatSnapshot = UserSettings.Default.NaniteMeshExportFormat;
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
_jsonHighlightThemeSnapshot = UserSettings.Default.JsonHighlightTheme;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
@ -286,13 +195,6 @@ public class SettingsViewModel : ViewModel
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedSocketExportFormat = _socketExportFormatSnapshot;
SelectedCompressionFormat = _selectedCompressionFormat;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedNaniteMeshExportFormat = _naniteMeshExportFormatSnapshot;
SelectedMaterialExportFormat = _materialExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
CriwareDecryptionKey = _criwareDecryptionKey;
UnluacOpcodeMap = _unluacOpcodeMap;
SelectedJsonHighlightTheme = _jsonHighlightThemeSnapshot;
@ -305,14 +207,6 @@ public class SettingsViewModel : ViewModel
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
SocketExportFormats = new ReadOnlyObservableCollection<ESocketFormat>(new ObservableCollection<ESocketFormat>(EnumerateSocketExportFormat()));
CompressionFormats = new ReadOnlyObservableCollection<EFileCompressionFormat>(new ObservableCollection<EFileCompressionFormat>(EnumerateCompressionFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
NaniteMeshExportFormats = new ReadOnlyObservableCollection<ENaniteMeshFormat>(new ObservableCollection<ENaniteMeshFormat>(EnumerateNaniteMeshExportFormat()));
MaterialExportFormats = new ReadOnlyObservableCollection<EMaterialFormat>(new ObservableCollection<EMaterialFormat>(EnumerateMaterialExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
JsonHighlightThemes = new ReadOnlyObservableCollection<EJsonHighlightTheme>(new ObservableCollection<EJsonHighlightTheme>(EnumerateJsonHighlightThemes()));
}
@ -327,13 +221,12 @@ public class SettingsViewModel : ViewModel
whatShouldIDo.Add(SettingsOut.ReloadMappings);
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_optionsSnapshot != SelectedOptions || // combobox
_mapStructTypesSnapshot != SelectedMapStructTypes ||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
restart = true;
UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions;
UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes;
@ -343,17 +236,12 @@ public class SettingsViewModel : ViewModel
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.SocketExportFormat = SelectedSocketExportFormat;
UserSettings.Default.CompressionFormat = SelectedCompressionFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.NaniteMeshExportFormat = SelectedNaniteMeshExportFormat;
UserSettings.Default.MaterialExportFormat = SelectedMaterialExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
UserSettings.Default.JsonHighlightTheme = SelectedJsonHighlightTheme;
Options.SaveAsUserDefaults();
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
@ -370,13 +258,5 @@ public class SettingsViewModel : ViewModel
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ESocketFormat> EnumerateSocketExportFormat() => Enum.GetValues<ESocketFormat>();
private IEnumerable<EFileCompressionFormat> EnumerateCompressionFormat() => Enum.GetValues<EFileCompressionFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
private IEnumerable<ENaniteMeshFormat> EnumerateNaniteMeshExportFormat() => Enum.GetValues<ENaniteMeshFormat>();
private IEnumerable<EMaterialFormat> EnumerateMaterialExportFormat() => Enum.GetValues<EMaterialFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
private IEnumerable<EJsonHighlightTheme> EnumerateJsonHighlightThemes() => Enum.GetValues<EJsonHighlightTheme>();
}

View File

@ -0,0 +1,192 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Dto;
namespace FModel.ViewModels;
public class StreamingLevelFilterViewModel
{
public string WorldName { get; }
public int TotalCount { get; }
public List<ActorNodeVm> Children { get; } = [];
public StreamingLevelFilterViewModel(StreamingLevelFilterArgs args)
{
WorldName = args.WorldName;
foreach (var actor in args.Actors)
{
Children.Add(new ActorNodeVm(actor));
}
var worldLevels = new ActorNodeVm("Streaming Levels") { IsExpanded = true };
foreach (var level in args.StreamingLevels)
{
worldLevels.Children.Add(new StreamingLevelNodeVm(level));
}
if (worldLevels.Children.Count > 0) Children.Add(worldLevels);
TotalCount = CountLevels(args.Actors) + args.StreamingLevels.Count;
}
public void SkipAll()
{
foreach (var child in Children)
{
child.IsChecked = false;
}
}
private int CountLevels(IReadOnlyList<ActorDto> actors)
{
var n = 0;
foreach (var a in actors) n += CountFromActor(a);
return n;
}
private int CountFromActor(ActorDto actor)
{
var n = actor.StreamingLevels?.Count ?? 0;
if (actor.RootComponent is { } comp) n += CountFromComponent(comp);
return n;
}
private int CountFromComponent(SceneComponentDto comp)
{
var n = 0;
foreach (var a in comp.AttachedActors) n += CountFromActor(a);
foreach (var c in comp.Children) n += CountFromComponent(c);
return n;
}
}
public class ActorNodeVm : TreeNodeVm
{
public override string Name { get; }
public bool IsExpanded { get; set; }
public ObservableCollection<TreeNodeVm> Children { get; } = [];
private static int _batch;
public ActorNodeVm(string name)
{
Name = name;
Children.CollectionChanged += (_, e) =>
{
if (e.NewItems is null) return;
foreach (TreeNodeVm child in e.NewItems)
{
child.PropertyChanged += (_, args) =>
{
if (args.PropertyName == nameof(IsChecked) && _batch == 0)
OnPropertyChanged(nameof(IsChecked));
};
}
};
}
public ActorNodeVm(ActorDto actor) : this(actor.Name)
{
if (actor.StreamingLevels is { Count: > 0 } streamingLevels)
foreach (var level in streamingLevels)
Children.Add(new StreamingLevelNodeVm(level));
CollectFromComponent(actor.RootComponent);
}
private ActorNodeVm(SceneComponentDto component) : this(component.Name)
{
CollectFromComponent(component);
}
private void NotifyIntermediates()
{
foreach (var child in Children.OfType<ActorNodeVm>())
{
child.NotifyIntermediates();
child.OnPropertyChanged(nameof(IsChecked));
}
}
private void CollectFromComponent(SceneComponentDto? comp)
{
if (comp is null) return;
foreach (var actor in comp.AttachedActors)
Children.Add(new ActorNodeVm(actor));
foreach (var component in comp.Children)
Children.Add(new ActorNodeVm(component));
}
private IEnumerable<StreamingLevelNodeVm> AllLevels()
{
foreach (var child in Children)
{
switch (child)
{
case StreamingLevelNodeVm sl:
yield return sl;
break;
case ActorNodeVm actor:
{
foreach (var l in actor.AllLevels())
{
yield return l;
}
break;
}
}
}
}
public bool? IsChecked
{
get
{
var all = AllLevels().ToList();
if (all.Count == 0) return false;
var trueCount = all.Count(l => l.IsChecked);
if (trueCount == all.Count) return true;
if (trueCount == 0) return false;
return null;
}
set
{
var v = value ?? false;
_batch++;
foreach (var l in AllLevels())
l.IsChecked = v;
NotifyIntermediates();
_batch--;
OnPropertyChanged();
}
}
}
public class StreamingLevelNodeVm(StreamingLevel level) : TreeNodeVm
{
public override string Name { get; } = level.World.Name;
public bool IsChecked
{
get => level.IsPersistent;
set
{
level.IsPersistent = value;
OnPropertyChanged();
}
}
}
public abstract class TreeNodeVm : INotifyPropertyChanged
{
public abstract string Name { get; }
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Media.Imaging;
using CUE4Parse_Conversion.Options;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.Utils;
@ -113,7 +114,7 @@ public class TabImage : ViewModel
else
{
ImageBuffer = imageData;
ExportName += "." + (NoAlpha ? "jpg" : "png");
ExportName += "." + (NoAlpha || UserSettings.Default.TextureExportFormat == ETextureFormat.Jpeg ? "jpg" : "png");
}
using var stream = new MemoryStream(imageData);
@ -132,8 +133,6 @@ public class TabImage : ViewModel
public class TabItem : ViewModel
{
public string ParentExportType { get; private set; }
private GameFile _entry;
public GameFile Entry
{
@ -267,10 +266,9 @@ public class TabItem : ViewModel
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
public TabItem(GameFile entry, string parentExportType)
public TabItem(GameFile entry)
{
Entry = entry;
ParentExportType = parentExportType;
_images = new ObservableCollection<TabImage>();
}
@ -278,7 +276,6 @@ public class TabItem : ViewModel
{
Entry = entry;
TitleExtra = string.Empty;
ParentExportType = string.Empty;
ScrollTrigger = null;
Application.Current.Dispatcher.Invoke(() =>
{
@ -472,7 +469,7 @@ public class TabControlViewModel : ViewModel
public void AddTab() => AddTab("New Tab");
public void AddTab(string title) => AddTab(new FakeGameFile(title));
public void AddTab(GameFile entry, string parentExportType = null)
public void AddTab(GameFile entry)
{
if (SelectedTab?.Header == "New Tab")
{
@ -483,7 +480,7 @@ public class TabControlViewModel : ViewModel
if (!CanAddTabs) return;
Application.Current.Dispatcher.Invoke(() =>
{
_tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty));
_tabItems.Add(new TabItem(entry));
SelectedTab = _tabItems.Last();
});
}

View File

@ -63,7 +63,7 @@ public partial class UpdateViewModel : ViewModel
var coAuthorMap = new Dictionary<GitHubCommit, HashSet<string>>();
foreach (var commit in Commits)
{
if (!commit.Commit.Message.Contains("Co-authored-by"))
if (!commit.Commit.Message.Contains("Co-authored-by", StringComparison.OrdinalIgnoreCase))
continue;
var regex = GetCoAuthorRegex();
@ -111,7 +111,7 @@ public partial class UpdateViewModel : ViewModel
foreach (var (commit, usernames) in coAuthorMap)
{
var coAuthors = usernames
.Where(username => authorCache.ContainsKey(username))
.Where(authorCache.ContainsKey)
.Select(username => authorCache[username])
.ToArray();

View File

@ -75,6 +75,8 @@ public partial class AudioPlayer
_applicationView.AudioPlayer.Previous();
else if (UserSettings.Default.NextAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Next();
else if (UserSettings.Default.RemoveAudio.IsTriggered(e.Key))
_applicationView.AudioPlayer.Remove();
}
private void OnAudioFileMouseDoubleClick(object sender, MouseButtonEventArgs e)

View File

@ -0,0 +1,547 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.ExportSessionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="512" d:DesignHeight="512"
d:DataContext="{d:DesignInstance Type=vm:ExportSessionViewModel, IsDesignTimeCreatable=False}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:serilog="clr-namespace:Serilog.Events;assembly=Serilog"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion"
DataContext="{x:Static vm:ExportSessionViewModel.Instance}"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}">
<Setter Property="Title" Value="Export Session" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<Style x:Key="StretchTreeViewItemStyle" TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border x:Name="Border"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}" />
<Border x:Name="SpotlightLayer"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BackgroundBrush}"
BorderBrush="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
adonisExtensions:CursorSpotlightExtension.MouseEventSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TreeViewItem}}"
SnapsToDevicePixels="False" />
<ToggleButton x:Name="Expander"
Grid.Row="0" Grid.Column="0"
Width="16" Height="16"
Margin="8,0,8,0"
Focusable="False" ClickMode="Press"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
RenderTransformOrigin="0.5,0.5">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24" Background="Transparent">
<Path Fill="{TemplateBinding Foreground}"
Data="{StaticResource ArrowIcon}" />
</Canvas>
</Viewbox>
</ControlTemplate>
</ToggleButton.Template>
<ToggleButton.RenderTransform>
<RotateTransform x:Name="ExpanderRotateTransform" Angle="-90" />
</ToggleButton.RenderTransform>
</ToggleButton>
<ContentPresenter x:Name="PART_Header"
Grid.Row="0" Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="0,4,8,4" />
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1" Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" To="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" From="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="HasItems" Value="False">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="LogEntryTemplate" DataType="{x:Type vm:LogEntryViewModel}">
<Grid Margin="0 1" ToolTip="{Binding Exception}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" Margin="0 0 5 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="LevelIcon"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Data="{StaticResource BugIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Timestamp, StringFormat='[{0:HH:mm:ss.fff}]'}"
FontSize="11" Margin="0 0 5 0" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Column="2"
Text="{Binding Message}" FontSize="11" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Verbose}">
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource NoteIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Debug}">
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource BugIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Information}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#42A5F5" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource InfoIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Warning}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#FFA726" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource WarningIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Error}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#EF5350" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource CloseIcon}" />
</DataTrigger>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Fatal}">
<Setter TargetName="LevelIcon" Property="Fill" Value="#EF5350" />
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource CloseIcon}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="ObjectGroupHeaderTemplate" DataType="{x:Type vm:ObjectGroupViewModel}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" Margin="0 0 5 0" VerticalAlignment="Center"
Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}">
<Canvas Width="24" Height="24">
<Path Fill="#EF5350" Data="{StaticResource CloseIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
ToolTip="{Binding Path}" />
<Button Grid.Column="3" ToolTip="Remove from queue" Click="OnRemoveFromQueueClick"
Visibility="{Binding DataContext.CanExport, RelativeSource={RelativeSource AncestorType=adonisControls:AdonisWindow}, Converter={StaticResource BoolToVisibilityConverter}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" Margin="0" Focusable="False"
Foreground="#EF5350" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"
Data="{StaticResource RemoveIcon}" />
</Canvas>
</Viewbox>
</Button>
<Button Grid.Column="4" ToolTip="Open in Explorer" Click="OnOpenInExplorerClick"
Tag="{Binding FirstFilePath}"
Visibility="{Binding HasFilePath, Converter={StaticResource BoolToVisibilityConverter}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Padding="0" Margin="0">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M11 13l9 -9" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M15 4h5v5" />
</Canvas>
</Viewbox>
</Button>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ClassGroupHeaderTemplate" DataType="{x:Type vm:ClassGroupViewModel}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" FontWeight="SemiBold" Text="{Binding Name}" VerticalAlignment="Center" />
<Grid Grid.Column="1" Margin="5 0 0 0"
Visibility="{Binding HasErrors, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" Margin="0 0 5 0">
<Canvas Width="24" Height="24">
<Path Fill="#EF5350" Data="{StaticResource CloseIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" Text="{Binding ErrorCount}" Foreground="#EF5350" VerticalAlignment="Center" />
</Grid>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Margin="5 0 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="{Binding Objects.Count, Mode=OneWay, StringFormat='({0})'}"
ToolTip="Assets sharing the same name across different packages are grouped together in this view. Exports are not affected."/>
</Grid>
</DataTemplate>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Margin="10">
<Grid DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Text="{Binding ElapsedTime, Mode=OneWay, Converter={x:Static converters:TimeSpanConverter.Instance}, ConverterParameter=elapsed, FallbackValue=00:00.00}"
FontSize="16" FontWeight="SemiBold" HorizontalAlignment="Right"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="1" FontSize="10" HorizontalAlignment="Right">
<Run Text="ETA " Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Run Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Text="{Binding EtaTime, Mode=OneWay, Converter={x:Static converters:TimeSpanConverter.Instance}, FallbackValue=--:--}" />
</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
FontSize="10" HorizontalAlignment="Right"
TextTrimming="CharacterEllipsis" MaxWidth="220">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding CurrentItemName, Mode=OneWay, FallbackValue=/}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsCanceled}" Value="True">
<Setter Property="Text" Value="Export canceled" />
<Setter Property="Foreground" Value="#EF5350" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFinished}" Value="True">
<Setter Property="Text" Value="Export finished" />
<Setter Property="Foreground" Value="#66BB6A" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
<Grid DockPanel.Dock="Left" VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{Binding Session.TotalQueued, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="in queue" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="1" Margin="16 2"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="2"
Text="{Binding CompletedCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="completed" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Grid.Column="3" Width="1" Margin="16 2"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="4"
Text="{Binding SucceededCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold" Foreground="#66BB6A" />
<TextBlock Grid.Row="1" Grid.Column="4" Text="succeeded" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.Column="5" Margin="5 0 0 0"
Text="{Binding FailedCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold" Foreground="#EF5350" />
<TextBlock Grid.Row="1" Grid.Column="5" Text="failed" FontSize="10" Margin="5 0 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</Grid>
</DockPanel>
<ProgressBar Grid.Row="1" Height="5" Margin="0 5 0 5" Maximum="1" Value="{Binding ProgressValue, Mode=OneWay, FallbackValue=0.5}" />
<TabControl Grid.Row="2" adonisExtensions:LayerExtension.Layer="2"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}">
<TabItem Header="Export Logs">
<Grid>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="13"
Text="Start an export to see results here." FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsRunning}" Value="False" />
<Condition Binding="{Binding ClassGroups.Count}" Value="0" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TreeView ItemsSource="{Binding ClassGroups}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource StretchTreeViewItemStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=Items.Count}" Value="1">
<Setter Property="IsExpanded" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:ClassGroupViewModel}" ItemsSource="{Binding Objects}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ClassGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type vm:ObjectGroupViewModel}" ItemsSource="{Binding Entries}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ObjectGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type vm:LogEntryViewModel}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource LogEntryTemplate}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
</TabItem>
<TabItem Header="Export Options" adonisExtensions:LayerExtension.Layer="2">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding Options.OverrideOptions, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="11" Margin="5 2 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="Override default export options for this session"
Visibility="{Binding Options.HasFeedback, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="11" Margin="5 2 0 0"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
FontWeight="SemiBold" Text="{Binding Options.FeedbackMessage}"
Visibility="{Binding Options.HasFeedback, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Grid.Column="2" Click="OnMakeDefaultOptions" ToolTip="Save current options as your default settings"
Padding="4" BorderThickness="1" Margin="5 0 0 0"
IsEnabled="{Binding Options.OverrideOptions}" Focusable="False"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</Button>
<Button Grid.Column="3" Click="OnResetOptions" ToolTip="Reset all options to default settings"
Padding="4" BorderThickness="1" Margin="5 0 0 0"
IsEnabled="{Binding Options.OverrideOptions}" Focusable="False"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}">
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
</Canvas>
</Viewbox>
</Button>
</Grid>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0 5 0 10" />
<controls:ExportOptionsControl Grid.Row="2"
DataContext="{Binding Options}"
IsEnabled="{Binding OverrideOptions}" />
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
<Border Grid.Row="3"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"
adonisExtensions:LayerExtension.IncreaseLayer="True">
<Grid Margin="30, 12, 6, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
Visibility="{Binding IsRunning, Converter={StaticResource BoolToVisibilityConverter}}"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" Click="OnCancelClick"
Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}">
<Button.Resources>
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentBrush}" Color="#E53935" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentHighlightBrush}" Color="#EF5350" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentIntenseHighlightBrush}" Color="#EF5350" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBrush}" Color="#C62828" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBorderBrush}" Color="#B71C1C" />
</Button.Resources>
</Button>
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
IsEnabled="{Binding CanExport}"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Clear Queue" Click="OnClearQueueClick">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFinished}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button Grid.Column="3" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Export"
IsEnabled="{Binding CanExport}"
Visibility="{Binding IsFinished, Converter={x:Static converters:InvertBoolToVisibilityConverter.Instance}}"
Click="OnExportClick" />
<Button Grid.Column="3" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"
Click="OnOkClick" />
</Grid>
</Border>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -0,0 +1,73 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using FModel.ViewModels;
namespace FModel.Views;
public partial class ExportSessionWindow
{
public ExportSessionWindow()
{
InitializeComponent();
}
private async void OnExportClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel { CanExport: true } viewModel })
await viewModel.ExportAsync();
}
private void OnOkClick(object sender, RoutedEventArgs e)
{
Close();
}
private void OnCancelClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.CancelExport();
}
private void OnClearQueueClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.ClearQueue();
}
private void OnRemoveFromQueueClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ObjectGroupViewModel item } && DataContext is ExportSessionViewModel viewModel)
viewModel.RemoveFromQueue(item);
}
private void OnMakeDefaultOptions(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.Options.SaveAsUserDefaults();
}
private void OnResetOptions(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportSessionViewModel viewModel })
viewModel.Options.ResetToUserDefaults();
}
private void OnOpenInExplorerClick(object sender, RoutedEventArgs e)
{
if (sender is not Button { Tag: string path }) return;
try
{
if (File.Exists(path) || Directory.Exists(path))
{
Process.Start("explorer.exe", $"/select,\"{path}\"");
}
}
catch
{
//
}
}
}

View File

@ -33,7 +33,6 @@ public class GamePathElementGenerator : VisualLineElementGenerator
if (!m.Success || m.Index != 0 ||
!m.Groups.TryGetValue("target", out var g)) return null;
var parentExportType = CurrentContext.Document.GetParentExportType(offset);
return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1);
return new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1);
}
}

View File

@ -17,16 +17,14 @@ public class GamePathVisualLineText : VisualLineText
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public delegate void GamePathOnClick(string gamePath, string parentExportType);
public delegate void GamePathOnClick(string gamePath);
public event GamePathOnClick OnGamePathClicked;
private readonly string _gamePath;
private readonly string _parentExportType;
public GamePathVisualLineText(string gamePath, string parentExportType, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length)
{
_gamePath = gamePath;
_parentExportType = parentExportType;
}
public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context)
@ -59,14 +57,14 @@ public class GamePathVisualLineText : VisualLineText
if (e.Handled || OnGamePathClicked == null)
return;
OnGamePathClicked(_gamePath, _parentExportType);
OnGamePathClicked(_gamePath);
e.Handled = true;
}
protected override VisualLineText CreateInstance(int length)
{
var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length);
a.OnGamePathClicked += async (gamePath, parentExportType) =>
var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length);
a.OnGamePathClicked += async gamePath =>
{
var obj = gamePath.SubstringAfterLast('.');
var package = gamePath.SubstringBeforeLast('.');
@ -87,7 +85,7 @@ public class GamePathVisualLineText : VisualLineText
}
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType));
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
};
return a;
}

View File

@ -4,7 +4,8 @@ namespace FModel.Views.Resources.Controls.Aup;
public enum ESourceEventType
{
Loading
Loading,
Clearing
}
public class SourceEventArgs : EventArgs
@ -15,4 +16,4 @@ public class SourceEventArgs : EventArgs
{
Event = e;
}
}
}

View File

@ -121,7 +121,7 @@ public sealed class SpectrumAnalyzer : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
if (e.Event != ESourceEventType.Loading) return;
if (e is not { Event: ESourceEventType.Loading } g) return;
_spectrumProvider = Source.Spectrum;
UpdateFrequencyMapping();
}

View File

@ -116,9 +116,9 @@ public sealed class Timeclock : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
if (Source == null) return;
if (e is not { Event: ESourceEventType.Loading } || Source == null) return;
Label = Source.PlayedFile.FileName;
Dispatcher.BeginInvoke((Action) CalculateTime);
Dispatcher.BeginInvoke(CalculateTime);
}
private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e)

View File

@ -100,8 +100,8 @@ public sealed class Timeline : UserControl
private void OnSourceEvent(object sender, SourceEventArgs e)
{
if (Source == null) return;
Dispatcher.BeginInvoke((Action) UpdateTimeline);
if (e is not { Event: ESourceEventType.Loading } || Source == null) return;
Dispatcher.BeginInvoke(UpdateTimeline);
}
private void OnSourcePropertyChangedEvent(object sender, SourcePropertyChangedEventArgs e)
@ -347,4 +347,4 @@ public sealed class Timeline : UserControl
}
}
}
}
}

View File

@ -110,146 +110,189 @@
</MenuItem.Style>
</MenuItem>
<Separator />
<MenuItem Command="{Binding RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})',
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Textures" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Meshes" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Animations" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Audio" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})',
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Textures" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Meshes" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Worlds" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Animations" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Audio" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">

View File

@ -5,145 +5,183 @@
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
<ContextMenu x:Key="FolderContextMenu" x:Shared="False"
Opened="FolderContextMenu_OnOpened">
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export Folder">
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Properties (.json)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Textures"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Decompiled Blueprints"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Code" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource CodeIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Models"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Animations"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Folder's Packages Audio"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AudioIcon}" />
Data="M2 6c0 -.796 .316 -1.558 .879 -2.121c.563 -.563 1.325 -.879 2.121 -.879h4l.099 .005c.229 .023 .444 .124 .608 .288l2.707 2.707h6.586c.796 0 1.558 .316 2.121 .879c.319 .319 .559 .703 .707 1.121l-14.523 0c-.407 0 -.805 .125 -1.14 .356c-.292 .203 -.525 .48 -.674 .801l-.058 .141l-1.379 3.676c-.194 .517 .068 1.093 .585 1.287c.517 .194 1.094 -.068 1.288 -.585l1.134 -3.027c.146 -.39 .519 -.649 .937 -.649h13.002l.217 .012c.216 .024 .426 .082 .624 .173c.054 .025 .107 .053 .159 .083c.199 .115 .377 .263 .525 .439c.188 .222 .325 .482 .403 .762c.077 .28 .092 .573 .045 .859c-.001 .008 -.003 .016 -.005 .024l-.995 5.21c-.131 .686 -.497 1.304 -1.036 1.749c-.47 .389 -1.046 .624 -1.65 .677l-.261 .012h-14.026c-.796 0 -1.558 -.316 -2.121 -.879c-.563 -.563 -.879 -1.325 -.879 -2.121v-11z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Textures"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Decompiled Blueprints"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Code" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource CodeIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Models"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Worlds"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animations"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio"
Command="{Binding RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="Tag"
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Favorite Directory" Click="OnFavoriteDirectoryClick"

View File

@ -0,0 +1,77 @@
<UserControl x:Class="FModel.Views.Resources.Controls.ListExplorer.FileRow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
mc:Ignorable="d"
d:DesignHeight="32"
d:DesignWidth="900"
d:DataContext="{d:DesignInstance Type=vm:GameFileViewModel, IsDesignTimeCreatable=False}">
<Grid Height="32"
Margin="8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ExplorerNameColumn}" />
<ColumnDefinition Width="{StaticResource ExplorerTypeColumn}" />
<ColumnDefinition Width="{StaticResource ExplorerDetailsColumn}" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center">
<Grid Width="20"
Height="20"
Margin="0 0 8 0">
<Image Stretch="Uniform"
Source="{Binding PreviewImage}" />
<Path x:Name="FallbackIcon"
Stretch="Uniform">
<Path.Style>
<Style TargetType="{x:Type Path}">
<Setter Property="Visibility"
Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding PreviewImage}"
Value="{x:Null}">
<Setter Property="Visibility"
Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
<Path.Data>
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
<Binding Path="AssetCategory" />
<Binding Path="ResolvedAssetType" />
</MultiBinding>
</Path.Data>
<Path.Fill>
<MultiBinding Converter="{x:Static converters:FileToGeometryConverter.Instance}">
<Binding Path="AssetCategory" />
<Binding Path="ResolvedAssetType" />
</MultiBinding>
</Path.Fill>
</Path>
</Grid>
<TextBlock Text="{Binding Asset.NameWithoutExtension}"
VerticalAlignment="Center"
FontSize="13"
FontWeight="DemiBold"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</StackPanel>
<TextBlock Grid.Column="1"
Text="{Binding ResolvedAssetType}"
VerticalAlignment="Center"
FontSize="12"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Column="2"
Text="{Binding Asset.Size, Converter={x:Static converters:SizeToStringConverter.Instance}}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
FontSize="12"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls.ListExplorer;
public partial class FileRow : UserControl
{
public FileRow()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,87 @@
<UserControl x:Class="FModel.Views.Resources.Controls.ListExplorer.FolderRow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
mc:Ignorable="d"
d:DesignHeight="32"
d:DesignWidth="900"
d:DataContext="{d:DesignInstance Type=vm:TreeItem, IsDesignTimeCreatable=False}">
<Grid Height="32"
Margin="8,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ExplorerNameColumn}" />
<ColumnDefinition Width="{StaticResource ExplorerTypeColumn}" />
<ColumnDefinition Width="{StaticResource ExplorerDetailsColumn}" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
VerticalAlignment="Center">
<Grid Width="20"
Height="20"
Margin="0 0 8 0">
<Viewbox>
<Canvas Width="128"
Height="128">
<Canvas.Resources>
<LinearGradientBrush x:Key="FolderTabGradient"
StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#FFE8C480"
Offset="0" />
<GradientStop Color="#FFD9AA63"
Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="FolderBodyGradient"
StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#FFF3D9A4"
Offset="0" />
<GradientStop Color="#FFE5BD77"
Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="FolderOutlineBrush"
Color="#D8A360" />
</Canvas.Resources>
<Path Fill="{StaticResource FolderTabGradient}"
Stroke="{StaticResource FolderOutlineBrush}"
StrokeThickness="2"
Data="M20 34 H54 L62 46 H108 C112 46 115 49 115 53 V60 H13 V45 C13 39 16 34 20 34 Z" />
<Path Fill="{StaticResource FolderBodyGradient}"
Stroke="{StaticResource FolderOutlineBrush}"
StrokeThickness="2"
Data="M13 60 H115 V104 C115 110 110 115 104 115 H24 C18 115 13 110 13 104 Z" />
</Canvas>
</Viewbox>
</Grid>
<TextBlock Text="{Binding Header}"
VerticalAlignment="Center"
FontSize="12"
FontWeight="DemiBold"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</StackPanel>
<TextBlock Grid.Column="1"
Text="Folder"
VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<StackPanel Grid.Column="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<TextBlock Text="{Binding Folders.Count, StringFormat={}{0} folders}"
FontSize="12"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Text=", "
FontSize="12"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Text="{Binding AssetsList.Assets.Count, StringFormat={}{0} assets}"
FontSize="12"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,11 @@
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls.ListExplorer;
public partial class FolderRow : UserControl
{
public FolderRow()
{
InitializeComponent();
}
}

View File

@ -3,16 +3,22 @@
xmlns:vwp="clr-namespace:WpfToolkit.Controls;assembly=VirtualizingWrapPanel"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:local="clr-namespace:FModel.Views.Resources.Controls.TiledExplorer"
xmlns:tiledExplorer="clr-namespace:FModel.Views.Resources.Controls.TiledExplorer"
xmlns:listExplorer="clr-namespace:FModel.Views.Resources.Controls.ListExplorer"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
xmlns:adonisConverters="clr-namespace:AdonisUI.Converters;assembly=AdonisUI"
x:Class="FModel.Views.Resources.Controls.TiledExplorer.ResourcesDictionary">
<controls:TypeDataTemplateSelector x:Key="TemplateSelector" />
<controls:TypeDataTemplateSelector x:Key="TiledExplorerTemplateSelector"
FolderTemplateKey="TiledFolderDataTemplate"
FileTemplateKey="TiledFileDataTemplate" />
<controls:TypeDataTemplateSelector x:Key="ListExplorerTemplateSelector"
FolderTemplateKey="ListFolderDataTemplate"
FileTemplateKey="ListFileDataTemplate" />
<Style x:Key="TiledExplorer" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="local:SmoothScroll.IsEnabled" Value="True" />
<Setter Property="local:SmoothScroll.Factor" Value="1.25" />
<Setter Property="tiledExplorer:SmoothScroll.IsEnabled" Value="True" />
<Setter Property="tiledExplorer:SmoothScroll.Factor" Value="1.25" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="ContextMenu" Value="{StaticResource FileContextMenu}" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
@ -66,7 +72,8 @@
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource TemplateSelector}" />
<ContentControl Content="{Binding}"
ContentTemplateSelector="{StaticResource TiledExplorerTemplateSelector}" />
</DataTemplate>
</Setter.Value>
</Setter>
@ -78,7 +85,9 @@
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="False">
<ScrollViewer Focusable="False"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}">
<ItemsPresenter />
</ScrollViewer>
</Border>
@ -107,12 +116,102 @@
</Style.Triggers>
</Style>
<GridLength x:Key="ExplorerNameColumn">3*</GridLength>
<GridLength x:Key="ExplorerTypeColumn">1*</GridLength>
<GridLength x:Key="ExplorerDetailsColumn">1.2*</GridLength>
<Style x:Key="ListExplorer"
TargetType="ListBox"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="tiledExplorer:SmoothScroll.IsEnabled"
Value="True" />
<Setter Property="tiledExplorer:SmoothScroll.Factor"
Value="0.1" />
<Setter Property="SelectionMode"
Value="Extended" />
<Setter Property="ContextMenu"
Value="{StaticResource FileContextMenu}" />
<Setter Property="VirtualizingPanel.IsVirtualizing"
Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode"
Value="Recycling" />
<Setter Property="ScrollViewer.CanContentScroll"
Value="True" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility"
Value="Visible" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Margin"
Value="0 4 0 0" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=OneWay}" />
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Padding"
Value="0" />
<Setter Property="Margin"
Value="0" />
<Setter Property="Cursor"
Value="Hand" />
<Setter Property="controls:ListBoxItemBehavior.IsBroughtIntoViewWhenSelected"
Value="True" />
<EventSetter Event="MouseDoubleClick"
Handler="OnMouseDoubleClick" />
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="OnPreviewMouseRightButtonDown" />
</Style>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding}"
ContentTemplateSelector="{StaticResource ListExplorerTemplateSelector}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ScrollViewer Focusable="False"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="TiledFileDataTemplate" DataType="{x:Type vm:GameFileViewModel}">
<local:FileButton2 />
<tiledExplorer:FileButton />
</DataTemplate>
<DataTemplate x:Key="TiledFolderDataTemplate" DataType="{x:Type vm:TreeItem}">
<local:FolderButton2 ContextMenu="{StaticResource FolderContextMenu}" />
<tiledExplorer:FolderButton ContextMenu="{StaticResource FolderContextMenu}" />
</DataTemplate>
<DataTemplate x:Key="ListFileDataTemplate"
DataType="{x:Type vm:GameFileViewModel}">
<listExplorer:FileRow />
</DataTemplate>
<DataTemplate x:Key="ListFolderDataTemplate"
DataType="{x:Type vm:TreeItem}">
<listExplorer:FolderRow ContextMenu="{StaticResource FolderContextMenu}" />
</DataTemplate>
<DataTemplate x:Key="TextureNumTemplate">
@ -140,16 +239,27 @@
</Border>
</DataTemplate>
<Style x:Key="AssetsListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding SelectedItem.AssetsList.AssetsView, ElementName=AssetsFolderName, IsAsync=True}" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="ContextMenu" Value="{StaticResource FileContextMenu}" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode" Value="NeverExpand"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement" Value="Docked"/>
<Style x:Key="AssetsListBox"
TargetType="ListBox"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource"
Value="{Binding SelectedItem.AssetsList.AssetsView, ElementName=AssetsFolderName, IsAsync=True}" />
<Setter Property="SelectionMode"
Value="Extended" />
<Setter Property="ContextMenu"
Value="{StaticResource FileContextMenu}" />
<Setter Property="VirtualizingPanel.IsVirtualizing"
Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode"
Value="Recycling" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode"
Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement"
Value="Docked" />
<Setter Property="adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode"
Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement"
Value="Docked" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
@ -159,22 +269,40 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="ListImage" Source="/FModel;component/Resources/unknown_asset.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3 0" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Asset.Name}" />
<Image x:Name="ListImage"
Source="/FModel;component/Resources/unknown_asset.png"
Width="16"
Height="16"
HorizontalAlignment="Center"
Margin="0 0 3 0" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Left"
Text="{Binding Asset.Name}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Asset.Extension}" Value="uasset">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
<DataTrigger Binding="{Binding Asset.Extension}"
Value="uasset">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset.png" />
</DataTrigger>
<DataTrigger Binding="{Binding Asset.Extension}" Value="ini">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_ini.png" />
<DataTrigger Binding="{Binding Asset.Extension}"
Value="ini">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset_ini.png" />
</DataTrigger>
<DataTrigger Binding="{Binding Asset.Extension}" Value="png">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_png.png" />
<DataTrigger Binding="{Binding Asset.Extension}"
Value="png">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset_png.png" />
</DataTrigger>
<DataTrigger Binding="{Binding Asset.Extension}" Value="psd">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_psd.png" />
<DataTrigger Binding="{Binding Asset.Extension}"
Value="psd">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset_psd.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
@ -182,23 +310,32 @@
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="5 3" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="controls:ListBoxItemBehavior.IsBroughtIntoViewWhenSelected" Value="True" />
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="Padding"
Value="5 3" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="controls:ListBoxItemBehavior.IsBroughtIntoViewWhenSelected"
Value="True" />
<EventSetter Event="PreviewMouseRightButtonDown" Handler="OnPreviewMouseRightButtonDown" />
<EventSetter Event="PreviewMouseRightButtonDown"
Handler="OnPreviewMouseRightButtonDown" />
</Style>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}" Value="0">
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}"
Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="No packages found in folder" FontWeight="SemiBold" TextAlignment="Center"
<TextBlock Text="No packages found in folder"
FontWeight="SemiBold"
TextAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</Grid>
</ControlTemplate>

View File

@ -1,4 +1,4 @@
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FileButton2"
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FileButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -2,9 +2,9 @@ using System.Windows.Controls;
namespace FModel.Views.Resources.Controls.TiledExplorer;
public partial class FileButton2 : UserControl
public partial class FileButton : UserControl
{
public FileButton2()
public FileButton()
{
InitializeComponent();
}

View File

@ -1,4 +1,4 @@
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FolderButton2"
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FolderButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

View File

@ -2,9 +2,9 @@ using System.Windows.Controls;
namespace FModel.Views.Resources.Controls.TiledExplorer;
public partial class FolderButton3 : UserControl
public partial class FolderButton : UserControl
{
public FolderButton3()
public FolderButton()
{
InitializeComponent();
}

View File

@ -0,0 +1,273 @@
<UserControl x:Class="FModel.Views.Resources.Controls.ExportOptionsControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="512"
d:DataContext="{d:DesignInstance Type=vm:ExportOptionsViewModel, IsDesignTimeCreatable=False}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory" VerticalAlignment="Center" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True"
Text="{Binding OutputDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Grid.Row="0" Grid.Column="4" Content="..." MinWidth="28" Click="OnBrowseOutputDirectory" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Export Immediately" VerticalAlignment="Center" Margin="0 8 0 0"
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}"
ToolTip="Start exporting automatically after all selected assets have been queued." />
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 8 0 0"
IsChecked="{Binding ExportImmediately, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Visibility="{Binding ShowExportImmediatelyOption, Converter={StaticResource BoolToVisibilityConverter}}"
ToolTip="Start exporting automatically after all selected assets have been queued." />
<Separator Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="MESH" Margin="0 10 0 10" />
<TextBlock Grid.Row="3" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding MeshFormats}"
SelectedItem="{Binding SelectedMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" FontSize="10" Margin="0 0 0 5">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedMeshFormat}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importer (Blender/UE): " />
<Hyperlink NavigateUri="https://github.com/h4lfheart/UEFormat">github.com/h4lfheart/UEFormat</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedMeshFormat}" Value="{x:Static c4pMeshes:EMeshFormat.ActorX}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importers (Blender): " />
<Hyperlink NavigateUri="https://github.com/DarklightGames/io_scene_psk_psa">github.com/DarklightGames/io_scene_psk_psa</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Mesh Quality" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding MeshQualities}"
SelectedItem="{Binding SelectedMeshQuality, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding SocketSettingsEnabled}" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding SocketFormats}"
SelectedItem="{Binding SelectedSocketFormat, Mode=TwoWay}"
IsEnabled="{Binding SocketSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Compression Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding CompressionSettingsEnabled}" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding CompressionFormats}"
SelectedItem="{Binding SelectedCompressionFormat, Mode=TwoWay}"
IsEnabled="{Binding CompressionSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Nanite Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding NaniteMeshFormats}"
SelectedItem="{Binding SelectedNaniteMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Border Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="5" Margin="0 10 0 5" CornerRadius="1"
Background="#1AFFA726" BorderBrush="#FFA726" BorderThickness="1" Padding="10"
Visibility="{Binding ShowNaniteWarning, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Viewbox Grid.Column="0" Height="{Binding Height, RelativeSource={RelativeSource AncestorType=Border}}"
Margin="0 0 10 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="#FFA726" Data="{StaticResource WarningIcon}" />
</Canvas>
</Viewbox>
<TextBlock Grid.Column="1" TextWrapping="Wrap" Foreground="#FFA726">
<Run FontWeight="Bold" Text="Nanite meshes are slower to export" />
<LineBreak />
<Run Text="It is not recommended for world exports, as this stacks up for every mesh in the level." />
</TextBlock>
<Viewbox Grid.Column="2" Height="{Binding Height, RelativeSource={RelativeSource AncestorType=Border}}"
Margin="10 0 0 0" VerticalAlignment="Center">
<Canvas Width="24" Height="24">
<Path Fill="#FFA726" Data="{StaticResource WarningIcon}" />
</Canvas>
</Viewbox>
</Grid>
</Border>
<Separator Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="TEXTURE" Margin="0 10 0 10" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5"
IsEnabled="{Binding TextureFormatsEnabled}" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
ItemsSource="{Binding TextureFormats}"
SelectedItem="{Binding SelectedTextureFormat, Mode=TwoWay}"
IsEnabled="{Binding TextureFormatsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Texture Platform" VerticalAlignment="Center" Margin="0 0 0 10" />
<ComboBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 10"
ItemsSource="{Binding TexturePlatforms}"
SelectedItem="{Binding SelectedTexturePlatform, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Texture Quality" VerticalAlignment="Center" Margin="0 0 0 12" />
<Slider Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" Minimum="1" Maximum="100" TickPlacement="None"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 0 0 12"
Value="{Binding TextureQuality, Mode=TwoWay}" />
<TextBlock Grid.Row="14" Grid.Column="0" Text="Save HDR as .hdr" VerticalAlignment="Center" Margin="0 0 0 12" />
<CheckBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3"
IsChecked="{Binding ExportHdrTexturesAsHdr, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Margin="0 0 0 12" />
<TextBlock Grid.Row="15"
Grid.Column="0"
Text="Export All Mips"
VerticalAlignment="Center" />
<CheckBox Grid.Row="15"
Grid.Column="2"
Grid.ColumnSpan="3"
IsChecked="{Binding ExportAllTextureMips, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<Separator Grid.Row="16" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" Tag="MATERIAL &amp; MORPH" Margin="0 10 0 10" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Material Depth" VerticalAlignment="Center" Margin="0 0 0 12" />
<ComboBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 12"
ItemsSource="{Binding MaterialDepths}"
SelectedItem="{Binding SelectedMaterialDepth, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="18" Grid.Column="0" Text="Auto-Export Attached Materials" VerticalAlignment="Center" Margin="0 0 0 15"
ToolTip="When exporting a mesh, also find and export its attached materials." />
<Grid Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding ExportMaterials, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="5 0 0 0" FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="For geometry-only exports, it can be significantly faster to disable this option." />
</Grid>
<TextBlock Grid.Row="19" Grid.Column="0" Text="Export Morph Targets" VerticalAlignment="Center" Margin="0 0 0 15" />
<CheckBox Grid.Row="19" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 15"
IsChecked="{Binding ExportMorphTargets, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
</Grid>
</UserControl>

View File

@ -0,0 +1,32 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using FModel.ViewModels;
using Ookii.Dialogs.Wpf;
namespace FModel.Views.Resources.Controls;
public partial class ExportOptionsControl
{
public ExportOptionsControl()
{
InitializeComponent();
}
private void OnBrowseOutputDirectory(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ExportOptionsViewModel viewModel })
{
var folderBrowser = new VistaFolderBrowserDialog { ShowNewFolderButton = false };
if (folderBrowser.ShowDialog() == true)
viewModel.OutputDirectory = folderBrowser.SelectedPath;
}
}
private void OnHyperlinkClick(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is Hyperlink hyperlink)
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.AbsoluteUri) { UseShellExecute = true });
}
}

View File

@ -28,6 +28,14 @@ public enum ELog
None
}
/// <summary>
/// this whole thing needs a refactor
/// TODO: source of truth for logs should be Serilog
/// a custom sink to reroute some of these logs to the UI
/// the ui should be an ObservableCollection of things
/// the things displayed in ListBox (virtualized) should use a custom template
/// see how logs work in ExportSessionViewModel
/// </summary>
public class FLogger : ITextFormatter
{
public static CustomRichTextBox Logger;
@ -107,14 +115,12 @@ public class FLogger : ITextFormatter
try
{
Logger.Document.ContentEnd.InsertTextInRun(message);
if (newLine) Logger.Document.ContentEnd.InsertLineBreak();
Logger.Selection.Select(Logger.Document.ContentStart.GetPositionAtOffset(_previous), Logger.Document.ContentEnd);
Logger.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color));
}
finally
{
Finally();
Finally(newLine);
}
}
@ -122,7 +128,7 @@ public class FLogger : ITextFormatter
{
try
{
new Hyperlink(new Run(newLine ? $"{message}{Environment.NewLine}" : message), Logger.Document.ContentEnd)
new Hyperlink(new Run(message), Logger.Document.ContentEnd)
{
NavigateUri = new Uri(url),
OverridesDefaultStyle = true,
@ -163,12 +169,21 @@ public class FLogger : ITextFormatter
}
finally
{
Finally();
Finally(newLine);
}
}
private static void Finally()
private const int MaxBlocks = 250; // in the meantime
private static void Finally(bool newLine)
{
if (newLine) Logger.Document.Blocks.Add(new Paragraph { Margin = new Thickness(0) });
while (Logger.Document.Blocks.Count > MaxBlocks)
{
Logger.Document.Blocks.Remove(Logger.Document.Blocks.FirstBlock);
}
Logger.ScrollToEnd();
_previous = Math.Abs(Logger.Document.ContentEnd.GetOffsetToPosition(Logger.Document.ContentStart)) - 2;
}

View File

@ -1,11 +0,0 @@
using System.Windows.Controls;
namespace FModel.Views.Resources.Controls.TiledExplorer;
public partial class FolderButton2 : UserControl
{
public FolderButton2()
{
InitializeComponent();
}
}

View File

@ -1,92 +0,0 @@
<UserControl x:Class="FModel.Views.Resources.Controls.TiledExplorer.FolderButton3"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="128" d:DesignHeight="192"
d:DataContext="{d:DesignInstance Type=vm:TreeItem, IsDesignTimeCreatable=False}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
Width="128" Height="192"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="2" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Height="128">
<Canvas Width="128" Height="128">
<Canvas.Resources>
<LinearGradientBrush x:Key="TabGradient" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#c9975f" Offset="0" />
<GradientStop Color="#b28c53" Offset="0.5" />
<GradientStop Color="#a67f47" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="BodyGradient" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#d4ae75" Offset="0" />
<GradientStop Color="#c9a167" Offset="0.3" />
<GradientStop Color="#b89456" Offset="1" />
</LinearGradientBrush>
</Canvas.Resources>
<Path Fill="{StaticResource TabGradient}" Data="M16.0018 36l0 -0.0701281 -0.00162402 -6.66681c-0.00509351,-0.616462 0.113829,-1.21757 0.342594,-1.77092 0.231423,-0.559917 0.572171,-1.07266 1.00696,-1.50532 0.433169,-0.431029 0.945252,-0.767794 1.50347,-0.997075 0.565823,-0.232308 1.16664,-0.355291 1.76937,-0.355291l21.1332 0c0.951601,0 1.85847,0.293947 2.61673,0.810088 0.736639,0.501452 1.3282,1.21875 1.67805,2.08502l0.00118111 -0.000442914 3.63056 8.46288 0.00346949 0.00797243 -33.6839 0z" />
<Path Fill="{StaticResource BodyGradient}" Data="M108.153 43.8407l-0.0177904 9.81969c0.785875,0.192373 1.50296,0.514888 2.10509,0.981201 1.08706,0.841681 1.75062,2.06176 1.75298,3.71021l-0.000664368 39.2644 0.00752957 0c0,1.58298 -0.652563,3.01883 -1.70758,4.06093 -1.045,1.03266 -2.48541,1.67126 -4.072,1.67126l-5.88529 0c-0.0760337,0.00967031 -0.159523,0.017126 -0.266782,0.017126l-76.1944 0c-2.15884,0 -4.12043,-0.871213 -5.54403,-2.27754 -1.43312,-1.4157 -2.32116,-3.36711 -2.32116,-5.51553l0.00752957 0 -0.0145423 -59.5726 33.6839 0 0.053814 0.125493 50.7405 0.0420768 0 -0.00745568c2.16629,0 4.07872,0.839394 5.45766,2.21856 1.38698,1.38706 2.22269,3.30945 2.22269,5.46209l-0.00752957 0z" />
<Path Fill="#b28c53" Data="M108 53.6604l-74.8763 0c-0.499091,0 -0.926356,0.196998 -1.22768,0.512895 -0.339715,0.356103 -0.542126,0.865894 -0.542126,1.43438l0 47.7579 -3.84043 0 0 -47.7579c0,-1.56880 0.600001,-3.01883 1.60733,-4.07466 1.00667,-1.05509 2.4017,-1.71304 4.00291,-1.71304l74.8763 0 0 3.84043z" />
<Ellipse Canvas.Left="80" Canvas.Top="80" Width="36" Height="36"
Opacity="0.95" Stroke="#b28c53" StrokeThickness="1.5"
Fill="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
Visibility="{Binding Visibility, ElementName=HintIcon}" />
<Path x:Name="HintIcon" Canvas.Left="88" Canvas.Top="88" Width="20" Height="20" Stretch="Uniform"
Data="{Binding Header, Converter={x:Static converters:FolderToGeometryConverter.Instance}}"
Fill="{Binding Header, Converter={x:Static converters:FolderToGeometryConverter.Instance}}">
<Path.Style>
<Style TargetType="Path">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Data, RelativeSource={RelativeSource Self}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Path.Style>
</Path>
</Canvas>
</Viewbox>
<Grid Grid.Row="2" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="2" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Header, FallbackValue=Folder Name}"
FontSize="13" TextWrapping="Wrap" TextTrimming="CharacterEllipsis" FontWeight="DemiBold"
TextAlignment="Center" HorizontalAlignment="Stretch" VerticalAlignment="Top"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition>
<ColumnDefinition.Width>
<MultiBinding Converter="{x:Static converters:RatioToGridLengthConverter.Instance}">
<Binding Path="Folders.Count" />
<Binding Path="AssetsList.Assets.Count" />
</MultiBinding>
</ColumnDefinition.Width>
</ColumnDefinition>
<ColumnDefinition Width="1" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="#E8C480" />
<Border Grid.Column="2" Background="#A8D8EA" />
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@ -4,15 +4,25 @@ using FModel.ViewModels;
namespace FModel.Views.Resources.Controls;
public class TypeDataTemplateSelector : DataTemplateSelector
public sealed class TypeDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
public object FolderTemplateKey { get; set; }
public object FileTemplateKey { get; set; }
public override DataTemplate? SelectTemplate(object? item, DependencyObject container)
{
return item switch
if (container is not FrameworkElement element)
return base.SelectTemplate(item, container);
var key = item switch
{
TreeItem when container is FrameworkElement f => f.FindResource("TiledFolderDataTemplate") as DataTemplate,
GameFileViewModel when container is FrameworkElement f => f.FindResource("TiledFileDataTemplate") as DataTemplate,
_ => base.SelectTemplate(item, container)
TreeItem => FolderTemplateKey,
GameFileViewModel => FileTemplateKey,
_ => null
};
return key is not null
? element.TryFindResource(key) as DataTemplate
: base.SelectTemplate(item, container);
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public sealed class EnumToBoolConverter : IValueConverter
{
public static readonly EnumToBoolConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is null || parameter is null)
return false;
var enumType = value.GetType();
if (!enumType.IsEnum)
return false;
var target = parameter is string text
? Enum.Parse(enumType, text, ignoreCase: true)
: parameter;
return value.Equals(target);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is true && parameter is not null
? parameter
: Binding.DoNothing;
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class IntGreaterThanZeroToVisibilityConverter : IValueConverter
{
public static readonly IntGreaterThanZeroToVisibilityConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is int n && n > 0 ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}

View File

@ -0,0 +1,16 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public sealed class ProjectNameToVisibilityConverter : IValueConverter
{
public static readonly ProjectNameToVisibilityConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
string.Equals(value as string, parameter as string, StringComparison.OrdinalIgnoreCase) ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing;
}

View File

@ -1,32 +0,0 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class RatioToGridLengthConverter : IMultiValueConverter
{
public static readonly RatioToGridLengthConverter Instance = new();
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
return new GridLength(1, GridUnitType.Star);
var count1 = values[0] is int c1 ? c1 : 0;
var count2 = values[1] is int c2 ? c2 : 0;
var total = count1 + count2;
if (total == 0) return new GridLength(1, GridUnitType.Star);
var ratio = (double)count1 / total;
return new GridLength(ratio, GridUnitType.Star);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class TimeSpanConverter : IValueConverter
{
public static readonly TimeSpanConverter Instance = new();
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not TimeSpan ts) return "--:--";
return parameter is "elapsed"
? $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}.{ts.Milliseconds / 10:D2}"
: $"{(int)ts.TotalMinutes:D2}:{ts.Seconds:D2}";
}
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
throw new NotImplementedException();
}

View File

@ -11,7 +11,8 @@
<Geometry x:Key="ExpanderIcon">M16,17.01V11c0-0.55-0.45-1-1-1s-1,0.45-1,1v6.01h-1.79c-0.45,0-0.67,0.54-0.35,0.85l2.79,2.78c0.2,0.19,0.51,0.19,0.71,0 l2.79-2.78c0.32-0.31,0.09-0.85-0.35-0.85H16z M8.65,3.35L5.86,6.14c-0.32,0.31-0.1,0.85,0.35,0.85H8V13c0,0.55,0.45,1,1,1 s1-0.45,1-1V6.99h1.79c0.45,0,0.67-0.54,0.35-0.85L9.35,3.35C9.16,3.16,8.84,3.16,8.65,3.35z</Geometry>
<Geometry x:Key="FolderIcon">M20,6h-8l-1.41-1.41C10.21,4.21,9.7,4,9.17,4H4C2.9,4,2,4.9,2,6v12c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2V8 C22,6.9,21.1,6,20,6z M15.98,15.74l-1.07-0.82l-1.07,0.82c-0.39,0.29-0.92-0.08-0.78-0.55l0.42-1.36l-1.2-0.95 C11.91,12.6,12.12,12,12.59,12H14l0.43-1.34c0.15-0.46,0.8-0.46,0.95,0L15.82,12h1.41c0.47,0,0.68,0.6,0.31,0.89l-1.2,0.95 l0.42,1.36C16.91,15.66,16.37,16.04,15.98,15.74z</Geometry>
<Geometry x:Key="ExportIcon">M19.41,7.41l-4.83-4.83C14.21,2.21,13.7,2,13.17,2H6C4.9,2,4.01,2.9,4.01,4L4,20c0,1.1,0.89,2,1.99,2H18c1.1,0,2-0.9,2-2 V8.83C20,8.3,19.79,7.79,19.41,7.41z M14.8,15H13v3c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H9.21c-0.45,0-0.67-0.54-0.35-0.85l2.8-2.79 c0.2-0.19,0.51-0.19,0.71,0l2.79,2.79C15.46,14.46,15.24,15,14.8,15z M14,9c-0.55,0-1-0.45-1-1V3.5L18.5,9H14z</Geometry>
<Geometry x:Key="SaveIcon">M16.17,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V7.83c0-0.53-0.21-1.04-0.59-1.41l-2.83-2.83 C17.21,3.21,16.7,3,16.17,3z M12,18c-1.66,0-3-1.34-3-3s1.34-3,3-3s3,1.34,3,3S13.66,18,12,18z M14,10H7c-0.55,0-1-0.45-1-1V7 c0-0.55,0.45-1,1-1h7c0.55,0,1,0.45,1,1v2C15,9.55,14.55,10,14,10z</Geometry>
<Geometry x:Key="MagnifierIcon">M14 3.072a8 8 0 0 1 2.617 11.424l4.944 4.943a1.5 1.5 0 0 1 -2.008 2.225l-.114 -.103l-4.943 -4.944a8 8 0 0 1 -12.49 -6.332l-.006 -.285l.005 -.285a8 8 0 0 1 11.995 -6.643z</Geometry>
<Geometry x:Key="SaveIcon">M16 3a1 1 0 0 1 .707 .293l4 4a1 1 0 0 1 .293 .707v10a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h1v4a1 1 0 0 0 .883 .993l.117 .007h6a1 1 0 0 0 1 -1v-4zm-4 8a2.995 2.995 0 0 0 -2.995 2.898a1 1 0 0 0 -.005 .102a3 3 0 1 0 3 -3m1 -8v3h-4v-3z</Geometry>
<Geometry x:Key="TextureIcon">M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M6.6,16.2l2-2.67 c0.2-0.27,0.6-0.27,0.8,0L11.25,16l2.6-3.47c0.2-0.27,0.6-0.27,0.8,0l2.75,3.67c0.25,0.33,0.01,0.8-0.4,0.8H7 C6.59,17,6.35,16.53,6.6,16.2z</Geometry>
<Geometry x:Key="ModelIcon">M12 6q-.825 0-1.412-.588Q10 4.825 10 4t.588-1.413Q11.175 2 12 2t1.413.587Q14 3.175 14 4q0 .825-.587 1.412Q12.825 6 12 6ZM9 22V9H3V7h18v2h-6v13h-2v-6h-2v6Z</Geometry>
<Geometry x:Key="AnimationIcon">M9 22q-1.45 0-2.725-.55Q5 20.9 4.05 19.95q-.95-.95-1.5-2.225Q2 16.45 2 15q0-2.025 1.05-3.7Q4.1 9.625 5.8 8.75q.5-.975 1.238-1.713Q7.775 6.3 8.75 5.8q.825-1.7 2.525-2.75T15 2q1.45 0 2.725.55Q19 3.1 19.95 4.05q.95.95 1.5 2.225Q22 7.55 22 9q0 2.125-1.05 3.75t-2.75 2.5q-.5.975-1.238 1.712-.737.738-1.712 1.238-.875 1.7-2.55 2.75Q11.025 22 9 22Zm0-2q.825 0 1.588-.25Q11.35 19.5 12 19q-1.45 0-2.725-.55Q8 17.9 7.05 16.95q-.95-.95-1.5-2.225Q5 13.45 5 12q-.5.65-.75 1.412Q4 14.175 4 15q0 1.05.4 1.95.4.9 1.075 1.575.675.675 1.575 1.075.9.4 1.95.4Zm3-3q.825 0 1.613-.25.787-.25 1.437-.75-1.475 0-2.75-.562-1.275-.563-2.225-1.513-.95-.95-1.513-2.225Q8 10.425 8 8.95q-.5.65-.75 1.437Q7 11.175 7 12q0 1.05.388 1.95.387.9 1.087 1.575.675.7 1.575 1.088.9.387 1.95.387Zm3-3q.45 0 .863-.075.412-.075.837-.225.55-1.5.163-2.888-.388-1.387-1.338-2.337-.95-.95-2.337-1.338Q11.8 6.75 10.3 7.3q-.15.425-.225.837Q10 8.55 10 9q0 1.05.387 1.95.388.9 1.088 1.575.675.7 1.575 1.088.9.387 1.95.387Zm4-1.95q.5-.65.75-1.438Q20 9.825 20 9q0-1.05-.387-1.95-.388-.9-1.088-1.575-.675-.7-1.575-1.088Q16.05 4 15 4q-.875 0-1.637.25-.763.25-1.413.75 1.475 0 2.75.562 1.275.563 2.225 1.513.95.95 1.513 2.225.562 1.275.562 2.75Z</Geometry>
@ -21,7 +22,7 @@
<Geometry x:Key="ExtractIcon">M20.29,10.29l-3.59-3.59C16.08,6.08,15,6.52,15,7.41V10H8c-2.76,0-5,2.24-5,5v3c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1v-3 c0-1.65,1.35-3,3-3h7v2.59c0,0.89,1.08,1.34,1.71,0.71l3.59-3.59C20.68,11.32,20.68,10.68,20.29,10.29z</Geometry>
<Geometry x:Key="GoToIcon">M9.5,5.5c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S8.4,5.5,9.5,5.5z M5.75,8.9L3.23,21.81C3.11,22.43,3.58,23,4.21,23H4.3 c0.47,0,0.88-0.33,0.98-0.79L6.85,15L9,17v5c0,0.55,0.45,1,1,1h0c0.55,0,1-0.45,1-1v-6.14c0-0.27-0.11-0.52-0.29-0.71L8.95,13.4 l0.6-3c1.07,1.32,2.58,2.23,4.31,2.51c0.6,0.1,1.14-0.39,1.14-1v0c0-0.49-0.36-0.9-0.84-0.98c-1.49-0.25-2.75-1.15-3.51-2.38 L9.7,6.95C9.35,6.35,8.7,6,8,6C7.75,6,7.5,6.05,7.25,6.15l-4.63,1.9C2.25,8.2,2,8.57,2,8.97V12c0,0.55,0.45,1,1,1h0 c0.55,0,1-0.45,1-1V9.65L5.75,8.9 M21,2h-7c-0.55,0-1,0.45-1,1v5c0,0.55,0.45,1,1,1h2.75v13.25c0,0.41,0.34,0.75,0.75,0.75 s0.75-0.34,0.75-0.75V9H21c0.55,0,1-0.45,1-1V3C22,2.45,21.55,2,21,2z M20.15,5.85l-1.28,1.29c-0.31,0.32-0.85,0.09-0.85-0.35V6.25 h-2.76c-0.41,0-0.75-0.34-0.75-0.75s0.34-0.75,0.75-0.75h2.76V4.21c0-0.45,0.54-0.67,0.85-0.35l1.28,1.29 C20.34,5.34,20.34,5.66,20.15,5.85z</Geometry>
<Geometry x:Key="DiscordIcon">M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z</Geometry>
<Geometry x:Key="BugIcon">M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z</Geometry>
<Geometry x:Key="BugIcon">M12 4a4 4 0 0 1 3.995 3.8l.005 .2a1 1 0 0 1 .428 .096l3.033 -1.938a1 1 0 1 1 1.078 1.684l-3.015 1.931a7.17 7.17 0 0 1 .476 2.227h3a1 1 0 0 1 0 2h-3v1a6.01 6.01 0 0 1 -.195 1.525l2.708 1.616a1 1 0 1 1 -1.026 1.718l-2.514 -1.501a6.002 6.002 0 0 1 -3.973 2.56v-5.918a1 1 0 0 0 -2 0v5.917a6.002 6.002 0 0 1 -3.973 -2.56l-2.514 1.503a1 1 0 1 1 -1.026 -1.718l2.708 -1.616a6.01 6.01 0 0 1 -.195 -1.526v-1h-3a1 1 0 0 1 0 -2h3.001v-.055a7 7 0 0 1 .474 -2.173l-3.014 -1.93a1 1 0 1 1 1.078 -1.684l3.032 1.939l.024 -.012l.068 -.027l.019 -.005l.016 -.006l.032 -.008l.04 -.013l.034 -.007l.034 -.004l.045 -.008l.015 -.001l.015 -.002l.087 -.004a4 4 0 0 1 4 -4zm0 2a2 2 0 0 0 -2 2h4a2 2 0 0 0 -2 -2z</Geometry>
<Geometry x:Key="GiftIcon">M22 10.92L19.26 9.33C21.9 7.08 19.25 2.88 16.08 4.31L15.21 4.68L15.1 3.72C15 2.64 14.44 1.87 13.7 1.42C12.06 .467 9.56 1.12 9.16 3.5L6.41 1.92C5.45 1.36 4.23 1.69 3.68 2.65L2.68 4.38C2.4 4.86 2.57 5.47 3.05 5.75L10.84 10.25L12.34 7.65L14.07 8.65L12.57 11.25L20.36 15.75C20.84 16 21.46 15.86 21.73 15.38L22.73 13.65C23.28 12.69 22.96 11.47 22 10.92M12.37 5C11.5 5.25 10.8 4.32 11.24 3.55C11.5 3.07 12.13 2.91 12.61 3.18C13.38 3.63 13.23 4.79 12.37 5M17.56 8C16.7 8.25 16 7.32 16.44 6.55C16.71 6.07 17.33 5.91 17.8 6.18C18.57 6.63 18.42 7.79 17.56 8M20.87 16.88C21.28 16.88 21.67 16.74 22 16.5V20C22 21.11 21.11 22 20 22H4C2.9 22 2 21.11 2 20V11H10.15L11 11.5V20H13V12.65L19.87 16.61C20.17 16.79 20.5 16.88 20.87 16.88Z</Geometry>
<Geometry x:Key="NoteIcon">M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM8 19h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zm0-6h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1s.45 1 1 1zM7 6c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H8c-.55 0-1 .45-1 1z</Geometry>
<Geometry x:Key="InfoIcon">M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z</Geometry>
@ -39,6 +40,7 @@
<Geometry x:Key="WholeWordIcon">M18 10v3H6v-3c0-.55-.45-1-1-1s-1 .45-1 1v4c0 .55.45 1 1 1h14c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1z</Geometry>
<Geometry x:Key="CloseIcon">M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm4.3 14.3c-.39.39-1.02.39-1.41 0L12 13.41 9.11 16.3c-.39.39-1.02.39-1.41 0-.39-.39-.39-1.02 0-1.41L10.59 12 7.7 9.11c-.39-.39-.39-1.02 0-1.41.39-.39 1.02-.39 1.41 0L12 10.59l2.89-2.89c.39-.39 1.02-.39 1.41 0 .39.39.39 1.02 0 1.41L13.41 12l2.89 2.89c.38.38.38 1.02 0 1.41z</Geometry>
<Geometry x:Key="PlayIcon">M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z</Geometry>
<Geometry x:Key="ResetIcon">M12 5V2L8 6l4 4V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z</Geometry>
<Geometry x:Key="PauseIcon">M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2zm6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2z</Geometry>
<Geometry x:Key="StopIcon">M8 6h8c1.1 0 2 .9 2 2v8c0 1.1-.9 2-2 2H8c-1.1 0-2-.9-2-2V8c0-1.1.9-2 2-2z</Geometry>
<Geometry x:Key="SkipNextIcon">M7.58 16.89l5.77-4.07c.56-.4.56-1.24 0-1.63L7.58 7.11C6.91 6.65 6 7.12 6 7.93v8.14c0 .81.91 1.28 1.58.82zM16 7v10c0 .55.45 1 1 1s1-.45 1-1V7c0-.55-.45-1-1-1s-1 .45-1 1z</Geometry>
@ -50,6 +52,7 @@
<Geometry x:Key="AnchorIcon">M13,9V7.82C14.16,7.4,15,6.3,15,5c0-1.65-1.35-3-3-3S9,3.35,9,5c0,1.3,0.84,2.4,2,2.82V9H9c-0.55,0-1,0.45-1,1v0 c0,0.55,0.45,1,1,1h2v8.92c-2.22-0.33-4.59-1.68-5.55-3.37l1.14-1.14c0.22-0.22,0.19-0.57-0.05-0.75L3.8,12.6 C3.47,12.35,3,12.59,3,13v2c0,3.88,4.92,7,9,7s9-3.12,9-7v-2c0-0.41-0.47-0.65-0.8-0.4l-2.74,2.05c-0.24,0.18-0.27,0.54-0.05,0.75 l1.14,1.14c-0.96,1.69-3.33,3.04-5.55,3.37V11h2c0.55,0,1-0.45,1-1v0c0-0.55-0.45-1-1-1H13z M12,4c0.55,0,1,0.45,1,1s-0.45,1-1,1 s-1-0.45-1-1S11.45,4,12,4z</Geometry>
<Geometry x:Key="FoldIcon">M8.12 19.3c.39.39 1.02.39 1.41 0L12 16.83l2.47 2.47c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41l-3.17-3.17c-.39-.39-1.02-.39-1.41 0l-3.17 3.17c-.4.38-.4 1.02-.01 1.41zm7.76-14.6c-.39-.39-1.02-.39-1.41 0L12 7.17 9.53 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.03 0 1.42l3.17 3.17c.39.39 1.02.39 1.41 0l3.17-3.17c.4-.39.4-1.03.01-1.42z</Geometry>
<Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry>
<Geometry x:Key="WarningIcon">M12 1.67c.955 0 1.845 .467 2.39 1.247l.105 .16l8.114 13.548a2.914 2.914 0 0 1 -2.307 4.363l-.195 .008h-16.225a2.914 2.914 0 0 1 -2.582 -4.2l.099 -.185l8.11 -13.538a2.914 2.914 0 0 1 2.491 -1.403zm.01 13.33l-.127 .007a1 1 0 0 0 0 1.986l.117 .007l.127 -.007a1 1 0 0 0 0 -1.986l-.117 -.007zm-.01 -7a1 1 0 0 0 -.993 .883l-.007 .117v4l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-4l-.007 -.117a1 1 0 0 0 -.993 -.883z</Geometry>
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
<Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry>
<Geometry x:Key="ArchiveIcon">M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z</Geometry>
@ -102,7 +105,14 @@
<Geometry x:Key="SQLIcon">M231.822 132.778c1.2-5.152 6.352-8.357 11.505-7.158 5.153 1.2 8.357 6.352 7.158 11.505l-20.773 88.972c-1.199 5.153-6.352 8.358-11.504 7.158-5.153-1.199-8.358-6.352-7.158-11.505l20.772-88.972zM21.123 259.247h16.714V52.395C37.837 23.601 61.438 0 90.232 0h230.505a7.998 7.998 0 016.39 3.183l96.876 104.809a7.937 7.937 0 012.118 5.411h.041v145.844h16.714c11.618 0 21.124 9.525 21.124 21.124v163.863c0 11.599-9.526 21.124-21.124 21.124h-17.058c-3.001 25.877-25.473 46.505-52.051 46.505H90.232c-26.72 0-49.082-20.526-52.055-46.505H21.123C9.526 465.358 0 455.853 0 444.234V280.371c0-11.619 9.506-21.124 21.123-21.124zm32.731 0h356.292V136.525h-32.219v-.033h-.124c-19.111-.302-34.068-5.373-44.736-14.486-11.073-9.458-17.282-22.969-18.52-39.763l-.075-1.098V16.016H90.232c-20.032 0-36.378 16.346-36.378 36.379v206.852zm355.795 206.111H54.344c2.862 17.157 17.981 30.488 35.888 30.488h283.535c17.803 0 32.993-13.394 35.882-30.488zM330.467 30.272V81.11c.913 12.412 5.265 22.192 12.97 28.775 7.822 6.682 19.34 10.418 34.49 10.676v-.03h32.219v-4.052l-79.679-86.207zM199.303 204.095c3.972 3.477 4.377 9.521.9 13.493-3.478 3.973-9.521 4.377-13.494.9l-36.354-31.854c-3.972-3.477-4.377-9.521-.9-13.493.295-.336.606-.644.933-.927l36.321-31.826c3.973-3.478 10.016-3.073 13.494.899 3.477 3.973 3.072 10.016-.9 13.494l-28.14 24.656 28.14 24.658zm77.99 14.393c-3.972 3.477-10.016 3.073-13.493-.9-3.478-3.972-3.073-10.016.899-13.493l28.14-24.658-28.14-24.656c-3.972-3.478-4.377-9.521-.899-13.494 3.477-3.972 9.521-4.377 13.493-.899l36.318 31.826c.328.283.639.591.933.927 3.477 3.972 3.073 10.016-.899 13.493l-36.352 31.854zM120.405 400.89l3.805-19.455c8.333 2.083 15.827 3.123 22.484 3.123 6.66 0 12.024-.27 16.101-.815v-9.755l-12.228-1.086c-11.051-.999-18.635-3.648-22.759-7.951-4.121-4.302-6.181-10.665-6.181-19.089 0-11.592 2.514-19.565 7.54-23.911 5.029-4.35 13.565-6.523 25.612-6.523 12.047 0 22.915 1.134 32.61 3.398l-3.398 18.978c-8.423-1.358-15.171-2.038-20.244-2.038s-9.375.226-12.908.68v9.416l9.783.952c11.865 1.177 20.063 4.008 24.591 8.492 4.531 4.484 6.795 10.71 6.795 18.682 0 5.706-.77 10.528-2.311 14.47-1.541 3.94-3.373 6.927-5.502 8.966-2.129 2.038-5.141 3.601-9.037 4.687-3.892 1.087-7.312 1.745-10.255 1.97-2.944.228-6.864.341-11.755.341-11.775 0-22.688-1.177-32.743-3.532zm128.036-1.631l-11.548 3.125c-15.219 0-25.414-3.926-30.707-11.684-2.674-3.923-4.621-8.266-5.708-13.111-1.087-4.844-1.628-10.712-1.628-17.595 0-15.491 2.899-26.789 8.695-33.9 5.797-7.109 16.212-10.666 31.249-10.666 15.036 0 25.498 3.579 31.386 10.734 5.887 7.156 8.83 18.432 8.83 33.832 0 11.502-2.399 20.786-7.199 27.851l9.782 5.708-7.067 15.623-26.085-4.754v-5.163zm-20.382-18.072h11.279c3.714 0 6.407-.429 8.083-1.29 1.675-.861 2.514-2.83 2.514-5.909v-35.325h-11.414c-3.62 0-6.272.429-7.947 1.29-1.675.861-2.515 2.834-2.515 5.909v35.325zm115.536 21.197h-54.349v-84.917h27.175v63.179h27.174v21.738z</Geometry>
<Geometry x:Key="PythonIcon">M19.14,7.5A2.86,2.86 0 0,1 22,10.36V14.14A2.86,2.86 0 0,1 19.14,17H12C12,17.39 12.32,17.96 12.71,17.96H17V19.64A2.86,2.86 0 0,1 14.14,22.5H9.86A2.86,2.86 0 0,1 7,19.64V15.89C7,14.31 8.28,13.04 9.86,13.04H15.11C16.69,13.04 17.96,11.76 17.96,10.18V7.5H19.14M14.86,19.29C14.46,19.29 14.14,19.59 14.14,20.18C14.14,20.77 14.46,20.89 14.86,20.89A0.71,0.71 0 0,0 15.57,20.18C15.57,19.59 15.25,19.29 14.86,19.29M4.86,17.5C3.28,17.5 2,16.22 2,14.64V10.86C2,9.28 3.28,8 4.86,8H12C12,7.61 11.68,7.04 11.29,7.04H7V5.36C7,3.78 8.28,2.5 9.86,2.5H14.14C15.72,2.5 17,3.78 17,5.36V9.11C17,10.69 15.72,11.96 14.14,11.96H8.89C7.31,11.96 6.04,13.24 6.04,14.82V17.5H4.86M9.14,5.71C9.54,5.71 9.86,5.41 9.86,4.82C9.86,4.23 9.54,4.11 9.14,4.11C8.75,4.11 8.43,4.23 8.43,4.82C8.43,5.41 8.75,5.71 9.14,5.71Z</Geometry>
<Geometry x:Key="CSharpIcon">M7878 15350 c-162 -15 -335 -63 -471 -131 -146 -74 -5492 -3082 -5567 -3133 -41 -28 -120 -96 -175 -151 -189 -189 -307 -411 -362 -682 -17 -84 -18 -245 -18 -3253 0 -3008 1 -3169 18 -3253 50 -249 159 -466 323 -645 64 -71 188 -173 269 -224 102 -64 5469 -3075 5535 -3106 367 -170 814 -166 1165 10 75 38 5370 3012 5482 3079 326 196 543 505 620 886 17 84 18 245 18 3253 0 3008 -1 3169 -18 3253 -55 271 -173 493 -362 682 -55 55 -134 123 -175 151 -75 51 -5421 3059 -5568 3133 -203 102 -479 152 -714 131z m591 -3534 c526 -70 1018 -235 1456 -489 298 -172 642 -446 866 -689 117 -126 259 -298 259 -312 -1 -6 -301 -184 -667 -396 -366 -212 -680 -393 -697 -403 l-31 -18 -85 82 c-583 568 -1404 778 -2184 559 -501 -141 -952 -471 -1245 -910 -249 -373 -374 -786 -374 -1240 0 -372 82 -709 250 -1030 337 -644 947 -1075 1678 -1185 133 -21 439 -23 570 -6 499 66 958 288 1304 631 l84 82 32 -18 c892 -514 1364 -791 1365 -800 1 -32 -300 -365 -462 -511 -583 -528 -1275 -856 -2043 -967 -200 -29 -382 -39 -638 -33 -277 6 -411 20 -672 72 -1512 301 -2719 1521 -3009 3040 -52 276 -61 378 -61 725 0 355 9 453 66 747 255 1307 1201 2410 2461 2867 312 113 669 190 1018 220 118 10 641 -2 759 -18z m3371 -2536 l0 -320 320 0 320 0 0 320 0 320 320 0 320 0 0 -320 0 -320 320 0 320 0 0 -320 0 -320 -320 0 -320 0 0 -320 0 -320 320 0 320 0 0 -320 0 -320 -320 0 -320 0 0 -320 0 -320 -320 0 -320 0 0 320 0 320 -320 0 -320 0 0 -320 0 -320 -320 0 -320 0 0 320 0 320 -320 0 -320 0 0 320 0 320 320 0 320 0 0 320 0 320 -320 0 -320 0 0 320 0 320 320 0 320 0 0 320 0 320 320 0 320 0 0 -320z M11840 8000 l0 -320 320 0 320 0 0 320 0 320 -320 0 -320 0 0 -320z</Geometry>
<Geometry x:Key="World1">M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0</Geometry>
<Geometry x:Key="World2">M3.6 9h16.8</Geometry>
<Geometry x:Key="World3">M3.6 15h16.8</Geometry>
<Geometry x:Key="World4">M11.5 3a17 17 0 0 0 0 18</Geometry>
<Geometry x:Key="World5">M12.5 3a17 17 0 0 1 0 18</Geometry>
<Geometry x:Key="ThemeIcon">M17.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,9A1.5,1.5 0 0,1 19,10.5A1.5,1.5 0 0,1 17.5,12M14.5,8A1.5,1.5 0 0,1 13,6.5A1.5,1.5 0 0,1 14.5,5A1.5,1.5 0 0,1 16,6.5A1.5,1.5 0 0,1 14.5,8M9.5,8A1.5,1.5 0 0,1 8,6.5A1.5,1.5 0 0,1 9.5,5A1.5,1.5 0 0,1 11,6.5A1.5,1.5 0 0,1 9.5,8M6.5,12A1.5,1.5 0 0,1 5,10.5A1.5,1.5 0 0,1 6.5,9A1.5,1.5 0 0,1 8,10.5A1.5,1.5 0 0,1 6.5,12M12,3A9,9 0 0,0 3,12A9,9 0 0,0 12,21A1.5,1.5 0 0,0 13.5,19.5C13.5,19.11 13.35,18.76 13.11,18.5C12.88,18.23 12.73,17.88 12.73,17.5A1.5,1.5 0 0,1 14.23,16H16A5,5 0 0,0 21,11C21,6.58 16.97,3 12,3Z</Geometry>
<Geometry x:Key="GridIcon">M3 11H11V3H3M3 21H11V13H3M13 21H21V13H13M13 3V11H21V3</Geometry>
<Geometry x:Key="ListIcon">M9 5V9H21V5M9 19H21V15H9M9 14H21V10H9M4 9H8V5H4M4 19H8V15H4M4 14H8V10H4V14Z</Geometry>
<!-- For specific games-->
<Geometry x:Key="BorderlandsIcon">M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6 ZM12,11A3,3 0 1,0 12,17A3,3 0 0,0 12,11 ZM12,12.5L14,16H13L12,14.5L11,16H10L12,12.5Z</Geometry>

View File

@ -98,33 +98,20 @@
</Setter>
</Style>
<Style x:Key="DirectoryFilesListBox"
TargetType="ListBox"
BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource"
Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode"
Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement"
Value="Docked" />
<Style x:Key="DirectoryFilesListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding CUE4Parse.GameDirectory.DirectoryFilesView, IsAsync=True}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="IsEnabled"
Value="{Binding IsEnabled}" />
<Setter Property="Margin"
Value="0 1" />
<Setter Property="Padding"
Value="7 5" />
<Setter Property="BorderBrush"
Value="Transparent" />
<Setter Property="BorderThickness"
Value="1" />
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsEnabled" Value="{Binding IsEnabled}" />
</Style>
</Setter.Value>
</Setter>
@ -135,97 +122,64 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="95" />
<ColumnDefinition Width="85" />
<ColumnDefinition Width="110" />
<ColumnDefinition Width="75" />
</Grid.ColumnDefinitions>
<Image x:Name="ListImage"
Source="/FModel;component/Resources/archive.png"
Width="16"
Height="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0 0 4 0"
<Image x:Name="ListImage" Source="/FModel;component/Resources/archive.png"
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 4 0"
RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Name}" TextTrimming="CharacterEllipsis" />
<TextBlock Grid.Column="2"
Margin="12 0 8 0"
VerticalAlignment="Center"
Margin="10 0 0 0"
HorizontalAlignment="Right"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
Opacity="0.75"
VerticalAlignment="Bottom"
FontSize="11"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="{Binding FileCount, StringFormat={}{0:N0} files}" />
<TextBlock x:Name="LengthText"
Grid.Column="3"
Margin="8 0 0 0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
Grid.Column="3" HorizontalAlignment="Right" Text="{Binding Length, Converter={x:Static converters:SizeToStringConverter.Instance}}"
Margin="10 0 0 0" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEnabled}"
Value="True">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/archive_enabled.png" />
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_enabled.png" />
</DataTrigger>
<DataTrigger Binding="{Binding IsEnabled}"
Value="False">
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/archive_disabled.png" />
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/archive_disabled.png" />
</DataTrigger>
<DataTrigger Binding="{Binding IsLooseFilesContainer}"
Value="True">
<Setter TargetName="LengthText"
Property="Visibility"
Value="Collapsed" />
<Setter TargetName="ListImage"
Property="Source"
Value="/FModel;component/Resources/asset.png" />
<DataTrigger Binding="{Binding IsLooseFilesContainer}" Value="True">
<Setter TargetName="LengthText" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}"
Value="0">
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="No archives found in the specified directory"
FontWeight="SemiBold"
TextAlignment="Center"
<TextBlock Text="No archives found in the specified directory" FontWeight="SemiBold" TextAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.Multiple}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.All}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.All}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.AllButNew}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButNew}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}"
Value="{x:Static local:ELoadingMode.AllButModified}">
<Setter Property="SelectionMode"
Value="Extended" />
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButModified}">
<Setter Property="SelectionMode" Value="Extended" />
</DataTrigger>
</Style.Triggers>
</Style>
@ -973,6 +927,26 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Show Metadata" Command="{Binding TabCommand}" CommandParameter="Assets_Show_Metadata">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemIsUePackageCondition />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<MenuItem Header="Find References" Command="{Binding TabCommand}" CommandParameter="Find_References">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -994,63 +968,118 @@
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<MenuItem Header="Decompile Blueprint" Command="{Binding TabCommand}" CommandParameter="Assets_Decompile">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CppIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Style.Triggers>
<DataTrigger Binding="{Binding ShowDecompileOption, Source={x:Static settings:UserSettings.Default}}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
<MenuItem.IsEnabled>
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}">
<Binding.Converter>
<converters:AnyItemMeetsConditionConverter>
<converters:AnyItemMeetsConditionConverter.Conditions>
<converters:ItemActionCondition Action="Code" />
</converters:AnyItemMeetsConditionConverter.Conditions>
</converters:AnyItemMeetsConditionConverter>
</Binding.Converter>
</Binding>
</MenuItem.IsEnabled>
</MenuItem>
<Separator />
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
<MenuItem.Header>
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" />
</MenuItem.Header>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
<MenuItem.Header>
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Raw Data', StringFormat='Raw Data (.{0})'}" />
</MenuItem.Header>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding TabCommand}" CommandParameter="Save_Worlds">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Open Properties" Command="{Binding TabCommand}" CommandParameter="Open_Properties">
@ -1073,7 +1102,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Copy Package Path" Command="{Binding TabCommand}" CommandParameter="Copy_Asset_Path">
<MenuItem Header="Copy">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -1081,6 +1110,11 @@
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Header="Package Path" Command="{Binding TabCommand}" CommandParameter="File_Path" />
<MenuItem Header="Package Name" Command="{Binding TabCommand}" CommandParameter="File_Name" />
<MenuItem Header="Directory Path" Command="{Binding TabCommand}" CommandParameter="Directory_Path" />
<MenuItem Header="Package Path w/o Extension" Command="{Binding TabCommand}" CommandParameter="File_Path_No_Extension" />
<MenuItem Header="Package Name w/o Extension" Command="{Binding TabCommand}" CommandParameter="File_Name_No_Extension" />
</MenuItem>
</ContextMenu>
</Setter.Value>
@ -1419,7 +1453,7 @@
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource CustomExpanderDownHeaderStyle}"/>
Style="{DynamicResource CustomExpanderDownHeaderStyle}"/>
<Border x:Name="ExpandSiteContainerWrapper"
DockPanel.Dock="Bottom"
@ -1881,6 +1915,20 @@
</Style.Triggers>
</Style>
<Style x:Key="StatusBarButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=StatusBar}}"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="adonisExtensions:CursorSpotlightExtension.BackgroundBrush" Value="#33FFFFFF"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="PlayPauseToolbarButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content">
<Setter.Value>
@ -2214,4 +2262,27 @@
</Setter>
</Style>
<!-- Just to keep border color during toggle -->
<Style x:Key="ExplorerViewToggleButton"
TargetType="{x:Type ToggleButton}"
BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Width"
Value="32" />
<Setter Property="Height"
Value="28" />
<Setter Property="Padding"
Value="7" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="BorderBrush"
Value="{DynamicResource {x:Static adonisUi:Brushes.Layer0BorderBrush}}" />
<Style.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="BorderBrush"
Value="{DynamicResource {x:Static adonisUi:Brushes.Layer1InteractionBorderBrush}}" />
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>

View File

@ -161,6 +161,21 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show Metadata" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Show_Metadata" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Find References" Click="OnFindRefs">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -209,99 +224,131 @@
</MenuItem.IsEnabled>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">
@ -529,6 +576,21 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Show Metadata" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Show_Metadata" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource InfoIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Find References" Click="OnFindRefs">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@ -554,99 +616,131 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem Header="Export">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
<Viewbox Width="16"
Height="16">
<Canvas Width="24"
Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M12 2l.117 .007a1 1 0 0 1 .876 .876l.007 .117v4l.005 .15a2 2 0 0 0 1.838 1.844l.157 .006h4l.117 .007a1 1 0 0 1 .876 .876l.007 .117v9a3 3 0 0 1 -2.824 2.995l-.176 .005h-10a3 3 0 0 1 -2.995 -2.824l-.005 -.176v-14a3 3 0 0 1 2.824 -2.995l.176 -.005zm1 10l-5 5v2h2l5 -5a1.414 1.414 0 0 0 -2 -2" />
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="M19 7h-4l-.001 -4.001z" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Raw Data',
StringFormat='Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</MenuItem.Header>
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Data" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Properties" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Textures" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Models" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="World" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Worlds" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World1}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World2}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World3}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World4}" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource World5}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Animations" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
<MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Save_Audio" />
<Binding Path="SelectedItems" />
</MultiBinding>
</MenuItem.CommandParameter>
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AudioIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<Separator />
<MenuItem Header="Copy">

View File

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FModel"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Meshes;assembly=CUE4Parse-Conversion"
xmlns:c4pMeshes="clr-namespace:CUE4Parse_Conversion.Options;assembly=CUE4Parse-Conversion"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
@ -127,19 +127,8 @@
</ComboBox.ItemTemplate>
</controls:FilterableComboBox>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
<TextBlock Grid.Row="6" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -148,8 +137,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
<TextBlock Grid.Row="7" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -158,32 +147,32 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 10" ToolTip="Auto-save packages following their game directory" />
<CheckBox Grid.Row="9" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="8" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 10" ToolTip="Auto-save packages following their game directory" />
<CheckBox Grid.Row="8" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Local Mapping File (drag &amp; drop)" VerticalAlignment="Center" Margin="0 5 0 0" />
<CheckBox Grid.Row="10" Grid.Column="2" Margin="0 5 0 0"
<TextBlock Grid.Row="9" Grid.Column="0" Text="Local Mapping File (drag &amp; drop)" VerticalAlignment="Center" Margin="0 5 0 0" />
<CheckBox Grid.Row="9" Grid.Column="2" Margin="0 5 0 0"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 10 0 0"
<TextBlock Grid.Row="10" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 10 0 0"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 10 0 0" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
<TextBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 10 0 0" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Grid.Row="11" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 10 0 0"
<Button Grid.Row="10" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 10 0 0"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
<Separator Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
<Separator Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
<TextBlock Grid.Row="12" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="5" />
@ -197,10 +186,10 @@
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
</Grid>
<TextBlock Grid.Row="14" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
<TextBlock Grid.Row="13" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
<ComboBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
ItemsSource="{Binding SettingsView.AesReloads}" SelectedItem="{Binding SettingsView.SelectedAesReload, Mode=TwoWay}"
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}">
@ -211,8 +200,8 @@
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="15" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
<TextBlock Grid.Row="14" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
<Grid Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
@ -224,45 +213,65 @@
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
</Grid>
<TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="15" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="15" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Inlined Shader Maps" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Inlined Shader Maps" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="Decompile Blueprint to Pseudo C++" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Adds a right click option to decompile UClass packages into a pseudo C++ friendly format" />
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="17" Grid.Column="0" Text="Decompile Blueprint to Pseudo C++" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Adds a right click option to decompile UClass packages into a pseudo C++ friendly format" />
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="19" Grid.Column="0" Text="Decompile Lua" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Decompile/Disassemble compiled lua files with an unluac." />
<CheckBox Grid.Row="19" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="18" Grid.Column="0" Text="Decompile Lua" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Decompile/Disassemble compiled lua files with an unluac." />
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding DecompileLua, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Checked="OnDecompileLuaChanged" />
<TextBlock Grid.Row="20"
<TextBlock Grid.Row="19"
Grid.Column="0"
Text="Convert Audio During Export (.wav)"
VerticalAlignment="Center"
Margin="0 0 0 5" />
<CheckBox Grid.Row="20"
<CheckBox Grid.Row="19"
Grid.Column="2"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="20"
Grid.Column="0"
Text="Merge Editor-Only Data (.o.uasset)"
VerticalAlignment="Center"
ToolTip="Reads .o.uasset packages and merges their editor-only data into the parent asset. Useful for JSON-based asset export workflows"
Margin="0 0 0 5"
Visibility="{Binding DataContext.CUE4Parse.Provider.ProjectName,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}},
Converter={x:Static converters:ProjectNameToVisibilityConverter.Instance},
ConverterParameter='FortniteGame'}" />
<CheckBox Grid.Row="20"
Grid.Column="2"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding MergeEditorOnlyDataExports, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}"
Visibility="{Binding DataContext.CUE4Parse.Provider.ProjectName,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}},
Converter={x:Static converters:ProjectNameToVisibilityConverter.Instance},
ConverterParameter='FortniteGame'}" />
<TextBlock Grid.Row="21"
Grid.Column="0"
Text="CRIWARE Decryption Key"
VerticalAlignment="Center"
Margin="0 0 0 10" />
<TextBox x:Name="CriwareKeyBox"
Grid.Row="21"
Grid.Column="2"
@ -274,8 +283,7 @@
ToolTip="Enter decryption key in numeric or hexadecimal format (valid key is up to 20 digits or 8 bytes long)"
TextAlignment="Right"
TextChanged="CriwareKeyBox_TextChanged"
Loaded="CriwareKeyBox_Loaded"/>
Loaded="CriwareKeyBox_Loaded" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="CreatorTemplate">
@ -343,7 +351,12 @@
</Grid>
</Grid>
</DataTemplate>
<DataTemplate x:Key="ModelsTemplate">
<DataTemplate x:Key="ExportTemplate">
<controls:ExportOptionsControl
adonisExtensions:LayerExtension.Layer="2"
DataContext="{Binding DataContext.SettingsView.Options, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" />
</DataTemplate>
<DataTemplate x:Key="PreviewTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -353,20 +366,6 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -376,176 +375,40 @@
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Model Export Directory *" VerticalAlignment="Center" Margin="0 0 0 5"
ToolTip="This will be the directory where Meshes, Materials and Animations will be exported" />
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5"/>
<ComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ContentControl Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" FontSize="10" Margin="0 0 0 5">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importer (Blender/UE): " />
<Hyperlink NavigateUri="https://github.com/h4lfheart/UEFormat">
github.com/h4lfheart/UEFormat
</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.ActorX}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock>
<Run Text="Importers (Blender): " />
<Hyperlink NavigateUri="https://github.com/DarklightGames/io_scene_psk_psa">
github.com/DarklightGames/io_scene_psk_psa
</Hyperlink>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Socket Format (ActorX)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}"/>
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.SocketExportFormats}" SelectedItem="{Binding SettingsView.SelectedSocketExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.SocketSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="4" Grid.Column="0" Text="Compression Format (UEFormat)" VerticalAlignment="Center" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}"/>
<ComboBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.CompressionFormats}" SelectedItem="{Binding SettingsView.SelectedCompressionFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5" IsEnabled="{Binding SettingsView.CompressionSettingsEnabled}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Level Of Detail Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.LodExportFormats}" SelectedItem="{Binding SettingsView.SelectedLodExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
<TextBlock Grid.Row="0" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="1" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="2" Grid.Column="0" Text="Preview Skeletal Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewSkeletalMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="10" Grid.Column="0" Text="Preview Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="3" Grid.Column="0" Text="Preview Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewAnimations, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="11" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="4" Grid.Column="0" Text="Preview Materials" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="12" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Worlds (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="13" Grid.Column="0" Text="Save Materials Embedded within Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveEmbeddedMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="14" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="15" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="6" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
<Separator Grid.Row="16" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Nanite Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.NaniteMeshExportFormats}" SelectedItem="{Binding SettingsView.SelectedNaniteMeshExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="18" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="19" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="19" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="20" Grid.Column="0" Text="Save HDR Textures as Radiance .hdr" VerticalAlignment="Center" />
<CheckBox Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveHdrTexturesAsHdr, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="KeybindingsTemplate">
@ -564,6 +427,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@ -610,6 +474,9 @@
<TextBlock Grid.Row="12" Grid.Column="0" Text="Next Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="12" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding NextAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
<TextBlock Grid.Row="13" Grid.Column="0" Text="Remove Selected Audio" VerticalAlignment="Center" Margin="0 0 0 5" />
<controls:HotkeyTextBox Grid.Row="13" Grid.Column="2" Style="{StaticResource TextBoxDefaultStyle}" Margin="0 0 0 5"
HotKey="{Binding RemoveAudio, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="UnluacTemplate">
@ -707,8 +574,6 @@
<DataTemplate x:Key="ThemesTemplate">
<Grid adonisExtensions:LayerExtension.Layer="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="*" />
@ -718,11 +583,11 @@
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="JSON Highlight Theme"
VerticalAlignment="Center"
Margin="0 0 0 5" />
VerticalAlignment="Center" />
<ComboBox Grid.Row="0"
Grid.Column="2"
ItemsSource="{Binding SettingsView.JsonHighlightThemes}"
@ -737,12 +602,8 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Style="{StaticResource CustomSeparator}"
Tag="PREVIEW" />
<Border Grid.Row="4"
<Border Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="3"
Padding="1"
@ -790,7 +651,10 @@
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem Tag="CreatorTemplate">
<TreeViewItem Tag="CreatorTemplate"
Visibility="{Binding CUE4Parse.Provider.ProjectName,
Converter={x:Static converters:ProjectNameToVisibilityConverter.Instance},
ConverterParameter='FortniteGame'}">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
@ -801,18 +665,21 @@
<TextBlock Text="Creator" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
<TreeViewItem.Style>
<Style TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CUE4Parse.Provider.ProjectName, Converter={x:Static converters:CaseInsensitiveStringEqualsConverter.Instance}, ConverterParameter='FortniteGame'}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeViewItem.Style>
</TreeViewItem>
<TreeViewItem Tag="ModelsTemplate">
<TreeViewItem Tag="ExportTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
<Canvas Width="24" Height="24">
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M14 3v4a1 1 0 0 0 1 1h4" />
<Path StrokeThickness="2" Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M11.5 21h-4.5a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v5m-5 6h7m-3 -3l3 3l-3 3" />
</Canvas>
</Viewbox>
<TextBlock Text="Export" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>
<TreeViewItem Tag="PreviewTemplate">
<TreeViewItem.Header>
<StackPanel Orientation="Horizontal">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center" Margin="-20 4 7.5 4">
@ -824,7 +691,7 @@
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="M19,14.87V9.13c0-0.72-0.38-1.38-1-1.73l-5-2.88c-0.31-0.18-0.65-0.27-1-0.27s-0.69,0.09-1,0.27L6,7.39 C5.38,7.75,5,8.41,5,9.13v5.74c0,0.72,0.38,1.38,1,1.73l5,2.88c0.31,0.18,0.65,0.27,1,0.27s0.69-0.09,1-0.27l5-2.88 C18.62,16.25,19,15.59,19,14.87z M11,17.17l-4-2.3v-4.63l4,2.33V17.17z M12,10.84L8.04,8.53L12,6.25l3.96,2.28L12,10.84z M17,14.87l-4,2.3v-4.6l4-2.33V14.87z" />
</Canvas>
</Viewbox>
<TextBlock Text="Models" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBlock Text="Preview" HorizontalAlignment="Left" VerticalAlignment="Center" />
</StackPanel>
</TreeViewItem.Header>
</TreeViewItem>

View File

@ -67,8 +67,10 @@ public partial class SettingsView
_applicationView.CUE4Parse.Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
_applicationView.CUE4Parse.Provider.ReadShaderMaps = UserSettings.Default.ReadShaderMaps;
_applicationView.CUE4Parse.Provider.Versions.Platform = UserSettings.Default.CurrentDir.TexturePlatform;
UserSettings.Save();
ExportSessionViewModel.Instance.Options.ResetToUserDefaults();
}
private void OnBrowseOutput(object sender, RoutedEventArgs e)
@ -203,24 +205,36 @@ public partial class SettingsView
_applicationView.SettingsView.SelectedMapStructTypes = editor.MapStructTypes;
}
private void OpenAesEndpoint(object sender, RoutedEventArgs e)
private async void OpenAesEndpoint(object sender, RoutedEventArgs e)
{
var editor = new EndpointEditor(
_applicationView.SettingsView.AesEndpoint, "Endpoint Configuration (AES)", EEndpointType.Aes);
if (_applicationView.Status.IsReady)
_applicationView.Status.SetStatus(EStatusKind.Configuring);
editor.ShowDialog();
if (editor.DialogResult == true)
{
await _applicationView.CUE4Parse.RefreshAes();
await _applicationView.AesManager.InitAes();
_applicationView.AesManager.HasChange = true;
await _applicationView.UpdateProvider(false);
}
if (_applicationView.Status.IsReady)
_applicationView.Status.SetStatus(EStatusKind.Ready);
}
private void OpenMappingEndpoint(object sender, RoutedEventArgs e)
private async void OpenMappingEndpoint(object sender, RoutedEventArgs e)
{
var editor = new EndpointEditor(
_applicationView.SettingsView.MappingEndpoint, "Endpoint Configuration (Mapping)", EEndpointType.Mapping);
if (_applicationView.Status.IsReady)
_applicationView.Status.SetStatus(EStatusKind.Configuring);
editor.ShowDialog();
if (editor.DialogResult == true)
{
await _applicationView.CUE4Parse.InitMappings(true);
await _applicationView.UpdateProvider(false);
}
if (_applicationView.Status.IsReady)
_applicationView.Status.SetStatus(EStatusKind.Ready);
}

View File

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Animations.PSA;
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Settings;
@ -12,7 +11,7 @@ using ImGuiNET;
namespace FModel.Views.Snooper.Animations;
public class Animation : IDisposable
public class Animation : IExportableThing, IDisposable
{
private readonly UObject _export;
@ -82,7 +81,7 @@ public class Animation : IDisposable
AttachedModels.Clear();
}
public void ImGuiAnimation(Snooper s, Save saver, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
public void ImGuiAnimation(Snooper s, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
{
var name = $"{Name}##{i}";
var p1 = new Vector2(timelineP0.X + StartTime * timeRatio.X + t, y + t);
@ -96,7 +95,7 @@ public class Animation : IDisposable
{
s.Renderer.Options.SelectAnimation(i);
}
Popup(s, saver, i);
Popup(s, i);
drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop);
for (int j = 0; j < Sequences.Length; j++)
@ -109,10 +108,10 @@ public class Animation : IDisposable
{
s.Renderer.Options.SelectAnimation(i);
}
Popup(s, saver, i);
Popup(s, i);
}
private void Popup(Snooper s, Save saver, int i)
private void Popup(Snooper s, int i)
{
SnimGui.Popup(() =>
{
@ -134,11 +133,16 @@ public class Animation : IDisposable
if (ImGui.MenuItem("Save"))
{
s.WindowShouldFreeze(true);
saver.Value = new Exporter(_export, UserSettings.Default.ExportOptions).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path);
ExportModal.Instance.Export([this], UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
s.WindowShouldFreeze(false);
}
ImGui.Separator();
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(Path);
});
}
public void AddToExportSession(ExportSession session)
{
session.Add(_export);
}
}

View File

@ -1,5 +1,5 @@
using System.Numerics;
using CUE4Parse_Conversion.Animations.PSA;
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
using ImGuiNET;
namespace FModel.Views.Snooper.Animations;

View File

@ -2,8 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion.Animations.PSA;
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
@ -64,6 +65,7 @@ public class Skeleton : IDisposable
bone.Rest.Relation = parentBone.Rest.Matrix;
parentBone.LoweredChildNames.Add(boneName);
}
else bone.Rest.Scale = FVector.OneVector;
if (boneIndex == 0) SelectedBone = boneName;
BonesByLoweredName[boneName] = bone;
@ -203,6 +205,7 @@ public class Skeleton : IDisposable
sequence.Tracks[bone.SkeletonIndex].GetBoneTransform(f, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale);
if (!bone.IsRoot) boneMatrix = _boneMatriceAtFrame[bone.ParentIndex];
else boneScale = bone.Rest.Scale;
bonePosition = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO;
boneMatrix = new Transform

View File

@ -61,7 +61,7 @@ public class TimeTracker : IDisposable
}
private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" };
public void ImGuiTimeline(Snooper s, Save saver, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
public void ImGuiTimeline(Snooper s, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
{
var dpiScale = ImGui.GetWindowDpiScale();
var thickness = 2.0f * dpiScale;
@ -151,7 +151,7 @@ public class TimeTracker : IDisposable
for (int i = 0; i < animations.Count; i++)
{
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
animations[i].ImGuiAnimation(s, saver, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
animations[i].ImGuiAnimation(s, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
DrawSeparator(drawList, timelineP0, y + timeStep.Y, animations[i].EndTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.End);
}
ImGui.PopStyleVar();

View File

@ -54,7 +54,7 @@ public class PickingTexture : IDisposable
Bind(0);
}
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, UModel> models)
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, IRenderableModel> models)
{
Bind();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

View File

@ -0,0 +1,664 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Options;
using CUE4Parse.Utils;
using FModel.Extensions;
using FModel.Views.Snooper.Models;
using ImGuiNET;
using Serilog;
using Serilog.Core;
using Serilog.Events;
namespace FModel.Views.Snooper;
public sealed class ExportModal
{
public static ExportModal Instance { get; } = new();
private const string Title = "Export Progress";
private const string IconXMark = "\uf057";
private const string IconFolder = "\uf08e";
private readonly Vector4[] _pieColors =
[
new(0.22f, 0.52f, 0.90f, 1f),
new(0.28f, 0.78f, 0.44f, 1f),
new(0.90f, 0.62f, 0.22f, 1f),
new(0.75f, 0.32f, 0.75f, 1f),
new(0.32f, 0.75f, 0.85f, 1f),
new(0.85f, 0.32f, 0.45f, 1f),
new(0.90f, 0.90f, 0.22f, 1f),
new(0.55f, 0.75f, 0.32f, 1f),
];
private static readonly Vector4 _redColor = new(1f, 0.4f, 0.4f, 1f);
private static readonly Vector4 _orangeColor = new(1f, 0.5f, 0f, 1f);
private static readonly Vector4 _yellowColor = new(1f, 1f, 0.4f, 1f);
private static readonly Vector4 _greenColor = new(0.4f, 1f, 0.4f, 1f);
private bool _openPopup;
private bool _modalOpen;
private bool _inProgress;
private CancellationTokenSource? _cts;
private IReadOnlyList<ExportResult>? _exportResults;
private ExportProgress _currentProgress;
private readonly IProgress<ExportProgress> _progress;
private readonly Stopwatch _stopwatch = new();
private readonly ConcurrentQueue<LogEvent> _pendingLogs = new();
private readonly List<ClassGroup> _classGroups = [];
private const int MaxGraphSamples = 4096;
private const float GraphSampleIntervalSec = 0.25f;
private readonly List<float> _graphSamples = [];
private float _graphNextSampleAt;
private int _graphLastCompleted;
private ExportModal()
{
ImGuiSink.Instance.OnExporterLogEvent += _pendingLogs.Enqueue;
_progress = new Progress<ExportProgress>(p => _currentProgress = p);
}
public void Export(IEnumerable<IExportableThing> nodes, string exportDirectory, ExportOptions options)
{
Reset();
_openPopup = true;
_inProgress = true;
_cts = new CancellationTokenSource();
_stopwatch.Restart();
var token = _cts.Token;
_ = Task.Run(async () =>
{
try
{
var session = new ExportSession();
foreach (var node in nodes) node.AddToExportSession(session);
_exportResults = await session.RunAsync(exportDirectory, options, _progress, token);
}
catch (OperationCanceledException)
{
Log.Error("Export cancelled by user");
}
catch (Exception ex)
{
Log.Error(ex, "Export failed");
}
finally
{
_stopwatch.Stop();
_inProgress = false;
}
}, token);
}
public void Draw()
{
if (_openPopup)
{
ImGui.OpenPopup(Title);
_modalOpen = true;
_openPopup = false;
}
if (!_modalOpen) return;
var viewport = ImGui.GetMainViewport();
ImGui.SetNextWindowSize(viewport.WorkSize * 0.75f, ImGuiCond.Always);
ImGui.SetNextWindowPos(viewport.GetCenter(), ImGuiCond.Always, new Vector2(0.5f, 0.5f));
var open = true;
if (ImGui.BeginPopupModal(Title, ref open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize))
{
if (ImGui.BeginChild("##ModalInfoBody", Vector2.Zero, ImGuiChildFlags.FrameStyle))
{
DrawProgressBar();
ImGui.Spacing();
ImGui.SeparatorText("Throughput");
DrawThroughputGraph();
ImGui.Spacing();
ImGui.SeparatorText("Export Log");
DrawExportLog();
}
ImGui.EndChild();
ImGui.EndPopup();
}
if (!open)
{
_modalOpen = false;
Reset();
}
}
private void Reset()
{
_pendingLogs.Clear();
_classGroups.Clear();
_exportResults = null;
_currentProgress = new ExportProgress(0, 0);
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
_graphSamples.Clear();
_graphNextSampleAt = 0f;
_graphLastCompleted = 0;
}
private void DrawProgressBar()
{
var e = _stopwatch.Elapsed;
ImGui.TextDisabled("\uf2f2");
ImGui.SameLine();
ImGui.TextUnformatted($"{e.Minutes:D2}:{e.Seconds:D2}.{e.Milliseconds / 10:D2}");
if (_inProgress && _currentProgress is { Total: > 0, Completed: > 1 })
{
var rate = _currentProgress.Completed / e.TotalSeconds;
if (rate > 0)
{
var remaining = (_currentProgress.Total - _currentProgress.Completed) / rate;
var eta = TimeSpan.FromSeconds(remaining);
ImGui.SameLine();
ImGui.TextDisabled("\uf017");
ImGui.SameLine();
ImGui.TextUnformatted($"ETA {eta.Minutes:D2}:{eta.Seconds:D2}");
}
}
else if (!_inProgress && _exportResults is { Count: > 0 })
{
ImGui.SameLine();
ImGui.TextColored(_greenColor, "\uf058");
ImGui.SameLine();
ImGui.TextUnformatted($"{_exportResults?.Count(r => r.Success) ?? 0} succeeded");
ImGui.SameLine();
ImGui.TextColored(_redColor, IconXMark);
ImGui.SameLine();
ImGui.TextUnformatted($"{_exportResults?.Count(r => !r.Success) ?? 0} failed");
}
ImGui.Spacing();
var barColor = _classGroups.Any(cg => cg.ErrorCount > 0) ? new Vector4(0.75f, 0.32f, 0.32f, 1f) : _inProgress ? new Vector4(0.22f, 0.52f, 0.90f, 1f) : new Vector4(0.28f, 0.78f, 0.44f, 1f);
var label = _inProgress && _currentProgress.Total > 0 ? _currentProgress.DisplayText : _inProgress ? "Preparing..." : "Done";
var barPos = ImGui.GetCursorScreenPos();
var barSize = new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetFrameHeight());
var hovered = ImGui.IsMouseHoveringRect(barPos, barPos + barSize);
if (hovered && _inProgress)
{
barColor = new Vector4(0.75f, 0.32f, 0.32f, 1f);
label = "\uf05e Cancel";
}
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 4f);
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, barColor);
ImGui.ProgressBar(_currentProgress.Percentage, barSize, label);
ImGui.PopStyleColor();
ImGui.PopStyleVar(2);
if (_inProgress)
{
ImGui.SetCursorScreenPos(barPos);
if (ImGui.InvisibleButton("##BarAction", barSize))
{
_cts?.Cancel();
}
}
}
private void DrawThroughputGraph()
{
var elapsedSec = (float)_stopwatch.Elapsed.TotalSeconds;
if (_inProgress && _currentProgress.Completed > 0 && elapsedSec >= _graphNextSampleAt && _graphSamples.Count < MaxGraphSamples)
{
var completed = _currentProgress.Completed;
var rate = (completed - _graphLastCompleted) / GraphSampleIntervalSec;
_graphSamples.Add(MathF.Max(rate, 0f));
_graphLastCompleted = completed;
_graphNextSampleAt = elapsedSec + GraphSampleIntervalSec;
}
var graphPos = ImGui.GetCursorScreenPos();
var graphW = ImGui.GetContentRegionAvail().X;
var graphH = ImGui.GetFrameHeight() * 3f;
var graphSize = new Vector2(graphW, graphH);
ImGui.InvisibleButton("##ThroughputGraph", graphSize);
var dl = ImGui.GetWindowDrawList();
dl.AddRectFilled(graphPos, graphPos + graphSize, 0xFF_14_14_14);
var count = _graphSamples.Count;
switch (count)
{
case 0 when !_inProgress:
{
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
return;
}
case < 2:
{
if (_inProgress)
{
const string msg = "Collecting data...";
var msgSz = ImGui.CalcTextSize(msg);
dl.AddText(graphPos + (graphSize - msgSz) * 0.5f, 0x44_FF_FF_FF, msg);
}
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
return;
}
}
// Y scale
var maxVal = 0f;
for (var i = 0; i < count; i++) maxVal = MathF.Max(maxVal, _graphSamples[i]);
if (maxVal < 0.01f) maxVal = 1f;
maxVal *= 1.15f; // top headroom
dl.PushClipRect(graphPos, graphPos + graphSize, true);
// Horizontal grid lines at 25 / 50 / 75 %
for (var g = 1; g < 4; g++)
{
var gy = graphPos.Y + graphH * g / 4f;
dl.AddLine(new Vector2(graphPos.X, gy), new Vector2(graphPos.X + graphW, gy), 0x18_FF_FF_FF, 0.5f);
}
var lineColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 1f));
var fillColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 0.15f));
// xStep shrinks as samples accumulate → graph zooms out naturally.
// Oldest sample is always at x=0, newest at x=graphW.
var xStep = graphW / (count - 1f);
// Filled area (series of convex quads)
for (var i = 0; i < count - 1; i++)
{
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
var x0 = graphPos.X + xStep * i;
var x1 = graphPos.X + xStep * (i + 1);
var y0 = graphPos.Y + graphH - graphH * t0;
var y1 = graphPos.Y + graphH - graphH * t1;
var bot = graphPos.Y + graphH;
dl.AddQuadFilled(new Vector2(x0, y0), new Vector2(x1, y1), new Vector2(x1, bot), new Vector2(x0, bot), fillColor);
}
// Line
for (var i = 0; i < count - 1; i++)
{
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
var x0 = graphPos.X + xStep * i;
var x1 = graphPos.X + xStep * (i + 1);
var y0 = graphPos.Y + graphH - graphH * t0;
var y1 = graphPos.Y + graphH - graphH * t1;
dl.AddLine(new Vector2(x0, y0), new Vector2(x1, y1), lineColor, 1.5f);
}
// Dot on the newest sample (always at the right edge)
var newestT = Math.Clamp(_graphSamples[count - 1] / maxVal, 0f, 1f);
var newestY = graphPos.Y + graphH - graphH * newestT;
dl.AddCircleFilled(new Vector2(graphPos.X + graphW, newestY), 3f, lineColor);
var rateStr = $"{_graphSamples[count - 1]:F1} items/s";
dl.AddText(new Vector2(graphPos.X + 4, graphPos.Y + 3), 0xCC_FF_FF_FF, rateStr);
dl.PopClipRect();
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
}
private void DrawExportLog()
{
var avail = ImGui.GetContentRegionAvail();
var rowH = ImGui.GetTextLineHeightWithSpacing();
var canvasSize = 15 * rowH + ImGui.GetFrameHeightWithSpacing();
var treeW = avail.X - canvasSize - ImGui.GetStyle().ItemSpacing.X;
DrainPendingLogs();
if (ImGui.BeginChild("##ExportLogTree", avail with { X = treeW }, ImGuiChildFlags.FrameStyle))
{
if (_classGroups.Count == 0)
{
ImGui.TextDisabled(_inProgress ? "Waiting for export data..." : "No export log.");
}
else for (var i = 0; i < _classGroups.Count; i++)
{
DrawClassGroup(i, _classGroups[i]);
}
}
ImGui.EndChild();
ImGui.SameLine();
if (ImGui.BeginChild("##RightPanel", new Vector2(canvasSize, -1), ImGuiChildFlags.FrameStyle))
{
DrawPieCanvas();
if (_classGroups.Count == 0)
{
ImGui.TextDisabled(_inProgress ? "Waiting for data..." : "No export data.");
}
else
{
var total = _classGroups.Sum(cg => cg.Objects.Count);
for (var i = 0; i < _classGroups.Count; i++)
{
var cg = _classGroups[i];
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[i % _pieColors.Length]);
ImGui.TextUnformatted("\uf111");
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.TextUnformatted(cg.Name);
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
ImGui.TextUnformatted($"({(float) cg.Objects.Count / total * 100f:F1}%)");
ImGui.PopStyleColor();
}
}
}
ImGui.EndChild();
}
private void DrawPieCanvas()
{
const int segments = 64;
var canvasPos = ImGui.GetCursorScreenPos();
var size = ImGui.GetContentRegionAvail().X;
var canvasVec = new Vector2(size);
ImGui.InvisibleButton("##PieCanvas", canvasVec);
var isHovered = ImGui.IsItemHovered();
var mousePos = ImGui.GetMousePos();
var dl = ImGui.GetWindowDrawList();
dl.AddRectFilled(canvasPos, canvasPos + canvasVec, 0xFF_14_14_14);
dl.AddRect(canvasPos, canvasPos + canvasVec, 0xFF_32_32_32);
var total = _classGroups.Sum(cg => cg.Objects.Count);
var padding = ImGui.GetFrameHeight() * 0.5f;
var radius = size * 0.5f - padding;
var center = canvasPos + new Vector2(size * 0.5f);
if (total == 0 || radius <= 0)
{
dl.AddCircleFilled(center, MathF.Max(radius, 1f), 0xFF_1F_1F_1F, segments);
return;
}
// Determine hovered slice by angle
var hoveredSlice = -1;
if (isHovered)
{
var dx = mousePos.X - center.X;
var dy = mousePos.Y - center.Y;
if (dx * dx + dy * dy <= radius * radius)
{
var angle = MathF.Atan2(dy, dx);
while (angle < -MathF.PI / 2f) angle += MathF.PI * 2f;
var cur = -MathF.PI / 2f;
for (var i = 0; i < _classGroups.Count; i++)
{
var sweep = (float)_classGroups[i].Objects.Count / total * MathF.PI * 2f;
if (angle >= cur && angle < cur + sweep) { hoveredSlice = i; break; }
cur += sweep;
}
}
}
float startAngle = -MathF.PI / 2f;
for (var i = 0; i < _classGroups.Count; i++)
{
var cg = _classGroups[i];
var ratio = (float) cg.Objects.Count / total;
var sliceAngle = ratio * MathF.PI * 2f;
var col = ImGui.GetColorU32(_pieColors[i % _pieColors.Length]);
var r = i == hoveredSlice ? radius + padding * 0.25f : radius;
dl.PathLineTo(center);
dl.PathArcTo(center, r, startAngle, startAngle + sliceAngle);
dl.PathFillConvex(col);
var midAngle = startAngle + sliceAngle * 0.5f;
var labelPos = center + new Vector2(MathF.Cos(midAngle), MathF.Sin(midAngle)) * (radius * 0.62f);
var pctStr = $"{ratio * 100f:F0}%";
dl.AddText(labelPos - ImGui.CalcTextSize(pctStr) * 0.5f, 0xFF_FF_FF_FF, pctStr);
startAngle += sliceAngle;
}
dl.AddCircle(center, radius, 0xAA_00_00_00, segments, 1.5f);
if (hoveredSlice >= 0)
{
ImGui.BeginTooltip();
var cg = _classGroups[hoveredSlice];
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[hoveredSlice % _pieColors.Length]);
ImGui.TextUnformatted("\uf111");
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.TextUnformatted(cg.Name);
ImGui.EndTooltip();
}
}
private void DrawClassGroup(int index, ClassGroup cg)
{
ImGui.PushStyleColor(ImGuiCol.Header, new Vector4(0.20f, 0.20f, 0.20f, 1.00f));
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
ImGui.PushStyleColor(ImGuiCol.HeaderActive, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
var open = ImGui.CollapsingHeader($"{cg.Name} ({cg.Objects.Count})##class_{cg.Name}");
ImGui.PopStyleColor(3);
var headerMin = ImGui.GetItemRectMin();
var headerMax = ImGui.GetItemRectMax();
var labelW = MathF.Floor(ImGui.GetStyle().ItemSpacing.X * 0.5f);
var col = ImGui.GetColorU32(_pieColors[index % _pieColors.Length]);
ImGui.GetWindowDrawList().AddRectFilled(headerMin, headerMax with { X = headerMin.X + labelW }, col);
if (cg.ErrorCount > 0 && ImGui.IsItemHovered())
{
ImGui.SetTooltip($"{cg.ErrorCount} error{(cg.ErrorCount > 1 ? "s" : "")} in this class");
}
if (!open) return;
foreach (var og in cg.Objects)
{
DrawObjectGroup(cg.Name, og);
}
}
private void DrawObjectGroup(string className, ObjectGroup og)
{
var rightEdge = ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X;
var hasErr = og.ErrorCount > 0;
if (hasErr) ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
var flags = ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.FramePadding;
var open = ImGui.TreeNodeEx($"{og.Name}##obj_{className}_{og.Name}", flags);
if (hasErr) ImGui.PopStyleColor();
if (og.Entries.FirstOrDefault(e => !string.IsNullOrEmpty(e.FilePath)) is { } first)
{
var style = ImGui.GetStyle();
var btnW = ImGui.CalcTextSize(IconFolder).X + style.FramePadding.X * 2;
ImGui.SameLine(rightEdge - btnW);
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, style.ItemSpacing with { X = 0 });
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
if (ImGui.Button($"{IconFolder}##obj_{className}_{og.Name}"))
{
OpenInExplorer(first.FilePath!);
}
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Open In Explorer");
ImGui.PopStyleColor();
ImGui.PopStyleVar();
}
if (!open) return;
foreach (var entry in og.Entries)
{
DrawLogEntry(entry);
}
ImGui.TreePop();
}
private void DrawLogEntry(LogEntry entry)
{
ImGui.PushStyleColor(ImGuiCol.Text, entry.Color);
ImGui.TextUnformatted(entry.Icon);
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.TextUnformatted(entry.Message);
if (ImGui.IsItemHovered() && entry.Exception != null)
{
DrawExceptionTooltip(entry.Exception);
}
}
private static void DrawExceptionTooltip(Exception ex)
{
ImGui.BeginTooltip();
ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
ImGui.TextUnformatted(ex.GetType().ToString());
ImGui.PopStyleColor();
ImGui.SameLine(0, 0);
ImGui.TextDisabled(":");
ImGui.SameLine();
ImGui.TextUnformatted(ex.Message);
if (!string.IsNullOrEmpty(ex.StackTrace))
{
ImGui.SetWindowFontScale(0.85f);
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
ImGui.TextUnformatted(ex.StackTrace);
ImGui.PopStyleColor();
ImGui.SetWindowFontScale(1.0f);
}
ImGui.EndTooltip();
}
private void OpenInExplorer(string path)
{
try
{
if (File.Exists(path) || Directory.Exists(path)) Process.Start("explorer.exe", $"/select, \"{path}\"");
else Log.Warning("File or directory does not exist: {Path}", path);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to open in explorer: {Path}", path);
}
}
private void DrainPendingLogs()
{
while (_pendingLogs.TryDequeue(out var log))
{
var className = log.GetContext("ClassName");
var objectPath = log.GetContext("ObjectPath");
var filePath = log.GetContext("FilePath");
var cg = FindOrCreateClass(className);
var og = FindOrCreateObject(cg, objectPath);
var entry = new LogEntry(log, filePath);
if (entry.Icon == IconXMark)
{
og.ErrorCount++;
cg.ErrorCount++;
}
og.Entries.Add(entry);
}
}
private ClassGroup FindOrCreateClass(string name)
{
foreach (var cg in _classGroups)
if (cg.Name == name) return cg;
var n = new ClassGroup(name);
_classGroups.Add(n);
return n;
}
private static ObjectGroup FindOrCreateObject(ClassGroup cg, string path)
{
foreach (var og in cg.Objects)
if (og.Path == path) return og;
var n = new ObjectGroup(path);
cg.Objects.Add(n);
return n;
}
private sealed class LogEntry(LogEvent log, string? filePath)
{
public string Icon { get; } = log.Level switch
{
LogEventLevel.Error or LogEventLevel.Fatal => IconXMark,
LogEventLevel.Warning => "\uf071",
LogEventLevel.Information => "\uf05a",
LogEventLevel.Debug => "\uf188",
_ => "\uf5dc"
};
public Vector4 Color { get; } = log.Level switch
{
LogEventLevel.Error or LogEventLevel.Fatal => _redColor,
LogEventLevel.Warning => _yellowColor,
_ => new Vector4(0.5f, 0.5f, 0.5f, 1f)
};
public string Message { get; } = $"[{log.Timestamp:HH:mm:ss.fff}] {log.RenderMessage()}";
public string? FilePath { get; } = filePath;
public Exception? Exception { get; } = log.Exception;
}
private sealed class ObjectGroup(string path)
{
public string Path { get; } = path;
public string Name { get; } = path.SubstringAfterLast('.');
public List<LogEntry> Entries { get; } = [];
public int ErrorCount { get; set; }
}
private sealed class ClassGroup(string name)
{
public string Name { get; } = name;
public List<ObjectGroup> Objects { get; } = [];
public int ErrorCount { get; set; }
}
}
public class ImGuiSink : ILogEventSink
{
public static ImGuiSink Instance { get; } = new();
private ImGuiSink()
{
}
public event Action<LogEvent>? OnExporterLogEvent;
public void Emit(LogEvent logEvent)
{
if (logEvent.Properties.TryGetValue("ExporterV2", out var state) && state is ScalarValue { Value: true })
{
OnExporterLogEvent?.Invoke(logEvent);
}
}
}

View File

@ -24,7 +24,7 @@ public class Attachment
_attachedFor = new List<string>();
}
public void Attach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
public void Attach(IRenderableModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Add(info);
@ -38,13 +38,13 @@ public class Attachment
transform.Scale = FVector.OneVector;
}
public void Detach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
public void Detach(IRenderableModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
{
socket.AttachedModels.Remove(info);
SafeDetach(attachedTo, transform);
}
public void SafeDetach(UModel attachedTo, Transform transform)
public void SafeDetach(IRenderableModel attachedTo, Transform transform)
{
_attachedTo = string.Empty;
attachedTo.Attachments.RemoveAttachment(_modelName);

View File

@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse.UE4.Objects.Core.Math;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
namespace FModel.Views.Snooper.Models;
public interface IRenderableModel : IDisposable
public interface IRenderableModel : IExportableThing, IDisposable
{
protected int Handle { get; set; }
protected BufferObject<uint> Ebo { get; set; }
@ -24,15 +26,36 @@ public interface IRenderableModel : IDisposable
public List<Transform> Transforms { get; }
public Attachment Attachments { get; }
public FBox Box { get; protected init; }
public List<Socket> Sockets { get; }
public List<Collision> Collisions { get; }
public Material[] Materials { get; protected init; }
public bool IsTwoSided { get; internal set; }
public bool IsProp { get; internal set; }
public bool HasSockets { get; }
public bool HasCollisions { get; }
public int TransformsCount { get; }
public bool IsSetup { get; set; }
public bool IsVisible { get; set; }
public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; }
public bool ShowCollisions { get; set; }
public int SelectedInstance { get; set; }
public void Setup(Options options);
public void SetupInstances();
public void Render(Shader shader, Texture checker = null, bool outline = false);
public void RenderCollision(Shader shader);
public void PickingRender(Shader shader);
public void Update(Options options);
public void AddInstance(Transform transform);
public Transform GetTransform();
}
public interface IExportableThing
{
public void AddToExportSession(ExportSession session);
}

View File

@ -61,7 +61,7 @@ public class Morph : IDisposable
}
}
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, int index = 0)
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, uint index = 0)
{
Name = morphTarget.Name;
Vertices = new float[vertices.Length];

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse_Conversion.Dto;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Objects.Core.Math;
@ -14,7 +13,7 @@ using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class SkeletalModel : UModel
public class SkeletalModel : UModel<SkinnedMeshVertex>
{
private BufferObject<float> _morphVbo;
@ -25,10 +24,10 @@ public class SkeletalModel : UModel
public float MorphTime;
public SkeletalModel(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform = null)
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Verts, skeletalMesh.LODs.Count, transform)
public SkeletalModel(USkeletalMesh export, SkeletalMeshDto skeletalMesh, Transform transform = null)
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Vertices, skeletalMesh.LODs.Count, transform)
{
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
Box = skeletalMesh.Bounds * Constants.SCALE_DOWN_RATIO;
Skeleton = new Skeleton(export.ReferenceSkeleton);
var sockets = new List<FPackageIndex>();
@ -100,11 +99,11 @@ public class SkeletalModel : UModel
foreach (var morph in export.MorphTargets)
{
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].LODIndex ||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].LODIndex].Vertices.Length < 1)
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].SourceLodIndex ||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].SourceLodIndex].Vertices.Length < 1)
continue;
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].LODIndex));
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].SourceLodIndex));
}
}

View File

@ -1,11 +1,8 @@
using System.Collections.Generic;
using System.Numerics;
using System.Runtime.InteropServices;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse_Conversion.Dto;
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
@ -74,7 +71,7 @@ public class SplineModel : StaticModel
private readonly List<GpuParams> _splineParams;
private BufferObject<GpuParams> _ssbo;
public SplineModel(UStaticMesh export, CStaticMesh staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform)
public SplineModel(UStaticMesh export, StaticMeshDto staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform)
{
_splineParams = [new GpuParams(splineMesh)];

View File

@ -1,6 +1,6 @@
using System;
using System.Numerics;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse_Conversion.Dto;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
@ -11,24 +11,24 @@ using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
public class StaticModel : UModel
public class StaticModel : UModel<MeshVertex>
{
public StaticModel(UMaterialInterface unrealMaterial, CStaticMesh staticMesh) : base(unrealMaterial)
public StaticModel(UMaterialInterface unrealMaterial, StaticMeshDto staticMesh) : base(unrealMaterial)
{
var lod = staticMesh.LODs[LodLevel];
Indices = new uint[lod.Indices.Value.Length];
Indices = new uint[lod.Indices.Length];
for (int i = 0; i < Indices.Length; i++)
{
Indices[i] = (uint) lod.Indices.Value[i];
Indices[i] = lod.Indices[i];
}
Vertices = new float[lod.NumVerts * VertexSize];
for (int i = 0; i < lod.Verts.Length; i++)
Vertices = new float[lod.Vertices.Length * VertexSize];
for (int i = 0; i < lod.Vertices.Length; i++)
{
var count = 0;
var baseIndex = i * VertexSize;
var vert = lod.Verts[i];
var vert = lod.Vertices[i];
Vertices[baseIndex + count++] = i;
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO;
@ -39,8 +39,8 @@ public class StaticModel : UModel
Vertices[baseIndex + count++] = vert.Tangent.X;
Vertices[baseIndex + count++] = vert.Tangent.Z;
Vertices[baseIndex + count++] = vert.Tangent.Y;
Vertices[baseIndex + count++] = vert.UV.U;
Vertices[baseIndex + count++] = vert.UV.V;
Vertices[baseIndex + count++] = vert.Uv.U;
Vertices[baseIndex + count++] = vert.Uv.V;
Vertices[baseIndex + count++] = .5f;
}
@ -52,7 +52,7 @@ public class StaticModel : UModel
AddInstance(Transform.Identity);
Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO;
Box = staticMesh.Bounds * 1.5f * Constants.SCALE_DOWN_RATIO;
}
public StaticModel(UPaperSprite paperSprite, UTexture2D texture) : base(paperSprite)
@ -108,8 +108,8 @@ public class StaticModel : UModel
Box = new FBox(-backward, backward) * Constants.SCALE_DOWN_RATIO;
}
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
public StaticModel(UStaticMesh export, StaticMeshDto staticMesh, Transform transform = null)
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Vertices, staticMesh.LODs.Count, transform)
{
if (export.BodySetup.TryLoad(out UBodySetup bodySetup) && bodySetup.AggGeom != null)
{
@ -135,7 +135,7 @@ public class StaticModel : UModel
}
}
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
Box = staticMesh.Bounds * Constants.SCALE_DOWN_RATIO;
for (int i = 0; i < export.Sockets.Length; i++)
{
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;

View File

@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets;
using CUE4Parse_Conversion.Dto;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.Utils;
using FModel.Settings;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
using OpenTK.Graphics.OpenGL4;
@ -24,7 +21,7 @@ public class VertexAttribute
public bool Enabled;
}
public abstract class UModel : IRenderableModel
public abstract class UModel<TVertex> : IRenderableModel where TVertex : struct, IMeshVertex
{
protected const int LodLevel = 0;
@ -58,12 +55,12 @@ public abstract class UModel : IRenderableModel
public List<Transform> Transforms { get; }
public Attachment Attachments { get; }
public FBox Box;
public readonly List<Socket> Sockets;
public readonly List<Collision> Collisions;
public Material[] Materials;
public bool IsTwoSided;
public bool IsProp;
public FBox Box { get; init; }
public List<Socket> Sockets { get; }
public List<Collision> Collisions { get; }
public Material[] Materials { get; init; }
public bool IsTwoSided { get; set; }
public bool IsProp { get; set; }
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
@ -76,7 +73,7 @@ public abstract class UModel : IRenderableModel
public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; }
public bool ShowCollisions { get; set; }
public int SelectedInstance;
public int SelectedInstance { get; set; }
protected UModel()
{
@ -110,16 +107,16 @@ public abstract class UModel : IRenderableModel
_vertexAttributes[(int) EAttribute.Layer].Enabled = true;
}
protected UModel(UObject export, CBaseMeshLod lod, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CMeshVertex> vertices, int numLods, Transform transform = null) : this(export)
protected UModel(UObject export, MeshLodDto<TVertex> lod, IReadOnlyList<FPackageIndex> materials, IReadOnlyList<TVertex> vertices, int numLods, Transform transform = null) : this(export)
{
var hasCustomUvs = lod.ExtraUV.IsValueCreated;
UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords;
var hasCustomUvs = lod.ExtraUvs.Length > 0;
UvCount = hasCustomUvs ? Math.Max(lod.ExtraUvs.Length, numLods) : lod.ExtraUvs.Length + 1;
IsTwoSided = lod.IsTwoSided;
Indices = new uint[lod.Indices.Value.Length];
Indices = new uint[lod.Indices.Length];
for (int i = 0; i < Indices.Length; i++)
{
Indices[i] = (uint) lod.Indices.Value[i];
Indices[i] = lod.Indices[i];
}
Materials = new Material[materials.Count];
@ -129,11 +126,11 @@ public abstract class UModel : IRenderableModel
Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material();
}
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0};
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0 };
_vertexAttributes[(int) EAttribute.BonesId].Enabled =
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[];
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is SkinnedMeshVertex[];
Vertices = new float[lod.NumVerts * VertexSize];
Vertices = new float[vertices.Count * VertexSize];
for (int i = 0; i < vertices.Count; i++)
{
var count = 0;
@ -149,18 +146,18 @@ public abstract class UModel : IRenderableModel
Vertices[baseIndex + count++] = vert.Tangent.X;
Vertices[baseIndex + count++] = vert.Tangent.Z;
Vertices[baseIndex + count++] = vert.Tangent.Y;
Vertices[baseIndex + count++] = vert.UV.U;
Vertices[baseIndex + count++] = vert.UV.V;
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U - 1 : .5f;
Vertices[baseIndex + count++] = vert.Uv.U;
Vertices[baseIndex + count++] = vert.Uv.V;
Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUvs[0][i].U - 1 : .5f;
if (HasVertexColors)
{
Vertices[baseIndex + count++] = lod.VertexColors[i].ToPackedARGB();
Vertices[baseIndex + count++] = lod.VertexColors![0].Colors[i].ToPackedARGB();
}
if (vert is CSkelMeshVertex skelVert)
if (vert is SkinnedMeshVertex skelVert)
{
int max = skelVert.Influences.Count;
int max = skelVert.Influences.Length;
for (int j = 0; j < 8; j++)
{
var boneID = j < max ? skelVert.Influences[j].Bone : (ushort) 0;
@ -172,10 +169,10 @@ public abstract class UModel : IRenderableModel
}
}
Sections = new Section[lod.Sections.Value.Length];
Sections = new Section[lod.Sections.Length];
for (var s = 0; s < Sections.Length; s++)
{
var section = lod.Sections.Value[s];
var section = lod.Sections[s];
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex);
if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
}
@ -392,10 +389,9 @@ public abstract class UModel : IRenderableModel
return socket.Transform.LocalMatrix * socketRelation;
}
public bool Save(out string label, out string savedFilePath)
public void AddToExportSession(ExportSession session)
{
var toSave = new Exporter(_export, UserSettings.Default.ExportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
session.Add(_export);
}
public virtual void Dispose()

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Exports.Texture;
@ -20,7 +21,7 @@ public class Options
public int SelectedMorph { get; private set; }
public int SelectedAnimation{ get; private set; }
public readonly Dictionary<FGuid, UModel> Models;
public readonly Dictionary<FGuid, IRenderableModel> Models;
public readonly Dictionary<FGuid, Texture> Textures;
public readonly List<Light> Lights;
@ -33,7 +34,7 @@ public class Options
public Options()
{
Models = new Dictionary<FGuid, UModel>();
Models = new Dictionary<FGuid, IRenderableModel>();
Textures = new Dictionary<FGuid, Texture>();
Lights = new List<Light>();
@ -110,30 +111,30 @@ public class Options
public void RemoveModel(FGuid guid)
{
if (!TryGetModel(guid, out var m) || m is not UModel model)
if (!TryGetModel(guid, out var m) || m is null)
return;
DetachAndRemoveModels(model, true);
model.Dispose();
DetachAndRemoveModels(m, true);
m.Dispose();
Models.Remove(guid);
}
private void DetachAndRemoveModels(UModel model, bool detach)
private void DetachAndRemoveModels(IRenderableModel model, bool detach)
{
foreach (var socket in model.Sockets.ToList())
{
foreach (var info in socket.AttachedModels)
{
if (!TryGetModel(info.Guid, out var m) || m is not UModel attachedModel)
if (!TryGetModel(info.Guid, out var m) || m is null)
continue;
var t = attachedModel.GetTransform();
if (attachedModel.IsProp)
var t = m.GetTransform();
if (m.IsProp)
{
attachedModel.Attachments.SafeDetach(model, t);
m.Attachments.SafeDetach(model, t);
RemoveModel(info.Guid);
}
else if (detach) attachedModel.Attachments.SafeDetach(model, t);
else if (detach) m.Attachments.SafeDetach(model, t);
}
if (socket.IsVirtual)
@ -209,8 +210,8 @@ public class Options
return texture != null;
}
public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model);
public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model);
public bool TryGetModel([MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(SelectedModel, out model);
public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(guid, out model);
public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section);
public bool TryGetSection(FGuid guid, out Section section)
@ -223,7 +224,7 @@ public class Options
section = null;
return false;
}
public bool TryGetSection(UModel model, out Section section)
public bool TryGetSection(IRenderableModel model, out Section section)
{
if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
section = model.Sections[SelectedSection]; else section = null;

View File

@ -6,13 +6,13 @@ using System.Threading;
using System.Windows;
using CUE4Parse_Conversion.Animations;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Options;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.Nanite;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
@ -29,7 +29,6 @@ using FModel.Views.Snooper.Lights;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
using OpenTK.Windowing.GraphicsLibraryFramework;
using UModel = FModel.Views.Snooper.Models.UModel;
namespace FModel.Views.Snooper;
@ -175,7 +174,7 @@ public class Renderer : IDisposable
t.Scale = offset.Scale3D;
}
UModel addedModel = null;
IRenderableModel addedModel = null;
switch (export)
{
case UStaticMesh st:
@ -185,7 +184,7 @@ public class Renderer : IDisposable
{
addedModel.AddInstance(t);
}
else if (st.TryConvert(out var mesh))
else if (st.TryConvert(out var mesh, EMeshQuality.Highest))
{
addedModel = new StaticModel(st, mesh, t);
Options.Models[guid] = addedModel;
@ -195,7 +194,7 @@ public class Renderer : IDisposable
case USkeletalMesh sk:
{
guid = Guid.NewGuid();
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh))
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh, EMeshQuality.Highest))
{
addedModel = new SkeletalModel(sk, mesh, t);
Options.Models[guid] = addedModel;
@ -344,7 +343,7 @@ public class Renderer : IDisposable
wnd.WindowShouldClose(true, true);
}
private void LoadStaticMesh(UStaticMesh original, ENaniteMeshFormat naniteFormat = ENaniteMeshFormat.OnlyNormalLODs)
private void LoadStaticMesh(UStaticMesh original, ENaniteMeshFormat naniteFormat = ENaniteMeshFormat.NoNanite)
{
var guid = original.LightingGuid;
if (Options.TryGetModel(guid, out var model))
@ -354,7 +353,7 @@ public class Renderer : IDisposable
return;
}
if (!original.TryConvert(out var mesh, naniteFormat))
if (!original.TryConvert(out var mesh, EMeshQuality.Highest, naniteFormat))
return;
Options.Models[guid] = new StaticModel(original, mesh);
@ -364,7 +363,7 @@ public class Renderer : IDisposable
private void LoadSkeletalMesh(USkeletalMesh original)
{
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh, EMeshQuality.Highest)) return;
var skeletalModel = new SkeletalModel(original, mesh);
Options.Models[guid] = skeletalModel;
@ -395,7 +394,7 @@ public class Renderer : IDisposable
return;
}
if (!editorCube.TryConvert(out var mesh))
if (!editorCube.TryConvert(out var mesh, EMeshQuality.Highest))
return;
Options.Models[guid] = new StaticModel(original, mesh);
@ -598,7 +597,7 @@ public class Renderer : IDisposable
if (bSpline && model is SplineModel splineModel)
splineModel.AddComponent((USplineMeshComponent)staticMeshComp);
}
else if (m.TryConvert(out var mesh, UserSettings.Default.NaniteMeshExportFormat))
else if (m.TryConvert(out var mesh, EMeshQuality.Highest, UserSettings.Default.NaniteMeshExportFormat))
{
model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform);
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
@ -713,7 +712,7 @@ public class Renderer : IDisposable
continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
if (!parameters.TryGetLinearColor(out var color, "Color"))
color = FLinearColor.Gray;
@ -726,7 +725,7 @@ public class Renderer : IDisposable
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
var parameters = new CMaterialParams2();
unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer);
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
indexAsByte = byte.MaxValue;

View File

@ -317,7 +317,7 @@ public class Material : IDisposable
}
}
public bool ImGuiTextures(Dictionary<string, Texture> icons, UModel model)
public bool ImGuiTextures(Dictionary<string, Texture> icons, IRenderableModel model)
{
if (ImGui.BeginTable("material_textures", 2))
{

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Framework;
using ImGuiNET;
@ -38,30 +37,10 @@ public class Swap
}
}
public class Save
{
public bool Value;
public string Label;
public string Path;
public Save()
{
Reset();
}
public void Reset()
{
Value = false;
Label = string.Empty;
Path = string.Empty;
}
}
public class SnimGui
{
public readonly ImGuiController Controller;
private readonly Swap _swapper = new ();
private readonly Save _saver = new ();
private readonly string _renderer;
private readonly string _version;
private readonly float _tableWidth;
@ -96,7 +75,7 @@ public class SnimGui
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
tracker.ImGuiTimeline(s, _saver, icons, animations, _outlinerSize, Controller.FontSemiBold));
tracker.ImGuiTimeline(s, icons, animations, _outlinerSize, Controller.FontSemiBold));
Window("World", () => DrawWorld(s), false);
@ -144,36 +123,13 @@ public class SnimGui
}
});
Modal("Saved", _saver.Value, () =>
{
ImGui.TextWrapped($"Successfully saved {_saver.Label}");
ImGui.Separator();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
_saver.Reset();
ImGui.CloseCurrentPopup();
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Show In Explorer", size))
{
Process.Start("explorer.exe", $"/select, \"{_saver.Path.Replace('/', '\\')}\"");
_saver.Reset();
ImGui.CloseCurrentPopup();
}
});
ExportModal.Instance.Draw();
}
private void DrawWorld(Snooper s)
{
if (ImGui.BeginTable("world_details", 2, ImGuiTableFlags.SizingStretchProp))
{
var b = false;
var length = s.Renderer.Options.Models.Count;
NoFramePaddingOnY(() =>
@ -184,31 +140,7 @@ public class SnimGui
if (ImGui.SmallButton("Save All"))
{
foreach (var model in s.Renderer.Options.Models.Values)
{
b |= model.Save(out _, out _);
}
}
});
Modal("Saved", b, () =>
{
ImGui.TextWrapped($"Successfully saved {length} models");
ImGui.Separator();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Show In Explorer", size))
{
Process.Start("explorer.exe", $"/select, \"{UserSettings.Default.ModelDirectory.Replace('/', '\\')}\"");
ImGui.CloseCurrentPopup();
ExportModal.Instance.Export(s.Renderer.Options.Models.Values, UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
}
});
@ -412,7 +344,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (ImGui.MenuItem("Save"))
{
s.WindowShouldFreeze(true);
_saver.Value = model.Save(out _saver.Label, out _saver.Path);
ExportModal.Instance.Export([model], UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
s.WindowShouldFreeze(false);
}
if (ImGui.MenuItem("Animate", model is SkeletalModel))
@ -591,7 +523,11 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
ImGui.PushID(0); ImGui.BeginDisabled(model.TransformsCount < 2);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.SliderInt("", ref model.SelectedInstance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp);
var instance = model.SelectedInstance;
if (ImGui.SliderInt("", ref instance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp))
{
model.SelectedInstance = instance;
}
ImGui.EndDisabled(); ImGui.PopID();
if (ImGui.BeginTable("guizmo_controls", 2, ImGuiTableFlags.SizingStretchProp))
@ -669,7 +605,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PopStyleVar();
}
private void DrawMaterialInspector(Dictionary<string, Texture> icons, UModel model, Section section)
private void DrawMaterialInspector(Dictionary<string, Texture> icons, IRenderableModel model, Section section)
{
var material = model.Materials[section.MaterialIndex];
@ -906,7 +842,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.End();
}
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel> content, bool styled = true)
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, IRenderableModel> content, bool styled = true)
{
Window(name, () =>
{
@ -915,7 +851,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
}, styled);
}
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel, Section> content, bool styled = true)
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, IRenderableModel, Section> content, bool styled = true)
{
MeshWindow(name, renderer, (icons, model) =>
{

View File

@ -0,0 +1,244 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.StreamingLevelFilterWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="520"
d:DataContext="{d:DesignInstance Type=vm:StreamingLevelFilterViewModel, IsDesignTimeCreatable=False}"
xmlns:vm="clr-namespace:FModel.ViewModels"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.60'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.28'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}">
<Setter Property="Title" Value="Streamed Levels" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<Style x:Key="LevelTreeViewItemStyle" TargetType="TreeViewItem" BasedOn="{StaticResource TreeViewItemStyle}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border x:Name="Border"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}" />
<Border x:Name="SpotlightLayer"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Background="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BackgroundBrush}"
BorderBrush="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
adonisExtensions:CursorSpotlightExtension.MouseEventSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TreeViewItem}}"
SnapsToDevicePixels="False" />
<ToggleButton x:Name="Expander"
Grid.Row="0" Grid.Column="0"
Width="16" Height="16"
Margin="8,0,8,0"
Focusable="False" ClickMode="Press"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
RenderTransformOrigin="0.5,0.5">
<ToggleButton.Template>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24" Background="Transparent">
<Path Fill="{TemplateBinding Foreground}"
Data="{StaticResource ArrowIcon}" />
</Canvas>
</Viewbox>
</ControlTemplate>
</ToggleButton.Template>
<ToggleButton.RenderTransform>
<RotateTransform x:Name="ExpanderRotateTransform" Angle="-90" />
</ToggleButton.RenderTransform>
</ToggleButton>
<ContentPresenter x:Name="PART_Header"
Grid.Row="0" Grid.Column="1"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="0,4,8,4" />
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1" Grid.Column="1" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="False">
<Setter TargetName="ItemsHost" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" To="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="ExpanderRotateTransform"
Storyboard.TargetProperty="Angle"
Duration="0:0:0.2" From="0" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
<Trigger Property="HasItems" Value="False">
<Setter TargetName="Expander" Property="Visibility" Value="Hidden" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="10 10 10 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Grid.Row="0">
<Grid DockPanel.Dock="Left" VerticalAlignment="Center" HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{Binding TotalCount, Mode=OneWay, FallbackValue=0}"
FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="streamed levels" FontSize="10"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" Width="1" Margin="16 2"
Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
<TextBlock Grid.Row="0" Grid.RowSpan="2" Grid.Column="2" TextWrapping="Wrap" FontSize="10" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}">
<Run Text="{Binding WorldName, Mode=OneWay, StringFormat='{}{0} loads these levels at runtime, select which ones to export.'}"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
<LineBreak />
<Run Text="Unchecked levels will be referenced but not exported." />
<LineBreak />
<Run Text="Skip to ignore all of them." />
</TextBlock>
</Grid>
</DockPanel>
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0 5" />
<TreeView Grid.Row="2" ItemsSource="{Binding Children}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True"
ItemContainerStyle="{StaticResource LevelTreeViewItemStyle}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:ActorNodeVm}" ItemsSource="{Binding Children}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" IsChecked="{Binding IsChecked, Mode=TwoWay}"
IsThreeState="True"
VerticalAlignment="Center" Margin="0 0 5 0" />
<TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center" />
<TextBlock Grid.Column="2" Margin="5 0 0 0" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="{Binding Children.Count, Mode=OneWay, StringFormat='({0})'}" />
</Grid>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type vm:StreamingLevelNodeVm}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
VerticalAlignment="Center" Margin="0 0 5 0" />
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
<Border Grid.Row="1"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer1BackgroundBrush}}"
adonisExtensions:LayerExtension.IncreaseLayer="True">
<Grid Margin="30, 12, 6, 12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="True"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Skip" Click="OnSkipClick"
Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}">
<Button.Resources>
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentBrush}" Color="#D49220" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentHighlightBrush}" Color="#E0A030" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentIntenseHighlightBrush}" Color="#E0A030" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBrush}" Color="#B87C10" />
<SolidColorBrush x:Key="{x:Static adonisUi:Brushes.AccentInteractionBorderBrush}" Color="#9A6800" />
</Button.Resources>
</Button>
<Button Grid.Column="2" MinWidth="78" Margin="0 0 12 0" IsDefault="False" IsCancel="False"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK"
Click="OnOkClick" />
</Grid>
</Border>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -0,0 +1,25 @@
using System.Windows;
using System.Windows.Controls;
using FModel.ViewModels;
namespace FModel.Views;
public partial class StreamingLevelFilterWindow
{
public StreamingLevelFilterWindow(StreamingLevelFilterViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
private void OnOkClick(object sender, RoutedEventArgs e) => Close();
private void OnSkipClick(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: StreamingLevelFilterViewModel viewModel })
{
viewModel.SkipAll();
Close();
}
}
}

View File

@ -17,9 +17,6 @@ For installation, follow the instructions from [here](https://github.com/4sval/F
### Sponsorship:
<p>
<a href="https://www.jetbrains.com/">
<img src="https://cdn.fmodel.app/i/svg/jetbrains.svg" width="256px">
</a>
<a href="https://1password.com/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.fmodel.app/i/svg/1password-light.svg">