using System; using System.Collections.Generic; using System.Linq; using Avalonia.Controls; using PolyTrader.Core.Modularity; namespace PolyTrader.App.Avalonia.Shell { /// /// Avalonia-Umsetzung von . /// /// Umstellung auf die Einfenster-Shell: Früher öffnete jede View ein eigenes /// Fenster. Jetzt gibt es genau ein Anwendungsfenster (); eine /// View ist eine -Seite, die in dessen Inhaltsbereich gehängt wird. /// Eine Seite wird beim ersten Aufruf erzeugt und bleibt danach für die Sitzung bestehen – /// das entspricht dem alten „Fenster bleibt offen": Timer, Log-Abos und der Bildlaufstand /// überleben den Seitenwechsel. /// /// Der Core-Contract ist toolkit-neutral ( liefert /// ) – hier wird auf gecastet. Ein falscher Typ ist ein /// Programmierfehler und soll laut scheitern, nicht still eine leere Seite erzeugen. /// public sealed class AvaloniaUiHost : IModuleUiHost { /// ID der Startseite (frühere LauncherForm/LauncherWindow). public const string StartViewId = "core.start"; private readonly List _views = new(); private readonly List _unavailable = new(); private readonly Dictionary _pages = new(); private Views.ShellWindow? _shell; private string? _activeViewId; private bool _shutdownDialogOpen; /// True, sobald das Herunterfahren über die Sicherheitsabfrage bestätigt wurde. public bool ShutdownConfirmed { get; private set; } public event Action? OpenStateChanged; public IReadOnlyList Views => _views; /// ID der gerade angezeigten Seite – die Seitenleiste hebt sie hervor. public string? ActiveViewId => _activeViewId; public void RegisterView(ModuleView view) => _views.Add(view); /// /// Ein bekanntes, in dieser Sitzung aber nicht geladenes Modul. Solche Module haben keine /// Seite – die Seitenleiste zeigt sie trotzdem, ausgegraut und mit dem Grund als Hinweis. /// /// Warum das Modul fehlt (Lizenz, Abschaltung, fehlender API-Key …). public sealed record UnavailableView(string Title, string? IconKey, int Order, string Reason); /// /// Meldet ein nicht geladenes Modul an. Ohne das verschwände es spurlos aus der Oberfläche – /// in der Mehrfenster-Fassung fiel das kaum auf, in einer Seitenleiste ist eine unerklärt /// leere Rubrik dagegen schlicht ein Fehler. /// public void RegisterUnavailable(UnavailableView view) => _unavailable.Add(view); /// Bekannte, aber nicht geladene Module (siehe ). public IReadOnlyList UnavailableViews => _unavailable; /// Setzt das Shell-Fenster – Ziel aller Seitenwechsel und Besitzer der Dialoge. public void SetShell(Views.ShellWindow shell) => _shell = shell; /// Das Shell-Fenster, oder null vor dessen Aufbau (z.B. im Smoke-Lauf). public Window? ShellWindow => _shell; /// /// Wurde die Seite in dieser Sitzung schon einmal aufgerufen? Erbt die Bedeutung des früheren /// „Fenster ist offen": die Seite existiert und arbeitet weiter, auch wenn gerade eine andere /// angezeigt wird. /// public bool IsOpen(string viewId) => _pages.ContainsKey(viewId); /// Zurück auf die Startseite. public void ActivateMain() => OpenView(StartViewId); public void OpenView(string viewId) { var view = _views.FirstOrDefault(v => v.Id == viewId); if (view != null) OpenView(view); } public void OpenView(ModuleView view) { if (!_pages.TryGetValue(view.Id, out var page)) { page = (Control)view.CreateView(); _pages[view.Id] = page; } _activeViewId = view.Id; _shell?.ShowPage(view.Title, page); OpenStateChanged?.Invoke(); } /// /// Zeigt die Sicherheitsabfrage (Beenden-Schaltfläche 10 s gesperrt) und fährt bei Bestätigung /// herunter. Aus jeder Seite aufrufbar – auch aus Modul-Seiten, die die App nicht kennen. /// public async void RequestShutdown() { if (ShutdownConfirmed || _shutdownDialogOpen) return; _shutdownDialogOpen = true; try { var dlg = new Views.ShutdownConfirmWindow(); bool confirmed = _shell != null ? await dlg.ShowDialog(_shell) : await dlg.ShowDialog(new Window()); if (!confirmed) return; ShutdownConfirmed = true; if (global::Avalonia.Application.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop) { desktop.Shutdown(); } } finally { _shutdownDialogOpen = false; } } } }