using Sandbox.Resources; using System.Reflection; namespace Editor; /// /// 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. /// public sealed class ResourceWrapperControlWidget : ControlWidget { public override bool SupportsMultiEdit => true; public Resource Resource => SerializedProperty.GetValue( 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; } /// /// Special case, when we switch to embedded, we can take the current GameResource properties and apply them to the inline resource /// void SwitchToEmbedded() { // // I think this is better than having a specific option to convert a file to embedded // var oldResource = SerializedProperty.GetValue(); 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(); } /// /// Do we have any options available for this resource? /// /// private bool HasAvailableOptions() { // // GameResources - CanEmbed? // var attribute = SerializedProperty.PropertyType.GetCustomAttribute(); 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; } /// /// Show the list of options this resource can come from /// 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(); 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(); } /// /// Rebuild the resource editing control /// 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() 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 } }