Files
sbox-public/game/addons/tools/Code/Editor/AssetPicker/AssetPicker.cs
s&box team 71f266059a Open source release
This commit imports the C# engine code and game files, excluding C++ source code.

[Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
2025-11-24 09:05:18 +00:00

242 lines
5.8 KiB
C#

using Editor.AssetPickers;
namespace Editor;
public abstract class AssetPicker : Dialog
{
public struct PickerOptions
{
/// <summary>
/// Allows searching and selecting cloud assets
/// </summary>
public bool EnableCloud { get; set; } = true;
/// <summary>
/// Cloud assets will be shown in a separate tab
/// </summary>
public bool SeparateCloudTab { get; set; } = true;
/// <summary>
/// Allows selecting multiple assets
/// </summary>
public bool EnableMultiselect { get; set; } = false;
/// <summary>
/// Pass-thru of additioanl types requested from native that aren't the primary resource type
/// </summary>
public List<AssetType> AdditionalTypes { get; internal set; }
public PickerOptions() { }
}
/// <summary>
/// Asset was highlighted, but not picked.
/// </summary>
public Action<Asset[]> OnAssetHighlighted { get; set; }
/// <summary>
/// An asset was picked. The asset picker will be closed after this.
/// </summary>
public Action<Asset[]> OnAssetPicked { get; set; }
/// <summary>
/// A package was picked. The asset picker will be closed after this.
/// </summary>
public Action<Package> OnPackagePicked { get; set; }
public AssetType AssetType { get; init; }
public PickerOptions Options { get; init; }
public string Title { set => Window.Title = value; }
public AssetPicker( Widget parent, AssetType assetType, PickerOptions options ) : base( parent, true )
{
AssetType = assetType;
Options = options;
Window.SetWindowIcon( "find_in_page" );
Window.WindowFlags = WindowFlags.Dialog | WindowFlags.Customized | WindowFlags.WindowTitle | WindowFlags.CloseButton | WindowFlags.WindowSystemMenuHint;
Window.SetModal( true );
}
public override void Show()
{
Window.Show();
}
public override void OnDestroyed()
{
base.OnDestroyed();
EditorUtility.StopAssetSound();
}
public void SetSelection( string resource )
{
Asset asset = AssetSystem.FindByPath( resource );
if ( asset is null && Package.TryGetCached( resource, out Package package ) )
{
SetSelection( package );
return;
}
SetSelection( asset );
}
protected virtual bool ShouldFilterAsset( Asset asset )
{
//
// Filter out unwanted crap
//
if ( asset.Name.Contains( ".generated", StringComparison.OrdinalIgnoreCase ) )
{
return true;
}
if ( asset.AbsolutePath.Contains( ".sbox" ) )
{
return true;
}
return false;
}
protected void Submit( Asset asset ) => Submit( [asset] );
protected virtual void Submit( Asset[] assets )
{
OnAssetPicked?.Invoke( assets );
Window.Close();
}
protected virtual async void Submit( Package package )
{
package = await Package.FetchAsync( package.FullIdent, false );
OnPackagePicked?.Invoke( package );
if ( AssetSystem.CanCloudInstall( package ) )
{
Asset a = null;
if ( !AssetSystem.IsCloudInstalled( package ) )
{
Hide();
a = await AssetSystem.InstallAsync( package.FullIdent );
if ( a is null )
{
Log.Error( $"Failed to install package: {package.FullIdent}" );
}
}
else
{
string assetPath = package.GetMeta<string>( "PrimaryAsset" );
a = AssetSystem.FindByPath( assetPath );
if ( a is null )
{
Log.Error( $"Failed to find installed package: {package.FullIdent}" );
}
}
Submit( a );
}
else
{
Hide();
}
}
public virtual void SetSelection( Asset asset ) { }
public virtual void SetSelection( Package package ) { }
public virtual void SetSearchText( string value ) { }
// --
public static AssetPicker Create( Widget parent, AssetType assetType, PickerOptions? options = null )
{
options ??= new();
Type resourceType = assetType?.ResourceType;
if ( resourceType is null && assetType is not null )
{
switch ( assetType.FileExtension )
{
case "vmap": resourceType = typeof( Map ); break;
case "vmat": resourceType = typeof( Material ); break;
case "vmdl": resourceType = typeof( Model ); break;
case "vsnd": resourceType = typeof( SoundFile ); break;
case "sound": resourceType = typeof( SoundEvent ); break;
}
}
if ( resourceType is not null )
{
var matchingTypes = EditorTypeLibrary
.GetTypesWithAttribute<AssetPickerAttribute>()
.Where( x => typeof( AssetPicker ).IsAssignableFrom( x.Type.TargetType ) )
.SelectMany( x => x.Attribute.ResourceTypes
.Where( assetType => resourceType.IsAssignableTo( assetType ) )
.Select( assetType => new { ParentType = x.Type, AssetType = assetType } ) )
.ToList();
var customType = matchingTypes
.OrderBy( x => GetSpecificityScore( x.AssetType, resourceType ) )
.Select( x => x.ParentType )
.FirstOrDefault();
if ( customType != null )
{
try
{
var picker = customType.Create<AssetPicker>( [parent, assetType, options] );
if ( picker.IsValid() )
return picker;
}
catch ( Exception ex )
{
Log.Error( ex, $"Failed to create AssetPicker for: {assetType} ({customType.Name})" );
}
}
}
else
{
//
// No resource type, so all types are allowed
//
return new GenericPicker( parent, null, options.Value );
}
var types = new List<AssetType>() { assetType };
if ( assetType.IsGameResource )
{
// also all types that inherit from the resource type
types.AddRange( EditorTypeLibrary.GetTypes( assetType.ResourceType ).Select( t => AssetType.FromType( t.TargetType ) ) );
}
if ( options?.AdditionalTypes != null )
{
types.AddRange( options?.AdditionalTypes );
}
return new GenericPicker( parent, types.Distinct().ToList(), options.Value );
}
// helper to weight the implementations by inheritance depth
private static int GetSpecificityScore( Type candidate, Type target )
{
int score = 0;
while ( target != null && target != typeof( object ) )
{
if ( target == candidate )
return score;
target = target.BaseType;
score++;
}
return int.MaxValue;
}
}