export session window

This commit is contained in:
Asval 2026-06-13 17:25:03 +02:00
parent ab5f6b06f5
commit 0ca914f07a
11 changed files with 1135 additions and 39 deletions

@ -1 +1 @@
Subproject commit 885dd3c4012cf5d85a66e2b06db36c692c53d751
Subproject commit b843741f014608e4ff4791829687683eb045aa59

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

@ -157,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">
@ -175,13 +182,6 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Export Session">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<StaticResource ResourceKey="ExportSessionIcon" />
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="Settings" Command="{Binding MenuCommand}" CommandParameter="Settings" />
<MenuItem Header="Help" >
@ -618,7 +618,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>
@ -736,6 +738,7 @@
<Button x:Name="ExportSessionButton"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Center"
Command="{Binding MenuCommand}" CommandParameter="Views_ExportSession"
ToolTip="There are items waiting in the export queue, click to open the export session and process them!"
Background="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}"
Style="{StaticResource StatusBarButton}"

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

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
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 ExportSessionOptionsViewModel : ViewModel
{
// --- Override toggle ---
public bool OverrideOptions { get; set => SetProperty(ref field, value); }
// --- Mesh ---
public IEnumerable<EMeshFormat> MeshFormats { get; } = Enum.GetValues<EMeshFormat>();
public EMeshFormat SelectedMeshFormat { get; set => SetProperty(ref field, value); }
public IEnumerable<ENaniteMeshFormat> NaniteMeshFormats { get; } = Enum.GetValues<ENaniteMeshFormat>();
public ENaniteMeshFormat SelectedNaniteMeshFormat { get; set => SetProperty(ref field, value); }
public IEnumerable<EMeshQuality> MeshQualities { get; } = Enum.GetValues<EMeshQuality>();
public EMeshQuality SelectedMeshQuality { get; set => SetProperty(ref field, value); }
// --- Socket / Compression ---
public IEnumerable<ESocketFormat> SocketFormats { get; } = Enum.GetValues<ESocketFormat>();
public ESocketFormat SelectedSocketFormat { get; set => SetProperty(ref field, value); }
public IEnumerable<EFileCompressionFormat> CompressionFormats { get; } = Enum.GetValues<EFileCompressionFormat>();
public EFileCompressionFormat SelectedCompressionFormat { get; set => SetProperty(ref field, value); }
// --- Material ---
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); }
// --- Texture ---
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); }
public bool ExportHdrTexturesAsHdr { get; set => SetProperty(ref field, value); }
// --- Morph ---
public bool ExportMorphTargets { get; set => SetProperty(ref field, value); }
public ExportSessionOptionsViewModel()
{
ResetToUserDefaults();
}
public void ResetToUserDefaults()
{
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;
}
public ExportOptions BuildOptions() => new(
SelectedMeshFormat,
SelectedNaniteMeshFormat,
SelectedMeshQuality,
SelectedTexturePlatform,
SelectedTextureFormat,
100,
ExportHdrTexturesAsHdr,
SelectedMaterialDepth,
ExportMaterials,
ExportMorphTargets,
SelectedSocketFormat,
SelectedCompressionFormat
);
}

View File

