Files
sbox-public/game/addons/tools/Code/Widgets/ControlWidgets/ResourceWrapperControlWidget.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

239 lines
5.8 KiB
C#

using Sandbox.Resources;
using System.Reflection;
namespace Editor;
/// <summary>
/// Allows resources to choose between different types of sources, such as embedded, disk, or a generator.
/// At its core this is just a button to switch types and a body cell holding the editor for whatever type is selected.
/// </summary>
public sealed class ResourceWrapperControlWidget : ControlWidget
{
public override bool SupportsMultiEdit => true;
public Resource Resource => SerializedProperty.GetValue<Resource>( null );
IconButton Button;
Layout Body;
string CurrentOption = null;
public ResourceWrapperControlWidget( SerializedProperty property ) : base( property )
{
HorizontalSizeMode = SizeMode.CanGrow | SizeMode.Expand;
MouseTracking = true;
AcceptDrops = true;
IsDraggable = true;
Layout = Layout.Row();
Layout.Spacing = 2;
Body = Layout.AddRow();
Layout.AddStretchCell();
if ( HasAvailableOptions() )
{
var buttonCell = Layout.AddColumn();
buttonCell.Alignment = TextFlag.RightTop;
Button = buttonCell.Add( new IconButton.WithCornerIcon( "category" )
{
OnClick = ShowMenu,
Background = Color.Transparent,
Foreground = Theme.Yellow,
IconSize = 16,
CornerIconSize = 16,
CornerIconOffset = 2
} );
Button.Enabled = SerializedProperty.IsEditable;
Button.FixedSize = Theme.RowHeight;
Button.ToolTip = "Source Type";
}
TryGetOption();
RebuildControl();
}
bool IsActive( string option ) => CurrentOption == option;
Option AddOption( Menu menu, string name, string icon = null, string option = null, Action action = null )
{
var o = menu.AddOption( name, icon, () =>
{
if ( action is null )
{
SwitchTo( option );
}
else
{
action();
}
} );
o.Checkable = true;
o.Checked = IsActive( option );
return o;
}
/// <summary>
/// Special case, when we switch to embedded, we can take the current GameResource properties and apply them to the inline resource
/// </summary>
void SwitchToEmbedded()
{
//
// I think this is better than having a specific option to convert a file to embedded
//
var oldResource = SerializedProperty.GetValue<GameResource>();
CurrentOption = "embed";
if ( oldResource is null )
{
RebuildControl();
return;
}
//
// Create a new embedded resource from the old one, this will copy all properties over
//
EmbeddedResourceControlWidget.CreateEmbeddedFromFile( SerializedProperty, oldResource );
RebuildControl();
}
/// <summary>
/// Do we have any options available for this resource?
/// </summary>
/// <returns></returns>
private bool HasAvailableOptions()
{
//
// GameResources - CanEmbed?
//
var attribute = SerializedProperty.PropertyType.GetCustomAttribute<AssetTypeAttribute>();
if ( attribute is not null && !attribute.Flags.Contains( AssetTypeFlags.NoEmbedding ) )
{
return true;
}
//
// Find the gernerators
//
var generators = EditorTypeLibrary.GetGenericTypes( typeof( ResourceGenerator<> ), [SerializedProperty.PropertyType] );
if ( generators.Any( x => !x.TargetType.IsAbstract ) )
{
return true;
}
//
// Anything else
//
return false;
}
/// <summary>
/// Show the list of options this resource can come from
/// </summary>
void ShowMenu()
{
var menu = new ContextMenu();
AddOption( menu, "From Disk", "folder" );
// Only allow embedded if the type is a subclass of GameResource - it doesn't make sense to embed native types
if ( SerializedProperty.PropertyType.IsSubclassOf( typeof( GameResource ) ) )
{
var attribute = SerializedProperty.PropertyType.GetCustomAttribute<AssetTypeAttribute>();
if ( attribute is not null && !attribute.Flags.Contains( AssetTypeFlags.NoEmbedding ) )
{
AddOption( menu, "Embedded", "document_scanner", "embed", SwitchToEmbedded );
}
}
//
// Add The Generators
//
var generators = EditorTypeLibrary.GetGenericTypes( typeof( ResourceGenerator<> ), [SerializedProperty.PropertyType] );
foreach ( var generator in generators.OrderBy( x => x.Order ).ThenBy( x => x.Name ) )
{
if ( generator.TargetType.IsAbstract ) continue;
AddOption( menu, generator.Title, generator.Icon, generator.ClassName );
}
menu.OpenNextTo( Button, WidgetAnchor.BottomEnd with { AdjustSize = true, ConstrainToScreen = true } );
}
void TryGetOption()
{
// No need to check if not embedded
if ( Resource is null || !Resource.EmbeddedResource.HasValue )
return;
//
// If we're using the embed path, we don't want to use the generator at all
//
if ( Resource.EmbeddedResource.Value.ResourceCompiler == "embed" )
{
CurrentOption = "embed";
return;
}
CurrentOption = Resource.EmbeddedResource.Value.ResourceGenerator;
}
void SwitchTo( string name = null )
{
CurrentOption = name;
RebuildControl();
}
/// <summary>
/// Rebuild the resource editing control
/// </summary>
public void RebuildControl()
{
Body.Clear( true );
var types = EditorTypeLibrary.GetGenericTypes( typeof( ResourceGenerator<> ), [SerializedProperty.PropertyType] );
var type = types.FirstOrDefault( x => x.ClassName == CurrentOption );
Widget editor = null;
// Which editor should we use?
{
if ( type is not null && type.Create<ResourceGenerator>() is var generator )
{
editor = ResourceGeneratorControlWidget.Create( generator, SerializedProperty );
if ( Button.IsValid() )
Button.Icon = DisplayInfo.For( generator ).Icon;
}
else if ( CurrentOption == "embed" )
{
editor = EmbeddedResourceControlWidget.CreateWidget( SerializedProperty );
if ( Button.IsValid() )
Button.Icon = "document_scanner";
}
else
{
editor = ControlWidget.Create( SerializedProperty );
if ( Button.IsValid() )
Button.Icon = "folder";
}
}
if ( editor is not null )
{
Body.Add( editor );
}
}
protected override void PaintUnder()
{
// nothing
}
}