Files
PolyTraderSharp/src/PolyTrader.App.Avalonia/Views/LauncherWindow.axaml.cs
T
RichardandClaude Opus 5 487ea4466d Dunkelmodus mit Umschalter in der Fensterleiste
Farbschema hell / dunkel / dem System folgen, umschaltbar per Klick, Stand wird in
server_settings.xml gemerkt (ServerSettings.Theme). Der Editor zeigt die Einstellung
automatisch als Auswahlfeld - kein Zusatzcode, weil sie ein Enum ist.

- App.axaml: 18 Farb-Token je Variante (Flaechen, Rahmen, Texte, Bedeutungsfarben,
  Zeilenfaerbung, Trading-Umschalter, Chat-Rollen). Die Standard-Steuerelemente stellt
  das FluentTheme selbst um.
- 22 fest verdrahtete Farben in 11 XAML-Dateien auf DynamicResource umgestellt - die
  wechseln damit von selbst mit.
- Umschalter (Sonne/Mond) sitzt in der gemeinsamen Fensterleiste, also auf JEDEM Fenster.

Der eigentliche Knackpunkt waren die Farben, die im Code gesetzt werden - die folgen dem
Thema NICHT von selbst:
- TradeRowPalette liefert jetzt Eigenschaften statt static readonly, loest also bei jedem
  Zugriff neu auf. Im Dunkeln gedaempfte Toene statt der hellen Pastelltoene, die dort
  blenden und den Text unlesbar machen wuerden.
- ChatEntry speichert die ROLLE statt eines fertigen Brush - dadurch stimmt der Verlauf
  nach dem Umschalten ohne Neuaufbau der Liste.
- Launcher-Umschalter, Dashboard-KPI und die Grid-Zeilenfarben zeichnen sich ueber
  ThemeManager.ThemeChanged neu.
- Das Terminal bleibt in beiden Schemata dunkel (Konsolen sind konventionell dunkel).

Nebenbei die Anzeige-Kultur gepinnt (stand ohnehin auf der Linux-Liste): auf Linux richtet
sie sich sonst nach LANG/LC_ALL, das unter systemd oft nicht gesetzt ist - dann faellt .NET
auf Invariant zurueck und aus '1.234,56 USDC' wird '1,234.56 USDC'. Maschinen-I/O laeuft
davon unabhaengig weiter invariant.

Smoke-UI prueft jetzt zusaetzlich, dass ALLE 18 Farben in BEIDEN Varianten aufloesen -
ein vertippter Ressourcenschluessel wuerde sonst still grau werden.

Verifiziert: Solution baut, 450 Tests gruen, --smoke-ui gruen (10 Fenster + Editor +
beide Farbschemata), App startet im gespeicherten Schema, Linux-Publish laeuft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 22:41:25 +02:00