@ -1,10 +1,19 @@
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 FModel.Extensions;
using FModel.Framework;
using FModel.Settings;
using FModel.Views.Snooper;
using Serilog.Events;
namespace FModel.ViewModels;
@ -12,12 +21,33 @@ public class ExportSessionViewModel : ViewModel
{
public static ExportSessionViewModel Instance { get; } = new();
private int _previousCount;
private DispatcherTimer? _toastTimer;
public bool ShowQueueToast
{
get;
set => SetProperty(ref field, value);
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;
@ -26,17 +56,90 @@ public class ExportSessionViewModel : ViewModel
get
{
if (_session != null) return _session;
_session = new ExportSession(UserSettings.Default.OutputDirectory, UserSettings.GetExportOptions());
_session = new ExportSession(UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
_session.PropertyChanged += OnSessionPropertyChanged;
return _session;
}
}
private ExportSessionViewModel()
{
public ExportSessionOptionsViewModel 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;
@ -44,42 +147,237 @@ public class ExportSessionViewModel : ViewModel
var count = _session?.TotalQueued ?? 0;
Application.Current?.Dispatcher.InvokeAsync(() =>
{
switch (count)
if (count > 0 && _previousCount == 0)
{
case 1 when _previousCount == 0:
ShowToast();
break;
case 0:
HideToast();
break;
ClearExportHistory();
}
ShowQueueToast = count switch
{
> 0 when _previousCount == 0 => true,
0 => false,
_ => ShowQueueToast
};
_previousCount = count;
RaisePropertyChanged(nameof(CanExport));
});
}
private void ShowToast()
public async Task ExportAsync()
{
ShowQueueToast = true;
if (_toastTimer == null)
if (IsRunning || Session.TotalQueued == 0) return;
IsRunning = true;
IsFinished = false;
IsCanceled = false;
CompletedCount = 0;
SucceededCount = 0;
FailedCount = 0;
_stopwatch.Restart();
_cts = new CancellationTokenSource();
StartUiTimer();
// TODO: when Options.OverrideOptions is true, propagate Options.BuildOptions() into Session before running.
// For now we run with whatever options the session was created with.
var progress = new Progress<ExportProgress>(p =>
{
_toastTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(7.5) };
_toastTimer.Tick += (_, _) => HideToast();
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(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();
});
}
_toastTimer.Stop();
_toastTimer.Start();
}
private void HideToast()
public void CancelExport()
{
ShowQueueToast = false;
_toastTimer?.Stop();
_cts?.Cancel();
}
public void ClearQueue()
{
_session?.Clear();
ClearExportHistory();
}
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 objectName = log.GetContext("ObjectName");
var filePath = log.GetContext("FilePath");
var cg = FindOrCreateClass(className);
var og = FindOrCreateObject(cg, objectName);
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 name)
{
var og = cg.Objects.FirstOrDefault(o => o.Name == name);
if (og != null) return og;
og = new ObjectGroupViewModel(name);
cg.Objects.Add(og);
return og;
}
private void ResetState()
{
_cts?.Cancel();
_cts = null;
_stopwatch.Reset();
IsRunning = false;
StopUiTimer();
ClearExportHistory();
}
public void Invalidate()
{
ResetState();
_toastTimer?.Stop();
_session?.PropertyChanged -= OnSessionPropertyChanged;
_session = null;
Application.Current?.Dispatcher.InvokeAsync(HideToast);
}
}
public class ClassGroupViewModel(string name) : ViewModel
{
public string Name { get; } = name;
public ObservableCollection<ObjectGroupViewModel> Objects { get; } = [];
public int ErrorCount
{
get;
set
{
SetProperty(ref field, value);
RaisePropertyChanged(nameof(HasErrors));
}
}
public override bool HasErrors => ErrorCount > 0;
}
public class ObjectGroupViewModel(string name) : ViewModel
{
public string Name { get; } = name;
public ObservableCollection<LogEntryViewModel> Entries { get; } = [];
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 string Message { get; } = $"[{log.Timestamp:HH:mm:ss.fff}] {log.RenderMessage()}";
public Exception? Exception { get; } = log.Exception;
}

View File

