Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions FModel/Framework/RangeObservableCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;

namespace FModel.Framework;

public sealed class RangeObservableCollection<T> : ObservableCollection<T>
{
private static readonly PropertyChangedEventArgs CountChanged = new(nameof(Count));
private static readonly PropertyChangedEventArgs IndexerChanged = new("Item[]");
private bool _suppressNotification;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
Expand All @@ -20,15 +23,26 @@ public void AddRange(IEnumerable<T> list)
if (list == null)
throw new ArgumentNullException(nameof(list));

_suppressNotification = true;

var changed = false;
foreach (var item in list)
Add(item);
{
Items.Add(item);
changed = true;
}

if (!changed || _suppressNotification)
return;

_suppressNotification = false;
OnPropertyChanged(CountChanged);
OnPropertyChanged(IndexerChanged);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

/// <summary>
/// Adds an item while constructing a collection that has not been published to a binding yet.
/// </summary>
public void AddWithoutNotification(T item) => Items.Add(item);

public void SetSuppressionState(bool state)
{
_suppressNotification = state;
Expand All @@ -38,4 +52,4 @@ public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedActio
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
}
}
}
6 changes: 3 additions & 3 deletions FModel/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
<MultiBinding StringFormat="{}'{0}' has {1} folders and {2} packages">
<Binding Path="SelectedItem.Header" ElementName="AssetsFolderName" FallbackValue="None" />
<Binding Path="SelectedItem.FoldersView.Count" ElementName="AssetsFolderName" FallbackValue="0" />
<Binding Path="SelectedItem.AssetsList.Assets.Count" ElementName="AssetsFolderName" FallbackValue="0" />
<Binding Path="SelectedItem.AssetsList.Count" ElementName="AssetsFolderName" FallbackValue="0" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Expand Down Expand Up @@ -371,7 +371,7 @@
<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.Count, ElementName=AssetsFolderName, FallbackValue=0}" 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="1" Text="Folders Count" VerticalAlignment="Center" HorizontalAlignment="Right" />
Expand All @@ -386,7 +386,7 @@
</Grid>
</TabItem>
<TabItem Style="{StaticResource TabItemFillSpace}"
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
Header="{Binding SelectedItem.AssetsList.Count, FallbackValue=0, ElementName=AssetsFolderName}"
HeaderStringFormat="{}{0} Packages">
<DockPanel>

Expand Down
116 changes: 107 additions & 9 deletions FModel/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
Expand All @@ -7,6 +8,8 @@
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
Expand Down Expand Up @@ -106,6 +109,14 @@ await Task.WhenAll(
ApplicationViewModel.InitZlib()
);

var auxiliaryInitialization = Task.WhenAll(
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
);

await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
Expand All @@ -116,16 +127,12 @@ await Task.WhenAll(
await Task.WhenAll(
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
auxiliaryInitialization,
Task.Run(() =>
{
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.GameDisplayName);
}),
UserSettings.Default.DecompileLua ? ApplicationViewModel.InitUnluac() : Task.CompletedTask
})
).ConfigureAwait(false);

#if DEBUG
Expand Down Expand Up @@ -254,6 +261,9 @@ private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
private void OnPreviewTexturesToggled(object sender, RoutedEventArgs e) => ItemContainerGenerator_StatusChanged(AssetsExplorer.ItemContainerGenerator, EventArgs.Empty);
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
if (_applicationView.IsAssetPreviewLoadingSuspended)
return;

if (sender is not ItemContainerGenerator { Status: GeneratorStatus.ContainersGenerated } generator)
return;

Expand All @@ -277,6 +287,94 @@ private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
}
}

public void RefreshVisibleAssetPreviews()
=> ItemContainerGenerator_StatusChanged(AssetsExplorer.ItemContainerGenerator, EventArgs.Empty);

public async Task<bool> SelectFolderAsync(IReadOnlyList<TreeItem> path)
{
if (path.Count == 0)
return false;

// Set the complete model path first. A virtualized container will pick up the
// expansion state whenever WPF realizes it, independently of UI timing.
for (var i = 0; i < path.Count - 1; i++)
path[i].IsExpanded = true;

ItemsControl parent = AssetsFolderName;
TreeViewItem container = null;

for (var i = 0; i < path.Count; i++)
{
container = await GetTreeViewItemAsync(parent, path[i]);
if (container == null)
return false;

// Only ancestors must be expanded. Expanding the target itself can realize a
// large child subtree even though Go To never needs to display those children.
if (i < path.Count - 1)
{
container.IsExpanded = true;
}

parent = container;
}

container.IsSelected = false;
container.IsSelected = true;
await Dispatcher.InvokeAsync(static () => { }, DispatcherPriority.Background);
return ReferenceEquals(AssetsFolderName.SelectedItem, path[^1]);
}

