mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-04-19 05:48:07 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
383 lines
9.9 KiB
C#
383 lines
9.9 KiB
C#
namespace Editor;
|
|
|
|
[CustomEditor( typeof( Resource ) )]
|
|
public class ResourceControlWidget : ControlWidget
|
|
{
|
|
AssetType AssetType;
|
|
|
|
IconButton PreviewButton;
|
|
|
|
public override bool IsControlButton => !IsControlDisabled;
|
|
public override bool SupportsMultiEdit => true;
|
|
|
|
bool IsInList => Parent?.Parent is ListControlWidget;
|
|
|
|
public ResourceControlWidget( SerializedProperty property ) : base( property )
|
|
{
|
|
AssetType = AssetType.FromType( property.PropertyType );
|
|
|
|
HorizontalSizeMode = SizeMode.CanGrow | SizeMode.Expand;
|
|
Cursor = CursorShape.Finger;
|
|
MouseTracking = true;
|
|
AcceptDrops = true;
|
|
IsDraggable = true;
|
|
|
|
if ( AssetType == AssetType.SoundFile )
|
|
{
|
|
PreviewButton = new PreviewButton( property )
|
|
{
|
|
Background = Theme.Blue,
|
|
Foreground = Color.White,
|
|
Icon = "volume_up",
|
|
Parent = this,
|
|
ToolTip = "Play Sound",
|
|
Visible = false,
|
|
OnClick = () => EditorUtility.PlayAssetSound( property.GetValue<SoundFile>() )
|
|
};
|
|
}
|
|
}
|
|
|
|
protected override void DoLayout()
|
|
{
|
|
base.DoLayout();
|
|
|
|
if ( PreviewButton.IsValid() )
|
|
{
|
|
PreviewButton.FixedSize = Height - 2;
|
|
PreviewButton.Position = new Vector2( Width - Height + 1, 1 );
|
|
}
|
|
}
|
|
|
|
protected override void PaintControl()
|
|
{
|
|
var resource = SerializedProperty.GetValue<Resource>( null );
|
|
var asset = resource != null ? AssetSystem.FindByPath( resource.ResourcePath ) : null;
|
|
|
|
var rect = new Rect( 0, Size );
|
|
|
|
var iconRect = rect.Shrink( 2 );
|
|
iconRect.Width = iconRect.Height;
|
|
|
|
rect.Left = iconRect.Right + 10;
|
|
|
|
Paint.ClearPen();
|
|
Paint.SetBrush( Theme.SurfaceBackground.WithAlpha( 0.2f ) * Tint );
|
|
Paint.DrawRect( iconRect, 2 );
|
|
|
|
var pickerName = DisplayInfo.ForType( SerializedProperty.PropertyType ).Name;
|
|
if ( AssetType is not null ) pickerName = AssetType.FriendlyName;
|
|
|
|
Pixmap icon = AssetType?.Icon64;
|
|
var alpha = IsControlDisabled ? 0.6f : 1f;
|
|
|
|
if ( SerializedProperty.IsMultipleDifferentValues )
|
|
{
|
|
var textRect = rect.Shrink( 0, 3 );
|
|
if ( icon != null ) Paint.Draw( iconRect, icon, alpha );
|
|
|
|
Paint.SetDefaultFont();
|
|
Paint.SetPen( Theme.MultipleValues.WithAlpha( alpha ) );
|
|
Paint.DrawText( textRect, $"Multiple Values", TextFlag.LeftCenter );
|
|
}
|
|
else if ( asset is not null && !asset.IsDeleted )
|
|
{
|
|
Paint.Draw( iconRect, asset.GetAssetThumb( true ), alpha );
|
|
|
|
DrawContent( rect, asset.Name, asset.RelativePath );
|
|
}
|
|
else if ( resource != null && !string.IsNullOrEmpty( resource.ResourcePath ) )
|
|
{
|
|
Paint.SetBrush( Theme.Red.Darken( 0.8f ).WithAlpha( alpha ) );
|
|
Paint.DrawRect( iconRect, 2 );
|
|
|
|
Paint.SetPen( Theme.Red.WithAlpha( alpha ) );
|
|
Paint.DrawIcon( iconRect, "error", Math.Max( 16, iconRect.Height / 2 ) );
|
|
|
|
DrawContent( rect, $"Missing {pickerName}", resource.ResourcePath );
|
|
}
|
|
else
|
|
{
|
|
var textRect = rect.Shrink( 0, 3 );
|
|
if ( icon != null ) Paint.Draw( iconRect, icon );
|
|
|
|
Paint.SetDefaultFont();
|
|
Paint.SetPen( Theme.Text.WithAlpha( 0.2f * alpha ) * Tint );
|
|
Paint.DrawText( textRect, $"{pickerName}", TextFlag.LeftCenter );
|
|
}
|
|
|
|
if ( PreviewButton.IsValid() )
|
|
{
|
|
PreviewButton.Visible = IsControlHovered;
|
|
}
|
|
}
|
|
|
|
private void DrawContent( Rect rect, string title, string path )
|
|
{
|
|
bool multiline = Height > 32;
|
|
Rect textRect = rect.Shrink( 0, 6 );
|
|
var alpha = IsControlDisabled ? 0.6f : 1f;
|
|
|
|
if ( multiline )
|
|
{
|
|
textRect = new Rect( textRect.TopLeft, new Vector2( textRect.Width, textRect.Height / 2 ) );
|
|
}
|
|
|
|
Paint.SetPen( Theme.Text.WithAlpha( 0.9f * alpha ) * Tint );
|
|
Paint.SetHeadingFont( 8, 450 );
|
|
var t = Paint.DrawText( textRect, title, multiline ? TextFlag.LeftCenter : TextFlag.LeftCenter );
|
|
|
|
if ( multiline )
|
|
{
|
|
textRect.Position += new Vector2( 0, textRect.Height );
|
|
}
|
|
else
|
|
{
|
|
textRect.Left = t.Right + 6;
|
|
}
|
|
|
|
Paint.SetDefaultFont( 7 );
|
|
Theme.DrawFilename( textRect, path, multiline ? TextFlag.LeftCenter : TextFlag.LeftBottom, Color.White.WithAlpha( 0.5f * alpha ) * Tint );
|
|
}
|
|
|
|
protected override void OnContextMenu( ContextMenuEvent e )
|
|
{
|
|
var m = new ContextMenu();
|
|
|
|
var resource = SerializedProperty.GetValue<Resource>( null );
|
|
var asset = (resource != null) ? AssetSystem.FindByPath( resource.ResourcePath ) : null;
|
|
|
|
m.AddOption( "Open in Editor", "edit", () => asset?.OpenInEditor() ).Enabled = asset != null && !asset.IsProcedural;
|
|
m.AddOption( "Find in Asset Browser", "search", () => LocalAssetBrowser.OpenTo( asset, true ) ).Enabled = asset is not null;
|
|
m.AddSeparator();
|
|
m.AddOption( "Copy", "file_copy", action: Copy ).Enabled = asset != null;
|
|
m.AddOption( "Paste", "content_paste", action: Paste );
|
|
m.AddSeparator();
|
|
m.AddOption( "Clear", "backspace", action: Clear ).Enabled = resource != null;
|
|
|
|
m.OpenAtCursor( false );
|
|
e.Accepted = true;
|
|
}
|
|
|
|
protected override void OnMouseClick( MouseEvent e )
|
|
{
|
|
base.OnMouseClick( e );
|
|
|
|
if ( ReadOnly ) return;
|
|
|
|
var resource = SerializedProperty.GetValue<Resource>( null );
|
|
var asset = resource != null ? AssetSystem.FindByPath( resource.ResourcePath ) : null;
|
|
|
|
var assetType = AssetType.FromType( resource.IsValid() ? resource.GetType() : SerializedProperty.PropertyType );
|
|
|
|
PropertyStartEdit();
|
|
|
|
var picker = AssetPicker.Create( null, assetType, new AssetPicker.PickerOptions()
|
|
{
|
|
EnableMultiselect = IsInList
|
|
} );
|
|
picker.Title = $"Select {SerializedProperty.DisplayName}";
|
|
picker.OnAssetHighlighted = ( o ) => UpdateFromAsset( o.FirstOrDefault() );
|
|
picker.OnAssetPicked = ( o ) =>
|
|
{
|
|
UpdateFromAssets( o );
|
|
PropertyFinishEdit();
|
|
};
|
|
picker.Show();
|
|
|
|
picker.SetSelection( asset );
|
|
}
|
|
|
|
private void UpdateFromAsset( Asset asset )
|
|
{
|
|
if ( asset is null ) return;
|
|
if ( !CanAssignAssetType( asset ) ) return;
|
|
|
|
Resource resource;
|
|
if ( SerializedProperty.PropertyType.IsInterface )
|
|
{
|
|
// For interface types, use the asset directly since we've already verified
|
|
// it implements our interface in CanAssignAssetType
|
|
resource = asset.LoadResource<GameResource>();
|
|
}
|
|
else
|
|
{
|
|
resource = asset.LoadResource( SerializedProperty.PropertyType );
|
|
}
|
|
|
|
SerializedProperty.Parent.NoteStartEdit( SerializedProperty );
|
|
SerializedProperty.SetValue( resource );
|
|
SerializedProperty.Parent.NoteFinishEdit( SerializedProperty );
|
|
}
|
|
|
|
private void UpdateFromAssets( Asset[] assets )
|
|
{
|
|
if ( assets.Length == 0 ) return;
|
|
|
|
if ( CanAssignAssetType( assets[0] ) )
|
|
{
|
|
var resource = assets[0].LoadResource( SerializedProperty.PropertyType );
|
|
SerializedProperty.SetValue( resource );
|
|
}
|
|
|
|
if ( IsInList )
|
|
{
|
|
var list = Parent.Parent as ListControlWidget;
|
|
for ( int i = 1; i < assets.Length; i++ )
|
|
{
|
|
if ( !CanAssignAssetType( assets[i] ) ) continue;
|
|
var newResource = assets[i].LoadResource( SerializedProperty.PropertyType );
|
|
list.Collection.Add( newResource );
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void OnDragHover( DragEvent ev )
|
|
{
|
|
// This might be a cloud asset
|
|
if ( ev.Data.Url?.Scheme == "https" )
|
|
{
|
|
ev.Action = DropAction.Link;
|
|
return;
|
|
}
|
|
|
|
if ( !ev.Data.HasFileOrFolder )
|
|
return;
|
|
|
|
var asset = AssetSystem.FindByPath( ev.Data.FileOrFolder );
|
|
|
|
if ( !CanAssignAssetType( asset ) )
|
|
return;
|
|
|
|
ev.Action = DropAction.Link;
|
|
}
|
|
|
|
public override void OnDragDrop( DragEvent ev )
|
|
{
|
|
if ( ev.Data.Url?.Scheme == "https" )
|
|
{
|
|
PropertyStartEdit();
|
|
_ = DroppedUrl( ev.Data.Text );
|
|
PropertyFinishEdit();
|
|
ev.Action = DropAction.Link;
|
|
return;
|
|
}
|
|
|
|
if ( !ev.Data.HasFileOrFolder )
|
|
return;
|
|
|
|
var asset = AssetSystem.FindByPath( ev.Data.FileOrFolder );
|
|
|
|
if ( !CanAssignAssetType( asset ) )
|
|
return;
|
|
|
|
PropertyStartEdit();
|
|
UpdateFromAsset( asset );
|
|
PropertyFinishEdit();
|
|
ev.Action = DropAction.Link;
|
|
}
|
|
|
|
private bool CanAssignAssetType( Asset asset )
|
|
{
|
|
if ( asset is null )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( AssetType == null )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Handle interface types
|
|
if ( SerializedProperty.PropertyType.IsInterface )
|
|
{
|
|
if ( !asset.AssetType.IsGameResource )
|
|
return false;
|
|
|
|
// Quick type check from asset
|
|
return asset.AssetType.ResourceType.IsAssignableTo( SerializedProperty.PropertyType );
|
|
}
|
|
|
|
if ( AssetType != asset.AssetType )
|
|
{
|
|
if ( !AssetType.IsGameResource )
|
|
return false;
|
|
|
|
if ( !asset.AssetType.IsGameResource )
|
|
return false;
|
|
|
|
if ( !asset.AssetType.ResourceType.IsAssignableTo( SerializedProperty.PropertyType ) )
|
|
return false;
|
|
}
|
|
|
|
// Check that we aren't changing this prefab to the same prefab we're already in (recursive nightmare)
|
|
if ( SerializedProperty.PropertyType == typeof( PrefabFile ) && SceneEditorSession.Active.Scene is PrefabScene prefabScene )
|
|
{
|
|
var prefab = asset.LoadResource<PrefabFile>();
|
|
var scenePrefab = prefabScene.ToPrefabFile();
|
|
if ( prefab is null ) return false;
|
|
if ( scenePrefab.ResourceId == prefab.ResourceId ) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async Task DroppedUrl( string identUrl )
|
|
{
|
|
var asset = await AssetSystem.InstallAsync( identUrl );
|
|
|
|
if ( !CanAssignAssetType( asset ) )
|
|
return;
|
|
|
|
UpdateFromAsset( asset );
|
|
}
|
|
|
|
protected override void OnDragStart()
|
|
{
|
|
var resource = SerializedProperty.GetValue<Resource>( null );
|
|
var asset = resource != null ? AssetSystem.FindByPath( resource.ResourcePath ) : null;
|
|
|
|
if ( asset == null )
|
|
return;
|
|
|
|
var drag = new Drag( this );
|
|
drag.Data.Url = new System.Uri( $"file://{asset.AbsolutePath}" );
|
|
drag.Execute();
|
|
}
|
|
|
|
void Copy()
|
|
{
|
|
var resource = SerializedProperty.GetValue<Resource>( null );
|
|
if ( resource == null ) return;
|
|
|
|
var asset = AssetSystem.FindByPath( resource.ResourcePath );
|
|
if ( asset == null ) return;
|
|
|
|
EditorUtility.Clipboard.Copy( asset.Path );
|
|
}
|
|
|
|
void Paste()
|
|
{
|
|
var path = EditorUtility.Clipboard.Paste();
|
|
var asset = AssetSystem.FindByPath( path );
|
|
UpdateFromAsset( asset );
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
SerializedProperty.Parent.NoteStartEdit( SerializedProperty );
|
|
SerializedProperty.SetValue( (Resource)null );
|
|
SerializedProperty.Parent.NoteFinishEdit( SerializedProperty );
|
|
}
|
|
}
|
|
|
|
file class PreviewButton : IconButton
|
|
{
|
|
private SerializedProperty property;
|
|
|
|
public PreviewButton( SerializedProperty property ) : base( "people" )
|
|
{
|
|
this.property = property;
|
|
}
|
|
}
|