mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-07-13 06:24:18 +00:00
UI: Actions Menu (Part 3 of 4) → Amiibo & Skylanders (#158)
Ayyyy, welcome to the UI: Actions Menu → Amiibo & Skylanders PR!
WOOOOO!!!!
This is the third PR in a series aimed at delivering the largest overhaul and improvements to the `Actions` menu yet.
This PR introduces visual and functionality improvements to the two `Scan Amiibo` options, as well as improving `Scan Skylanders`.
### GENERAL
* **Renamed:** Open Mii Applet → Open Mii Editor in `MainMenuBarView.axaml.cs`
### LOCALISATION
* **Fractured:** More locales
* `Amiibo.json` - all UI-related locales to the `Scan Amiibo` window
* `Common_Buttons.json` - all locales related to buttons
* `Common_FilePicker.json` - all locales related to the file picker that are implemented in many places
* `Dialog_Amiibo.json` - all Amiibo dialogs
* `Dialog_Skylanders.json` - all Skylander dialogs
* **Removed:** Some weird duplicated locales (they appeared after some sort of rebase, but they were few)
**NOTE:** `Common_Buttons.json` and `Common_FilePicker.json` were not fully populated, but many more locales will be added and cleaned up in future PRs.
### SCAN AMIIBO (WINDOW)
* **Improved:** The UI
* The UI now more cleanly utilises both Normal and Flush Controls.
* All buttons have been moved to the top row and features a whole lot more updates (read below).
* **Reworded:** `Hack: Random UUID Tag` → `Use Random UUID`
* The option is inherently already a hack/workaround, therefore, having "Hack" is not necessary. It is also implied that it's a tag.
* **Implemented:** [Feature Request #271](https://github.com/Ryubing/Issues/issues/271)
* The feature request is for the ability to pause emulation while navigating the `Scan Amiibo` window (so as to reduce/remove timeouts) and for an Amiibo "name" sorting feature. You asked, we deliver!
* **Pause Emulation:**
* The top row now features a "setting" button dropdown, which houses `Use Random UUID` and `Pause Emulation While Scanning`
* Note: Both options were moved to a button dropdown so as to not have to increase window size by a 1000x
* `Pause Emulation While Scanning` is a dynamic, real-time implementation.
* By default, the checkbox is not checked.
* When it's checked, emulation _instantly_ pauses for the duration while the window is open. Pressing "Scan Amiibo" or "Cancel" resumes emulation. Reopening the window pauses the emulation instantly as well.
* This setting persists both while in-game and upon restarting Ryujinx, so users don't have to constantly go back and check the option again.
* Additionally, this feature has been propagated to `Scan Amiibo (BIN)`. Perhaps some users would also want emulation to pause while scanning via BIN, so if the checkbox is checked, it'll pause emulation when the file picker is open as well.
* **Sorting & Search:**
* Additionally, implemented a simple "sort by name", in ascending and descending order.
* Added a search bar (related to the change below) to help with searching through the long Amiibo list.
* Search is semi-alphabetical, semi-series based. If I want to search for Samus Amiibo, then if I type "Samus", it'll first show Dark Samus, as it appears in a series earlier in the list. This behaviour can be modified to just be alphabetical, but it is also more categorisation.
* **Modified & Renamed:** `Show Incompatible Amiibo` → `Show All Amiibo`
* Pressing `Show Incompatible Amiibo` would display all Amiibo available, so the option was renamed.
* The option now forces all Amiibo to appear in a list, without a preselected series. Unchecked the option restores original behaviour by breaking down Amiibo by their series (and only those directly compatible with the game).
* **Removed:** Series label
* It's clear that it's an Amiibo series.
* **Fixed:** Amiibo Usage visual structure
* When an Amiibo had multiple in-game usages (multiple purposes), they would all be squished together with no spaces in between them, reducing readability.
* Now, they are all separated and given an extra line between each usage.
* Additionally, fixed "Unknown" text position as well by giving it another extra empty line.
* **Renamed:** Writeable to Usage (Can Save Data)
* Usage and Usage (Can Save Data) are separate strings because some languages must add space before or after the colon
* **Fixed:** Sometimes incorrect Amiibo images could be shown when pressing `Show Incompatible Amiibo`/`Show All Amiibo` in certain instances.
* Sometimes = pressing the checkbox 10 or 20...maybe 30 times in a row (I pressed it a lot!)
* **Reworded:** Cabinet Dialog strings
* Cabinet Dialog is the dialog that appears when you want to rename your Amiibo. Previously, the options didn't reflect the actual window and what they were asking the user, but now clearly tell the user that they are renaming their Amiibo, and when the dialog prompts the user to scan their amiibo again.
### SCAN AMIIBO (BIN)
* **Renamed:** `Scan Amiibo (.BIN)` to `Scan Amiibo (BIN)` to be similar to `Install Keys` > `KEYS` and similar menu items
* **Adjusted:** File picker title when loading Amiibo from BIN to...literally tell the user what to select and what will happen (similar to `Install Firmware` and `Install Keys` file picker titles)
### SCAN/REMOVE SKYLANDER
* **Merged:** `Scan Skylander` and `Remove Skylander`
* Instead of having two separate buttons, let's have one button that actively updates state. Instead of using two shortcuts, it now uses one, and the label updates based on whether the game is asking to `Scan Skylander` or whether you can `Remove Skylander`.
* **Fixed:** `Scan Skylander`/`Remove Skylander` were not visible properly
* Initial implementation only displayed the menu options/buttons when a particular Skylander Imaginators game was being played (i.e. specific ID)
* While there is only one Skylander game on the Switch, the game has been distributed in multiple regions under different game IDs and has different languages. People who owned Skylander Imaginators with a 0100ccc0002e6000 ID could use this feature and _nobody else._
* This has now been fixed by allowing those options to be visible at all times, regardless of the game
* For Skylander games, this is not an issue because the backend implementation for scanning/removing a skylander is the same (tested)
* For other games, then it doesn't matter since the feature is not used, but it will let users know the feature exists, in case they wished to previously play the game, but couldn't due to the missing backend.
* **Renamed:** Skylander functions and methods
* You don't open a window (like Scan Amiibo) but you open a file picker.
* **Fixed:** Inconsistent button state and shortcut update
* This was a bug in the previous implementation as well - you couldn't scan/remove skylander with the keyboard shortcut unless you opened the `Actions` menu (and only then would the shortcut register). This has been fixed.
As such...Skylanders Imaginators are now **Playable**! Updated the entry and added another game ID.
_If there are any features or changes that you wish to be implemented, please comment down below and I'll be happy to accommodate!_
Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/158
This commit is contained in:
@@ -502,15 +502,20 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Show toggles for dirty hacks in the UI.
|
||||
/// Show toggles for dirty hacks in the UI.
|
||||
/// </summary>
|
||||
public bool ShowDirtyHacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The packed values of the enabled dirty hacks.
|
||||
/// The packed values of the enabled dirty hacks.
|
||||
/// </summary>
|
||||
public ulong[] DirtyHacks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pause emulation while scanning Amiibo
|
||||
/// </summary>
|
||||
public bool PauseEmulationWhileScanningAmiibo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
/// </summary>
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
System.SkipUserProfilesManager.Value = cff.SkipUserProfiles;
|
||||
System.UseHypervisor.Value = cff.UseHypervisor;
|
||||
System.GCLowLatency.Value = cff.GCLowLatency;
|
||||
|
||||
|
||||
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
|
||||
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
|
||||
UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
|
||||
@@ -151,6 +151,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
|
||||
UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
|
||||
UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
|
||||
UI.PauseEmulationWhileScanningAmiibo.Value = cff.PauseEmulationWhileScanningAmiibo;
|
||||
|
||||
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
|
||||
Hid.EnableMouse.Value = cff.EnableMouse;
|
||||
|
||||
@@ -196,6 +196,11 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> ShowDynamicInputSwapWarning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Pause emulation while scanning Amiibo
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> PauseEmulationWhileScanningAmiibo { get; private set; }
|
||||
|
||||
public UISection()
|
||||
{
|
||||
GuiColumns = new Columns();
|
||||
@@ -217,6 +222,8 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
ShowConsole.Event += static (_, e) => ConsoleHelper.SetConsoleWindowState(e.NewValue);
|
||||
ShowDynamicInputSwapWarning = new ReactiveObject<bool>();
|
||||
ShowDynamicInputSwapWarning.Value = true;
|
||||
PauseEmulationWhileScanningAmiibo = new ReactiveObject<bool>();
|
||||
PauseEmulationWhileScanningAmiibo.Value = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
|
||||
ShowDirtyHacks = Hacks.ShowDirtyHacks,
|
||||
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
|
||||
PauseEmulationWhileScanningAmiibo = UI.PauseEmulationWhileScanningAmiibo,
|
||||
};
|
||||
|
||||
return configurationFile;
|
||||
@@ -262,6 +263,7 @@ namespace Ryujinx.Ava.Systems.Configuration
|
||||
UI.WindowStartup.WindowPositionX.Value = 0;
|
||||
UI.WindowStartup.WindowPositionY.Value = 0;
|
||||
UI.WindowStartup.WindowMaximized.Value = false;
|
||||
UI.PauseEmulationWhileScanningAmiibo.Value = true;
|
||||
Hid.EnableKeyboard.Value = false;
|
||||
Hid.EnableMouse.Value = false;
|
||||
Hid.DisableInputWhenOutOfFocus.Value = false;
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
StringLengthMax = 25
|
||||
};
|
||||
(UserResult result, string userInput) =
|
||||
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
|
||||
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_RenameAmiiboTitle], args);
|
||||
if (result == UserResult.Ok)
|
||||
{
|
||||
inputText = userInput;
|
||||
@@ -212,11 +212,11 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
{
|
||||
dialogCloseEvent.Set();
|
||||
await ContentDialogHelper.CreateInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboMessage],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.CabinetTitle]
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboTitle]
|
||||
);
|
||||
});
|
||||
dialogCloseEvent.WaitOne();
|
||||
|
||||
@@ -74,10 +74,14 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
content._host = contentDialog;
|
||||
contentDialog.Title = title;
|
||||
contentDialog.PrimaryButtonText = args.SubmitText;
|
||||
|
||||
contentDialog.PrimaryButtonText = string.IsNullOrWhiteSpace(args.SubmitText)
|
||||
? LocaleManager.Instance[LocaleKeys.InputDialogOk]
|
||||
: args.SubmitText;
|
||||
|
||||
contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length);
|
||||
contentDialog.SecondaryButtonText = string.Empty;
|
||||
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel];
|
||||
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.Common_Buttons_Cancel];
|
||||
contentDialog.Content = content;
|
||||
|
||||
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
|
||||
|
||||
@@ -3,8 +3,10 @@ using Avalonia.Collections;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Path = System.IO.Path;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Common.Models.Amiibo;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Ava.Utilities;
|
||||
@@ -20,31 +22,39 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public partial class AmiiboWindowViewModel : BaseModel, IDisposable
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static bool _cachedUseRandomUuid;
|
||||
|
||||
private const string DefaultJson = "{ \"amiibo\": [] }";
|
||||
private const float AmiiboImageSize = 350f;
|
||||
|
||||
private readonly string _amiiboJsonPath;
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AmiiboWindow _owner;
|
||||
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
public enum AmiiboSortField
|
||||
{
|
||||
Name,
|
||||
|
||||
}
|
||||
|
||||
private int _amiiboSelectedIndex;
|
||||
private int _seriesSelectedIndex;
|
||||
private bool _showAllAmiibo;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static bool _cachedUseRandomUuid;
|
||||
public bool IsSortedByName => _sortField == AmiiboSortField.Name;
|
||||
private const string DefaultJson = "{ \"amiibo\": [] }";
|
||||
private const float AmiiboImageSize = 350f;
|
||||
public string TitleId { get; set; }
|
||||
public string LastScannedAmiiboId { get; set; }
|
||||
public string SortingFieldName => LocaleManager.Instance[LocaleKeys.Common_Sort_NameLabel];
|
||||
private readonly string _amiiboJsonPath;
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly AmiiboWindow _owner;
|
||||
private List<AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<AmiiboApi> _amiibos;
|
||||
private ObservableCollection<string> _amiiboSeries;
|
||||
private CancellationTokenSource _imageCts = new();
|
||||
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AmiiboWindowViewModel(AmiiboWindow owner, string lastScannedAmiiboId, string titleId)
|
||||
@@ -73,9 +83,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public AmiiboWindowViewModel() { }
|
||||
|
||||
public string TitleId { get; set; }
|
||||
public string LastScannedAmiiboId { get; set; }
|
||||
|
||||
public UserResult Response { get; private set; }
|
||||
|
||||
public bool UseRandomUuid
|
||||
@@ -151,6 +158,42 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool PauseEmulationWhileScanningAmiibo
|
||||
{
|
||||
get => ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value;
|
||||
set
|
||||
{
|
||||
ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value = value;
|
||||
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _searchText = string.Empty;
|
||||
private bool _sortingAscending = true;
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get => _searchText;
|
||||
set
|
||||
{
|
||||
_searchText = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SortingAscending
|
||||
{
|
||||
get => _sortingAscending;
|
||||
set
|
||||
{
|
||||
_sortingAscending = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Bitmap AmiiboImage { get; set; }
|
||||
|
||||
@@ -166,14 +209,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
_owner.ScannedAmiibo = AmiiboList[AmiiboSelectedIndex];
|
||||
_owner.IsScanned = true;
|
||||
_owner.Close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_owner.IsScanned = false;
|
||||
_owner.Close();
|
||||
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
@@ -315,42 +358,46 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private void ParseAmiiboData()
|
||||
{
|
||||
_amiiboSeries.Clear();
|
||||
_amiibos.Clear();
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
foreach (AmiiboApi amiibo in _amiiboList)
|
||||
{
|
||||
if (!_amiiboSeries.Contains(_amiiboList[i].AmiiboSeries))
|
||||
if (!_amiiboSeries.Contains(amiibo.AmiiboSeries))
|
||||
{
|
||||
if (!ShowAllAmiibo)
|
||||
if (_showAllAmiibo)
|
||||
{
|
||||
foreach (AmiiboApiGamesSwitch game in _amiiboList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_amiiboSeries.Add(amiibo.AmiiboSeries);
|
||||
}
|
||||
else
|
||||
{
|
||||
AmiiboSeries.Add(_amiiboList[i].AmiiboSeries);
|
||||
bool hasCompatible = amiibo.GamesSwitch.Any(game =>
|
||||
game != null && game.GameId.Contains(TitleId));
|
||||
|
||||
if (hasCompatible)
|
||||
{
|
||||
_amiiboSeries.Add(amiibo.AmiiboSeries);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LastScannedAmiiboId != string.Empty)
|
||||
if (_showAllAmiibo)
|
||||
{
|
||||
SeriesSelectedIndex = -1;
|
||||
}
|
||||
else if (LastScannedAmiiboId != string.Empty)
|
||||
{
|
||||
SelectLastScannedAmiibo();
|
||||
}
|
||||
else
|
||||
else if (_amiiboSeries.Count > 0)
|
||||
{
|
||||
SeriesSelectedIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SeriesSelectedIndex = -1;
|
||||
}
|
||||
|
||||
FilterAmiibo();
|
||||
}
|
||||
|
||||
private void SelectLastScannedAmiibo()
|
||||
@@ -360,104 +407,109 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
SeriesSelectedIndex = AmiiboSeries.IndexOf(scanned.AmiiboSeries);
|
||||
AmiiboSelectedIndex = AmiiboList.IndexOf(scanned);
|
||||
}
|
||||
|
||||
|
||||
private void FilterAmiibo()
|
||||
{
|
||||
_amiibos.Clear();
|
||||
|
||||
List<AmiiboApi> amiiboSortedList;
|
||||
IEnumerable<AmiiboApi> query = _amiiboList.AsEnumerable();
|
||||
|
||||
// If no series selected, just display all available amiibos
|
||||
if (_seriesSelectedIndex < 0)
|
||||
if (_seriesSelectedIndex >= 0 && _seriesSelectedIndex < _amiiboSeries.Count)
|
||||
{
|
||||
amiiboSortedList = _amiiboList
|
||||
.OrderBy(amiibo => amiibo.AmiiboSeries)
|
||||
.ThenBy(x => x.Name)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
amiiboSortedList = _amiiboList
|
||||
.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeries[SeriesSelectedIndex])
|
||||
.OrderBy(amiibo => amiibo.Name).ToList();
|
||||
string selectedSeries = _amiiboSeries[_seriesSelectedIndex];
|
||||
query = query.Where(amiibo => amiibo.AmiiboSeries == selectedSeries);
|
||||
}
|
||||
|
||||
|
||||
|
||||
for (int i = 0; i < amiiboSortedList.Count; i++)
|
||||
if (!string.IsNullOrWhiteSpace(_searchText))
|
||||
{
|
||||
if (!_amiibos.Contains(amiiboSortedList[i]))
|
||||
query = query.Where(amiibo =>
|
||||
amiibo.Name.Contains(_searchText, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
query = query.Where(amiibo =>
|
||||
amiibo.GamesSwitch.Any(game => game != null && game.GameId.Contains(TitleId)));
|
||||
}
|
||||
|
||||
query = _sortingAscending
|
||||
? query.OrderBy(amiibo => amiibo.Name)
|
||||
: query.OrderByDescending(amiibo => amiibo.Name);
|
||||
|
||||
_amiibos.AddRange(query);
|
||||
|
||||
int restoredIndex = -1;
|
||||
for (int i = 0; i < _amiibos.Count; i++)
|
||||
{
|
||||
if (_amiibos[i].GetId() == LastScannedAmiiboId)
|
||||
{
|
||||
if (!_showAllAmiibo)
|
||||
{
|
||||
foreach (AmiiboApiGamesSwitch game in amiiboSortedList[i].GamesSwitch)
|
||||
{
|
||||
if (game != null)
|
||||
{
|
||||
if (game.GameId.Contains(TitleId))
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_amiibos.Add(amiiboSortedList[i]);
|
||||
}
|
||||
restoredIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AmiiboSelectedIndex = 0;
|
||||
AmiiboSelectedIndex = restoredIndex != -1
|
||||
? restoredIndex
|
||||
: (_amiibos.Count > 0 ? 0 : -1);
|
||||
|
||||
SetAmiiboDetails();
|
||||
}
|
||||
|
||||
private AmiiboSortField _sortField = AmiiboSortField.Name;
|
||||
|
||||
public AmiiboSortField SortingField
|
||||
{
|
||||
get => _sortField;
|
||||
set
|
||||
{
|
||||
_sortField = value;
|
||||
FilterAmiibo();
|
||||
OnPropertyChanged(nameof(SortingFieldName));
|
||||
OnPropertyChanged(nameof(IsSortedByName));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAmiiboDetails()
|
||||
{
|
||||
ResetAmiiboPreview();
|
||||
|
||||
Usage = string.Empty;
|
||||
|
||||
if (_amiiboSelectedIndex < 0 || _amiibos.Count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AmiiboApi selected = _amiibos[_amiiboSelectedIndex];
|
||||
|
||||
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Equals(selected)).Image;
|
||||
string imageUrl = selected.Image;
|
||||
|
||||
StringBuilder usageStringBuilder = new();
|
||||
bool writable = false;
|
||||
|
||||
for (int i = 0; i < _amiiboList.Count; i++)
|
||||
foreach (AmiiboApiGamesSwitch game in selected.GamesSwitch)
|
||||
{
|
||||
if (_amiiboList[i].Equals(selected))
|
||||
if (game != null && game.GameId.Contains(TitleId))
|
||||
{
|
||||
bool writable = false;
|
||||
|
||||
foreach (AmiiboApiGamesSwitch item in _amiiboList[i].GamesSwitch)
|
||||
foreach (AmiiboApiUsage usageItem in game.AmiiboUsage)
|
||||
{
|
||||
if (item.GameId.Contains(TitleId))
|
||||
{
|
||||
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||
{
|
||||
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||
|
||||
writable = usageItem.Write;
|
||||
}
|
||||
}
|
||||
usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}");
|
||||
if (usageItem.Write)
|
||||
writable = true;
|
||||
}
|
||||
|
||||
if (usageStringBuilder.Length == 0)
|
||||
{
|
||||
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
|
||||
}
|
||||
|
||||
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : string.Empty)} : {usageStringBuilder}";
|
||||
}
|
||||
}
|
||||
|
||||
string usageLabel = writable
|
||||
? LocaleManager.Instance[LocaleKeys.Amiibo_WritableLabel]
|
||||
: LocaleManager.Instance[LocaleKeys.Amiibo_UsageLabel];
|
||||
|
||||
if (usageStringBuilder.Length == 0)
|
||||
{
|
||||
usageStringBuilder.Append(Environment.NewLine + Environment.NewLine + LocaleManager.Instance[LocaleKeys.Amiibo_UnknownLabel]);
|
||||
}
|
||||
else
|
||||
{
|
||||
usageStringBuilder.Replace(Environment.NewLine + "-", Environment.NewLine + Environment.NewLine + "-");
|
||||
}
|
||||
|
||||
Usage = usageLabel + usageStringBuilder.ToString();
|
||||
_ = UpdateAmiiboPreview(imageUrl);
|
||||
}
|
||||
|
||||
@@ -510,8 +562,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to request amiibo data: {exception}");
|
||||
}
|
||||
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APITitle],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APIFailFetchMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
@@ -526,26 +578,33 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private async Task UpdateAmiiboPreview(string imageUrl)
|
||||
{
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl);
|
||||
_imageCts.Cancel();
|
||||
_imageCts = new CancellationTokenSource();
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
try
|
||||
{
|
||||
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
||||
using MemoryStream memoryStream = new(amiiboPreviewBytes);
|
||||
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_imageCts.Token);
|
||||
|
||||
Bitmap bitmap = new(memoryStream);
|
||||
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl, linkedCts.Token);
|
||||
|
||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
|
||||
AmiiboImageSize / bitmap.Size.Height);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
byte[] bytes = await response.Content.ReadAsByteArrayAsync(linkedCts.Token);
|
||||
using MemoryStream ms = new(bytes);
|
||||
|
||||
int resizeHeight = (int)(bitmap.Size.Height * ratio);
|
||||
int resizeWidth = (int)(bitmap.Size.Width * ratio);
|
||||
Bitmap bitmap = new(ms);
|
||||
|
||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
|
||||
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width, AmiiboImageSize / bitmap.Size.Height);
|
||||
int newWidth = (int)(bitmap.Size.Width * ratio);
|
||||
int newHeight = (int)(bitmap.Size.Height * ratio);
|
||||
|
||||
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(newWidth, newHeight));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to get amiibo preview. Response status code: {response.StatusCode}");
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to load amiibo preview: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,8 +619,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private static async Task ShowInfoDialog()
|
||||
{
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
|
||||
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APITitle],
|
||||
LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_APIConnectErrorMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogOk],
|
||||
string.Empty,
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
|
||||
|
||||
@@ -160,6 +160,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
[ObservableProperty] public partial bool UpdateAvailable { get; set; }
|
||||
|
||||
[ObservableProperty] public partial string SkylanderButtonHeader { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty] public partial bool SkylanderButtonEnabled { get; set; }
|
||||
|
||||
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
|
||||
{
|
||||
if (Updater.CanUpdate(true))
|
||||
@@ -204,6 +208,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
internal AppHost AppHost { get; set; }
|
||||
|
||||
public RelayCommand ToggleSkylanderCommand { get; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
Applications = [];
|
||||
@@ -233,6 +239,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Volume = ConfigurationState.Instance.System.AudioVolume;
|
||||
CustomVSyncInterval = ConfigurationState.Instance.Graphics.CustomVSyncInterval.Value;
|
||||
}
|
||||
|
||||
ToggleSkylanderCommand = new RelayCommand(ExecuteToggleSkylander);
|
||||
}
|
||||
|
||||
~MainWindowViewModel()
|
||||
@@ -376,6 +384,50 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
||||
|
||||
[RelayCommand]
|
||||
private void UpdateSkylanderButton()
|
||||
{
|
||||
SkylanderButtonHeader = HasSkylander
|
||||
? LocaleManager.Instance[LocaleKeys.MenuBar_Actions_RemoveSkylanderButton]
|
||||
: LocaleManager.Instance[LocaleKeys.MenuBar_Actions_ScanSkylanderButton];
|
||||
|
||||
SkylanderButtonEnabled = HasSkylander || IsSkylanderRequested;
|
||||
|
||||
OnPropertyChanged(nameof(SkylanderButtonHeader));
|
||||
OnPropertyChanged(nameof(SkylanderButtonEnabled));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
OnPropertyChanged(nameof(SkylanderButtonHeader));
|
||||
}, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
private void UpdateSkylanderState()
|
||||
{
|
||||
if (AppHost?.Device?.System == null) return;
|
||||
|
||||
IsSkylanderRequested = AppHost.Device.System.SearchingForSkylander(out _);
|
||||
HasSkylander = AppHost.Device.System.HasSkylander(out _);
|
||||
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
|
||||
private async void ExecuteToggleSkylander()
|
||||
{
|
||||
if (!IsGameRunning) return;
|
||||
|
||||
if (HasSkylander)
|
||||
{
|
||||
await RemoveSkylander();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ScanSkylander();
|
||||
}
|
||||
|
||||
UpdateSkylanderState();
|
||||
}
|
||||
|
||||
public bool IsSkylanderRequested
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
@@ -384,6 +436,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,17 +448,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSkylanderActions
|
||||
{
|
||||
get => field && _isGameRunning;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1496,7 +1539,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Firmware_InstallFromFileFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.xci", "*.zip"],
|
||||
AppleUniformTypeIdentifiers = ["com.ryujinx.xci", "public.zip-archive"],
|
||||
@@ -1680,7 +1723,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_FileMenu_LoadApplicationFromFileFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.nsp", "*.xci", "*.nca", "*.nro", "*.nso"],
|
||||
AppleUniformTypeIdentifiers =
|
||||
@@ -2010,40 +2053,58 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OpenBinFile()
|
||||
public async Task ScanAmiiboFromBin()
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
|
||||
{
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.bin"],
|
||||
}
|
||||
}
|
||||
});
|
||||
bool shouldPause = ConfigurationState.Instance.UI.PauseEmulationWhileScanningAmiibo.Value && IsGameRunning;
|
||||
|
||||
if (result.HasValue)
|
||||
if (shouldPause && AppHost?.Device?.System != null)
|
||||
{
|
||||
AppHost?.Pause();
|
||||
}
|
||||
try
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
|
||||
{
|
||||
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Amiibo_ScanAmiiboFromBinFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.bin"],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.HasValue)
|
||||
{
|
||||
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldPause && AppHost?.Device?.System != null)
|
||||
{
|
||||
AppHost?.Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task OpenSkylanderWindow()
|
||||
|
||||
[RelayCommand]
|
||||
public async Task ScanSkylander()
|
||||
{
|
||||
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
|
||||
{
|
||||
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||
new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||
Title = LocaleManager.Instance[LocaleKeys.Dialog_Skylanders_ScanSkylanderFilePickerTitle],
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
|
||||
},
|
||||
@@ -2066,12 +2127,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AppHost.Device.System.ScanSkylander(deviceId, data);
|
||||
}
|
||||
}
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public async Task RemoveSkylander()
|
||||
{
|
||||
AppHost.Device.System.RemoveSkylander();
|
||||
UpdateSkylanderButton();
|
||||
}
|
||||
|
||||
public void ReloadRenderDocApi()
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AllowMultiple = true,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.nsp"],
|
||||
AppleUniformTypeIdentifiers = ["com.ryujinx.nsp"],
|
||||
|
||||
@@ -190,7 +190,7 @@
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Click="Close">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<Binding Path="!Cancel" />
|
||||
</MultiBinding>
|
||||
</Button.IsVisible>
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
<Button Name="CloseButton" MinWidth="90" Click="Close" IsVisible="{Binding !Processing}">
|
||||
<TextBlock Text="{ext:Locale SettingsButtonClose}" />
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
InputGesture="Ctrl + A"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding OpenBinFile}"
|
||||
Command="{Binding ScanAmiiboFromBin}"
|
||||
AttachedToVisualTree="ScanBinAmiiboMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_ScanAmiiboFromBinButton}"
|
||||
Icon="{ext:Icon fa-solid fa-chess-rook}"
|
||||
@@ -146,21 +146,12 @@
|
||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||
<Separator IsVisible="{Binding CanScanAmiiboBinaries}"/>
|
||||
<MenuItem
|
||||
Command="{Binding OpenSkylanderWindow}"
|
||||
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_ScanSkylanderButton}"
|
||||
Command="{Binding ToggleSkylanderCommand}"
|
||||
AttachedToVisualTree="SkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{Binding SkylanderButtonHeader}"
|
||||
Icon="{ext:Icon fa-solid fa-dragon}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + S"
|
||||
IsEnabled="{Binding IsSkylanderRequested}" />
|
||||
<MenuItem
|
||||
Command="{Binding RemoveSkylander}"
|
||||
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
|
||||
Header="{ext:Locale MenuBar_Actions_RemoveSkylanderButton}"
|
||||
Icon="{ext:Icon fa-solid fa-dragon}"
|
||||
IsVisible="{Binding ShowSkylanderActions}"
|
||||
InputGesture="Ctrl + D"
|
||||
IsEnabled="{Binding HasSkylander}" />
|
||||
IsEnabled="{Binding SkylanderButtonEnabled}" />
|
||||
<MenuItem
|
||||
Command="{Binding SimulateWakeUpMessage}"
|
||||
Header="{ext:Locale MenuBar_Actions_SimulateWakeUpMessageButton}"
|
||||
@@ -215,7 +206,7 @@
|
||||
<Separator />
|
||||
<MenuItem Header="{ext:Locale MenuBar_Actions_ToolsLabel}" Icon="{ext:Icon fa-solid fa-toolbox}">
|
||||
<MenuItem
|
||||
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
|
||||
Name="MiiEditorMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
|
||||
<MenuItem Name="XCITrimmerMenuItem" Header="{ext:Locale MenuBar_Actions_XCITrimmerButton}" Icon="{ext:Icon fa-solid fa-scissors}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
ToggleFileTypesMenuItem.ItemsSource = GenerateToggleFileTypeItems();
|
||||
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
||||
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
|
||||
MiiEditorMenuItem.Command = Commands.Create(OpenMiiEditor);
|
||||
CloseRyujinxMenuItem.Command = Commands.Create(() => Window?.Close());
|
||||
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
|
||||
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
|
||||
@@ -150,11 +150,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.LoadConfigurableHotKeys();
|
||||
}
|
||||
|
||||
public AppletMetadata MiiApplet => new(ViewModel.ContentManager, LocaleManager.Instance[LocaleKeys.MenuBar_Actions_MiiEditorButton], 0x0100000000001009);
|
||||
public AppletMetadata MiiEditor => new(ViewModel.ContentManager, LocaleManager.Instance[LocaleKeys.MenuBar_Actions_MiiEditorButton], 0x0100000000001009);
|
||||
|
||||
public async Task OpenMiiApplet()
|
||||
public async Task OpenMiiEditor()
|
||||
{
|
||||
if (!MiiApplet.CanStart(out ApplicationData appData, out BlitStruct<ApplicationControlProperty> nacpData))
|
||||
if (!MiiEditor.CanStart(out ApplicationData appData, out BlitStruct<ApplicationControlProperty> nacpData))
|
||||
return;
|
||||
|
||||
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
|
||||
@@ -172,18 +172,13 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
||||
}
|
||||
|
||||
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
private void SkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
{
|
||||
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
|
||||
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
if (sender is MenuItem)
|
||||
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
|
||||
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeWindowSize(string resolution)
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||
new(LocaleManager.Instance[LocaleKeys.Common_FilePicker_AllSupportedFormats])
|
||||
{
|
||||
Patterns = ["*.jpg", "*.jpeg", "*.png", "*.bmp"],
|
||||
AppleUniformTypeIdentifiers = ["public.jpeg", "public.png", "com.microsoft.bmp"],
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:pt="using:Projektanker.Icons.Avalonia"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="400"
|
||||
d:DesignHeight="350"
|
||||
@@ -14,82 +15,163 @@
|
||||
x:DataType="viewModels:AmiiboWindowViewModel"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="800"
|
||||
Width="950"
|
||||
MinHeight="650"
|
||||
Height="650"
|
||||
Height="700"
|
||||
SizeToContent="Manual"
|
||||
MinWidth="600"
|
||||
Focusable="True">
|
||||
<Design.DataContext>
|
||||
<viewModels:AmiiboWindowViewModel />
|
||||
</Design.DataContext>
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto" Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
|
||||
<Grid Name="FlushControls" ColumnDefinitions="Auto,Auto" Grid.Row="1" Margin="-15">
|
||||
<controls:RyujinxLogo
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Margin="8, 8, 7, 0"
|
||||
ToolTip.Tip="{ext:WindowTitle Amiibo}" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="0, 0, 0, 20">
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" Text="{ext:Locale AmiiboSeriesLabel}" />
|
||||
<ComboBox Margin="0, 8, 0, 0" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="0" RowDefinitions="Auto,Auto,*,Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
|
||||
<!-- UI FlushControls -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" Name="FlushControls">
|
||||
<controls:RyujinxLogo Grid.Column="0" Margin="15,0,7,0" ToolTip.Tip="{ext:WindowTitle Amiibo_WindowTitle}" />
|
||||
<TextBox Grid.Column="1"
|
||||
Margin="0,5,0,7"
|
||||
Width="250"
|
||||
Watermark="{ext:Locale Common_Search_SearchAmiiboWatermark}"
|
||||
Text="{Binding SearchText}" />
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<ComboBox Margin="10,5,0,5" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="140" />
|
||||
</StackPanel>
|
||||
<DropDownButton Grid.Column="3" Margin="10,5,0,5" Content="{Binding SortingFieldName}">
|
||||
<DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Margin="0" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton GroupName="AmiiboSort" IsChecked="{Binding IsSortedByName}" Content="{ext:Locale Common_Sort_NameLabel}" Tag="Name" Checked="Sort_Checked" />
|
||||
</StackPanel>
|
||||
<Border Width="60" Height="2" Margin="5" HorizontalAlignment="Stretch" BorderBrush="White" BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding SortingAscending}" Content="{ext:Locale Common_Sort_OrderAscending}" Tag="Ascending" Checked="Order_Checked" />
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding !SortingAscending}" Content="{ext:Locale Common_Sort_OrderDescending}" Tag="Descending" Checked="Order_Checked" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
Margin="10,5,0,5"
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Click="SettingsButton_Click">
|
||||
<pt:Icon Value="fa-solid fa-gear" />
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel>
|
||||
<CheckBox
|
||||
IsChecked="{Binding UseRandomUuid}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_UseRandomUuidLabel}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding PauseEmulationWhileScanningAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_PauseEmulationWhileScanningAmiibo}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</FlyoutBase.AttachedFlyout>
|
||||
</Button>
|
||||
<CheckBox Grid.Column="5" Margin="10,5,0,5" IsChecked="{Binding ShowAllAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_ShowAllAmiiboLabel}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
<Grid Name="NormalControls" ColumnDefinitions="*,*" Grid.Row="1" HorizontalAlignment="Stretch" >
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<TextBlock VerticalAlignment="Center" Text="{ext:Locale AmiiboSeriesLabel}" />
|
||||
<ComboBox SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="100" />
|
||||
|
||||
<!-- UI NormalControls -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,Auto" Name="NormalControls">
|
||||
<TextBox Grid.Column="0"
|
||||
Margin="15,5,0,7"
|
||||
Width="250"
|
||||
Watermark="{ext:Locale Common_Search_SearchAmiiboWatermark}"
|
||||
Text="{Binding SearchText}" />
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal">
|
||||
<ComboBox Margin="10,5,0,5" SelectedIndex="{Binding SeriesSelectedIndex}" ItemsSource="{Binding AmiiboSeries}" MinWidth="140" />
|
||||
</StackPanel>
|
||||
<DropDownButton Grid.Column="2" Margin="10,5,0,5" Content="{Binding SortingFieldName}">
|
||||
<DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Margin="0" HorizontalAlignment="Stretch" Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton GroupName="AmiiboSort" IsChecked="{Binding IsSortedByName}" Content="{ext:Locale Common_Sort_NameLabel}" Tag="Name" Checked="Sort_Checked" />
|
||||
</StackPanel>
|
||||
<Border Width="60" Height="2" Margin="5" HorizontalAlignment="Stretch" BorderBrush="White" BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding SortingAscending}" Content="{ext:Locale Common_Sort_OrderAscending}" Tag="Ascending" Checked="Order_Checked" />
|
||||
<RadioButton GroupName="AmiiboOrder" IsChecked="{Binding !SortingAscending}" Content="{ext:Locale Common_Sort_OrderDescending}" Tag="Descending" Checked="Order_Checked" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
Margin="10,5,0,5"
|
||||
MinWidth="32"
|
||||
MinHeight="32"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Click="SettingsButton_Click">
|
||||
<pt:Icon Value="fa-solid fa-gear" />
|
||||
<FlyoutBase.AttachedFlyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel>
|
||||
<CheckBox
|
||||
IsChecked="{Binding UseRandomUuid}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_UseRandomUuidLabel}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
IsChecked="{Binding PauseEmulationWhileScanningAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_PauseEmulationWhileScanningAmiibo}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</FlyoutBase.AttachedFlyout>
|
||||
</Button>
|
||||
<CheckBox Grid.Column="4" Margin="10,5,0,5" IsChecked="{Binding ShowAllAmiibo}">
|
||||
<TextBlock Text="{ext:Locale Amiibo_ShowAllAmiiboLabel}" />
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
<DockPanel Grid.Row="2">
|
||||
|
||||
<!-- Amiibo list, images, and usage -->
|
||||
<StackPanel Grid.Row="2" Margin="0,20,0,20" Orientation="Horizontal" HorizontalAlignment="Center" Spacing="50">
|
||||
<ListBox
|
||||
DockPanel.Dock="Left"
|
||||
Width="300"
|
||||
Margin="20"
|
||||
Padding="5"
|
||||
SelectionMode="Single"
|
||||
ItemsSource="{Binding AmiiboList}"
|
||||
SelectedIndex="{Binding AmiiboSelectedIndex}">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="10, 0, 0 ,0" Text="{Binding Name}" />
|
||||
<TextBlock Margin="10,0,0,0" Text="{Binding Name}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<StackPanel Margin="20">
|
||||
<Image Source="{Binding AmiiboImage}" Height="350" Width="150" HorizontalAlignment="Center" />
|
||||
<ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"
|
||||
Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch">
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center"
|
||||
TextAlignment="Center" />
|
||||
<StackPanel Width="300">
|
||||
<Image Source="{Binding AmiiboImage}" Height="400" Width="200" />
|
||||
<ScrollViewer MaxHeight="120" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" Margin="20" VerticalAlignment="Top" HorizontalAlignment="Stretch">
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Usage}" HorizontalAlignment="Center" TextAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
<Grid Grid.Row="3" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<CheckBox Margin="10" Grid.Column="0" VerticalContentAlignment="Center"
|
||||
IsChecked="{Binding ShowAllAmiibo}"
|
||||
Content="{ext:Locale AmiiboOptionsShowAllLabel}" />
|
||||
<CheckBox HotKey="H"
|
||||
Margin="10" VerticalContentAlignment="Center" Grid.Column="1"
|
||||
IsChecked="{Binding UseRandomUuid}"
|
||||
Content="{ext:Locale AmiiboOptionsUsRandomTagLabel}" />
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="3" Margin="0,0,10,10" ColumnDefinitions="Auto,Auto,*,Auto,Auto">
|
||||
<Button Grid.Column="3"
|
||||
IsEnabled="{Binding EnableScanning}"
|
||||
Width="80"
|
||||
Name="ScanButton"
|
||||
HotKey="Return"
|
||||
Content="{ext:Locale AmiiboScanButtonLabel}"
|
||||
Content="{ext:Locale Amiibo_ScanButton}"
|
||||
Click="ScanButton_Click" />
|
||||
<Button Grid.Column="4"
|
||||
Margin="10,0"
|
||||
Margin="10,0,0,0"
|
||||
Width="80"
|
||||
Name="CancelButton"
|
||||
HotKey="Escape"
|
||||
Content="{ext:Locale InputDialogCancel}"
|
||||
Content="{ext:Locale Common_Buttons_Cancel}"
|
||||
Click="CancelButton_Click" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</window:StyleableAppWindow>
|
||||
</window:StyleableAppWindow>
|
||||
@@ -1,8 +1,12 @@
|
||||
using Avalonia.Interactivity;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Common.Models.Amiibo;
|
||||
using Ryujinx.Ava.Systems;
|
||||
using Ryujinx.Ava.Systems.Configuration;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
@@ -20,7 +24,50 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
FlushControls.IsVisible = !ConfigurationState.Instance.ShowOldUI;
|
||||
NormalControls.IsVisible = ConfigurationState.Instance.ShowOldUI;
|
||||
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo_WindowTitle);
|
||||
|
||||
if (ViewModel.PauseEmulationWhileScanningAmiibo && RyujinxApp.MainWindow?.ViewModel?.AppHost != null)
|
||||
{
|
||||
RyujinxApp.MainWindow.ViewModel.AppHost.Pause();
|
||||
}
|
||||
|
||||
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
}
|
||||
|
||||
private void SettingsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FlyoutBase.ShowAttachedFlyout((Control)sender!);
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(ViewModel.PauseEmulationWhileScanningAmiibo))
|
||||
return;
|
||||
|
||||
AppHost host = RyujinxApp.MainWindow?.ViewModel?.AppHost;
|
||||
if (host == null)
|
||||
return;
|
||||
|
||||
if (ViewModel.PauseEmulationWhileScanningAmiibo)
|
||||
host.Pause();
|
||||
else
|
||||
host.Resume();
|
||||
}
|
||||
|
||||
private void AlwaysResumeOnClose()
|
||||
{
|
||||
if (RyujinxApp.MainWindow?.ViewModel?.AppHost != null)
|
||||
{
|
||||
RyujinxApp.MainWindow.ViewModel.AppHost.Resume();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
ViewModel.PropertyChanged -= ViewModel_PropertyChanged;
|
||||
AlwaysResumeOnClose();
|
||||
base.OnClosed(e);
|
||||
}
|
||||
|
||||
public AmiiboWindow()
|
||||
@@ -31,7 +78,26 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
if (Program.PreviewerDetached)
|
||||
{
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo);
|
||||
Title = RyujinxApp.FormatTitle(LocaleKeys.Amiibo_WindowTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Sort_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is RadioButton { Tag: string tag } && ViewModel != null)
|
||||
{
|
||||
if (Enum.TryParse<AmiiboWindowViewModel.AmiiboSortField>(tag, out var sortField))
|
||||
{
|
||||
ViewModel.SortingField = sortField;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Order_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is RadioButton { Tag: string tag } && ViewModel != null)
|
||||
{
|
||||
ViewModel.SortingAscending = tag == "Ascending";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +105,18 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
public AmiiboApi ScannedAmiibo { get; set; }
|
||||
public AmiiboWindowViewModel ViewModel;
|
||||
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e) => ViewModel.Scan();
|
||||
private void ScanButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AlwaysResumeOnClose();
|
||||
ViewModel.Scan();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e) => ViewModel.Cancel();
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AlwaysResumeOnClose();
|
||||
ViewModel.Cancel();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
Margin="5"
|
||||
HotKey="Escape"
|
||||
Command="{Binding Close}">
|
||||
<TextBlock Text="{ext:Locale InputDialogCancel}" />
|
||||
<TextBlock Text="{ext:Locale Common_Buttons_Cancel}" />
|
||||
</Button>
|
||||
</DockPanel>
|
||||
</DockPanel>
|
||||
|
||||
@@ -40,10 +40,11 @@
|
||||
<KeyBinding Gesture="F9" Command="{Binding ToggleDockMode}" />
|
||||
<KeyBinding Gesture="Escape" Command="{Binding ExitCurrentState}" />
|
||||
<KeyBinding Gesture="Ctrl+A" Command="{Binding OpenAmiiboWindow}" />
|
||||
<KeyBinding Gesture="Ctrl+B" Command="{Binding OpenBinFile}" />
|
||||
<KeyBinding Gesture="Ctrl+B" Command="{Binding ScanAmiiboFromBin}" />
|
||||
<KeyBinding Gesture="Ctrl+R" Command="{Binding RestartEmulation}" />
|
||||
<KeyBinding Gesture="Ctrl+Shift+R" Command="{Binding ReloadRenderDocApi}" />
|
||||
<KeyBinding Gesture="Ctrl+Shift+C" Command="{Binding ToggleCapture}" />
|
||||
<KeyBinding Gesture="Ctrl+S" Command="{Binding ToggleSkylanderCommand}" />
|
||||
<KeyBinding Gesture="Ctrl+M" Command="{Binding SimulateWakeUpMessage}" />
|
||||
<KeyBinding Gesture="Cmd+O" Command="{Binding LoadApplicationFromFile}" />
|
||||
<KeyBinding Gesture="Ctrl+O" Command="{Binding LoadApplicationFromFile}" />
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
Command="{Binding OkButton}" />
|
||||
<Button
|
||||
HotKey="Escape"
|
||||
Content="{ext:Locale SettingsButtonCancel}"
|
||||
Content="{ext:Locale Common_Buttons_Cancel}"
|
||||
Command="{Binding CancelButton}" />
|
||||
<Button
|
||||
Content="{ext:Locale SettingsButtonApply}"
|
||||
|
||||
Reference in New Issue
Block a user