Minecraft-Console-Client/MinecraftClient/Tui/InventoryTuiHost.cs
2026-03-28 02:18:11 +08:00

209 lines
7.1 KiB
C#

using System;
using System.Threading;
using Avalonia;
using Avalonia.Threading;
using Consolonia;
using MinecraftClient.Inventory;
namespace MinecraftClient.Tui
{
/// <summary>
/// Manages the lifecycle of the inventory TUI.
/// In TUI mode: opens as a Consolonia dialog window.
/// In classic mode: launches standalone Consolonia on a dedicated thread.
/// </summary>
public static class InventoryTuiHost
{
private static volatile bool _isRunning;
private static bool _classicEverLaunched;
public static McClient? ActiveHandler { get; private set; }
public static int ActiveWindowId { get; private set; }
public static bool IsRunning => _isRunning;
/// <summary>
/// Called by McClient.OnInventoryClose when the server closes a container.
/// If the closed window matches the active TUI window, auto-close the TUI.
/// </summary>
public static void NotifyInventoryClosed(int windowId)
{
if (!_isRunning || windowId != ActiveWindowId)
return;
if (ConsoleIO.Backend is TuiConsoleBackend)
{
Dispatcher.UIThread.Post(() =>
{
var view = TuiConsoleBackend.Instance?.GetView();
view?.HideOverlay();
});
}
else
{
(Avalonia.Application.Current?.ApplicationLifetime
as Avalonia.Controls.ApplicationLifetimes.IControlledApplicationLifetime)?.Shutdown();
}
}
/// <summary>
/// Whether the TUI can be launched (classic mode has a one-shot limit).
/// </summary>
public static bool CanLaunch
{
get
{
if (_isRunning) return false;
if (ConsoleIO.Backend is TuiConsoleBackend) return true;
return !_classicEverLaunched;
}
}
/// <summary>
/// Called before standalone TUI takes over the terminal (classic mode only).
/// </summary>
public static Action? OnSuspendConsole { get; set; }
/// <summary>
/// Called after standalone TUI releases the terminal (classic mode only).
/// </summary>
public static Action? OnResumeConsole { get; set; }
public static bool Launch(McClient handler, int windowId)
{
if (_isRunning)
return false;
Container? container = handler.GetInventory(windowId);
if (container == null)
return false;
_isRunning = true;
ActiveHandler = handler;
ActiveWindowId = windowId;
if (ConsoleIO.Backend is TuiConsoleBackend)
{
LaunchAsDialog();
}
else
{
if (_classicEverLaunched)
{
_isRunning = false;
ActiveHandler = null;
return false;
}
var tuiThread = new Thread(RunClassicTui) { Name = "InventoryTUI", IsBackground = false };
tuiThread.Start();
}
return true;
}
/// <summary>
/// Open inventory as an overlay panel within the main TUI view.
/// </summary>
private static void LaunchAsDialog()
{
Dispatcher.UIThread.Post(() =>
{
try
{
var view = TuiConsoleBackend.Instance?.GetView();
if (view != null)
{
var container = ActiveHandler!.GetInventory(ActiveWindowId);
var content = ContainerViewBase.CreateView(
container?.Type ?? ContainerType.PlayerInventory,
ActiveHandler, ActiveWindowId);
view.ShowOverlay(content, () =>
{
ActiveHandler = null;
_isRunning = false;
});
}
else
{
ActiveHandler = null;
_isRunning = false;
}
}
catch (Exception ex)
{
ConsoleIO.WriteLineFormatted($"§c[InventoryTUI] Error: {ex.Message}");
ConsoleIO.WriteLineFormatted($"§c[InventoryTUI] Stack: {ex.StackTrace}");
if (ex.InnerException != null)
ConsoleIO.WriteLineFormatted($"§c[InventoryTUI] Inner: {ex.InnerException.Message}");
ActiveHandler = null;
_isRunning = false;
}
});
}
/// <summary>
/// Classic mode: run standalone Consolonia on a dedicated thread.
/// </summary>
private static void RunClassicTui()
{
try
{
OnSuspendConsole?.Invoke();
_classicEverLaunched = true;
AppBuilder builder = AppBuilder.Configure<InventoryApp>()
.UseConsolonia()
.UseAutoDetectedConsole()
.LogToException();
builder.StartWithConsoleLifetime(Array.Empty<string>());
}
catch (Exception ex)
{
System.Console.Error.WriteLine($"[InventoryTUI] Error: {ex.Message}");
System.Console.Error.WriteLine($"[InventoryTUI] Stack: {ex.StackTrace}");
if (ex.InnerException != null)
System.Console.Error.WriteLine($"[InventoryTUI] Inner: {ex.InnerException}");
}
finally
{
RestoreTerminalState();
OnResumeConsole?.Invoke();
ActiveHandler = null;
_isRunning = false;
}
}
private static void RestoreTerminalState()
{
try
{
System.Console.Write("\x1b[?1049l");
System.Console.Write("\x1b[?25h");
System.Console.Write("\x1b[?1000l");
System.Console.Write("\x1b[?1002l");
System.Console.Write("\x1b[?1003l");
System.Console.Write("\x1b[?1006l");
System.Console.Write("\x1b[?2004l");
System.Console.Write("\x1b[0m");
System.Console.Write("\x1b(B");
System.Console.Out.Flush();
try
{
using var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "stty",
Arguments = "sane",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
});
proc?.WaitForExit(2000);
}
catch { }
}
catch { }
}
}
}