namespace Editor; /// /// This is a callback control widget, which is used to edit classes. /// It shows key properties, and an edit button. On clicking the edit button /// it'll show a propery sheet popup. /// public class GenericControlWidget : ControlObjectWidget { public override bool SupportsMultiEdit => true; public override bool IsWideMode => _isInlineEditor; public override bool IncludeLabel => !_isInlineEditor; bool _isInlineEditor; RealTimeSince _visibilityDebounce = 0; Widget PlaceholderWidget; Dictionary KeyPropertyWidgets; public GenericControlWidget( SerializedProperty property ) : base( property, true ) { PaintBackground = false; if ( property.TryGetAttribute( out var inlineEditor ) ) { _isInlineEditor = true; BuildInlineEditor( inlineEditor ); return; } Layout = Layout.Row(); Layout.Spacing = 2; KeyPropertyWidgets = new(); var keys = SerializedObject?.Where( x => x.HasAttribute() ).ToArray(); if ( keys is not null && keys.Length > 0 ) { foreach ( var key in keys ) { var widget = Layout.Add( ControlSheetRow.CreateEditor( key ) ); widget.Visible = key.ShouldShow(); KeyPropertyWidgets.Add( key, widget ); } // Only show the full edit button if we have editable properties that aren't showing on the front bar. var propertiesThatArentKeys = SerializedObject?.Where( x => x.IsEditable && x.ShouldShow() && !x.IsMethod && !x.HasAttribute() ).ToArray(); if ( propertiesThatArentKeys.Length > 0 ) { var row = Layout.AddColumn(); var popupButton = new IconButton( "edit_note" ); popupButton.OnClick = OpenPopup; popupButton.ToolTip = $"Edit"; popupButton.Background = Theme.ControlBackground; popupButton.Foreground = Theme.Text; popupButton.IconSize = 16; row.Add( popupButton ); row.AddStretchCell( 1 ); } } else { PlaceholderWidget = new Widget( this ); PlaceholderWidget.Size = Theme.RowHeight; PlaceholderWidget.VerticalSizeMode = SizeMode.CanGrow; PlaceholderWidget.HorizontalSizeMode = SizeMode.Flexible; PlaceholderWidget.OnPaintOverride = PaintPlaceholder; PlaceholderWidget.MouseClick = OpenPopup; PlaceholderWidget.Cursor = CursorShape.Finger; PlaceholderWidget.ToolTip = $"Edit"; Layout.Add( PlaceholderWidget, 1 ); } } StickyPopup Popup; public override void OnDestroyed() { Popup?.Destroy(); Popup = null; base.OnDestroyed(); } protected void OpenPopup() { if ( Popup.IsValid() ) { Popup?.Destroy(); Popup = null; return; } var obj = SerializedObject; // if it's nullable, create for the actual value rather than the nullable container if ( SerializedProperty.IsNullable ) { // best way to do this? obj = SerializedProperty.GetValue().GetSerialized(); obj.ParentProperty = SerializedProperty; } if ( obj is null ) { Log.Error( "Cannot create ControlSheet for a null object" ); return; } // // Create a popup for the control sheet editor // { var popup = new StickyPopup( null ) { Owner = this, MinimumWidth = Width, Position = ScreenRect.BottomLeft }; var editor = EditorUtility.OpenControlSheet( obj, this, false ); popup.Layout.Add( editor ); popup.OnPaintOverride = PaintPopupBackground; popup.Visible = true; popup.Focus( true ); Popup = popup; // // Clear any unrelated popups // Popup.DestroyUnrelatedPopups(); } } bool PaintPopupBackground() { Paint.ClearPen(); Paint.SetBrushLinear( 0, Vector2.Down * 256, Theme.SurfaceBackground.Lighten( 0.2f ).WithAlpha( 0.98f ), Theme.SurfaceBackground.WithAlpha( 0.95f ) ); Paint.DrawRect( Paint.LocalRect ); Paint.ClearBrush(); Paint.SetPen( Color.Black.WithAlpha( 0.33f ), 2, PenStyle.Solid ); Paint.DrawRect( Paint.LocalRect.Shrink( 0, -10, 1, 1 ), 4 ); return true; } bool PaintPlaceholder() { var type = SerializedProperty.IsNullable ? SerializedProperty.NullableType : SerializedProperty.PropertyType; var value = SerializedProperty.GetValue(); type = value?.GetType() ?? type; var displayInfo = DisplayInfo.ForType( type ); string labelText = displayInfo.Name; // If the type has a custom ToString(), use that instead of the type name. if ( type.GetMethod( "ToString" ).DeclaringType != typeof( object ) ) { labelText = value?.ToString() ?? labelText; } Theme.DrawDropdown( LocalRect, labelText, displayInfo.Icon ?? "edit_note", Popup.IsValid(), IsControlDisabled ); return true; } /// /// The property has [InlineEditor], so we want to unfold this and show it inline. /// void BuildInlineEditor( InlineEditorAttribute attribute ) { Layout = Layout.Column(); if ( attribute.Label ) { Layout.Add( ControlSheet.CreateLabel( SerializedProperty ) ); } var cs = new ControlSheet(); cs.AddObject( SerializedObject ); cs.Margin = 0; Layout.Add( cs ); } protected override void OnContextMenu( ContextMenuEvent e ) { if ( !_isInlineEditor ) return; e.Accepted = true; var property = SerializedProperty; var menu = new ContextMenu( this ); menu.AddOption( $"Copy {property.DisplayName}", "content_copy", () => { var str = ToClipboardString(); EditorUtility.Clipboard.Copy( str ); } ); menu.AddOption( $"Paste as {property.DisplayName}", "content_paste", () => { var str = EditorUtility.Clipboard.Paste(); FromClipboardString( str ); } ); menu.AddOption( "Reset to Default", "restart_alt", () => { property.Parent.NoteStartEdit( property ); property.SetValue( property.GetDefault() ); property.Parent.NoteFinishEdit( property ); } ); menu.OpenAt( e.ScreenPosition, false ); } [EditorEvent.Frame] public void UpdateVisibility() { if ( KeyPropertyWidgets is null ) return; if ( _visibilityDebounce < 0.2f ) return; _visibilityDebounce = Random.Shared.Float( 0, 0.1f ); foreach ( var kvp in KeyPropertyWidgets ) { kvp.Value.Visible = kvp.Key.ShouldShow(); } } }