Editor/WeaponImporter/Tool/UI/Widgets/DropArea.cs
using Editor;
using Sandbox;
namespace WeaponImporter.Tool.UI;
/// <summary>
/// First-load surface: a dark-gray rounded box with a centered prompt, a green choose button and
/// a template button. Accepts files from the OS and assets from the asset browser.
/// </summary>
public sealed class DropArea : Widget
{
private readonly Action<string> _import;
private int _hover;
public DropArea( Widget parent, Action<string> import, Action chooseFile, Action chooseFromDisk, Action useTemplate ) : base( parent )
{
_import = import;
AcceptDrops = true;
Layout = Layout.Column();
Layout.Margin = 12;
Layout.Spacing = 8;
Layout.AddStretchCell();
var row = Layout.AddRow();
row.AddStretchCell();
var center = row.AddColumn();
center.Spacing = 12;
center.Add( new BigIcon( this ) );
center.Add( new Label.Subtitle( "Drop a weapon", this ) { Alignment = TextFlag.Center } );
center.Add( UiStyle.Muted( new Label( "FBX, GLB, glTF or VMDL", this ) { Alignment = TextFlag.Center } ) );
center.Add( UiStyle.Muted( new Label( "It is analyzed and set up automatically: type, grips, animations and attachments.", this ) { Alignment = TextFlag.Center, WordWrap = true, MaximumWidth = 420 }, small: true ) );
center.AddSpacingCell( 4 );
var choice = center.AddRow();
choice.Spacing = 8;
choice.AddStretchCell();
choice.Add( new Button.Primary( "Choose File" ) { Icon = "folder_open", Tint = Theme.Green, MinimumWidth = 140, FixedHeight = 32, Clicked = chooseFile, ToolTip = "Pick a weapon model from the asset browser (.fbx, .glb, .gltf, .vmdl)" } );
choice.Add( new UiButton( this, "From disk…", "file_open", chooseFromDisk, "Pick a weapon file outside the project", 32 ) );
choice.Add( new UiButton( this, "Use a template…", "content_copy", useTemplate, "Start from a weapon you already set up: its grips, animation mapping, events and attachments are adapted to the new model", 32 ) );
choice.AddStretchCell();
row.AddStretchCell();
Layout.AddStretchCell();
}
public override void OnDragHover( DragEvent e )
{
var path = DropPaths.From( e.Data );
var valid = path is not null;
_hover = valid ? 1 : -1;
if ( valid )
e.Action = DropAction.Link;
Update();
}
public override void OnDragDrop( DragEvent e )
{
_hover = 0;
var path = DropPaths.From( e.Data );
if ( path is not null )
{
e.Action = DropAction.Link;
_import( path );
}
Update();
}
public override void OnDragLeave()
{
_hover = 0;
Update();
}
protected override void OnPaint()
{
Paint.Antialiasing = true;
Paint.SetPen( _hover == 1 ? Theme.Green : _hover < 0 ? Theme.Red : Theme.ControlBackground.Lighten( .2f ), _hover == 0 ? 1 : 2 );
Paint.SetBrush( _hover == 1 ? Theme.Green.WithAlpha( .06f ) : _hover < 0 ? Theme.Red.WithAlpha( .05f ) : Paint.HasMouseOver ? Theme.ControlBackground.Lighten( .3f ) : Theme.ControlBackground );
Paint.DrawRect( LocalRect.Shrink( 1 ), 6 );
}
private sealed class BigIcon : Widget
{
public BigIcon( Widget parent ) : base( parent )
{
FixedHeight = 56;
}
protected override void OnPaint()
{
Paint.SetPen( Theme.TextLight );
Paint.DrawIcon( new Rect( (Width - 48) * .5f, 4, 48, 48 ), "back_hand", 48 );
}
}
}
/// <summary>What can be dropped on the importer: weapon models and saved setups.</summary>
public static class DropPaths
{
public static bool IsWeaponFile( string path )
{
if ( string.IsNullOrEmpty( path ) )
return false;
if ( IsSetupFile( path ) )
return true;
return System.IO.Path.GetExtension( path ).ToLowerInvariant() is ".fbx" or ".glb" or ".gltf" or ".vmdl";
}
public static bool IsSetupFile( string path ) => path?.EndsWith( ".weapon.json", StringComparison.OrdinalIgnoreCase ) ?? false;
/// <summary>The first supported absolute path in a drag, or null.</summary>
public static string From( DragData data )
{
if ( data is null )
return null;
try
{
if ( data.Files is { Length: > 0 } files )
{
var file = files.FirstOrDefault( IsWeaponFile );
if ( file is not null )
return file;
}
if ( data.HasFileOrFolder && IsWeaponFile( data.FileOrFolder ) )
return data.FileOrFolder;
if ( data.Assets is { Count: > 0 } assets )
{
foreach ( var a in assets )
{
var p = a?.AssetPath;
if ( !IsWeaponFile( p ) )
continue;
var asset = AssetSystem.FindByPath( p );
return asset?.AbsolutePath ?? (System.IO.Path.IsPathRooted( p ) ? p : AssetCompiler.Absolute( p ));
}
}
}
catch ( Exception )
{
// A drag with nothing we understand.
}
return null;
}
}