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 @@
-
+
@@ -70,7 +70,8 @@
+ DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144"
+ ToolTip.Tip="{Binding SpeciesTooltip}" />
@@ -96,7 +97,8 @@
+ DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left"
+ ToolTip.Tip="{Binding NatureTooltip}" />
0)
+ vm.PreviousBoxCommand.Execute(null);
+ else if (e.Delta.Y < 0)
+ vm.NextBoxCommand.Execute(null);
+ e.Handled = true;
+ }
}
}