mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Fix critical bugs: Level write-back, null safety, UX improvements
Critical bug fixes: - Level now written back in PreparePKM (was causing data loss) - Undo/Redo null safety on GetBoxSlotAtIndex result - SKSurface.Create null check in UpdateLegality UX improvements: - Unsaved changes confirmation on window close - Box mouse wheel navigation (scroll to change box) - Tooltips: PID hex info, Species number, Nature stat effects
This commit is contained in:
parent
1d046b8459
commit
a8b60cf4ab
|
|
@ -51,6 +51,12 @@ public partial class MainWindowViewModel : ObservableObject
|
|||
[ObservableProperty]
|
||||
private string _statusMessage = "Ready. Open a save file to begin.";
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the currently loaded save file has been modified since it was last saved or loaded.
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private bool _hasUnsavedChanges;
|
||||
|
||||
/// <summary>
|
||||
/// The currently active UI language code, matching <see cref="GameLanguage"/> codes.
|
||||
/// </summary>
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -62,6 +62,27 @@ public partial class PKMEditorViewModel : ObservableObject
|
|||
UpdateSprite();
|
||||
}
|
||||
|
||||
/// <summary>Tooltip showing the numeric species ID.</summary>
|
||||
public string SpeciesTooltip => Entity is null ? "" : $"Species #{Entity.Species:000}";
|
||||
|
||||
/// <summary>
|
||||
/// Tooltip showing which stats are raised/lowered by the current nature.
|
||||
/// </summary>
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
<!-- PID -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="PID:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBox Text="{Binding PidHex}" Height="25" Width="72" FontFamily="Courier New,Consolas,monospace" />
|
||||
<TextBox Text="{Binding PidHex}" Height="25" Width="72" FontFamily="Courier New,Consolas,monospace" ToolTip.Tip="Personality ID (hex)" />
|
||||
<Button Content="☆" Command="{Binding ShinytizeCommand}" Width="24" Height="25" Padding="0" FontSize="14" Margin="4,0,0,0" ToolTip.Tip="Toggle Shiny" />
|
||||
<Button Content="Reroll" Command="{Binding RerollPidCommand}" Height="25" Padding="4,0" FontSize="9" Margin="2,0,0,0" />
|
||||
</StackPanel>
|
||||
|
|
@ -70,7 +70,8 @@
|
|||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Species:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ComboBox ItemsSource="{Binding SpeciesList}" SelectedItem="{Binding SelectedSpecies}"
|
||||
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" />
|
||||
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144"
|
||||
ToolTip.Tip="{Binding SpeciesTooltip}" />
|
||||
<TextBlock Text="{Binding GenderSymbol}" FontSize="14" FontWeight="Bold"
|
||||
VerticalAlignment="Center" Margin="6,0,0,0" Width="16" />
|
||||
</StackPanel>
|
||||
|
|
@ -96,7 +97,8 @@
|
|||
<!-- Nature -->
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="Nature:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0" />
|
||||
<ComboBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding NatureList}" SelectedItem="{Binding SelectedNature}"
|
||||
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left" />
|
||||
DisplayMemberBinding="{Binding Text, DataType=core:ComboItem}" Height="25" Width="144" HorizontalAlignment="Left"
|
||||
ToolTip.Tip="{Binding NatureTooltip}" />
|
||||
|
||||
<!-- Stat Nature (Gen 8+) -->
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Stat Nature:" TextAlignment="Right" VerticalAlignment="Center" Margin="0,0,6,0"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using PKHeX.Avalonia.ViewModels;
|
||||
|
||||
namespace PKHeX.Avalonia.Views;
|
||||
|
||||
|
|
@ -7,5 +10,20 @@ public partial class SAVEditorView : UserControl
|
|||
public SAVEditorView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Tunnel handler so we intercept wheel events on the box area for box navigation
|
||||
AddHandler(PointerWheelChangedEvent, OnBoxWheel, RoutingStrategies.Tunnel);
|
||||
}
|
||||
|
||||
private void OnBoxWheel(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
if (DataContext is SAVEditorViewModel vm && vm.IsLoaded)
|
||||
{
|
||||
if (e.Delta.Y > 0)
|
||||
vm.PreviousBoxCommand.Execute(null);
|
||||
else if (e.Delta.Y < 0)
|
||||
vm.NextBoxCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user