mirror of
https://github.com/4sval/FModel.git
synced 2026-09-23 16:55:49 -05:00
Merge remote-tracking branch 'origin/dev' into feature/new-viewer
# Conflicts: # CUE4Parse
This commit is contained in:
36
FModel/Extensions/ListBoxExtensions.cs
Normal file
36
FModel/Extensions/ListBoxExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace FModel.Extensions;
|
||||
|
||||
public static class ListBoxExtensions
|
||||
{
|
||||
public static ListBoxItem RevealItem(this ListBox list, object item, bool alignToTop = false)
|
||||
{
|
||||
var index = list.Items.IndexOf(item);
|
||||
if (index < 0)
|
||||
return null;
|
||||
|
||||
list.ApplyTemplate();
|
||||
list.UpdateLayout();
|
||||
if (alignToTop && list.FindVisualChild<ScrollViewer>() is { } scroll)
|
||||
{
|
||||
scroll.ScrollToVerticalOffset(index);
|
||||
list.UpdateLayout();
|
||||
}
|
||||
|
||||
if (list.FindVisualChild<VirtualizingPanel>() is { } panel)
|
||||
{
|
||||
panel.BringIndexIntoViewPublic(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.ScrollIntoView(item);
|
||||
}
|
||||
|
||||
list.UpdateLayout();
|
||||
var container = list.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
|
||||
container?.BringIntoView();
|
||||
list.UpdateLayout();
|
||||
return container;
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,20 @@ public static class VisualTreeExtensions
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static T? FindVisualChild<T>(this DependencyObject parent) where T : DependencyObject
|
||||
{
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var child = VisualTreeHelper.GetChild(parent, i);
|
||||
if (child is T result)
|
||||
return result;
|
||||
|
||||
if (FindVisualChild<T>(child) is { } descendant)
|
||||
return descendant;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
@@ -17,8 +18,7 @@ public sealed class RangeObservableCollection<T> : ObservableCollection<T>
|
||||
|
||||
public void AddRange(IEnumerable<T> list)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
ArgumentNullException.ThrowIfNull(list);
|
||||
|
||||
_suppressNotification = true;
|
||||
|
||||
@@ -29,6 +29,24 @@ public sealed class RangeObservableCollection<T> : ObservableCollection<T>
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public void ReplaceRange(IEnumerable<T> collection)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
var items = collection as IList<T> ?? [.. collection];
|
||||
|
||||
CheckReentrancy();
|
||||
|
||||
Items.Clear();
|
||||
|
||||
foreach (var item in items)
|
||||
Items.Add(item);
|
||||
|
||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Count)));
|
||||
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
|
||||
public void SetSuppressionState(bool state)
|
||||
{
|
||||
_suppressNotification = state;
|
||||
@@ -38,4 +56,4 @@ public sealed class RangeObservableCollection<T> : ObservableCollection<T>
|
||||
{
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,16 +344,16 @@
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" TextTrimming="CharacterEllipsis">
|
||||
<TextBlock.Text>
|
||||
<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="CUE4Parse.AssetsFolder.SelectedFolder.Header" FallbackValue="None" />
|
||||
<Binding Path="CUE4Parse.AssetsFolder.SelectedFolder.Folders.Count" FallbackValue="0" />
|
||||
<Binding Path="CUE4Parse.AssetsFolder.SelectedFolder.AssetsList.Assets.Count" FallbackValue="0" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Bring Selected Folder To View" Padding="4"
|
||||
Command="{Binding MenuCommand}" CommandParameter="{Binding SelectedItem, ElementName=AssetsFolderName}">
|
||||
Command="{Binding MenuCommand}" CommandParameter="{Binding CUE4Parse.AssetsFolder.SelectedFolder}">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource LocateMeIcon}" />
|
||||
@@ -379,13 +379,13 @@
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Margin="0" />
|
||||
<TreeView Grid.Row="2"
|
||||
x:Name="AssetsFolderName"
|
||||
Style="{StaticResource AssetsFolderTreeView}"
|
||||
SelectedItemChanged="OnAssetsTreeSelectedItemChanged"
|
||||
PreviewKeyDown="OnFoldersPreviewKeyDown"
|
||||
PreviewMouseDoubleClick="OnAssetsTreeMouseDoubleClick">
|
||||
</TreeView>
|
||||
<controls:FolderTree Grid.Row="2" x:Name="AssetsFolderName"
|
||||
Style="{StaticResource AssetsFolderListBox}"
|
||||
Folders="{Binding CUE4Parse.AssetsFolder}"
|
||||
ItemsSource="{Binding CUE4Parse.AssetsFolder.VisibleFolders}"
|
||||
SelectedItem="{Binding CUE4Parse.AssetsFolder.SelectedFolder, Mode=TwoWay}"
|
||||
SelectionChanged="OnAssetsTreeSelectedItemChanged"
|
||||
OpenAssets="OnFolderOpenAssets" />
|
||||
<Separator Grid.Row="3" Style="{StaticResource CustomSeparator}" Tag="INFORMATION" />
|
||||
<StackPanel Grid.Row="4" Orientation="Vertical" Margin="0 0 0 5">
|
||||
<Grid HorizontalAlignment="Stretch">
|
||||
@@ -401,27 +401,27 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<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="0" Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.AssetsList.Assets.Count, 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, StringFormat={}{0:N0}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.Folders.Count, 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="0" Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.Archive, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.MountPoint, ElementName=AssetsFolderName, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.MountPoint, FallbackValue='/', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="Archive Mount Point" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Version, ElementName=AssetsFolderName, FallbackValue='VER_UE4_LATEST', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.Version, FallbackValue='VER_UE4_LATEST', Converter={x:Static converters:TrimRightToLeftConverter.Instance}, ConverterParameter=275}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="Archive Version" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
<TabItem Style="{StaticResource TabItemFillSpace}"
|
||||
Header="{Binding SelectedItem.AssetsList.Assets.Count, FallbackValue=0, ElementName=AssetsFolderName}"
|
||||
Header="{Binding CUE4Parse.AssetsFolder.SelectedFolder.AssetsList.Assets.Count, FallbackValue=0}"
|
||||
HeaderStringFormat="{}{0} Packages">
|
||||
<DockPanel>
|
||||
|
||||
<inputs:SearchTextBox DockPanel.Dock="Top" x:Name="AssetsSearchTextBox"
|
||||
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ClearButtonClick="OnClearFilterClick" />
|
||||
|
||||
<Grid DockPanel.Dock="Top">
|
||||
@@ -434,7 +434,7 @@
|
||||
|
||||
<controls:Breadcrumb Grid.Row="0" HorizontalAlignment="Left" Margin="0 5 0 5"
|
||||
MaxWidth="{Binding ActualWidth, ElementName=AssetsSearchTextBox}"
|
||||
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName, FallbackValue='No/Directory/Detected/In/Folder'}"/>
|
||||
DataContext="{Binding CUE4Parse.AssetsFolder.SelectedFolder.PathAtThisPoint, FallbackValue='No/Directory/Detected/In/Folder'}"/>
|
||||
|
||||
<ListBox Grid.Row="1" x:Name="AssetsListName"
|
||||
Style="{StaticResource AssetsListBox}"
|
||||
@@ -508,12 +508,12 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<inputs:SearchTextBox x:Name="AssetsExplorerSearch" Grid.Column="0"
|
||||
Text="{Binding SelectedItem.SearchText, ElementName=AssetsFolderName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Text="{Binding CUE4Parse.AssetsFolder.SelectedFolder.SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
ClearButtonClick="OnClearFilterClick" />
|
||||
|
||||
<ComboBox x:Name="CategoriesSelector" Grid.Column="2"
|
||||
ItemsSource="{Binding Categories}"
|
||||
SelectedItem="{Binding SelectedItem.SelectedCategory, ElementName=AssetsFolderName, Mode=TwoWay}" />
|
||||
SelectedItem="{Binding CUE4Parse.AssetsFolder.SelectedFolder.SelectedCategory, Mode=TwoWay}" />
|
||||
|
||||
<StackPanel Grid.Column="4"
|
||||
Orientation="Horizontal"
|
||||
@@ -555,11 +555,11 @@
|
||||
|
||||
<controls:Breadcrumb Grid.Row="1" Margin="0 5 0 0"
|
||||
HorizontalAlignment="Left"
|
||||
DataContext="{Binding SelectedItem.PathAtThisPoint, ElementName=AssetsFolderName}" />
|
||||
DataContext="{Binding CUE4Parse.AssetsFolder.SelectedFolder.PathAtThisPoint}" />
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<ListBox x:Name="AssetsExplorer"
|
||||
ItemsSource="{Binding SelectedItem.CombinedEntries, ElementName=AssetsFolderName, IsAsync=True}"
|
||||
ItemsSource="{Binding CUE4Parse.AssetsFolder.SelectedFolder.CombinedEntries}"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
||||
PreviewKeyDown="OnPreviewKeyDown">
|
||||
<ListBox.Style>
|
||||
@@ -577,7 +577,7 @@
|
||||
</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}"
|
||||
ItemsSource="{Binding CUE4Parse.AssetsFolder.SelectedFolder.CombinedEntries}"
|
||||
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}"
|
||||
PreviewKeyDown="OnPreviewKeyDown">
|
||||
<ListBox.Style>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace FModel;
|
||||
/// </summary>
|
||||
public partial class MainWindow
|
||||
{
|
||||
public static MainWindow YesWeCats;
|
||||
public static MainWindow Instance => (MainWindow) Application.Current.MainWindow;
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
@@ -48,7 +48,7 @@ public partial class MainWindow
|
||||
else if (LeftTabControl.SelectedIndex == 1 && AssetsFolderName.SelectedItem is TreeItem { Parent: TreeItem parent })
|
||||
{
|
||||
AssetsFolderName.Focus();
|
||||
parent.IsSelected = true;
|
||||
SelectFolder(parent);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -63,7 +63,6 @@ public partial class MainWindow
|
||||
AssetsListName.SelectionChanged += (_, e) => SyncSelection(AssetsExplorer, e);
|
||||
|
||||
FLogger.Logger = LogRtbName;
|
||||
YesWeCats = this;
|
||||
}
|
||||
|
||||
// Hack to sync selection between packages tab and explorer
|
||||
@@ -236,7 +235,7 @@ public partial class MainWindow
|
||||
DirectoryFilesListBox.Focus();
|
||||
break;
|
||||
case 1:
|
||||
AssetsFolderName.Focus();
|
||||
AssetsFolderName.FocusSelection();
|
||||
break;
|
||||
case 2:
|
||||
AssetsListName.Focus();
|
||||
@@ -264,12 +263,7 @@ public partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAssetsTreeMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not TreeView { SelectedItem: TreeItem treeItem } || treeItem.Folders.Count > 0) return;
|
||||
|
||||
_applicationView.SelectedLeftTabIndex++;
|
||||
}
|
||||
private void OnFolderOpenAssets(object sender, EventArgs e) => _applicationView.SelectedLeftTabIndex = 2;
|
||||
|
||||
private void OnPreviewTexturesToggled(object sender, RoutedEventArgs e) => ItemContainerGenerator_StatusChanged(AssetsExplorer.ItemContainerGenerator, EventArgs.Empty);
|
||||
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
|
||||
@@ -297,14 +291,43 @@ public partial class MainWindow
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAssetsTreeSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
|
||||
private void OnAssetsTreeSelectedItemChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not TreeView { SelectedItem: TreeItem }) return;
|
||||
if (sender is not ListBox { SelectedItem: TreeItem })
|
||||
return;
|
||||
|
||||
_applicationView.IsAssetsExplorerVisible = true;
|
||||
_applicationView.SelectedLeftTabIndex = 1;
|
||||
}
|
||||
|
||||
public void SelectFolder(TreeItem folder)
|
||||
{
|
||||
_applicationView.SelectedLeftTabIndex = 1;
|
||||
UpdateLayout();
|
||||
AssetsFolderName.SelectFolder(folder);
|
||||
AssetsFolderName.FocusSelection();
|
||||
}
|
||||
|
||||
public void SelectAsset(GameFileViewModel asset)
|
||||
{
|
||||
var useExplorer = UserSettings.Default.FeaturePreviewNewAssetExplorer;
|
||||
|
||||
_applicationView.SelectedLeftTabIndex = useExplorer ? 1 : 2;
|
||||
if (useExplorer)
|
||||
{
|
||||
_applicationView.IsAssetsExplorerVisible = true;
|
||||
}
|
||||
|
||||
var list = useExplorer ? UserSettings.Default.ExplorerViewMode == EExplorerViewMode.List ? AssetsListExplorer : AssetsExplorer : AssetsListName;
|
||||
list.GetBindingExpression(ItemsControl.ItemsSourceProperty)?.UpdateTarget();
|
||||
list.UnselectAll();
|
||||
list.SelectedItem = asset;
|
||||
UpdateLayout();
|
||||
var container = list.RevealItem(asset);
|
||||
Activate();
|
||||
container?.Focus();
|
||||
}
|
||||
|
||||
private async void OnAssetsListMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is not ListBox listBox) return;
|
||||
@@ -348,24 +371,18 @@ public partial class MainWindow
|
||||
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.ExtractSelected(cancellationToken, [file.Asset]));
|
||||
break;
|
||||
case TreeItem folder:
|
||||
e.Handled = true;
|
||||
ApplicationService.ApplicationView.SelectedLeftTabIndex = 1;
|
||||
|
||||
var parent = folder.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsExpanded = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
var childFolder = folder;
|
||||
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
|
||||
{
|
||||
childFolder.IsExpanded = true;
|
||||
_applicationView.CUE4Parse.AssetsFolder.Expand(childFolder);
|
||||
childFolder = childFolder.Folders[0];
|
||||
}
|
||||
|
||||
childFolder.IsExpanded = true;
|
||||
childFolder.IsSelected = true;
|
||||
_applicationView.CUE4Parse.AssetsFolder.Expand(childFolder);
|
||||
SelectFolder(childFolder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -375,34 +392,12 @@ public partial class MainWindow
|
||||
_applicationView.IsAssetsExplorerVisible = false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_applicationView.SelectedLeftTabIndex++;
|
||||
return;
|
||||
}
|
||||
|
||||
var childFolder = folder;
|
||||
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
|
||||
{
|
||||
childFolder.IsExpanded = true;
|
||||
childFolder = childFolder.Folders[0];
|
||||
}
|
||||
|
||||
childFolder.IsExpanded = true;
|
||||
childFolder.IsSelected = true;
|
||||
}
|
||||
|
||||
private void OnExportHotkey(string trigger)
|
||||
{
|
||||
if (!_applicationView.Status.IsReady || Keyboard.FocusedElement is not DependencyObject focused)
|
||||
return;
|
||||
|
||||
IList selection = focused.FindAncestor<TreeView>() == AssetsFolderName
|
||||
IList selection = focused.FindAncestor<ListBox>() == AssetsFolderName
|
||||
? new[] { AssetsFolderName.SelectedItem }
|
||||
: focused.FindAncestor<ListBox>()?.SelectedItems;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
@@ -80,7 +79,16 @@ public class TreeItem : ViewModel
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedCategory, value))
|
||||
_ = OnSelectedCategoryChanged();
|
||||
{
|
||||
if (value == EAssetCategory.All)
|
||||
{
|
||||
RefreshFilters();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = OnSelectedCategoryChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,19 +96,6 @@ public class TreeItem : ViewModel
|
||||
public AssetsListViewModel AssetsList { get; } = new();
|
||||
public RangeObservableCollection<TreeItem> Folders { get; } = [];
|
||||
|
||||
private ICollectionView _foldersView;
|
||||
public ICollectionView FoldersView
|
||||
{
|
||||
get
|
||||
{
|
||||
_foldersView ??= new ListCollectionView(Folders)
|
||||
{
|
||||
SortDescriptions = { new SortDescription(nameof(Header), ListSortDirection.Ascending) }
|
||||
};
|
||||
return _foldersView;
|
||||
}
|
||||
}
|
||||
|
||||
private ICollectionView? _filteredFoldersView;
|
||||
public ICollectionView? FilteredFoldersView
|
||||
{
|
||||
@@ -116,35 +111,15 @@ public class TreeItem : ViewModel
|
||||
}
|
||||
|
||||
private CompositeCollection _combinedEntries;
|
||||
public CompositeCollection CombinedEntries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_combinedEntries == null)
|
||||
{
|
||||
void CreateCombinedEntries()
|
||||
{
|
||||
_combinedEntries = new CompositeCollection
|
||||
{
|
||||
new CollectionContainer { Collection = FilteredFoldersView },
|
||||
new CollectionContainer { Collection = AssetsList.AssetsView }
|
||||
};
|
||||
}
|
||||
|
||||
if (!Application.Current.Dispatcher.CheckAccess())
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(CreateCombinedEntries);
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateCombinedEntries();
|
||||
}
|
||||
}
|
||||
return _combinedEntries;
|
||||
}
|
||||
}
|
||||
public CompositeCollection CombinedEntries => _combinedEntries ??=
|
||||
[
|
||||
new CollectionContainer { Collection = FilteredFoldersView },
|
||||
new CollectionContainer { Collection = AssetsList.AssetsView }
|
||||
];
|
||||
|
||||
public TreeItem Parent { get; init; }
|
||||
public int Depth => Parent?.Depth + 1 ?? 0;
|
||||
public Thickness Indent => new(Depth * 16, 0, 0, 0); // For folder tree indentation
|
||||
|
||||
public TreeItem(string header, GameFile entry, string pathHere)
|
||||
{
|
||||
@@ -198,15 +173,177 @@ public class TreeItem : ViewModel
|
||||
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
|
||||
}
|
||||
|
||||
public class AssetsFolderViewModel
|
||||
public class AssetsFolderViewModel : ViewModel
|
||||
{
|
||||
public RangeObservableCollection<TreeItem> Folders { get; }
|
||||
public ICollectionView FoldersView { get; }
|
||||
public RangeObservableCollection<TreeItem> Folders { get; } = [];
|
||||
public RangeObservableCollection<TreeItem> VisibleFolders { get; } = [];
|
||||
|
||||
public AssetsFolderViewModel()
|
||||
private TreeItem? _selectedFolder;
|
||||
public TreeItem? SelectedFolder
|
||||
{
|
||||
Folders = [];
|
||||
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
||||
get => _selectedFolder;
|
||||
set
|
||||
{
|
||||
var previous = _selectedFolder;
|
||||
if (!SetProperty(ref _selectedFolder, value))
|
||||
return;
|
||||
|
||||
previous?.IsSelected = false;
|
||||
value?.IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SelectedFolder = null;
|
||||
VisibleFolders.Clear();
|
||||
Folders.Clear();
|
||||
}
|
||||
|
||||
public void CollapseAll()
|
||||
{
|
||||
static void CollapseChildren(TreeItem folder)
|
||||
{
|
||||
folder.IsExpanded = false;
|
||||
foreach (var child in folder.Folders)
|
||||
{
|
||||
CollapseChildren(child);
|
||||
}
|
||||
}
|
||||
|
||||
var selected = SelectedFolder;
|
||||
while (selected?.Parent != null)
|
||||
selected = selected.Parent;
|
||||
|
||||
foreach (var folder in Folders)
|
||||
{
|
||||
CollapseChildren(folder);
|
||||
}
|
||||
|
||||
SelectedFolder = selected;
|
||||
for (var i = VisibleFolders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (VisibleFolders[i].Parent != null)
|
||||
{
|
||||
VisibleFolders.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Expand(TreeItem folder)
|
||||
{
|
||||
if (folder.Folders.Count == 0)
|
||||
return;
|
||||
|
||||
var index = VisibleFolders.IndexOf(folder);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
if (index + 1 < VisibleFolders.Count && ReferenceEquals(VisibleFolders[index + 1].Parent, folder))
|
||||
{
|
||||
folder.IsExpanded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var child in EnumerateVisibleChildren(folder))
|
||||
{
|
||||
VisibleFolders.Insert(++index, child);
|
||||
}
|
||||
|
||||
folder.IsExpanded = true;
|
||||
}
|
||||
|
||||
public void Collapse(TreeItem folder)
|
||||
{
|
||||
if (!folder.IsExpanded)
|
||||
return;
|
||||
|
||||
var index = VisibleFolders.IndexOf(folder);
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
var selected = SelectedFolder;
|
||||
var start = index + 1;
|
||||
var count = 0;
|
||||
while (start + count < VisibleFolders.Count && VisibleFolders[start + count].Depth > folder.Depth)
|
||||
count++;
|
||||
|
||||
if (selected != null && IsDescendantOf(selected, folder))
|
||||
{
|
||||
SelectedFolder = folder;
|
||||
}
|
||||
|
||||
for (var i = start + count - 1; i >= start; i--)
|
||||
{
|
||||
VisibleFolders.RemoveAt(i);
|
||||
}
|
||||
|
||||
folder.IsExpanded = false;
|
||||
}
|
||||
|
||||
public void Toggle(TreeItem folder)
|
||||
{
|
||||
if (folder.IsExpanded)
|
||||
{
|
||||
Collapse(folder);
|
||||
}
|
||||
else
|
||||
{
|
||||
Expand(folder);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reveal(TreeItem folder)
|
||||
{
|
||||
var parents = new Stack<TreeItem>();
|
||||
|
||||
for (var parent = folder.Parent; parent != null; parent = parent.Parent)
|
||||
parents.Push(parent);
|
||||
|
||||
while (parents.Count > 0)
|
||||
Expand(parents.Pop());
|
||||
|
||||
SelectedFolder = folder;
|
||||
}
|
||||
|
||||
private static bool IsDescendantOf(TreeItem item, TreeItem parent)
|
||||
{
|
||||
for (var current = item.Parent; current != null; current = current.Parent)
|
||||
{
|
||||
if (ReferenceEquals(current, parent))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerable<TreeItem> EnumerateVisibleChildren(TreeItem folder)
|
||||
{
|
||||
foreach (var child in folder.Folders)
|
||||
{
|
||||
yield return child;
|
||||
|
||||
if (!child.IsExpanded)
|
||||
continue;
|
||||
|
||||
foreach (var descendant in EnumerateVisibleChildren(child))
|
||||
{
|
||||
yield return descendant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SortFolders(RangeObservableCollection<TreeItem> folders)
|
||||
{
|
||||
if (folders.Count > 1)
|
||||
{
|
||||
folders.ReplaceRange([.. folders.OrderBy(x => x.Header, StringComparer.OrdinalIgnoreCase)]);
|
||||
}
|
||||
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
SortFolders(folder.Folders);
|
||||
}
|
||||
}
|
||||
|
||||
public void BulkPopulate(IReadOnlyCollection<GameFile> entries)
|
||||
@@ -283,14 +420,15 @@ public class AssetsFolderViewModel
|
||||
lastNode?.AssetsList.Add(entry);
|
||||
}
|
||||
|
||||
SortFolders(treeItems);
|
||||
|
||||
Folders.AddRange(treeItems);
|
||||
VisibleFolders.AddRange(treeItems);
|
||||
|
||||
if (treeItems.Count > 0)
|
||||
{
|
||||
// Select after publishing the collection. Selecting a detached TreeItem lets WPF
|
||||
// auto-select the first root (usually the synthetic "Content" bucket) instead.
|
||||
var projectName = ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName;
|
||||
(treeItems.FirstOrDefault(x => x.Header.Equals(projectName, StringComparison.OrdinalIgnoreCase)) ?? treeItems[0]).IsSelected = true;
|
||||
SelectedFolder = treeItems.FirstOrDefault(x => x.Header.Equals(projectName, StringComparison.OrdinalIgnoreCase)) ?? treeItems[0];
|
||||
}
|
||||
|
||||
ApplicationService.ApplicationView.CUE4Parse.SearchVm.ChangeCollection(entries);
|
||||
|
||||
@@ -347,7 +347,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||
{
|
||||
if (Provider == null) return;
|
||||
|
||||
AssetsFolder.Folders.Clear();
|
||||
AssetsFolder.Clear();
|
||||
SearchVm.SearchResults.Clear();
|
||||
Helper.CloseWindow<AdonisWindow>("Search For Packages");
|
||||
Provider.UnloadNonStreamedVfs();
|
||||
|
||||
@@ -4,54 +4,56 @@ using FModel.Services;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
|
||||
public class GoToCommand(CustomDirectoriesViewModel contextViewModel) : ViewModelCommand<CustomDirectoriesViewModel>(contextViewModel)
|
||||
{
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (parameter is not string s || string.IsNullOrEmpty(s)) return;
|
||||
|
||||
JumpTo(s);
|
||||
var folder = JumpTo(s);
|
||||
if (folder != null)
|
||||
{
|
||||
MainWindow.Instance.SelectFolder(folder);
|
||||
}
|
||||
}
|
||||
|
||||
public TreeItem JumpTo(string directory)
|
||||
{
|
||||
_applicationView.SelectedLeftTabIndex = 1; // folders tab
|
||||
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
|
||||
if (root is not { Count: > 0 }) return null;
|
||||
_applicationView.SelectedLeftTabIndex = 1;
|
||||
|
||||
var i = 0;
|
||||
var done = false;
|
||||
var folders = directory.Split('/');
|
||||
while (!done)
|
||||
var current = _applicationView.CUE4Parse.AssetsFolder.Folders;
|
||||
if (current.Count == 0)
|
||||
return null;
|
||||
|
||||
var folders = directory.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
TreeItem result = null;
|
||||
for (var i = 0; i < folders.Length; i++)
|
||||
{
|
||||
foreach (var folder in root)
|
||||
result = null;
|
||||
|
||||
foreach (var folder in current)
|
||||
{
|
||||
if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
folder.IsExpanded = true; // folder found = expand
|
||||
|
||||
// is this the last folder aka the one we want to jump in
|
||||
if (i >= folders.Length - 1)
|
||||
{
|
||||
folder.IsSelected = true; // select it
|
||||
return folder;
|
||||
}
|
||||
|
||||
root = folder.Folders; // grab his subfolders
|
||||
result = folder;
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
done = i == folders.Length || root.Count == 0;
|
||||
if (result == null)
|
||||
return null;
|
||||
|
||||
current = result.Folders;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (result != null)
|
||||
{
|
||||
_applicationView.CUE4Parse.AssetsFolder.Reveal(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||
#if DEBUG
|
||||
var loadingTime = Stopwatch.StartNew();
|
||||
#endif
|
||||
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
||||
_applicationView.CUE4Parse.AssetsFolder.Clear();
|
||||
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
||||
_applicationView.SelectedLeftTabIndex = 1; // folders tab
|
||||
_applicationView.IsAssetsExplorerVisible = true;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using AdonisUI.Controls;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
@@ -73,60 +71,13 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||
case "ToolBox_Open_Output_Directory":
|
||||
Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true });
|
||||
break;
|
||||
// case "ToolBox_Expand_All":
|
||||
// await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
// {
|
||||
// SetFoldersIsExpanded(contextViewModel.CUE4Parse.AssetsFolder, true, cancellationToken);
|
||||
// });
|
||||
// break;
|
||||
case "ToolBox_Collapse_All":
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
SetFoldersIsExpanded(contextViewModel.CUE4Parse.AssetsFolder, false, cancellationToken);
|
||||
});
|
||||
contextViewModel.CUE4Parse.AssetsFolder.CollapseAll();
|
||||
break;
|
||||
case TreeItem selectedFolder:
|
||||
selectedFolder.IsSelected = false;
|
||||
selectedFolder.IsSelected = true;
|
||||
MainWindow.Instance.SelectFolder(selectedFolder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetFoldersIsExpanded(AssetsFolderViewModel root, bool expand, CancellationToken cancellationToken)
|
||||
{
|
||||
var nodes = new LinkedList<TreeItem>();
|
||||
foreach (TreeItem folder in root.Folders)
|
||||
nodes.AddLast(folder);
|
||||
|
||||
var current = nodes.First;
|
||||
while (current != null)
|
||||
{
|
||||
var folder = current.Value;
|
||||
|
||||
// Collapse top-down (reduce layout updates)
|
||||
if (!expand && folder.IsExpanded)
|
||||
{
|
||||
folder.IsExpanded = false;
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
foreach (var child in folder.Folders)
|
||||
{
|
||||
nodes.AddLast(child);
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
if (!expand) return;
|
||||
|
||||
// Expand bottom-up (reduce layout updates)
|
||||
for (var node = nodes.Last; node != null; node = node.Previous)
|
||||
{
|
||||
node.Value.IsExpanded = true;
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,6 @@ public partial class Breadcrumb
|
||||
var directory = string.Join('/', pathAtThisPoint.Split('/').Take(index));
|
||||
if (pathAtThisPoint.Equals(directory)) return;
|
||||
|
||||
ApplicationService.ApplicationView.CustomDirectories.GoToCommand.JumpTo(directory);
|
||||
ApplicationService.ApplicationView.CustomDirectories.GoToCommand.Execute(directory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
TargetType="ListBox"
|
||||
BasedOn="{StaticResource {x:Type ListBox}}">
|
||||
<Setter Property="ItemsSource"
|
||||
Value="{Binding SelectedItem.AssetsList.AssetsView, ElementName=AssetsFolderName, IsAsync=True}" />
|
||||
Value="{Binding CUE4Parse.AssetsFolder.SelectedFolder.AssetsList.AssetsView}" />
|
||||
<Setter Property="SelectionMode"
|
||||
Value="Extended" />
|
||||
<Setter Property="ContextMenu"
|
||||
|
||||
@@ -29,24 +29,12 @@ public partial class ResourcesDictionary
|
||||
case TreeItem folder:
|
||||
ApplicationService.ApplicationView.SelectedLeftTabIndex = 1;
|
||||
|
||||
// Expand all parent folders if not expanded
|
||||
var parent = folder.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsExpanded = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
// Auto expand single child folders
|
||||
var childFolder = folder;
|
||||
while (childFolder.Folders.Count == 1 && childFolder.AssetsList.Assets.Count == 0)
|
||||
{
|
||||
childFolder.IsExpanded = true;
|
||||
childFolder = childFolder.Folders[0];
|
||||
}
|
||||
|
||||
childFolder.IsExpanded = true;
|
||||
childFolder.IsSelected = true;
|
||||
MainWindow.Instance.SelectFolder(childFolder);
|
||||
ApplicationService.ApplicationView.CUE4Parse.AssetsFolder.Expand(childFolder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
158
FModel/Views/Resources/Controls/FolderTree.cs
Normal file
158
FModel/Views/Resources/Controls/FolderTree.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using FModel.Extensions;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public sealed class FolderTree : ListBox
|
||||
{
|
||||
public static readonly DependencyProperty FoldersProperty = DependencyProperty.Register(nameof(Folders), typeof(AssetsFolderViewModel), typeof(FolderTree));
|
||||
|
||||
public AssetsFolderViewModel Folders
|
||||
{
|
||||
get => (AssetsFolderViewModel) GetValue(FoldersProperty);
|
||||
set => SetValue(FoldersProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler OpenAssets;
|
||||
|
||||
public FolderTree()
|
||||
{
|
||||
IsVisibleChanged += (_, e) =>
|
||||
{
|
||||
if (e.NewValue is true)
|
||||
{
|
||||
RevealSelection();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void SelectFolder(TreeItem folder)
|
||||
{
|
||||
Folders.Reveal(folder);
|
||||
SetCurrentValue(SelectedItemProperty, folder);
|
||||
this.RevealItem(folder);
|
||||
}
|
||||
|
||||
public void FocusSelection()
|
||||
{
|
||||
if (SelectedItem is TreeItem folder)
|
||||
{
|
||||
this.RevealItem(folder)?.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
Focus();
|
||||
}
|
||||
}
|
||||
|
||||
public void RevealSelection()
|
||||
{
|
||||
if (SelectedItem is TreeItem folder)
|
||||
{
|
||||
this.RevealItem(folder, alignToTop: true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is DependencyObject source && source.FindAncestor<ToggleButton>() is { DataContext: TreeItem folder } && ContainerFromElement(this, source) is ListBoxItem)
|
||||
{
|
||||
e.Handled = true;
|
||||
Folders?.Toggle(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnPreviewMouseLeftButtonDown(e);
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseDoubleClick(MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left && e.OriginalSource is DependencyObject source && source.FindAncestor<ToggleButton>() == null && ContainerFromElement(this, source) is ListBoxItem { DataContext: TreeItem folder })
|
||||
{
|
||||
e.Handled = true;
|
||||
if (folder.Folders.Count > 0)
|
||||
{
|
||||
Folders?.Toggle(folder);
|
||||
}
|
||||
else if (folder.AssetsList.Assets.Count > 0)
|
||||
{
|
||||
OpenAssets?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
base.OnPreviewMouseDoubleClick(e);
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (Folders == null || SelectedItem is not TreeItem folder)
|
||||
{
|
||||
base.OnPreviewKeyDown(e);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Up:
|
||||
SelectFolder((TreeItem) Items[Math.Max(0, SelectedIndex - 1)]);
|
||||
break;
|
||||
case Key.Down:
|
||||
SelectFolder((TreeItem) Items[Math.Min(Items.Count - 1, SelectedIndex + 1)]);
|
||||
break;
|
||||
case Key.Home:
|
||||
SelectFolder((TreeItem) Items[0]);
|
||||
break;
|
||||
case Key.End:
|
||||
SelectFolder((TreeItem) Items[Items.Count - 1]);
|
||||
break;
|
||||
case Key.Left:
|
||||
if (folder.IsExpanded)
|
||||
{
|
||||
Folders?.Toggle(folder);
|
||||
}
|
||||
else if (folder.Parent != null)
|
||||
{
|
||||
SelectFolder(folder.Parent);
|
||||
}
|
||||
break;
|
||||
case Key.Right:
|
||||
if (!folder.IsExpanded)
|
||||
{
|
||||
Folders?.Toggle(folder);
|
||||
}
|
||||
else if (folder.Folders.Count > 0)
|
||||
{
|
||||
SelectFolder(folder.Folders[0]);
|
||||
}
|
||||
break;
|
||||
case Key.Enter:
|
||||
if ((folder.IsExpanded || folder.Folders.Count == 0) && folder.AssetsList.Assets.Count > 0)
|
||||
{
|
||||
e.Handled = true;
|
||||
OpenAssets?.Invoke(this, EventArgs.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
while (folder.Folders.Count == 1 && folder.AssetsList.Assets.Count == 0)
|
||||
folder = folder.Folders[0];
|
||||
|
||||
SelectFolder(folder);
|
||||
if (!folder.IsExpanded)
|
||||
{
|
||||
Folders?.Toggle(folder);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
base.OnPreviewKeyDown(e);
|
||||
return;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
FocusSelection();
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public sealed class TreeViewItemBehavior
|
||||
{
|
||||
public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem)
|
||||
{
|
||||
return (bool) treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
|
||||
}
|
||||
|
||||
public static void SetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem, bool value)
|
||||
{
|
||||
treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
|
||||
DependencyProperty.RegisterAttached("IsBroughtIntoViewWhenSelected", typeof(bool), typeof(TreeViewItemBehavior),
|
||||
new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));
|
||||
|
||||
private static void OnIsBroughtIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (depObj is not TreeViewItem item)
|
||||
return;
|
||||
|
||||
if (e.NewValue is not bool value)
|
||||
return;
|
||||
|
||||
if (value)
|
||||
item.Selected += OnTreeViewItemSelected;
|
||||
else
|
||||
item.Selected -= OnTreeViewItemSelected;
|
||||
}
|
||||
|
||||
private static void OnTreeViewItemSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is TreeViewItem item)
|
||||
item.BringIntoView();
|
||||
}
|
||||
}
|
||||
57
FModel/Views/Resources/Converters/FolderTreeIconConverter.cs
Normal file
57
FModel/Views/Resources/Converters/FolderTreeIconConverter.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public sealed class FolderTreeIconConverter : IValueConverter
|
||||
{
|
||||
public static readonly FolderTreeIconConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
var name = value as string;
|
||||
|
||||
// This should get reworked to be aligned with asset explorer somehow
|
||||
|
||||
return name?.ToLowerInvariant() switch
|
||||
{
|
||||
"config" => "/FModel;component/Resources/gear.png",
|
||||
"plugins" => "/FModel;component/Resources/puzzle.png",
|
||||
|
||||
"windows" => "/FModel;component/Resources/windows.png",
|
||||
"android" => "/FModel;component/Resources/android.png",
|
||||
"ios" or "mac" => "/FModel;component/Resources/apple.png",
|
||||
"linux" or "linuxaarch64" => "/FModel;component/Resources/linux.png",
|
||||
"unix" => "/FModel;component/Resources/unix.png",
|
||||
"platforms" => "/FModel;component/Resources/pc.png",
|
||||
|
||||
"weapons" => "/FModel;component/Resources/weapon.png",
|
||||
"localization" or "internationalization" => "/FModel;component/Resources/localization.png",
|
||||
"ui" => "/FModel;component/Resources/ui.png",
|
||||
"sounds" or "audio" => "/FModel;component/Resources/sound.png",
|
||||
"2dassets" or "textures" => "/FModel;component/Resources/texture.png",
|
||||
"engine" or "enginecontent" => "/FModel;component/Resources/engine.png",
|
||||
"materials" => "/FModel;component/Resources/materialicon.png",
|
||||
"blueprints" => "/FModel;component/Resources/blueprint.png",
|
||||
"cinematics" or "movies" => "/FModel;component/Resources/cinematics.png",
|
||||
|
||||
"fortnite" => "/FModel;component/Resources/fortnite.png",
|
||||
"fortnitegame" => "/FModel;component/Resources/fortnitebr.png",
|
||||
"worldexplorers" => "/FModel;component/Resources/battlebreakers.png",
|
||||
"oakgame" => "/FModel;component/Resources/borderlands.png",
|
||||
"shootergame" => "/FModel;component/Resources/valorant.png",
|
||||
"roguecompany" => "/FModel;component/Resources/roguecompany.png",
|
||||
"g3" => "/FModel;component/Resources/spellbreak.png",
|
||||
"swgame" => "/FModel;component/Resources/fallenorder.png",
|
||||
"prospect" => "/FModel;component/Resources/thecycle.png",
|
||||
"creative" => "/FModel;component/Resources/creative.png",
|
||||
"athena" => "/FModel;component/Resources/athena.png",
|
||||
"stateofdecay2" => "/FModel;component/Resources/stateofdecay2.png",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
@@ -230,69 +230,251 @@
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AssetsFolderTreeView"
|
||||
TargetType="TreeView"
|
||||
BasedOn="{StaticResource {x:Type TreeView}}">
|
||||
<Setter Property="ItemsSource"
|
||||
Value="{Binding CUE4Parse.AssetsFolder.FoldersView, IsAsync=True}" />
|
||||
<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="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<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>
|
||||
<DrawingImage x:Key="TreeFolderClosedImage">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="{StaticResource FolderTabGradient}"
|
||||
Geometry="M20 34 H54 L62 46 H108 C112 46 115 49 115 53 V60 H13 V45 C13 39 16 34 20 34 Z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#AC783F" Thickness="5" LineJoin="Round" />
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="{StaticResource FolderBodyGradient}"
|
||||
Geometry="M13 60 H115 V104 C115 110 110 115 104 115 H24 C18 115 13 110 13 104 Z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#AC783F" Thickness="5" LineJoin="Round" />
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingImage x:Key="TreeFolderOpenImage">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup>
|
||||
<GeometryDrawing Brush="{StaticResource FolderTabGradient}"
|
||||
Geometry="M20 34 H54 L62 46 H103 C108 46 111 49 111 54 V105 H13 V45 C13 39 16 34 20 34 Z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#AC783F" Thickness="5" LineJoin="Round" />
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="{StaticResource FolderBodyGradient}"
|
||||
Geometry="M30 65 H121 L104 108 C102 113 99 115 93 115 H24 C18 115 14 111 13 106 Z">
|
||||
<GeometryDrawing.Pen>
|
||||
<Pen Brush="#AC783F" Thickness="5" LineJoin="Round" />
|
||||
</GeometryDrawing.Pen>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<Style x:Key="FolderExpanderStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Width" Value="20" />
|
||||
<Setter Property="Height" Value="20" />
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="ClickMode" Value="Press" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ListBoxItem}}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="Transparent">
|
||||
<Viewbox x:Name="Arrow"
|
||||
Tag="-90"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RenderTransformOrigin="0.5,0.5">
|
||||
<Viewbox.RenderTransform>
|
||||
<RotateTransform x:Name="ArrowRotation"
|
||||
Angle="{Binding Tag, ElementName=Arrow}" />
|
||||
</Viewbox.RenderTransform>
|
||||
<Canvas Width="24"
|
||||
Height="24">
|
||||
<Path Fill="{TemplateBinding Foreground}"
|
||||
Data="{StaticResource ArrowIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked"
|
||||
Value="True">
|
||||
<Setter TargetName="Arrow"
|
||||
Property="Tag"
|
||||
Value="-45" />
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard x:Name="ExpandArrow">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ArrowRotation"
|
||||
Storyboard.TargetProperty="Angle"
|
||||
From="-90"
|
||||
To="-45"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard x:Name="CollapseArrow">
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetName="ArrowRotation"
|
||||
Storyboard.TargetProperty="Angle"
|
||||
From="-45"
|
||||
To="-90"
|
||||
Duration="0:0:0.2" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
<!-- Don't replay animation whe not needed -->
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||
<RemoveStoryboard BeginStoryboardName="ExpandArrow" />
|
||||
<RemoveStoryboard BeginStoryboardName="CollapseArrow" />
|
||||
</EventTrigger>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Unloaded">
|
||||
<RemoveStoryboard BeginStoryboardName="ExpandArrow" />
|
||||
<RemoveStoryboard BeginStoryboardName="CollapseArrow" />
|
||||
</EventTrigger>
|
||||
<DataTrigger Binding="{Binding Folders.Count}"
|
||||
Value="0">
|
||||
<Setter Property="Visibility"
|
||||
Value="Hidden" />
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AssetsFolderListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="True" />
|
||||
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />
|
||||
<Setter Property="VirtualizingPanel.IsVirtualizing" Value="True" />
|
||||
<Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
|
||||
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Item" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<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="ItemsPanel">
|
||||
<Setter.Value>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="{x:Type ListBoxItem}"
|
||||
BasedOn="{StaticResource {x:Type ListBoxItem}}">
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="ContextMenu"
|
||||
Value="{StaticResource FolderContextMenu}" />
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="Margin"
|
||||
Value="{Binding Indent}" />
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<HierarchicalDataTemplate ItemsSource="{Binding FoldersView, IsAsync=True}">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image x:Name="TreeImage"
|
||||
Source="/FModel;component/Resources/folder.png"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 0 3.5 0" />
|
||||
<Border x:Name="TreeBadge"
|
||||
CornerRadius="5"
|
||||
Height="10"
|
||||
MinWidth="10"
|
||||
Background="#705542"
|
||||
Margin="-10.5 0 4.5 0"
|
||||
VerticalAlignment="Bottom">
|
||||
<TextBlock Text="{Binding AssetsList.Assets.Count}"
|
||||
Margin="2.5 0 2.5 0"
|
||||
Padding="1 0 0 0"
|
||||
FontSize="7"
|
||||
Foreground="White"
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ToggleButton Grid.Column="0"
|
||||
Style="{StaticResource FolderExpanderStyle}"
|
||||
IsChecked="{Binding IsExpanded, Mode=OneWay}" />
|
||||
<StackPanel Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
Margin="0 3 0 3">
|
||||
<Grid Width="16"
|
||||
Height="16"
|
||||
Margin="0 0 3.5 0"
|
||||
VerticalAlignment="Center">
|
||||
<Image x:Name="DefaultFolderIcon"
|
||||
Width="16"
|
||||
Height="14"
|
||||
Visibility="Collapsed"
|
||||
Source="{StaticResource TreeFolderClosedImage}" />
|
||||
<Image x:Name="CustomFolderIcon"
|
||||
Width="16"
|
||||
Height="16"
|
||||
Source="{Binding Header, Converter={x:Static converters:FolderTreeIconConverter.Instance}}" />
|
||||
</Grid>
|
||||
<Border x:Name="TreeBadge"
|
||||
CornerRadius="5"
|
||||
Height="10"
|
||||
MinWidth="10"
|
||||
Background="#705542"
|
||||
Margin="-10.5 0 4.5 0"
|
||||
VerticalAlignment="Bottom">
|
||||
<TextBlock Text="{Binding AssetsList.Assets.Count}"
|
||||
Margin="2.5 0 2.5 0"
|
||||
Padding="1 0 0 0"
|
||||
FontSize="7"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Header}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center" />
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Header}"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} has {1} folders and {2} packages">
|
||||
<Binding Path="Header" />
|
||||
<Binding Path="FoldersView.Count" />
|
||||
<Binding Path="AssetsList.Assets.Count" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<HierarchicalDataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding FoldersView.Count}"
|
||||
Value="0">
|
||||
<Setter TargetName="TreeImage"
|
||||
HorizontalAlignment="Left">
|
||||
<TextBlock.ToolTip>
|
||||
<ToolTip>
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} has {1} folders and {2} packages">
|
||||
<Binding Path="Header" />
|
||||
<Binding Path="Folders.Count" />
|
||||
<Binding Path="AssetsList.Assets.Count" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</TextBlock.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<DataTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding IsExpanded}"
|
||||
Value="True">
|
||||
<Setter TargetName="DefaultFolderIcon"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/empty_folder.png" />
|
||||
Value="{StaticResource TreeFolderOpenImage}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}"
|
||||
Value="True">
|
||||
<Setter TargetName="DefaultFolderIcon"
|
||||
Property="Source"
|
||||
Value="{StaticResource TreeFolderOpenImage}" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Source, ElementName=CustomFolderIcon}"
|
||||
Value="{x:Null}">
|
||||
<Setter TargetName="CustomFolderIcon"
|
||||
Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
<Setter TargetName="DefaultFolderIcon"
|
||||
Property="Visibility"
|
||||
Value="Visible" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding AssetsList.Assets.Count}"
|
||||
Value="0">
|
||||
@@ -300,408 +482,10 @@
|
||||
Property="Visibility"
|
||||
Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Config">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/gear.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Plugins">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/puzzle.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Windows">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/windows.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Android">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/android.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="IOS">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/apple.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Mac">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/apple.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Linux">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/linux.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="LinuxAArch64">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/linux.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Unix">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/unix.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Weapons">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/weapon.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Localization">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/localization.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Internationalization">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/localization.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="UI">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/ui.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Sounds">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/sound.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="2dAssets">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/texture.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Textures">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/texture.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Engine">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/engine.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="EngineContent">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/engine.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Platforms">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/pc.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Fortnite">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/fortnite.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="FortniteGame">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/fortnitebr.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="WorldExplorers">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/battlebreakers.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="OakGame">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/borderlands.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="ShooterGame">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/valorant.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="RogueCompany">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/roguecompany.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="g3">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/spellbreak.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="SwGame">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/fallenorder.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Prospect">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/thecycle.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Materials">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/materialicon.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header, Converter={x:Static converters:CaseInsensitiveStringEqualsConverter.Instance}, ConverterParameter='Blueprints'}"
|
||||
Value="True">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/blueprint.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Cinematics">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/cinematics.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Movies">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/cinematics.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Audio">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/sound.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Creative">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/creative.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header}"
|
||||
Value="Athena">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/athena.png" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Header, Converter={x:Static converters:CaseInsensitiveStringEqualsConverter.Instance}, ConverterParameter='StateOfDecay2'}"
|
||||
Value="True">
|
||||
<Setter TargetName="TreeImage"
|
||||
Property="Source"
|
||||
Value="/FModel;component/Resources/stateofdecay2.png" />
|
||||
</DataTrigger>
|
||||
</HierarchicalDataTemplate.Triggers>
|
||||
</HierarchicalDataTemplate>
|
||||
</DataTemplate.Triggers>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="ItemContainerStyle">
|
||||
<Setter.Value>
|
||||
<Style TargetType="{x:Type TreeViewItem}"
|
||||
BasedOn="{StaticResource TreeViewItemStyle}">
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Stretch" />
|
||||
<Setter Property="IsSelected"
|
||||
Value="{Binding IsSelected, Mode=TwoWay}" />
|
||||
<Setter Property="IsExpanded"
|
||||
Value="{Binding IsExpanded, Mode=TwoWay}" />
|
||||
<Setter Property="ContextMenu"
|
||||
Value="{StaticResource FolderContextMenu}" />
|
||||
<Setter Property="controls:TreeViewItemBehavior.IsBroughtIntoViewWhenSelected"
|
||||
Value="True" />
|
||||
<Setter Property="Padding"
|
||||
Value="5 3" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="TreeViewItem">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<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="3"
|
||||
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="3"
|
||||
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="12"
|
||||
Height="12"
|
||||
Focusable="False"
|
||||
ClickMode="Press"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
ContentTemplate="{DynamicResource {x:Static adonisUi:Templates.Expander}}"
|
||||
RenderTransformOrigin="0.5, 0.5">
|
||||
<ToggleButton.Template>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="Transparent">
|
||||
<Viewbox Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Center">
|
||||
<Canvas Width="24"
|
||||
Height="24">
|
||||
<Path Fill="{TemplateBinding Foreground}"
|
||||
Data="{StaticResource ArrowIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</ToggleButton.Template>
|
||||
<ToggleButton.Margin>
|
||||
<MultiBinding Converter="{x:Static adonisConverters:ValuesToThicknessConverter.Instance}">
|
||||
<Binding Path="Padding.Left"
|
||||
RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
<Binding Source="0" />
|
||||
<Binding Source="8" />
|
||||
<Binding Source="0" />
|
||||
</MultiBinding>
|
||||
</ToggleButton.Margin>
|
||||
<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}">
|
||||
<ContentPresenter.Margin>
|
||||
<MultiBinding Converter="{x:Static adonisConverters:ValuesToThicknessConverter.Instance}">
|
||||
<Binding>
|
||||
<Binding.Source>
|
||||
<system:Double>-5</system:Double>
|
||||
</Binding.Source>
|
||||
</Binding>
|
||||
<Binding Path="Padding.Top"
|
||||
RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
<Binding Path="Padding.Right"
|
||||
RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
<Binding Path="Padding.Bottom"
|
||||
RelativeSource="{RelativeSource TemplatedParent}" />
|
||||
</MultiBinding>
|
||||
</ContentPresenter.Margin>
|
||||
</ContentPresenter>
|
||||
|
||||
<ItemsPresenter x:Name="ItemsHost"
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2" />
|
||||
</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.TargetProperty="Angle"
|
||||
Storyboard.TargetName="ExpanderRotateTransform"
|
||||
Duration="00:00:0.2"
|
||||
To="-45" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Angle"
|
||||
Storyboard.TargetName="ExpanderRotateTransform"
|
||||
Duration="00:00:0.2"
|
||||
From="-45" />
|
||||
</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>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}, FallbackValue=0}"
|
||||
Value="0">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Grid>
|
||||
<TextBlock Text="No folders found in archives, make sure you loaded at least one"
|
||||
FontWeight="SemiBold"
|
||||
TextAlignment="Center"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GameFilesTabControl" TargetType="TabControl" BasedOn="{StaticResource {x:Type TabControl}}">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
@@ -124,47 +123,41 @@ public partial class SearchView
|
||||
await CurrentViewModel?.CycleSortSizeMode();
|
||||
}
|
||||
|
||||
private async void OnAssetDoubleClick(object sender, RoutedEventArgs e)
|
||||
private void OnAssetDoubleClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (CurrentListView?.SelectedItem is not GameFile entry)
|
||||
return;
|
||||
|
||||
await NavigateToAssetAndSelect(entry);
|
||||
NavigateToAssetAndSelect(entry);
|
||||
}
|
||||
private async void OnGoToRefPackage(object sender, RoutedEventArgs e)
|
||||
private void OnGoToRefPackage(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_refViewModel.RefFile is not GameFile entry)
|
||||
return;
|
||||
|
||||
await NavigateToAssetAndSelect(entry);
|
||||
NavigateToAssetAndSelect(entry);
|
||||
}
|
||||
|
||||
private async Task NavigateToAssetAndSelect(GameFile entry)
|
||||
private void NavigateToAssetAndSelect(GameFile entry)
|
||||
{
|
||||
WindowState = WindowState.Minimized;
|
||||
MainWindow.YesWeCats.AssetsListName.ItemsSource = null;
|
||||
|
||||
var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(entry.Directory);
|
||||
if (folder == null)
|
||||
return;
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
folder.SearchText = string.Empty;
|
||||
folder.SelectedCategory = EAssetCategory.All;
|
||||
|
||||
do
|
||||
{ await Task.Delay(100); } while (MainWindow.YesWeCats.AssetsListName.Items.Count < folder.AssetsList.Assets.Count);
|
||||
var mainWindow = MainWindow.Instance;
|
||||
mainWindow.Activate();
|
||||
mainWindow.SelectFolder(folder);
|
||||
|
||||
while (!folder.IsSelected || MainWindow.YesWeCats.AssetsFolderName.SelectedItem != folder)
|
||||
await Task.Delay(50); // stops assets tab from opening too early
|
||||
var vm = folder.AssetsList.Assets.FirstOrDefault(x => x.Asset == entry);
|
||||
if (vm == null)
|
||||
return;
|
||||
|
||||
ApplicationService.ApplicationView.SelectedLeftTabIndex = 2; // assets tab
|
||||
do
|
||||
{
|
||||
await Task.Delay(100);
|
||||
var vm = MainWindow.YesWeCats.AssetsListName.Items
|
||||
.OfType<GameFileViewModel>()
|
||||
.FirstOrDefault(x => x.Asset == entry);
|
||||
MainWindow.YesWeCats.AssetsListName.SelectedItem = vm;
|
||||
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(vm);
|
||||
} while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null);
|
||||
mainWindow.SelectAsset(vm);
|
||||
}
|
||||
|
||||
private async void OnAssetExtract(object sender, RoutedEventArgs e)
|
||||
@@ -175,7 +168,7 @@ public partial class SearchView
|
||||
WindowState = WindowState.Minimized;
|
||||
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, entry, true));
|
||||
|
||||
MainWindow.YesWeCats.Activate();
|
||||
MainWindow.Instance.Activate();
|
||||
}
|
||||
|
||||
private void OnWindowKeyDown(object sender, KeyEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user