private async Task<TreeViewItem> GetTreeViewItemAsync(ItemsControl parent, TreeItem item)
{
// FoldersView is bound with IsAsync=True. For large sibling collections its sorted
// view can take substantially longer than a fixed number of dispatcher turns.
var timeoutAt = DateTime.UtcNow + TimeSpan.FromSeconds(15);
while (DateTime.UtcNow < timeoutAt)
{
parent.ApplyTemplate();
var presenter = parent.Template.FindName("ItemsHost", parent) as ItemsPresenter ??
FindVisualChild<ItemsPresenter>(parent);
if (presenter == null)
{
parent.UpdateLayout();
presenter = FindVisualChild<ItemsPresenter>(parent);
}

presenter?.ApplyTemplate();
var index = parent.Items.IndexOf(item);
if (index >= 0 && presenter != null && VisualTreeHelper.GetChildrenCount(presenter) > 0 &&
VisualTreeHelper.GetChild(presenter, 0) is NavigableVirtualizingStackPanel panel)
{
_ = panel.Children; // Ensure that the item generator is connected.
panel.BringItemIntoView(index);
parent.UpdateLayout();

if (parent.ItemContainerGenerator.ContainerFromIndex(index) is TreeViewItem container)
return container;
}

await Task.Delay(1);
}

return null;
}

private static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is T match)
return match;

if (FindVisualChild<T>(child) is { } descendant)
return descendant;
}

return null;
}

private void OnAssetsTreeSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (sender is not TreeView { SelectedItem: TreeItem }) return;
Expand Down Expand Up @@ -338,7 +436,7 @@ private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
}

var childFolder = folder;
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Count == 0)
{
childFolder.IsExpanded = true;
childFolder = childFolder.Folders[0];
Expand All @@ -360,14 +458,14 @@ private async void OnFoldersPreviewKeyDown(object sender, KeyEventArgs e)
if (e.Key != Key.Enter || sender is not TreeView treeView || treeView.SelectedItem is not TreeItem folder)
return;

if ((folder.IsExpanded || folder.Folders.Count == 0) && folder.AssetsList.Assets.Count > 0)
if ((folder.IsExpanded || folder.Folders.Count == 0) && folder.AssetsList.Count > 0)
{
_applicationView.SelectedLeftTabIndex++;
return;
}

var childFolder = folder;
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Count == 0)
{
childFolder.IsExpanded = true;
childFolder = childFolder.Folders[0];
Expand Down
39 changes: 37 additions & 2 deletions FModel/ViewModels/ApplicationViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using CUE4Parse_Conversion.Textures.BC;
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
Expand All @@ -27,6 +28,10 @@ namespace FModel.ViewModels;

public class ApplicationViewModel : ViewModel
{
private readonly object _providerStatusLock = new();
private (string Label, string Prefix)? _pendingProviderStatus;
private bool _providerStatusScheduled;

private EBuildKind _build;
public EBuildKind Build
{
Expand Down Expand Up @@ -60,6 +65,8 @@ public bool IsAssetsExplorerVisible
}
}

public bool IsAssetPreviewLoadingSuspended { get; set; }

private int _selectedLeftTabIndex;
public int SelectedLeftTabIndex
{
Expand Down Expand Up @@ -113,13 +120,13 @@ public ApplicationViewModel()
CUE4Parse.Provider.VfsRegistered += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count} Archives ({reader.Name})", "Registered");
QueueProviderStatus($"{count} Archives ({reader.Name})", "Registered");
CUE4Parse.GameDirectory.Add(reader);
};
CUE4Parse.Provider.VfsMounted += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count:N0} Packages ({reader.Name})", "Mounted");
QueueProviderStatus($"{count:N0} Packages ({reader.Name})", "Mounted");
CUE4Parse.GameDirectory.Verify(reader);
};
CUE4Parse.Provider.VfsUnmounted += (sender, _) =>
Expand All @@ -135,6 +142,34 @@ public ApplicationViewModel()
Status.SetStatus(EStatusKind.Ready);
}

private void QueueProviderStatus(string label, string prefix)
{
lock (_providerStatusLock)
{
_pendingProviderStatus = (label, prefix);
if (_providerStatusScheduled)
return;

_providerStatusScheduled = true;
}

_ = Application.Current.Dispatcher.BeginInvoke(PublishProviderStatus, DispatcherPriority.Background);
}

private void PublishProviderStatus()
{
(string Label, string Prefix)? update;
lock (_providerStatusLock)
{
update = _pendingProviderStatus;
_pendingProviderStatus = null;
_providerStatusScheduled = false;
}

if (update is { } status)
Status.UpdateStatusLabel(status.Label, status.Prefix);
}

public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
Expand Down
Loading