diff --git a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs index 60b080e27..2fac9adb2 100644 --- a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs @@ -51,6 +51,12 @@ public partial class MainWindowViewModel : ObservableObject [ObservableProperty] private string _statusMessage = "Ready. Open a save file to begin."; + /// + /// Indicates whether the currently loaded save file has been modified since it was last saved or loaded. + /// + [ObservableProperty] + private bool _hasUnsavedChanges; + /// /// The currently active UI language code, matching codes. /// @@ -216,6 +222,7 @@ private async Task SaveFileAsync() CreateAutoBackup(path); ExportSAV(SaveFile, path); + HasUnsavedChanges = false; StatusMessage = $"Saved to {Path.GetFileName(path)}"; } catch (Exception ex) @@ -365,6 +372,7 @@ private void LoadSaveFile(SaveFile sav, string path) { SaveFile = sav; HasSaveFile = true; + HasUnsavedChanges = true; _loadedFilePath = path; SpriteUtil.Initialize(sav); diff --git a/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs b/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs index d0cb27f37..539581378 100644 --- a/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs +++ b/PKHeX.Avalonia/ViewModels/PKMEditorViewModel.cs @@ -62,6 +62,27 @@ public partial class PKMEditorViewModel : ObservableObject UpdateSprite(); } + /// Tooltip showing the numeric species ID. + public string SpeciesTooltip => Entity is null ? "" : $"Species #{Entity.Species:000}"; + + /// + /// Tooltip showing which stats are raised/lowered by the current nature. + /// + public string NatureTooltip + { + get + { + var n = Nature; + var idx = (int)n; + if ((uint)idx >= 25) return n.ToString(); + var up = idx / 5; + var down = idx % 5; + if (up == down) return $"{n} (Neutral)"; + var statNames = new[] { "Atk", "Def", "Spe", "SpA", "SpD" }; + return $"{n} (+{statNames[up]} / -{statNames[down]})"; + } + } + // Stat Nature (Gen 8+) [ObservableProperty] private Nature _statNature; [ObservableProperty] private bool _hasStatNature; @@ -656,6 +677,7 @@ private void UpdateRegionNames() try { Nickname = speciesName; } finally { _isPopulating = false; } } + OnPropertyChanged(nameof(SpeciesTooltip)); UpdateSprite(); UpdateLegality(); } @@ -672,6 +694,7 @@ private void UpdateRegionNames() OnPropertyChanged(nameof(SpAColor)); OnPropertyChanged(nameof(SpDColor)); OnPropertyChanged(nameof(SpeColor)); + OnPropertyChanged(nameof(NatureTooltip)); RecalcStats(); UpdateLegality(); } @@ -1545,6 +1568,7 @@ public void PopulateFields(PKM pk) if (uint.TryParse(PidHex, System.Globalization.NumberStyles.HexNumber, null, out var pid)) Entity.PID = pid; Entity.EXP = Exp; + Entity.Stat_Level = Level; Entity.CurrentFriendship = (byte)Math.Clamp(Friendship, 0, 255); Entity.Language = Language; @@ -2268,6 +2292,7 @@ private void UpdateLegality() var color = valid ? SKColors.Green : SKColors.Red; using var surface = SKSurface.Create(new SKImageInfo(24, 24)); + if (surface is null) { LegalityImage = null; return; } var canvas = surface.Canvas; canvas.Clear(SKColors.Transparent); using var paint = new SKPaint { Color = color, IsAntialias = true }; diff --git a/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs b/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs index 921aca052..d0fa8a1b4 100644 --- a/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs +++ b/PKHeX.Avalonia/ViewModels/SAVEditorViewModel.cs @@ -64,6 +64,7 @@ public void Undo() var current = change.IsParty ? _sav.GetPartySlotAtIndex(change.Slot) : _sav.GetBoxSlotAtIndex(change.Box, change.Slot); + if (current is null) return; _redoStack.Push(new SlotChange(change.Box, change.Slot, current.DecryptedBoxData, change.IsParty)); // Restore old state @@ -92,6 +93,7 @@ public void Redo() var current = change.IsParty ? _sav.GetPartySlotAtIndex(change.Slot) : _sav.GetBoxSlotAtIndex(change.Box, change.Slot); + if (current is null) return; _undoStack.Push(new SlotChange(change.Box, change.Slot, current.DecryptedBoxData, change.IsParty)); // Restore redo state diff --git a/PKHeX.Avalonia/Views/MainWindow.axaml.cs b/PKHeX.Avalonia/Views/MainWindow.axaml.cs index dacfd17ff..f67e4a391 100644 --- a/PKHeX.Avalonia/Views/MainWindow.axaml.cs +++ b/PKHeX.Avalonia/Views/MainWindow.axaml.cs @@ -3,6 +3,9 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Media; using Avalonia.Styling; using PKHeX.Avalonia.ViewModels; @@ -10,12 +13,72 @@ namespace PKHeX.Avalonia.Views; public partial class MainWindow : Window { + private bool _forceClose; + public MainWindow() { InitializeComponent(); AddHandler(DragDrop.DropEvent, OnDrop); AddHandler(DragDrop.DragOverEvent, OnDragOver); + Closing += OnWindowClosing; + } + + private async void OnWindowClosing(object? sender, WindowClosingEventArgs e) + { + if (_forceClose) + return; + + if (DataContext is MainWindowViewModel vm && vm.HasUnsavedChanges) + { + e.Cancel = true; + + var confirmDialog = new Window + { + Title = "Unsaved Changes", + Width = 360, + Height = 140, + CanResize = false, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + + var result = false; + + var cancelBtn = new Button { Content = "Cancel", Width = 80 }; + cancelBtn.Click += (_, _) => { result = false; confirmDialog.Close(); }; + + var closeBtn = new Button { Content = "Close Anyway", Width = 100 }; + closeBtn.Click += (_, _) => { result = true; confirmDialog.Close(); }; + + confirmDialog.Content = new StackPanel + { + Margin = new Thickness(20), + Spacing = 16, + Children = + { + new TextBlock + { + Text = "You have unsaved changes. Close without saving?", + TextWrapping = TextWrapping.Wrap, + }, + new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Right, + Spacing = 8, + Children = { cancelBtn, closeBtn }, + }, + }, + }; + + await confirmDialog.ShowDialog(this); + + if (result) + { + _forceClose = true; + Close(); + } + } } private void OnDragOver(object? sender, DragEventArgs e) diff --git a/PKHeX.Avalonia/Views/PKMEditorView.axaml b/PKHeX.Avalonia/Views/PKMEditorView.axaml index 53e03ab59..0c6ed1e52 100644 --- a/PKHeX.Avalonia/Views/PKMEditorView.axaml +++ b/PKHeX.Avalonia/Views/PKMEditorView.axaml @@ -61,7 +61,7 @@ - +