using System.IO; using Avalonia.Media.Imaging; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Poe2Trade.Bot; using Poe2Trade.Navigation; using Serilog; namespace Poe2Trade.Ui.ViewModels; public partial class AtlasViewModel : ObservableObject, IDisposable { private readonly BotOrchestrator _bot; private readonly CancellationTokenSource _pollCts = new(); [ObservableProperty] private Bitmap? _canvasImage; [ObservableProperty] private bool _isCapturing; [ObservableProperty] private string _atlasStatus = ""; [ObservableProperty] private int _tilesCaptured; public AtlasViewModel(BotOrchestrator bot) { _bot = bot; _ = PollLoop(_pollCts.Token); } [RelayCommand] private async Task CaptureAtlas() { if (IsCapturing) return; IsCapturing = true; AtlasStatus = "Starting..."; TilesCaptured = 0; void OnProgress(AtlasProgress p) { Dispatcher.UIThread.Post(() => { AtlasStatus = $"{p.Phase} ({p.TilesCaptured} tiles)"; TilesCaptured = p.TilesCaptured; }); } _bot.AtlasExecutor.ProgressUpdated += OnProgress; try { var path = await Task.Run(() => _bot.AtlasExecutor.CaptureAtlasPanorama()); AtlasStatus = path != null ? $"Saved: {path}" : "Capture failed or cancelled"; } catch (Exception ex) { AtlasStatus = $"Error: {ex.Message}"; } finally { _bot.AtlasExecutor.ProgressUpdated -= OnProgress; IsCapturing = false; } } [RelayCommand] private void StopAtlasCapture() { _bot.AtlasExecutor.Stop(); AtlasStatus = "Stopping..."; } [RelayCommand] private async Task CalibratePerspective() { if (IsCapturing) return; IsCapturing = true; AtlasStatus = "Auto-scrolling atlas..."; TilesCaptured = 0; void OnProgress(AtlasProgress p) { Dispatcher.UIThread.Post(() => { AtlasStatus = $"{p.Phase} ({p.TilesCaptured} frames)"; TilesCaptured = p.TilesCaptured; }); } _bot.AtlasExecutor.ProgressUpdated += OnProgress; try { var result = await Task.Run(() => _bot.AtlasExecutor.CalibratePerspective()); if (result != null) AtlasStatus = $"Best factor: {result.BestFactor:F3} (conf: {result.BestConfidence:F4})"; else AtlasStatus = "Calibration cancelled (not enough frames)"; } catch (Exception ex) { AtlasStatus = $"Calibration error: {ex.Message}"; Log.Error(ex, "Calibration failed"); } finally { _bot.AtlasExecutor.ProgressUpdated -= OnProgress; IsCapturing = false; } } /// /// Poll ActivePanorama for viewport snapshots at ~2fps while capturing. /// private async Task PollLoop(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { var bytes = _bot.AtlasExecutor.ActivePanorama?.GetViewportSnapshot(); if (bytes != null) { var bmp = new Bitmap(new MemoryStream(bytes)); Dispatcher.UIThread.Post(() => { var old = CanvasImage; CanvasImage = bmp; TilesCaptured = _bot.AtlasExecutor.ActivePanorama?.TilesCaptured ?? TilesCaptured; old?.Dispose(); }); } await Task.Delay(500, ct); } catch (OperationCanceledException) { break; } catch (Exception ex) { Log.Debug(ex, "Atlas poll error"); await Task.Delay(1000, ct); } } } public void Dispose() { _pollCts.Cancel(); _pollCts.Dispose(); CanvasImage?.Dispose(); } }