using System.Text.Json.Nodes; namespace Editor; [CustomEmbeddedEditor( typeof( Sprite ) )] public class EmbeddedSpriteControlWidget : EmbeddedResourceControlWidget { /// /// Whether or not we are displaying an advanced version of the control /// public bool AdvancedMode => (Sprite?.Animations?.Count ?? 0) > 1 || (Sprite?.Animations?.FirstOrDefault()?.Frames?.Count ?? 0) > 1; /// /// An easy accessor for the first texture in the sprite, great for embedded sprites /// Texture Texture { get { var sprite = Sprite; CheckForSpriteTexture( sprite ); // Ensure frame/animation exists return sprite.Animations[0].Frames[0].Texture; } set { PropertyStartEdit(); var sprite = Sprite; if ( sprite == null || AdvancedMode ) { // Create a new sprite if we don't have one, or if we're in advanced mode to avoid setting texture of pre-existing sprite sprite = new Sprite(); } CheckForSpriteTexture( sprite ); // Ensure frame/animation exists sprite.Animations[0].Frames[0].Texture = value; SerializedProperty.SetValue( sprite ); PropertyFinishEdit(); } } Sprite Sprite => SerializedProperty.GetValue( null ); Widget ContentWidget; public EmbeddedSpriteControlWidget( SerializedProperty property ) : base( property ) { ContentWidget = new Widget( this ); ContentWidget.Layout = Layout.Row(); ContentWidget.Layout.Spacing = 4; ContentWidget.Visible = false; AcceptDrops = true; var so = this.GetSerialized(); var texControlWidget = new Widget(); texControlWidget.Layout = Layout.Row(); var texControl = ControlSheetRow.CreateEditor( so.GetProperty( nameof( this.Texture ) ) ); texControlWidget.Layout.Add( texControl ); ContentWidget.Layout.Add( texControlWidget ); texControlWidget.OnPaintOverride = () => { var rect = texControlWidget.LocalRect; Paint.SetBrushAndPen( Theme.ControlBackground, Color.Transparent, 0 ); Paint.DrawRect( rect, 2 ); bool active = IsControlActive; bool hovered = IsControlHovered; if ( hovered && IsBeingDroppedOn ) { Paint.SetPen( ControlHighlightSecondary.WithAlpha( 0.8f ), 2, PenStyle.Dot ); Paint.SetBrush( ControlHighlightSecondary.WithAlpha( 0.2f ) ); Paint.DrawRect( rect.Shrink( 2 ), Theme.ControlRadius ); } return false; }; var btnAdvanced = new IconButton( "edit_note" ); btnAdvanced.Background = Theme.ControlBackground; btnAdvanced.Foreground = Theme.Text; btnAdvanced.IconSize = 16; btnAdvanced.Name = "dropdown"; btnAdvanced.ToolTip = "Open Advanced Settings"; btnAdvanced.OnClick = () => { if ( _popup.IsValid() ) { _popup?.Destroy(); _popup = null; return; } OpenPopupEditor(); }; ContentWidget.Layout.Add( btnAdvanced ); System.HashCode.Combine( Texture, AdvancedMode ); } public override void OnDragHover( DragEvent ev ) { ev.Action = DropAction.Move; base.OnDragHover( ev ); } public override void OnDragDrop( DragEvent ev ) { var asset = AssetSystem.FindByPath( ev.Data.FileOrFolder ); if ( asset is not null ) { var so = this.GetSerialized(); var prop = so.GetProperty( nameof( this.Texture ) ); var didDrag = false; if ( asset.AssetType == AssetType.ImageFile ) { var texture = Texture.Load( asset.RelativePath ); texture.EmbeddedResource = new() { ResourceCompiler = "texture", ResourceGenerator = "imagefile", Data = new JsonObject() { ["FilePath"] = asset.RelativePath } }; prop.SetValue( texture ); didDrag = true; } if ( asset.AssetType == AssetType.Texture ) { prop.SetValue( asset.LoadResource() ); didDrag = true; } if ( asset.TryLoadResource( out var sprite ) ) { SerializedProperty.SetValue( sprite ); didDrag = true; } if ( didDrag ) { if ( Parent is ResourceWrapperControlWidget rwcw ) { rwcw.RebuildControl(); } return; } } base.OnDragDrop( ev ); } protected override void OnPaint() { if ( !AdvancedMode ) return; base.OnPaint(); } protected override void DoLayout() { base.DoLayout(); ContentWidget.Visible = !AdvancedMode; if ( ContentWidget.Visible ) { CleanseWidgets( ContentWidget ); ContentWidget.Width = Width; ContentWidget.Height = Height; } } protected override void OnMouseReleased( MouseEvent e ) { if ( !AdvancedMode ) return; base.OnMouseReleased( e ); } private void CheckForSpriteTexture( Sprite sprite ) { if ( sprite is null ) { sprite = new(); SerializedProperty.SetValue( sprite ); } // Ensure first animation exists if ( (sprite.Animations?.Count ?? 0) == 0 ) { sprite.Animations = [new Sprite.Animation()]; } // Ensure first frame exists var anim = sprite.Animations[0]; if ( (anim.Frames?.Count ?? 0) == 0 ) { anim.Frames = [new Sprite.Frame()]; SerializedProperty.SetValue( sprite ); } } // Disable any IconButtons and disable drop events void CleanseWidgets( Widget widget ) { foreach ( var child in widget.Children ) { if ( child is TextureWidget ) { child.Visible = false; continue; } if ( child is IconButton && child is not IconButton.WithCornerIcon && child.Name != "dropdown" ) { child.Visible = false; continue; } if ( child is ControlWidget cw ) { cw.PaintBackground = false; } child.AcceptDrops = false; CleanseWidgets( child ); } } private RealTimeSince _debounce = 0; private int _lastHash; [EditorEvent.Frame] void OnFrame() { if ( _debounce < 1 ) return; var currentHash = System.HashCode.Combine( Texture, AdvancedMode ); if ( currentHash != _lastHash ) { Update(); DoLayout(); } _lastHash = currentHash; _debounce = Random.Shared.Float(); } }