diff --git a/PKHeX.Avalonia/App.axaml.cs b/PKHeX.Avalonia/App.axaml.cs
index c284776f7..cdac788b4 100644
--- a/PKHeX.Avalonia/App.axaml.cs
+++ b/PKHeX.Avalonia/App.axaml.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Linq;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@@ -34,10 +35,30 @@ public override void OnFrameworkInitializationCompleted()
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var mainViewModel = new MainWindowViewModel();
- desktop.MainWindow = new MainWindow
+ var mainWindow = new MainWindow
{
DataContext = mainViewModel,
};
+ desktop.MainWindow = mainWindow;
+
+ // Auto-load save file on startup, after the window is shown
+ mainWindow.Opened += (_, _) =>
+ {
+ try
+ {
+ var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
+ var startup = StartupUtil.GetStartup(args, Settings);
+ if (startup.SAV is { } sav)
+ {
+ var path = sav.Metadata.FilePath ?? string.Empty;
+ mainViewModel.LoadInitialSave(sav, startup.Entity, path);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Auto-load on startup failed: {ex.Message}");
+ }
+ };
desktop.ShutdownRequested += (_, _) =>
{
@@ -61,6 +82,11 @@ private void LoadSettings()
Settings.LocalResources.SetLocalPath(WorkingDirectory);
SpriteBuilder.LoadSettings(Settings.Sprite);
+ // Handle HaX and version tracking (mirrors WinForms FormLoadInitialActions)
+ var args = Environment.GetCommandLineArgs().Skip(1).ToArray();
+ var init = StartupUtil.FormLoadInitialActions(args, Settings, CurrentVersion);
+ HaX = init.HaX;
+
var language = Settings.Startup.Language;
LocalizeUtil.InitializeStrings(language);
}
diff --git a/PKHeX.Avalonia/Controls/SlotChangeManager.cs b/PKHeX.Avalonia/Controls/SlotChangeManager.cs
index 5a0224e15..56b2c7fe2 100644
--- a/PKHeX.Avalonia/Controls/SlotChangeManager.cs
+++ b/PKHeX.Avalonia/Controls/SlotChangeManager.cs
@@ -1,10 +1,12 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using PKHeX.Avalonia.ViewModels;
+using PKHeX.Avalonia.ViewModels.Subforms;
using PKHeX.Core;
namespace PKHeX.Avalonia.Controls;
@@ -23,17 +25,33 @@ public sealed class SlotChangeManager
private readonly SAVEditorViewModel _editor;
+ /// Registered BoxViewer VMs whose slots participate in drag-and-drop.
+ private readonly List _boxViewers = [];
+
/// Tracks the source slot when a drag operation is in progress.
private SlotModel? _sourceSlot;
/// Indicates whether a drag operation is currently in progress.
public bool IsDragInProgress { get; private set; }
+ /// Gets the that owns this manager.
+ public SAVEditorViewModel Editor => _editor;
+
public SlotChangeManager(SAVEditorViewModel editor)
{
_editor = editor;
}
+ ///
+ /// Registers a so its slots can participate in drag-and-drop.
+ ///
+ public void RegisterBoxViewer(BoxViewerViewModel viewer) => _boxViewers.Add(viewer);
+
+ ///
+ /// Unregisters a when its window is closed.
+ ///
+ public void UnregisterBoxViewer(BoxViewerViewModel viewer) => _boxViewers.Remove(viewer);
+
///
/// Initiates a drag operation from the given slot.
/// Call this from move after a press.
@@ -46,7 +64,7 @@ public SlotChangeManager(SAVEditorViewModel editor)
if (IsDragInProgress)
return null;
- var pk = _editor.GetSlotPKM(slot);
+ var pk = GetSlotPKM(slot);
if (pk is null || pk.Species == 0)
return null;
@@ -156,6 +174,12 @@ private void HandleFileDrop(SlotModel destSlot, DragEventArgs e)
_editor.PushUndo(undoEntry);
WriteSlot(destSlot, pk);
_editor.ReloadSlots();
+ // Refresh box viewer if the drop target was in one
+ foreach (var bv in _boxViewers)
+ {
+ if (bv.BoxSlots.Contains(destSlot))
+ bv.RefreshBox();
+ }
_editor.SetStatusMessage?.Invoke($"Loaded {Path.GetFileName(filePath)} into slot.");
}
catch (Exception ex)
@@ -179,6 +203,57 @@ private static DropModifier GetDropModifier(KeyModifiers keys)
return DropModifier.None;
}
+ ///
+ /// Resolved location of a within the save file.
+ ///
+ private readonly record struct ResolvedSlot(int Box, int Index, bool IsParty, BoxViewerViewModel? BoxViewer);
+
+ ///
+ /// Resolves a to its (box, index) coordinate by checking
+ /// the main editor's box/party slots and all registered box viewers.
+ ///
+ private ResolvedSlot? ResolveSlot(SlotModel slot)
+ {
+ // Check main editor box slots
+ int boxIndex = _editor.BoxSlots.IndexOf(slot);
+ if (boxIndex >= 0)
+ return new ResolvedSlot(_editor.CurrentBox, boxIndex, false, null);
+
+ // Check main editor party slots
+ int partyIndex = _editor.PartySlots.IndexOf(slot);
+ if (partyIndex >= 0)
+ return new ResolvedSlot(0, partyIndex, true, null);
+
+ // Check registered box viewers
+ foreach (var bv in _boxViewers)
+ {
+ int bvIndex = bv.BoxSlots.IndexOf(slot);
+ if (bvIndex >= 0)
+ return new ResolvedSlot(bv.CurrentBox, bvIndex, false, bv);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the PKM at the resolved slot location.
+ ///
+ private PKM? GetSlotPKM(SlotModel slot)
+ {
+ var sav = _editor.SAV;
+ if (sav is null)
+ return null;
+
+ var resolved = ResolveSlot(slot);
+ if (resolved is null)
+ return null;
+
+ var r = resolved.Value;
+ if (r.IsParty)
+ return sav.GetPartySlotAtIndex(r.Index);
+ return sav.GetBoxSlotAtIndex(r.Box, r.Index);
+ }
+
///
/// Executes the slot move/swap/clone operation.
///
@@ -188,15 +263,15 @@ private void PerformSlotOperation(SlotModel source, SlotModel dest, DropModifier
if (sav is null)
return;
- var sourcePkm = _editor.GetSlotPKM(source);
+ var sourcePkm = GetSlotPKM(source);
if (sourcePkm is null || sourcePkm.Species == 0)
return;
- var destPkm = _editor.GetSlotPKM(dest);
+ var destPkm = GetSlotPKM(dest);
bool destIsEmpty = destPkm is null || destPkm.Species == 0;
// Collect undo entries and push as a single atomic group
- var entries = new System.Collections.Generic.List();
+ var entries = new List();
var destEntry = CreateUndoEntry(dest);
if (destEntry is not null)
entries.Add(destEntry);
@@ -222,8 +297,9 @@ private void PerformSlotOperation(SlotModel source, SlotModel dest, DropModifier
case DropModifier.Overwrite:
// Don't clear source if it's the last party member
{
- int sourcePartyIdx = _editor.PartySlots.IndexOf(source);
- if (sourcePartyIdx < 0 || sav.PartyCount > 1)
+ var sourceResolved = ResolveSlot(source);
+ bool isParty = sourceResolved is { IsParty: true };
+ if (!isParty || sav.PartyCount > 1)
ClearSlot(source);
}
break;
@@ -232,8 +308,9 @@ private void PerformSlotOperation(SlotModel source, SlotModel dest, DropModifier
if (destIsEmpty)
{
// Don't clear source if it's the last party member
- int sourcePartyIdx = _editor.PartySlots.IndexOf(source);
- if (sourcePartyIdx < 0 || sav.PartyCount > 1)
+ var sourceResolved = ResolveSlot(source);
+ bool isParty = sourceResolved is { IsParty: true };
+ if (!isParty || sav.PartyCount > 1)
ClearSlot(source);
}
else
@@ -245,6 +322,20 @@ private void PerformSlotOperation(SlotModel source, SlotModel dest, DropModifier
}
_editor.ReloadSlots();
+ // Refresh any box viewers that were involved
+ RefreshBoxViewers(source, dest);
+ }
+
+ ///
+ /// Refreshes box viewers whose slots were involved in a drag-drop operation.
+ ///
+ private void RefreshBoxViewers(SlotModel source, SlotModel dest)
+ {
+ foreach (var bv in _boxViewers)
+ {
+ if (bv.BoxSlots.Contains(source) || bv.BoxSlots.Contains(dest))
+ bv.RefreshBox();
+ }
}
///
@@ -257,23 +348,23 @@ private void PerformSlotOperation(SlotModel source, SlotModel dest, DropModifier
if (sav is null)
return null;
- int boxIndex = _editor.BoxSlots.IndexOf(slot);
- if (boxIndex >= 0)
- {
- var existing = sav.GetBoxSlotAtIndex(_editor.CurrentBox, boxIndex);
- if (existing is null) return null;
- return new SAVEditorViewModel.SlotChangeEntry(_editor.CurrentBox, boxIndex, existing.DecryptedBoxData, false);
- }
+ var resolved = ResolveSlot(slot);
+ if (resolved is null)
+ return null;
- int partyIndex = _editor.PartySlots.IndexOf(slot);
- if (partyIndex >= 0)
+ var r = resolved.Value;
+ if (r.IsParty)
{
- var existing = sav.GetPartySlotAtIndex(partyIndex);
+ var existing = sav.GetPartySlotAtIndex(r.Index);
if (existing is null) return null;
- return new SAVEditorViewModel.SlotChangeEntry(0, partyIndex, existing.DecryptedBoxData, true);
+ return new SAVEditorViewModel.SlotChangeEntry(0, r.Index, existing.DecryptedBoxData, true);
+ }
+ else
+ {
+ var existing = sav.GetBoxSlotAtIndex(r.Box, r.Index);
+ if (existing is null) return null;
+ return new SAVEditorViewModel.SlotChangeEntry(r.Box, r.Index, existing.DecryptedBoxData, false);
}
-
- return null;
}
///
@@ -285,18 +376,15 @@ private void WriteSlot(SlotModel slot, PKM pk)
if (sav is null)
return;
- int boxIndex = _editor.BoxSlots.IndexOf(slot);
- if (boxIndex >= 0)
- {
- sav.SetBoxSlotAtIndex(pk, _editor.CurrentBox, boxIndex);
+ var resolved = ResolveSlot(slot);
+ if (resolved is null)
return;
- }
- int partyIndex = _editor.PartySlots.IndexOf(slot);
- if (partyIndex >= 0)
- {
- sav.SetPartySlotAtIndex(pk, partyIndex);
- }
+ var r = resolved.Value;
+ if (r.IsParty)
+ sav.SetPartySlotAtIndex(pk, r.Index);
+ else
+ sav.SetBoxSlotAtIndex(pk, r.Box, r.Index);
}
///
@@ -308,19 +396,20 @@ private void ClearSlot(SlotModel slot)
if (sav is null)
return;
- int boxIndex = _editor.BoxSlots.IndexOf(slot);
- if (boxIndex >= 0)
- {
- sav.SetBoxSlotAtIndex(sav.BlankPKM, _editor.CurrentBox, boxIndex);
+ var resolved = ResolveSlot(slot);
+ if (resolved is null)
return;
- }
- int partyIndex = _editor.PartySlots.IndexOf(slot);
- if (partyIndex >= 0)
+ var r = resolved.Value;
+ if (r.IsParty)
{
if (sav.PartyCount <= 1)
return;
- sav.DeletePartySlot(partyIndex);
+ sav.DeletePartySlot(r.Index);
+ }
+ else
+ {
+ sav.SetBoxSlotAtIndex(sav.BlankPKM, r.Box, r.Index);
}
}
}
diff --git a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
index 30ff66f4a..30833a11b 100644
--- a/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/MainWindowViewModel.cs
@@ -135,6 +135,11 @@ private void RefreshGameDataAfterLanguageChange()
///
private PluginLoadResult? _pluginLoadResult;
+ ///
+ /// Plugin host that provides ISaveFileProvider to plugins.
+ ///
+ private AvaloniaPluginHost? _pluginHost;
+
public MainWindowViewModel() : this(new AvaloniaDialogService())
{
}
@@ -168,15 +173,27 @@ public MainWindowViewModel(IDialogService dialogService)
///
private void LoadPlugins()
{
+ if (!App.Settings.Startup.PluginLoadEnable)
+ return;
+
var pluginPath = Path.Combine(App.WorkingDirectory, "plugins");
try
{
- _pluginLoadResult = PluginLoader.LoadPlugins(pluginPath, Plugins, false);
+ _pluginLoadResult = PluginLoader.LoadPlugins(pluginPath, Plugins, App.Settings.Startup.PluginLoadMerged);
+
+ // Create the plugin host that provides ISaveFileProvider to plugins
+ var blankSav = BlankSaveFile.Get(App.Settings.Startup.DefaultSaveVersion, null);
+ _pluginHost = new AvaloniaPluginHost(
+ blankSav,
+ () => SavEditor?.CurrentBox ?? 0,
+ () => SavEditor?.ReloadSlots()
+ );
+
foreach (var plugin in Plugins.OrderBy(p => p.Priority))
{
try
{
- plugin.Initialize(this);
+ plugin.Initialize(_pluginHost, App.CurrentVersion);
}
catch (Exception ex)
{
@@ -353,6 +370,17 @@ private async Task DumpAllBoxesAsync()
}
}
+ ///
+ /// Loads the initial save file and entity from startup arguments.
+ /// Called by App.axaml.cs after the main window is shown.
+ ///
+ public void LoadInitialSave(SaveFile sav, PKM? entity, string path)
+ {
+ LoadSaveFile(sav, path);
+ if (entity is not null)
+ PkmEditor?.PopulateFields(entity);
+ }
+
public async Task LoadFileAsync(string path)
{
if (_isLoading)
@@ -411,6 +439,9 @@ private void LoadSaveFile(SaveFile sav, string path)
SavEditor?.LoadSaveFile(sav);
PkmEditor?.Initialize(sav);
+ // Update the plugin host with the new save file
+ _pluginHost?.UpdateSaveFile(sav);
+
App.Settings.Startup.LoadSaveFile(path);
Title = $"PKHeX - {sav.GetType().Name} ({Path.GetFileName(path)})";
@@ -574,10 +605,14 @@ private void OpenBoxViewer()
try
{
+ var slotManager = SavEditor?.SlotManager;
var vm = new BoxViewerViewModel(SaveFile, SavEditor?.CurrentBox ?? 0)
{
SlotSelected = pk => PkmEditor?.PopulateFields(pk),
+ SlotManager = slotManager,
};
+ slotManager?.RegisterBoxViewer(vm);
+
var view = new BoxViewerView { DataContext = vm };
var mainWindow = (Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow;
@@ -585,7 +620,11 @@ private void OpenBoxViewer()
{
view.Show(mainWindow); // non-modal
_openSubWindows.Add(view);
- view.Closed += (_, _) => _openSubWindows.Remove(view);
+ view.Closed += (_, _) =>
+ {
+ _openSubWindows.Remove(view);
+ slotManager?.UnregisterBoxViewer(vm);
+ };
}
}
catch (Exception ex)
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/BoxViewerViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/BoxViewerViewModel.cs
index f6c3a41a0..e62726bbf 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/BoxViewerViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/BoxViewerViewModel.cs
@@ -30,6 +30,12 @@ public partial class BoxViewerViewModel : ObservableObject
/// Callback invoked when a slot is clicked to view its PKM.
public Action? SlotSelected { get; set; }
+ ///
+ /// Shared from the main editor, enabling drag-and-drop
+ /// between the BoxViewer and the main SAV editor.
+ ///
+ public SlotChangeManager? SlotManager { get; set; }
+
public BoxViewerViewModel(SaveFile sav, int initialBox = 0)
{
_sav = sav;
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/FolderListViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/FolderListViewModel.cs
index ed735bb75..4f5290052 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/FolderListViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/FolderListViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
@@ -73,10 +74,22 @@ public partial class FolderListViewModel : ObservableObject
public string WindowTitle => "Save File Folder List";
+ ///
+ /// Callback invoked when the user requests to open a file from the Recent or Backup list.
+ /// The caller (MainWindowViewModel) should subscribe to this to load the selected file.
+ ///
+ public Action? FileOpenRequested { get; set; }
+
+ ///
+ /// Set to true when a file has been opened (so the dialog can close).
+ ///
+ [ObservableProperty]
+ private bool _fileOpened;
+
///
/// Creates the view model with the given folder paths and backup directory.
///
- public FolderListViewModel(string[] folderPaths, string backupPath)
+ public FolderListViewModel(string[] folderPaths, string backupPath, IReadOnlyList? recentlyLoaded = null)
{
foreach (var path in folderPaths)
{
@@ -84,18 +97,31 @@ public FolderListViewModel(string[] folderPaths, string backupPath)
Folders.Add(new FolderEntry(Path.GetFileName(path), path));
}
- // Load recent files from common save locations
- foreach (var folder in Folders)
+ // Load recent files from the settings RecentlyLoaded list (matching WinForms behavior)
+ if (recentlyLoaded is { Count: > 0 })
{
- if (!Directory.Exists(folder.FullPath))
- continue;
- try
+ foreach (var recentPath in recentlyLoaded)
{
- foreach (var file in Directory.EnumerateFiles(folder.FullPath, "*", SearchOption.TopDirectoryOnly).Take(50))
- RecentFiles.Add(new SaveFileEntry(file));
+ if (File.Exists(recentPath))
+ RecentFiles.Add(new SaveFileEntry(recentPath));
+ }
+ }
+
+ // Also include files from folder paths if no recently loaded entries
+ if (RecentFiles.Count == 0)
+ {
+ foreach (var folder in Folders)
+ {
+ if (!Directory.Exists(folder.FullPath))
+ continue;
+ try
+ {
+ foreach (var file in Directory.EnumerateFiles(folder.FullPath, "*", SearchOption.TopDirectoryOnly).Take(50))
+ RecentFiles.Add(new SaveFileEntry(file));
+ }
+ catch (UnauthorizedAccessException) { }
+ catch (IOException) { }
}
- catch (UnauthorizedAccessException) { }
- catch (IOException) { }
}
// Load backup files
@@ -155,4 +181,18 @@ private void OpenFolder(FolderEntry? folder)
}
catch { /* ignore failures to open folder */ }
}
+
+ ///
+ /// Opens the selected save file entry by invoking the callback.
+ /// Triggered by double-clicking a row in the Recent or Backup DataGrid.
+ ///
+ [RelayCommand]
+ private void OpenSaveFile(SaveFileEntry? entry)
+ {
+ if (entry is null || !File.Exists(entry.FilePath))
+ return;
+
+ FileOpenRequested?.Invoke(entry.FilePath);
+ FileOpened = true;
+ }
}
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Misc4ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Misc4ViewModel.cs
index adb68c8bb..41c6bd713 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Misc4ViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Misc4ViewModel.cs
@@ -69,6 +69,7 @@ public WalkerCourseModel(int index, string name, bool unlocked)
///
public partial class Misc4ViewModel : SaveEditorViewModelBase
{
+ private readonly SAV4 _origin;
private readonly SAV4 SAV4;
// General
@@ -130,35 +131,36 @@ public partial class Misc4ViewModel : SaveEditorViewModelBase
public Misc4ViewModel(SAV4 sav) : base(sav)
{
- SAV4 = sav;
- Record = sav.Records;
+ _origin = sav;
+ SAV4 = (SAV4)sav.Clone();
+ Record = SAV4.Records;
- _maxCoins = (uint)sav.MaxCoins;
- _coins = Math.Clamp(sav.Coin, 0, _maxCoins);
- _bp = Math.Clamp(sav.BP, 0, 9999);
+ _maxCoins = (uint)SAV4.MaxCoins;
+ _coins = Math.Clamp(SAV4.Coin, 0, _maxCoins);
+ _bp = Math.Clamp(SAV4.BP, 0, 9999);
PoketchAppNames = GameInfo.Strings.poketchapps;
// Fly destinations
- var locations = sav is SAV4Sinnoh ? LocationIDsSinnoh : LocationIDsHGSS;
- var flags = sav is SAV4Sinnoh ? FlyWorkFlagSinnoh : FlyWorkFlagHGSS;
+ var locations = SAV4 is SAV4Sinnoh ? LocationIDsSinnoh : LocationIDsHGSS;
+ var flags = SAV4 is SAV4Sinnoh ? FlyWorkFlagSinnoh : FlyWorkFlagHGSS;
for (int i = 0; i < locations.Length; i++)
{
var flagIndex = FlyFlagStart + flags[i];
- var state = sav.GetEventFlag(flagIndex);
+ var state = SAV4.GetEventFlag(flagIndex);
var locationID = locations[i];
var name = GameInfo.Strings.Gen4.Met0[locationID];
FlyDestinations.Add(new FlyDestModel(flagIndex, name, state));
}
// Sinnoh-specific
- ShowPoketch = sav is SAV4Sinnoh;
- ShowUGFlags = sav is SAV4Sinnoh;
- ShowWalker = sav is SAV4HGSS;
- ShowPokeathlon = sav is SAV4HGSS;
- ShowMap = sav is SAV4HGSS;
+ ShowPoketch = SAV4 is SAV4Sinnoh;
+ ShowUGFlags = SAV4 is SAV4Sinnoh;
+ ShowWalker = SAV4 is SAV4HGSS;
+ ShowPokeathlon = SAV4 is SAV4HGSS;
+ ShowMap = SAV4 is SAV4HGSS;
- if (sav is SAV4Sinnoh sinnoh)
+ if (SAV4 is SAV4Sinnoh sinnoh)
{
_ugFlagsCaptured = Math.Clamp(sinnoh.UG_FlagsCaptured, 0, SAV4Sinnoh.UG_MAX);
@@ -170,7 +172,7 @@ public Misc4ViewModel(SAV4 sav) : base(sav)
}
_currentPoketchApp = sinnoh.CurrentPoketchApp;
}
- else if (sav is SAV4HGSS hgss)
+ else if (SAV4 is SAV4HGSS hgss)
{
// Walker
ReadOnlySpan walkerCourseNames = GameInfo.Sources.Strings.walkercourses;
@@ -275,7 +277,7 @@ private void Save()
Record.SetRecord32(Record32Index, Record32Value);
Record.EndAccess();
- SAV.State.Edited = true;
+ _origin.CopyChangesFrom(SAV4);
Modified = true;
}
}
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs
index 9532748a1..62a3fd7ae 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/Misc5ViewModel.cs
@@ -72,6 +72,7 @@ public PropModel(int index, string name, bool obtained)
///
public partial class Misc5ViewModel : SaveEditorViewModelBase
{
+ private readonly SAV5 _origin;
private readonly SAV5 SAV5;
private readonly BattleSubway5 Subway;
private readonly BattleSubwayPlay5 SubwayPlay;
@@ -141,10 +142,11 @@ public partial class Misc5ViewModel : SaveEditorViewModelBase
public Misc5ViewModel(SAV5 sav) : base(sav)
{
- SAV5 = sav;
- Subway = sav.BattleSubway;
- SubwayPlay = sav.BattleSubwayPlay;
- Record = sav.Records;
+ _origin = sav;
+ SAV5 = (SAV5)sav.Clone();
+ Subway = SAV5.BattleSubway;
+ SubwayPlay = SAV5.BattleSubwayPlay;
+ Record = SAV5.Records;
ReadFly();
ReadRoamer();
@@ -153,8 +155,8 @@ public Misc5ViewModel(SAV5 sav) : base(sav)
ReadSubway();
ReadRecords();
- ShowRoamer = sav is SAV5BW;
- ShowKeySystem = sav is SAV5B2W2;
+ ShowRoamer = SAV5 is SAV5BW;
+ ShowKeySystem = SAV5 is SAV5B2W2;
}
private void ReadFly()
@@ -321,7 +323,7 @@ private void Save()
SaveSubway();
SaveRecords();
- SAV.State.Edited = true;
+ _origin.CopyChangesFrom(SAV5);
Modified = true;
}
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs
index 5fa38892f..14e062897 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/SAVSuperTrain6ViewModel.cs
@@ -99,11 +99,12 @@ public SAVSuperTrain6ViewModel(SAV6 sav) : base(sav)
SelectedStageIndex = 0;
}
- partial void OnSelectedStageIndexChanged(int value)
+ partial void OnSelectedStageIndexChanged(int oldValue, int newValue)
{
- if (value < 0)
- return;
- LoadStageRecord(value);
+ if (oldValue >= 0)
+ SaveStageRecord(oldValue);
+ if (newValue >= 0)
+ LoadStageRecord(newValue);
}
private void LoadStageRecord(int index)
diff --git a/PKHeX.Avalonia/ViewModels/Subforms/SimpleTrainerViewModel.cs b/PKHeX.Avalonia/ViewModels/Subforms/SimpleTrainerViewModel.cs
index 081837038..4d1e0cb35 100644
--- a/PKHeX.Avalonia/ViewModels/Subforms/SimpleTrainerViewModel.cs
+++ b/PKHeX.Avalonia/ViewModels/Subforms/SimpleTrainerViewModel.cs
@@ -11,6 +11,7 @@ namespace PKHeX.Avalonia.ViewModels.Subforms;
///
public partial class SimpleTrainerViewModel : SaveEditorViewModelBase
{
+ private readonly SaveFile _origin;
private readonly SaveFile _clone;
[ObservableProperty]
@@ -69,7 +70,8 @@ public partial class SimpleTrainerViewModel : SaveEditorViewModelBase
public SimpleTrainerViewModel(SaveFile sav) : base(sav)
{
- _clone = sav;
+ _origin = sav;
+ _clone = (SaveFile)sav.Clone();
MaxNameLength = sav.MaxStringLengthTrainer;
MaxMoney = (uint)sav.MaxMoney;
@@ -142,6 +144,7 @@ private void Save()
if (Badge8) badgeval |= 1 << 7;
SetBadgeValue(sav, badgeval);
+ _origin.CopyChangesFrom(_clone);
Modified = true;
}
diff --git a/PKHeX.Avalonia/Views/SlotControl.axaml.cs b/PKHeX.Avalonia/Views/SlotControl.axaml.cs
index 4b3efbee0..18ec9d415 100644
--- a/PKHeX.Avalonia/Views/SlotControl.axaml.cs
+++ b/PKHeX.Avalonia/Views/SlotControl.axaml.cs
@@ -7,6 +7,7 @@
using Avalonia.VisualTree;
using PKHeX.Avalonia.Controls;
using PKHeX.Avalonia.ViewModels;
+using PKHeX.Avalonia.ViewModels.Subforms;
namespace PKHeX.Avalonia.Views;
@@ -168,18 +169,36 @@ private void OnDrop(object? sender, DragEventArgs e)
///
/// Walks up the visual tree to find the .
+ /// Falls back to reaching it through
+ /// when the slot lives inside a BoxViewer window.
///
private SAVEditorViewModel? FindSAVEditorViewModel()
{
var itemsControl = this.FindAncestorOfType();
- return itemsControl?.DataContext as SAVEditorViewModel;
+ if (itemsControl?.DataContext is SAVEditorViewModel savVm)
+ return savVm;
+
+ // BoxViewer path: reach the main editor through the shared SlotChangeManager
+ if (itemsControl?.DataContext is BoxViewerViewModel boxVm)
+ return boxVm.SlotManager?.Editor;
+
+ return null;
}
///
- /// Finds the from the .
+ /// Finds the from either the
+ /// or a ancestor.
///
private SlotChangeManager? FindSlotChangeManager()
{
- return FindSAVEditorViewModel()?.SlotManager;
+ var itemsControl = this.FindAncestorOfType();
+
+ if (itemsControl?.DataContext is SAVEditorViewModel savVm)
+ return savVm.SlotManager;
+
+ if (itemsControl?.DataContext is BoxViewerViewModel boxVm)
+ return boxVm.SlotManager;
+
+ return null;
}
}
diff --git a/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml b/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml
index 18f9e2081..4e2501fc7 100644
--- a/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml
+++ b/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml
@@ -47,11 +47,13 @@
-
+ GridLinesVisibility="Horizontal"
+ DoubleTapped="OnDataGridDoubleTapped">
@@ -60,11 +62,13 @@
-
+ GridLinesVisibility="Horizontal"
+ DoubleTapped="OnDataGridDoubleTapped">
diff --git a/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml.cs b/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml.cs
index 70cfabdb4..22da7c570 100644
--- a/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml.cs
+++ b/PKHeX.Avalonia/Views/Subforms/FolderListView.axaml.cs
@@ -1,4 +1,7 @@
+using Avalonia.Controls;
+using Avalonia.Input;
using Avalonia.Interactivity;
+using PKHeX.Avalonia.ViewModels.Subforms;
namespace PKHeX.Avalonia.Views.Subforms;
@@ -13,4 +16,20 @@ private void OnCancelClick(object? sender, RoutedEventArgs e)
{
CloseWithResult(false);
}
+
+ private void OnDataGridDoubleTapped(object? sender, TappedEventArgs e)
+ {
+ if (sender is not DataGrid grid)
+ return;
+ if (grid.SelectedItem is not SaveFileEntry entry)
+ return;
+ if (DataContext is not FolderListViewModel vm)
+ return;
+
+ vm.OpenSaveFileCommand.Execute(entry);
+
+ // Close the dialog after the file is opened
+ if (vm.FileOpened)
+ CloseWithResult(true);
+ }
}
diff --git a/PKHeX.Avalonia/Views/Subforms/MemoryAmieView.axaml b/PKHeX.Avalonia/Views/Subforms/MemoryAmieView.axaml
index 61c0023ef..59e6b132c 100644
--- a/PKHeX.Avalonia/Views/Subforms/MemoryAmieView.axaml
+++ b/PKHeX.Avalonia/Views/Subforms/MemoryAmieView.axaml
@@ -79,7 +79,7 @@
VerticalAlignment="Center" IsVisible="{Binding HasSociability}"
FontWeight="SemiBold" FontSize="11" />