diff --git a/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/DefaultPropertyTemplateProviders.axaml b/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/DefaultPropertyTemplateProviders.axaml index 8a9e5eddb3..622e14d1e0 100644 --- a/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/DefaultPropertyTemplateProviders.axaml +++ b/sources/editor/Stride.Core.Assets.Editor.Avalonia/Views/DefaultPropertyTemplateProviders.axaml @@ -854,9 +854,9 @@ + Value="{Binding NodeValue, Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, UpdateSourceTrigger=Explicit}" + Minimum="{Binding [Minimum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MinValue}}" + Maximum="{Binding [Maximum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MaxValue}}"> @@ -872,9 +872,9 @@ + Value="{Binding NodeValue, Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, UpdateSourceTrigger=Explicit}" + Minimum="{Binding [Minimum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MinValue}}" + Maximum="{Binding [Maximum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MaxValue}}"> @@ -918,9 +918,9 @@ LargeChange="{Binding [LargeStep], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}}"/> + Value="{Binding NodeValue, Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, UpdateSourceTrigger=Explicit}" + Minimum="{Binding [Minimum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MinValue}}" + Maximum="{Binding [Maximum], Converter={sd:Chained {caec:DifferentValuesToNull}, {sd:ToDouble}}, FallbackValue={x:Static s:Decimal.MaxValue}}"> diff --git a/sources/editor/Stride.Core.Assets.Editor/ViewModels/AssetCollectionViewModel.cs b/sources/editor/Stride.Core.Assets.Editor/ViewModels/AssetCollectionViewModel.cs index 1fafb48c32..138d7b080c 100644 --- a/sources/editor/Stride.Core.Assets.Editor/ViewModels/AssetCollectionViewModel.cs +++ b/sources/editor/Stride.Core.Assets.Editor/ViewModels/AssetCollectionViewModel.cs @@ -20,8 +20,10 @@ public sealed class AssetCollectionViewModel : DispatcherViewModel private readonly ObservableSet selectedContent = []; private object? singleSelectedContent; + private bool discardSelectionChanges; + public AssetCollectionViewModel(SessionViewModel session) - : base(session.ServiceProvider) + : base(session.SafeArgument().ServiceProvider) { Session = session; @@ -128,6 +130,11 @@ internal IReadOnlyCollection GetSelectedDirectories(bool return result.ToList(); } + internal void UpdateAssetsCollection(ICollection newAssets) + { + UpdateAssetsCollection(newAssets, true); + } + private void AssetsCollectionInDirectoryChanged(object? sender, NotifyCollectionChangedEventArgs e) { // If the changes are too important, rebuild completely the collection. @@ -190,6 +197,9 @@ private async void SelectedContentCollectionChanged(object? sender, NotifyCollec } } + if (discardSelectionChanges) + return; + AssetViewProperties.UpdateTypeAndName(SelectedAssets, x => x.TypeDisplayName, x => x.Url, "assets"); await AssetViewProperties.GenerateSelectionPropertiesAsync(SelectedAssets); } @@ -219,7 +229,7 @@ private void UpdateAssetsCollection(ICollection newAssets, bool // If the selection can be restored as it is currently, prevent the CollectionChanged handler to rebuild the view model for nothing. if (previousSelection.Count == SelectedAssets.Count) { - //discardSelectionChanges = true; + discardSelectionChanges = true; } assets.Clear(); @@ -233,7 +243,7 @@ private void UpdateAssetsCollection(ICollection newAssets, bool //RefreshFilters(); - //discardSelectionChanges = false; + discardSelectionChanges = false; } private void UpdateLocations() diff --git a/sources/editor/Stride.Core.Assets.Editor/ViewModels/ReferencesViewModel.cs b/sources/editor/Stride.Core.Assets.Editor/ViewModels/ReferencesViewModel.cs new file mode 100644 index 0000000000..fe5d2c12db --- /dev/null +++ b/sources/editor/Stride.Core.Assets.Editor/ViewModels/ReferencesViewModel.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Core.Assets.Presentation.ViewModels; +using Stride.Core.Extensions; +using Stride.Core.Presentation.ViewModels; + +namespace Stride.Core.Assets.Editor.ViewModels; + +/// +/// A view model that represents the referencers and referencees of a selection of asset. +/// +public sealed class ReferencesViewModel : DispatcherViewModel +{ + /// + /// The asset collection view model of assets for which we want to gather references. + /// + private readonly AssetCollectionViewModel assetCollection; + /// + /// The collection of referencers for the current selection of assets. + /// + private readonly HashSet referencerAssets = []; + /// + /// The collection of referencees for the current selection of assets. + /// + private readonly HashSet referencedAssets = []; + private bool showReferencers; + private string typeCountersAsText = ""; + + public ReferencesViewModel(SessionViewModel session) + : base(session.SafeArgument().ServiceProvider) + { + assetCollection = session.AssetCollection; + DisplayedReferences = new AssetCollectionViewModel(session); + + assetCollection.SelectedAssets.CollectionChanged += (_, _) => RefreshReferences(); + session.AssetPropertiesChanged += (_, _) => Dispatcher.Invoke(RefreshReferences); + } + + /// + /// Gets the that should be currently displayed according to other properties values. + /// + public AssetCollectionViewModel DisplayedReferences { get; } + + /// + /// Gets or sets whether to show the referencers of the selection of assets. If false, the referenced assets will be displayed instead. + /// + public bool ShowReferencers + { + get => showReferencers; + set => SetValue(ref showReferencers, value, UpdateDisplayedContent); + } + + /// + /// Gets the counter of asset references grouped by types. + /// + public string TypeCountersAsText + { + get => typeCountersAsText; + private set => SetValue(ref typeCountersAsText, value); + } + + /// + /// Rebuilds the references collections from the current selection in the asset view model collection passed to the constructor of this instance. + /// + private void RefreshReferences() + { + Dispatcher.EnsureAccess(); + + var referencers = assetCollection.SelectedAssets.SelectMany(x => x.Dependencies.ReferencerAssets); + referencerAssets.Clear(); + referencerAssets.AddRange(referencers); + + var referenced = AssetViewModel.ComputeRecursiveReferencedAssets(assetCollection.SelectedAssets); + referencedAssets.Clear(); + referencedAssets.AddRange(referenced); + + UpdateDisplayedContent(); + } + + /// + /// Updates the collection. + /// + private void UpdateDisplayedContent() + { + var assets = ShowReferencers ? referencerAssets : referencedAssets; + + DisplayedReferences.UpdateAssetsCollection(assets); + UpdateStats(assets); + } + + /// + /// Updates the property. + /// + /// + private void UpdateStats(IEnumerable assets) + { + var typeCounters = assets.GroupBy(a => a.TypeDisplayName).Select(grp => + { + var count = grp.Count(); + return $"{count} {Pluralize(grp.Key, count)}"; + }); + TypeCountersAsText = string.Join(", ", typeCounters); + } + + private static string Pluralize(string word, int count) + { + if (count == 1) + return word; + + // special case + return string.Equals(word, "entity", StringComparison.OrdinalIgnoreCase) + ? $"{word[..^1]}es" + : $"{word}s"; + } +} diff --git a/sources/editor/Stride.Core.Assets.Editor/ViewModels/SessionViewModel.cs b/sources/editor/Stride.Core.Assets.Editor/ViewModels/SessionViewModel.cs index b034feaab3..b0b88331de 100644 --- a/sources/editor/Stride.Core.Assets.Editor/ViewModels/SessionViewModel.cs +++ b/sources/editor/Stride.Core.Assets.Editor/ViewModels/SessionViewModel.cs @@ -83,6 +83,9 @@ private SessionViewModel(IViewModelServiceProvider serviceProvider, PackageSessi // Initialize logs AssetLog = new AssetLogViewModel(ServiceProvider, this); + // Initialize the reference view model related to the main asset view + References = new ReferencesViewModel(this); + // Construct package categories var localPackageName = session.SolutionPath != null ? string.Format(Tr._(@"Solution '{0}'"), session.SolutionPath.GetFileNameWithoutExtension()) : LocalPackageCategoryName; packageCategories.Add(LocalPackageCategoryName, new PackageCategoryViewModel(localPackageName, this)); @@ -173,6 +176,8 @@ private set public IReadOnlyDictionary PackageCategories => packageCategories; + public ReferencesViewModel References { get; } + public UFile SolutionPath => session.SolutionPath; public IAssetSourceTrackerViewModel SourceTracker { get; private set; } diff --git a/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml b/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml index 037b084f50..9e5b38af04 100644 --- a/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml +++ b/sources/editor/Stride.GameStudio.Avalonia/Views/MainView.axaml @@ -4,7 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sd="http://schemas.stride3d.net/xaml/presentation" xmlns:caect="using:Stride.Core.Assets.Editor.Components.Transactions" + xmlns:caevm="using:Stride.Core.Assets.Editor.ViewModels" + xmlns:capvm="using:Stride.Core.Assets.Presentation.ViewModels" xmlns:cpc="using:Stride.Core.Presentation.Commands" + xmlns:gsc="using:Stride.GameStudio.Avalonia.Converters" xmlns:gsh="using:Stride.GameStudio.Avalonia.Helpers" xmlns:gsvm="using:Stride.GameStudio.Avalonia.ViewModels" xmlns:gsvw="using:Stride.GameStudio.Avalonia.Views" @@ -161,9 +164,47 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -