namespace Editor; [CustomEditor( typeof( GameObject ) )] public class GameObjectControlWidget : ControlWidget { public override bool SupportsMultiEdit => true; bool IsInList => Parent?.Parent is ListControlWidget; IconButton PickerButton; public GameObjectControlWidget( SerializedProperty property ) : base( property ) { if ( !property.IsEditable ) ReadOnly = true; IsDraggable = true; SetSizeMode( SizeMode.Default, SizeMode.Default ); Layout = Layout.Column(); Layout.Spacing = 2; var inner = Layout.Add( Layout.Row() ); inner.Margin = 2; inner.AddStretchCell( 1 ); PickerButton = inner.Add( new IconButton( "colorize", () => { PropertyStartEdit(); EyeDropperTool.SetTargetProperty( property ); EyeDropperTool.OnBackToLastTool = () => PropertyFinishEdit(); }, this ) ); PickerButton.FixedWidth = Theme.RowHeight - 4; PickerButton.FixedHeight = Theme.RowHeight - 4; PickerButton.Visible = false; Cursor = CursorShape.Finger; AcceptDrops = true; } protected override void OnContextMenu( ContextMenuEvent e ) { var m = new ContextMenu( this ); var go = SerializedProperty.GetValue(); if ( go is PrefabScene prefab ) { var asset = AssetSystem.FindByPath( prefab.Source.ResourcePath ); 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(); } else if ( go is not null ) { m.AddOption( "Find in Scene", "search", () => FindInScene( go ) ); m.AddSeparator(); } //m.AddOption( "Copy", "file_copy" ); //m.AddOption( "Paste", "content_paste" ); //m.AddSeparator(); if ( !ReadOnly ) { m.AddOption( "Clear", "backspace", action: Clear ); } if ( m.OptionCount > 0 ) { m.OpenAtCursor( false ); } e.Accepted = true; } protected override void PaintControl() { var rect = LocalRect.Shrink( 6, 0 ); var go = SerializedProperty.GetValue(); Paint.SetDefaultFont(); if ( SerializedProperty.IsMultipleDifferentValues ) { Paint.SetPen( Theme.MultipleValues ); Paint.DrawIcon( rect, "panorama_wide_angle_select", 14, TextFlag.LeftCenter ); rect.Left += 22; Paint.DrawText( rect, "Multiple Values", TextFlag.LeftCenter ); } else if ( !go.IsValid() ) { Paint.SetPen( Theme.TextControl.WithAlpha( 0.3f ) ); Paint.DrawIcon( rect, "radio_button_unchecked", 14, TextFlag.LeftCenter ); rect.Left += 22; Paint.DrawText( rect, "None (GameObject)", TextFlag.LeftCenter ); } else if ( go is PrefabScene ) { Paint.SetPen( Theme.Blue ); Paint.DrawIcon( rect, "panorama_wide_angle_select", 14, TextFlag.LeftCenter ); rect.Left += 22; Paint.DrawText( rect, go.Name, TextFlag.LeftCenter ); } else { Paint.SetPen( Theme.Green ); Paint.DrawIcon( rect, "panorama_wide_angle_select", 14, TextFlag.LeftCenter ); rect.Left += 22; Paint.DrawText( rect, BuildName( go ), TextFlag.LeftCenter ); } if ( EyeDropperTool.TargetProperty == SerializedProperty ) { Paint.SetPen( ControlHighlightSecondary.WithAlpha( 0.8f ), 2, PenStyle.Dot ); Paint.SetBrush( ControlHighlightSecondary.WithAlpha( 0.2f ) ); Paint.DrawRect( LocalRect.Shrink( 2 ), Theme.ControlRadius ); } PickerButton.Visible = IsControlHovered; } private string BuildName( GameObject go ) { string str = ""; if ( go.Parent.IsValid() && go.Parent is not Scene ) { str += $"{BuildName( go.Parent )} > "; } str += $"{go.Name}"; return str; } void UpdateFromAsset( Asset asset ) { if ( asset.TryLoadResource( out PrefabFile prefabFile ) ) { PropertyStartEdit(); SerializedProperty.SetValue( SceneUtility.GetPrefabScene( prefabFile ) ); PropertyFinishEdit(); } } void UpdateFromAssets( Asset[] assets ) { if ( assets.Length == 0 ) return; if ( assets[0].TryLoadResource( out PrefabFile prefabFile ) ) { PropertyStartEdit(); SerializedProperty.SetValue( SceneUtility.GetPrefabScene( prefabFile ) ); PropertyFinishEdit(); } if ( IsInList ) { var list = Parent.Parent as ListControlWidget; for ( int i = 1; i < assets.Length; i++ ) { if ( assets[i].TryLoadResource( out PrefabFile newPrefabFile ) ) { list.Collection.Add( SceneUtility.GetPrefabScene( newPrefabFile ) ); } } } } protected override void OnMouseClick( MouseEvent e ) { if ( e.LeftMouseButton ) { e.Accepted = true; var go = SerializedProperty.GetValue(); if ( go.IsValid() && go is not PrefabScene ) { // If the current value exists in the scene, select it FindInScene( go ); } else { if ( ReadOnly ) return; var resource = (go as PrefabScene)?.Source ?? null; var asset = resource != null ? AssetSystem.FindByPath( resource.ResourcePath ) : null; // Open Prefab Asset Picker var prefabAssetType = AssetType.Find( "prefab", false ); var picker = AssetPicker.Create( null, prefabAssetType, new AssetPicker.PickerOptions() { EnableMultiselect = IsInList } ); picker.Window.Title = $"Select Prefab"; picker.OnAssetHighlighted = ( o ) => UpdateFromAsset( o.FirstOrDefault() ); picker.OnAssetPicked = ( o ) => UpdateFromAssets( o ); picker.Show(); picker.SetSelection( asset ); } } } void Clear() { PropertyStartEdit(); SerializedProperty.SetValue( null ); SignalValuesChanged(); PropertyFinishEdit(); } protected override void OnDragStart() { var drag = new Drag( this ); drag.Data.Object = SerializedProperty.GetValue(); drag.Execute(); } public override void OnDragHover( DragEvent ev ) { ev.Action = DropAction.Ignore; if ( ev.Data.OfType().Any() ) { ev.Action = DropAction.Link; return; } if ( ev.Data.HasFileOrFolder ) { var asset = AssetSystem.FindByPath( ev.Data.Files.First() ); if ( asset is null ) return; if ( asset.AssetType.FileExtension != "prefab" ) return; if ( asset.TryLoadResource( out PrefabFile prefabFile ) ) { ev.Action = DropAction.Link; return; } } } public override void OnDragDrop( DragEvent ev ) { if ( ev.Data.OfType().FirstOrDefault() is { } go ) { PropertyStartEdit(); SerializedProperty.SetValue( go ); PropertyFinishEdit(); return; } if ( ev.Data.HasFileOrFolder ) { var asset = AssetSystem.FindByPath( ev.Data.Files.First() ); if ( asset is null ) return; if ( asset.AssetType.FileExtension != "prefab" ) return; if ( asset.TryLoadResource( out PrefabFile prefabFile ) ) { PropertyStartEdit(); SerializedProperty.SetValue( SceneUtility.GetPrefabScene( prefabFile ) ); PropertyFinishEdit(); return; } } } static void FindInScene( GameObject go ) { if ( !go.IsValid() ) return; using ( SceneEditorSession.Active.UndoScope( $"Selected {go}" ).Push() ) { SceneEditorSession.Active?.Selection.Set( go ); } } }