Editor/CaptureDialog.cs
using System;
using System.IO;
using System.Threading.Tasks;
using Editor;
using Sandbox;
namespace HumanoidMocapX.EditorTools;
/// <summary>The X window: an account bar across the top (provider account and queue, the processing
/// machine, this computer, or a pointer to settings) above the shared workspace. Until a processing
/// choice is saved the drop area is locked and the settings button shows a red dot; the choice is made
/// in settings › Processing › Setup, after which the window rebuilds and reopens that page.</summary>
sealed class CaptureDialog : Dialog
{
const string SetupPage = "Setup";
readonly CaptureOptions options;
readonly HostingSettings hosting;
readonly HostedCapture hosted;
readonly SshSettings machine;
readonly SshCapture remote;
readonly LocalCapture local;
readonly string models;
QueueTracker queue;
RetargetWindow workspace;
FlowCapture capture;
internal CaptureDialog(CaptureOptions options) : base(null)
{
this.options = options;
hosting = new HostingSettings
{
Enabled = EditorCookie.Get(ProcessingSetup.HostedCookie, false),
ServiceUrl = EditorCookie.Get(ProcessingSetup.AddressCookie, ""),
Dispatch = MainThread.Queue
};
var cache = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "HumanoidMocapX");
hosted = new HostedCapture(hosting, options, Path.Combine(cache, "hosted"));
models = Path.Combine(cache, "models");
machine = ProcessingSetup.SavedMachine();
remote = new SshCapture(machine, options, Path.Combine(cache, "captures"));
local = new LocalCapture(options, models, Path.Combine(cache, "captures"));
Window.Title = MocapWindow.Title; Window.SetWindowIcon("videocam");
Layout = Layout.Column();
ShowWorkspace();
}
// Deferred: the button that asked belongs to the view being replaced.
void Later(Action show) => MainThread.Queue(() => { if (this.IsValid()) show(); });
void ShowWorkspace()
{
Layout.Clear(true);
queue?.Dispose(); queue = null;
if (hosting.Enabled)
queue = new QueueTracker(hosting, hosted, path => workspace.IsValid() ? workspace.OpenMotionAsync(path) : Task.CompletedTask);
var configured = ProcessingSetup.Configured;
Layout.Add(new AccountBar(this, hosting, machine, queue, configured, OpenSetup));
capture = new FlowCapture(options, hosting, hosted, machine, remote, local);
workspace = Layout.Add(new RetargetWindow(this, MocapWindow.Title, capture,
host => MocapWindow.Settings(host, options, hosting, machine, () => Later(Applied), RefreshGate)), 1);
var account = workspace.AddSettingsPage("Processing", "account_circle", "Account");
account.Layout.Add(new AccountPage(account, hosting, machine, configured, OpenSetup));
RefreshGate();
Fit(new Vector2(760, 520), new Vector2(1040, 700));
if (hosting.Enabled && hosting.SignedIn) _ = RefreshProfileAsync();
}
void OpenSetup() { if (workspace.IsValid()) workspace.ShowAdjustments(SetupPage); }
// Capture stays locked (grayed drop area, red dot on settings) until a processing choice is saved
// and, on this computer, every model file exists. Rechecked after downloads and model changes.
void RefreshGate()
{
if (!workspace.IsValid()) return;
var reason = !ProcessingSetup.Configured ? ProcessingSetup.SetupMessage
: !hosting.Enabled && !machine.Enabled && ModelStore.Missing(models, options.Model) is { } missing ? ProcessingSetup.ModelsMessage(missing) : null;
capture.Blocked = reason;
workspace.SetSetupRequired(reason);
}
// A saved processing choice changes the account bar, queue and capture backend, so the window
// rebuilds around it; settings reopen where the choice was made.
void Applied() { ShowWorkspace(); OpenSetup(); }
// Picks up whitelist and role changes made since sign-in. An unreachable provider already
// shows in the queue, so a failed refresh keeps the current profile.
async Task RefreshProfileAsync()
{
using var client = new HostSignIn();
try { await client.RefreshAsync(hosting, default); }
catch (Exception failure) when (failure is System.Net.Http.HttpRequestException or InvalidDataException
or System.Text.Json.JsonException or OperationCanceledException or ArgumentException)
{ Log.Info($"Humanoid Mocap X: profile refresh failed ({failure.Message})."); }
}
void Fit(Vector2 minimum, Vector2 preferred)
{
Window.MinimumSize = minimum;
if (Window.Size.x < preferred.x || Window.Size.y < preferred.y) Window.Size = Vector2.Max(Window.Size, preferred);
}
public override void OnDestroyed()
{
queue?.Dispose(); hosted.Dispose();
base.OnDestroyed();
}
}