using Sandbox.Diagnostics; using System.IO; namespace Editor; /// /// Resources stored as strings /// [CustomEditor( typeof( string ), WithAllAttributes = new[] { typeof( AssetPathAttribute ) } )] public class ResourceStringControlWidget : ControlWidget { AssetType AssetType; public override bool IsControlButton => true; public override bool SupportsMultiEdit => true; public string WarningText { get; set; } public ResourceStringControlWidget( SerializedProperty property ) : base( property ) { property.TryGetAttribute( out var attr ); Assert.NotNull( attr, "ResourceStringControlWidget property has no AssetPathAttribute" ); var resourceExtension = attr.AssetTypeExtension; resourceExtension = resourceExtension.Replace( "resource:", "" ); resourceExtension = resourceExtension.Trim().ToLower(); AssetType = AssetType.All.FirstOrDefault( x => x.FileExtension == resourceExtension ); Cursor = CursorShape.Finger; MouseTracking = true; AcceptDrops = true; IsDraggable = true; OnValueChanged(); } protected override void OnValueChanged() { base.OnValueChanged(); if ( AssetType == AssetType.MapFile ) { var resource = SerializedProperty.GetValue( null ); var asset = resource != null ? AssetSystem.FindByPath( resource ) : null; if ( asset is not null ) { bool isCompiled = File.Exists( Path.ChangeExtension( asset.AbsolutePath, ".vpk" ) ); if ( !isCompiled ) WarningText = ToolTip = "Map is not compiled"; } } } protected override void PaintControl() { var resource = SerializedProperty.GetValue( null ); var asset = resource != null ? AssetSystem.FindByPath( resource ) : 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 ) ); Paint.DrawRect( iconRect, 2 ); var pickerName = "Unknown Resource"; if ( AssetType is not null ) pickerName = AssetType.FriendlyName; if ( !string.IsNullOrEmpty( WarningText ) ) { Rect warningRect = iconRect; warningRect.Left = rect.Left + 16; Paint.SetPen( Theme.Yellow ); Paint.DrawIcon( warningRect, "warning", Math.Max( 16, warningRect.Height / 2 ) ); rect.Left += 16; } Pixmap icon = AssetType?.Icon64; if ( SerializedProperty.IsMultipleDifferentValues ) { var textRect = rect.Shrink( 0, 3 ); if ( icon != null ) Paint.Draw( iconRect, icon ); Paint.SetDefaultFont(); Paint.SetPen( Theme.MultipleValues ); Paint.DrawText( textRect, $"Multiple Values", TextFlag.LeftCenter ); } else if ( asset is not null && !asset.IsDeleted ) { Paint.Draw( iconRect, asset.GetAssetThumb( true ) ); var textRect = rect.Shrink( 0, 3 ); Paint.SetPen( Theme.Text.WithAlpha( 0.9f ) ); Paint.SetHeadingFont( 8, 450 ); var t = Paint.DrawText( textRect, $"{asset.Name}", TextFlag.LeftTop ); textRect.Left = t.Right + 6; Paint.SetDefaultFont( 7 ); Theme.DrawFilename( textRect, asset.RelativePath, TextFlag.LeftCenter, Theme.Text.WithAlpha( 0.5f ) ); } else if ( !string.IsNullOrWhiteSpace( resource ) ) { var textRect = rect.Shrink( 0, 3 ); bool isPackage = !resource.Contains( ".vmap" ) && Package.TryParseIdent( resource, out _ ); if ( !isPackage ) { Paint.SetBrush( Theme.Red.Darken( 0.8f ) ); Paint.DrawRect( iconRect, 2 ); Paint.SetPen( Theme.Red ); Paint.DrawIcon( iconRect, "error", Math.Max( 16, iconRect.Height / 2 ) ); } else if ( icon != null ) Paint.Draw( iconRect, icon ); Paint.SetPen( Theme.Text.WithAlpha( 0.9f ) ); Paint.SetHeadingFont( 8, 450 ); var t = Paint.DrawText( textRect, isPackage ? (Package.TryGetCached( resource, out Package package ) ? $"{package.Title} ☁️" : $"Cloud {pickerName}") : $"Missing {pickerName}", TextFlag.LeftTop ); textRect.Left = t.Right + 6; Paint.SetDefaultFont( 7 ); Theme.DrawFilename( textRect, resource, TextFlag.LeftCenter, Theme.Text.WithAlpha( 0.5f ) ); } else { var textRect = rect.Shrink( 0, 3 ); if ( icon != null ) Paint.Draw( iconRect, icon ); Paint.SetDefaultFont( italic: true ); Paint.SetPen( Theme.Text.WithAlpha( 0.2f ) ); Paint.DrawText( textRect, $"{pickerName}", TextFlag.LeftCenter ); } } protected override void OnContextMenu( ContextMenuEvent e ) { var m = new ContextMenu(); var resource = SerializedProperty.GetValue( null ); var asset = (resource != null) ? AssetSystem.FindByPath( resource ) : 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 = AssetType == AssetType.MapFile ? !string.IsNullOrWhiteSpace( resource ) : 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( null ); var asset = resource != null ? AssetSystem.FindByPath( resource ) : null; var picker = AssetPicker.Create( null, AssetType ); PropertyStartEdit(); picker.SetSelection( resource ); picker.Title = $"Select {SerializedProperty.DisplayName}"; if ( AssetType == AssetType.MapFile ) { picker.OnPackagePicked = ( o ) => { SerializedProperty.SetValue( o.FullIdent ); PropertyFinishEdit(); }; } else { picker.OnAssetHighlighted = ( o ) => { UpdateFromAsset( o.FirstOrDefault() ); PropertyFinishEdit(); }; } picker.OnAssetPicked = ( o ) => UpdateFromAsset( o.FirstOrDefault() ); picker.Show(); picker.SetSelection( asset ); } private void UpdateFromAsset( Asset asset ) { if ( asset is null ) return; SerializedProperty.Parent?.NoteStartEdit( SerializedProperty ); SerializedProperty.SetValue( asset.RelativePath ); SerializedProperty.Parent?.NoteFinishEdit( SerializedProperty ); } public override void OnDragHover( DragEvent ev ) { if ( AssetType == AssetType.MapFile && ev.Data.Url?.Scheme == "https" ) { ev.Action = DropAction.Link; return; } if ( !ev.Data.HasFileOrFolder ) return; var asset = AssetSystem.FindByPath( ev.Data.FileOrFolder ); if ( asset == null || AssetType != asset.AssetType ) return; ev.Action = DropAction.Link; } public override void OnDragDrop( DragEvent ev ) { if ( AssetType == AssetType.MapFile && ev.Data.Url?.Scheme == "https" ) { if ( !Package.TryParseIdent( ev.Data.Url.ToString(), out var ident ) ) return; PropertyStartEdit(); SerializedProperty.SetValue( $"{ident.org}.{ident.package}" ); PropertyFinishEdit(); ev.Action = DropAction.Link; return; } if ( !ev.Data.HasFileOrFolder ) return; var asset = AssetSystem.FindByPath( ev.Data.FileOrFolder ); if ( asset == null || AssetType != asset.AssetType ) return; PropertyStartEdit(); UpdateFromAsset( asset ); PropertyFinishEdit(); ev.Action = DropAction.Link; } protected override void OnDragStart() { var resource = SerializedProperty.GetValue( null ); var asset = resource != null ? AssetSystem.FindByPath( resource ) : null; if ( asset == null ) return; var drag = new Drag( this ); drag.Data.Url = new Uri( $"file://{asset.AbsolutePath}" ); drag.Execute(); } void Copy() { var resource = SerializedProperty.GetValue( null ); if ( resource == null ) return; var asset = AssetSystem.FindByPath( resource ); if ( asset != null ) resource = asset.RelativePath; EditorUtility.Clipboard.Copy( resource ); } 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 ); } }