145 lines
5.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.ObjectModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.App.Avalonia.Shell;
using PolyTrader.Core.Modularity;
using PolyTraderSharp;
namespace PolyTrader.App.Avalonia.Views
{
/// <summary>
/// „Startleiste" der Anwendung Gegenstück zur bisherigen LauncherForm. Öffnet die Fenster,
/// spiegelt deren Offen-Status und zeigt die Kernkennzahlen in der Statusleiste.
/// Layout vollständig in LauncherWindow.axaml.
/// </summary>
public partial class LauncherWindow : Window
{
private readonly AvaloniaUiHost _uiHost;
private readonly IServiceProvider _services;
private readonly TradingState _state;
private readonly DispatcherTimer _statusTimer = new() { Interval = TimeSpan.FromSeconds(1) };
private readonly ObservableCollection<WindowMenuEntry> _windowButtons = new();
public LauncherWindow() => AvaloniaXamlLoader.Load(this);
public LauncherWindow(AvaloniaUiHost uiHost, IServiceProvider services) : this()
{
_uiHost = uiHost;
_services = services;
_state = services.GetRequiredService<TradingState>();
// currentViewId == null => dies ist der Launcher (rechte Menueaktion "Beenden").
this.FindControl<Controls.WindowMenuBar>("menuBar")!.Attach(uiHost, null, this);
this.FindControl<ItemsControl>("windowButtons")!.ItemsSource = _windowButtons;
RebuildWindowButtons();
_uiHost.OpenStateChanged += OnOpenStateChanged;
this.FindControl<Button>("btnLiveTrading")!.Click += (_, _) =>
{
_state.LiveTradingMode = Next(_state.LiveTradingMode);
UpdateTradingToggles();
};
this.FindControl<Button>("btnDemoTrading")!.Click += (_, _) =>
{
_state.DemoTradingMode = Next(_state.DemoTradingMode);
UpdateTradingToggles();
};
_statusTimer.Tick += (_, _) => UpdateStatus();
_statusTimer.Start();
UpdateStatus();
// Schliessen des Launchers laeuft ueber dieselbe Sicherheitsabfrage wie "Beenden".
Closing += (_, e) =>
{
if (_uiHost.ShutdownConfirmed) return;
e.Cancel = true;
Dispatcher.UIThread.Post(_uiHost.RequestShutdown);
};
// Die Umschalterflaechen werden im Code gesetzt und folgen dem Thema nicht von selbst.
ThemeManager.ThemeChanged += UpdateTradingToggles;
Closed += (_, _) =>
{
_statusTimer.Stop();
_uiHost.OpenStateChanged -= OnOpenStateChanged;
ThemeManager.ThemeChanged -= UpdateTradingToggles;
};
}
/// <summary>Dreistufiger Wechsel wie bisher: Inactive -> SellOnly -> Active -> Inactive.</summary>
private static TradingMode Next(TradingMode mode) => mode switch
{
TradingMode.Inactive => TradingMode.SellOnly,
TradingMode.SellOnly => TradingMode.Active,
_ => TradingMode.Inactive
};
private void OnOpenStateChanged()
{
if (Dispatcher.UIThread.CheckAccess()) RebuildWindowButtons();
else Dispatcher.UIThread.Post(RebuildWindowButtons);
}
private void RebuildWindowButtons()
{
_windowButtons.Clear();
foreach (var view in _uiHost.Views.OrderBy(v => v.Order).ThenBy(v => v.Title))
{
string id = view.Id;
bool open = _uiHost.IsOpen(id);
_windowButtons.Add(new WindowMenuEntry
{
Title = view.Title,
Icon = ViewIcons.Resolve(view.IconKey),
IsCurrent = open,
IsChecked = open,
Command = new RelayCommand(() => _uiHost.OpenView(id))
});
}
}
private void UpdateTradingToggles()
{
Apply(this.FindControl<Button>("btnLiveTrading")!, "LiveTrading", _state.LiveTradingMode);
Apply(this.FindControl<Button>("btnDemoTrading")!, "DemoTrading", _state.DemoTradingMode);
// Aussage wie in der WinForms-Oberflaeche (gruen/orange/rot); die konkreten Toene
// kommen aus dem Thema, damit sie auf hellem wie dunklem Grund lesbar bleiben.
static void Apply(Button btn, string label, TradingMode mode)
{
(string text, string brushKey) = mode switch
{
TradingMode.Active => ($"{label} (AKTIV)", "AppToggleActiveBrush"),
TradingMode.SellOnly => ($"{label} (SELL-ONLY)", "AppToggleSellOnlyBrush"),
_ => ($"{label} (DEAKTIVIERT)", "AppToggleInactiveBrush")
};
btn.Content = text;
btn.Background = ThemeManager.Brush(brushKey);
}
}
private void UpdateStatus()
{
string trading = _state.GlobalTradingPaused
? "Pausiert"
: $"Live={_state.LiveTradingMode} / Demo={_state.DemoTradingMode}";
this.FindControl<TextBlock>("lblTrading")!.Text = $"Trading: {trading}";
int moduleCount = _services.GetServices<IPolyTraderModule>().Count();
this.FindControl<TextBlock>("lblModules")!.Text = $"Module: {moduleCount}";
this.FindControl<TextBlock>("lblRatelimit")!.Text =
$"API: {(_state.IsAlchemyHealthy ? "WSS aktiv" : "Polling")}";
UpdateTradingToggles();
}
}
}