@ -0,0 +1,632 @@
<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}"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer0BackgroundBrush}}"
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:viewModels="clr-namespace:FModel.ViewModels"
xmlns:serilog="clr-namespace:Serilog.Events;assembly=Serilog"
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="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 viewModels:LogEntryViewModel}">
<Grid Margin="0 1" ToolTip="{Binding Exception}">
<Grid.ColumnDefinitions>
<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" x:Name="LevelText"
Text="{Binding Message}"
FontSize="11" FontFamily="Consolas"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
VerticalAlignment="Center" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Level}" Value="{x:Static serilog:LogEventLevel.Verbose}">
<Setter TargetName="LevelIcon" Property="Data" Value="{StaticResource NoteIcon}" />
<Setter TargetName="LevelIcon" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}" />
</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}" />
<Setter TargetName="LevelText" Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</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}" />
<Setter TargetName="LevelText" Property="Foreground" Value="#FFA726" />
</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}" />
<Setter TargetName="LevelText" Property="Foreground" Value="#EF5350" />
</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}" />
<Setter TargetName="LevelText" Property="Foreground" Value="#EF5350" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="ObjectGroupHeaderTemplate" DataType="{x:Type viewModels:ObjectGroupViewModel}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<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}}" />
<Button Grid.Column="3" 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 viewModels: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}}">
<Run.Style>
<Style TargetType="Run">
<Setter Property="Text" Value="{Binding EtaTime, Mode=OneWay, Converter={x:Static converters:TimeSpanConverter.Instance}, FallbackValue=--:--}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsFinished}" Value="True">
<Setter Property="Text" Value="Done" />
</DataTrigger>
<DataTrigger Binding="{Binding IsCanceled}" Value="True">
<Setter Property="Text" Value="--:--" />
</DataTrigger>
</Style.Triggers>
</Style>
</Run.Style>
</Run>
</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>
</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}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.CanContentScroll="True">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:ClassGroupViewModel}" ItemsSource="{Binding Objects}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ClassGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type viewModels:ObjectGroupViewModel}" ItemsSource="{Binding Entries}"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<ContentPresenter Content="{Binding}"
ContentTemplate="{StaticResource ObjectGroupHeaderTemplate}"
HorizontalAlignment="Stretch" />
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels: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="10" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" VerticalAlignment="Center"
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="2" VerticalAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
Text="Override default export options for this session" />
</Grid>
<Grid Grid.Row="2" IsEnabled="{Binding Options.OverrideOptions}">
<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" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Separator Grid.Row="0" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" Tag="MESH" 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" Margin="0 0 0 5"
ItemsSource="{Binding Options.MeshFormats}"
SelectedItem="{Binding Options.SelectedMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Mesh Quality" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="2" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.MeshQualities}"
SelectedItem="{Binding Options.SelectedMeshQuality, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Socket Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="3" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.SocketFormats}"
SelectedItem="{Binding Options.SelectedSocketFormat, Mode=TwoWay}">
<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" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="4" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.CompressionFormats}"
SelectedItem="{Binding Options.SelectedCompressionFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="5" Grid.Column="0" Text="Nanite Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="5" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.NaniteMeshFormats}"
SelectedItem="{Binding Options.SelectedNaniteMeshFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Separator Grid.Row="6" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" Tag="TEXTURE" Margin="0 5 0 5" />
<TextBlock Grid.Row="7" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="7" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.TextureFormats}"
SelectedItem="{Binding Options.SelectedTextureFormat, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Texture Platform" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="8" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.TexturePlatforms}"
SelectedItem="{Binding Options.SelectedTexturePlatform, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="9" Grid.Column="0" Text="Save HDR as .hdr" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="9" Grid.Column="2" Margin="0 5 0 5"
IsChecked="{Binding Options.ExportHdrTexturesAsHdr, Mode=TwoWay}"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<Separator Grid.Row="10" Grid.ColumnSpan="3" Style="{StaticResource CustomSeparator}" Tag="MATERIAL &amp; MORPH" Margin="0 5 0 5" />
<TextBlock Grid.Row="11" Grid.Column="0" Text="Material Depth" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="11" Grid.Column="2" Margin="0 0 0 5"
ItemsSource="{Binding Options.MaterialDepths}"
SelectedItem="{Binding Options.SelectedMaterialDepth, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</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" Click="OnExportOrOkClick">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Content" Value="Export" />
<Setter Property="IsEnabled" Value="{Binding CanExport}" />
<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="Content" Value="OK" />
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</Border>
</Grid>
</adonisControls:AdonisWindow>

View File

@ -0,0 +1,54 @@
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();
DataContext = ExportSessionViewModel.Instance;
}
private async void OnExportOrOkClick(object sender, RoutedEventArgs e)
{
if (sender is not Button { DataContext: ExportSessionViewModel viewModel })
return;
if (viewModel.IsFinished) Close();
else if (viewModel.CanExport) await viewModel.ExportAsync();
}
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 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

@ -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

@ -21,7 +21,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>
@ -50,6 +50,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>