An Editor UI widget that displays a drag-and-drop area for importing video or Record3D (.r3d) files and provides buttons to choose a video or upload from phone. It validates file extensions on drag hover/drop, calls the provided import callback with the file path, and draws visual hover states.
using System;
using System.IO;
using Editor;
using Sandbox;
namespace HumanoidMocap.EditorTools;
// First-load surface, styled after Humanoid Rigger's ModelDropArea: a dark-gray
// rounded box on the gray window, centered prompt and a green choose button.
sealed class VideoDropArea : Widget
{
readonly Action<string> import;
int hover;
public VideoDropArea(Widget parent, Action<string> import, Action chooseVideo, Action phoneUpload) : base(parent)
{
this.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 VideoIcon(this));
center.Add(new Label.Subtitle("Video to motion"){Alignment=TextFlag.Center});
var help=center.Add(new Label("Please drag and drop a video here (.mp4, .mov) or a Record3D LiDAR recording (.r3d)",this){Alignment=TextFlag.Center});
help.SetStyles($"color: {Theme.TextLight.Hex};");
center.Add(new Label("or",this){Alignment=TextFlag.Center});
var choice=center.AddRow();choice.Spacing=8;choice.AddStretchCell();
choice.Add(new Button.Primary("Choose Video"){Icon="video_file",Tint=Theme.Green,MinimumWidth=140,FixedHeight=32,Clicked=chooseVideo});
choice.Add(new Button("From phone","qr_code_2"){FixedHeight=32,Clicked=phoneUpload});
choice.AddStretchCell();
row.AddStretchCell();Layout.AddStretchCell();
}
static bool Supported(string path)=>Path.GetExtension(path).ToLowerInvariant() is ".mp4" or ".mov" or ".m4v" or ".r3d";
public override void OnDragHover(DragEvent e)
{bool valid=e.Data.HasFileOrFolder&&Supported(e.Data.FileOrFolder);hover=valid?1:-1;if(valid)e.Action=DropAction.Link;Update();}
public override void OnDragDrop(DragEvent e)
{hover=0;if(e.Data.HasFileOrFolder&&Supported(e.Data.FileOrFolder)){e.Action=DropAction.Link;import(e.Data.FileOrFolder);}Update();}
public override void OnDragLeave(){hover=0;Update();}
protected override void OnPaint()
{
Paint.SetPen(hover==1?Theme.Green:hover<0?Theme.Red:Theme.ControlBackground.Lighten(.2f),1);
Paint.SetBrush(hover==1?Theme.Green.WithAlpha(.06f):Paint.HasMouseOver?Theme.ControlBackground.Lighten(.3f):Theme.ControlBackground);
Paint.DrawRect(LocalRect.Shrink(1),4);
}
sealed class VideoIcon : Widget
{
public VideoIcon(Widget parent):base(parent){FixedHeight=48;}
protected override void OnPaint(){Paint.SetPen(Theme.TextLight);Paint.DrawIcon(new Rect((Width-40)*.5f,4,40,40),"video_file",40);}
}
}