Compare commits

..

18 Commits

Author SHA1 Message Date
62f8ceb60b Resolution scaling hotkeys (#3185)
* hotkeys

* comments

* update implementation to include custom scales

* copypasta

* review changes

* hotkeys

* comments

* update implementation to include custom scales

* copypasta

* review changes

* Remove outdated configuration and force hotkeys unbound

* Add avalonia support

* Fix configuration file

* Update GTK implementation and fix config... again.

* Remove legacy implementation + nits

* Avalonia locales (DeepL)

* review

* Remove colon from chinese locale

* Update ConfigFile

* locale fix
2022-07-24 15:44:47 -03:00
1a888ae087 Add support for conditional (with CC) shader Exit instructions (#3470)
* Add support for conditional (with CC) shader Exit instructions

* Shader cache version bump

* Make CSM conditions default to false for EXIT.CC
2022-07-24 15:33:30 -03:00
84d0ca5645 feat: add traditional chinese translate (Avalonia) (#3474)
* feat: add traditional chinese translate

* update translate
2022-07-24 15:18:21 -03:00
31b8d413d5 Change MenuHeaders to embedded textblocks (#3469) 2022-07-24 14:50:06 -03:00
6e02cac952 Avalonia - Use content dialog for user profile manager (#3455)
* remove content dialog placeholder from all windows

* remove redundant window argument

* redesign user profile window

* wip

* use avalonia auto name generator

* add edit and new user options

* move profile image selection to content dialog

* remove usings

* fix updater

* address review

* adjust avatar dialog size

* add validation for user editor

* fix typo

* Shorten some labels
2022-07-24 14:38:38 -03:00
3a3380fa25 fix: Ensure to load latest version of ffmpeg libraries first (#3473)
Fix a possible crash related to older version of ffmpeg being loaded
instewad of the one shipped with the emulator.
2022-07-24 11:39:56 +02:00
2d252db0a7 GTK & Avalonia changes (#3480) 2022-07-23 12:05:51 -03:00
7f8a3541eb Fix decoding of block after shader BRA.CC instructions without predicate (#3472)
* Fix decoding of block after BRA.CC instructions without predicate

* Shader cache version bump
2022-07-23 11:53:14 -03:00
b34de74f81 Avoid adding shader buffer descriptors for constant buffers that are not used (#3478)
* Avoid adding shader buffer descriptors for constant buffers that are not used

* Shader cache version
2022-07-23 11:15:58 -03:00
5811d121df Avoid scaling 2d textures that could be used as 3d (#3464) 2022-07-15 09:24:13 -03:00
6eb85e846f Reduce some unnecessary allocations in DMA handler (#2886)
* experimental changes to try and reduce allocations in kernel threading and DMA handler

* Simplify the changes in this branch to just 1. Don't make unnecessary copies of data just for texture-texture transfers and 2. Add a fast path for 1bpp linear byte copies

* forgot to check src + dst linearity in 1bpp DMA fast path. Fixes the UE4 regression.

* removing dev log I left in

* Generalizing the DMA linear fast path to cases other than 1bpp copies

* revert kernel changes

* revert whitespace

* remove unneeded references

* PR feedback

Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
Co-authored-by: gdk <gab.dark.100@gmail.com>
2022-07-14 15:45:56 -03:00
c5bddfeab8 Remove dependency for FFmpeg.AutoGen and Update FFmpeg to 5.0.1 for Windows (#3466)
* Remove dependency for FFMpeg.AutoGen

Also prepare for FFMpeg 5.0 and 5.1

* Update Ryujinx.Graphics.Nvdec.Dependencies to 5.0.1-build10

* Address gdkchan's comments

* Address Ack's comment

* Address gdkchan's comment
2022-07-14 15:13:23 +02:00
70ec5def9c BSD: Allow use of DontWait flag in Receive (#3462) 2022-07-14 11:47:25 +02:00
7853faa334 Ava/MainWindow: Do not show Show Console menu item on non-Windows (#3461) 2022-07-12 12:58:31 +00:00
b7fb474bfe Handle the case where byte optionValues are sent to BSD (#3405)
Some games and the Mario Odyssey Multiplayer mod do this.

The SMO multiplayer mod also needs you to revert #3394 as it uses a blocking socket to receive (otherwise it hangs), and it doesn't seem to like being forced as non-blocking.
2022-07-12 00:50:01 +02:00
2fa6413ed8 Avalonia - Add border to Flyouts (#3341)
* add borders to menus

* apply to dropdowns

* darken the border for dark theme

* fix duplicate keys
2022-07-12 00:44:35 +02:00
4523a73f75 Propagate Shader phi nodes with the same source value from all blocks (#3457)
* Propagate Shader phi nodes with the same source value from all incoming blocks

* Shader cache version bump
2022-07-12 00:36:58 +02:00
f4c47f3c9a Avalonia - Make tooltips more useful and descriptive, update Spanish localization (#3453)
* expand English tooltips and clean up

* small oversight

* update Spanish locale

* wording

* Internet

* address feedback

* update localization accordingly
2022-07-12 00:32:14 +02:00
93 changed files with 2546 additions and 1180 deletions

View File

@ -55,7 +55,6 @@ namespace Ryujinx.Ava
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var result = await ContentDialogHelper.CreateConfirmationDialog(
(desktop.MainWindow as MainWindow).SettingsWindow,
LocaleManager.Instance["DialogThemeRestartMessage"],
LocaleManager.Instance["DialogThemeRestartSubMessage"],
LocaleManager.Instance["InputDialogYes"],

View File

@ -53,6 +53,7 @@ namespace Ryujinx.Ava
internal class AppHost
{
private const int CursorHideIdleTime = 8; // Hide Cursor seconds
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
@ -417,10 +418,12 @@ namespace Ryujinx.Ava
{
if (userError == UserError.NoFirmware)
{
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"], firmwareVersion.VersionString);
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedMessage"],
firmwareVersion.VersionString);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_parent,
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance["DialogFirmwareNoFirmwareInstalledMessage"], message,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
if (result != UserResult.Yes)
{
@ -450,12 +453,12 @@ namespace Ryujinx.Ava
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallEmbeddedSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(_parent,
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message,
LocaleManager.Instance["InputDialogOk"],
"",
LocaleManager.Instance["RyujinxInfo"]);
await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogFirmwareInstalledMessage"], firmwareVersion.VersionString),
message,
LocaleManager.Instance["InputDialogOk"],
"",
LocaleManager.Instance["RyujinxInfo"]);
}
}
else
@ -879,7 +882,7 @@ namespace Ryujinx.Ava
}
_dialogShown = true;
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog(_parent);
shouldExit = await ContentDialogHelper.CreateStopEmulationDialog();
_dialogShown = false;
}
@ -896,7 +899,7 @@ namespace Ryujinx.Ava
{
Dispatcher.UIThread.Post(() =>
{
_parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
_parent.Cursor = _isMouseInRenderer ? InvisibleCursor : Cursor.Default;
});
}
else
@ -974,6 +977,13 @@ namespace Ryujinx.Ava
_parent.ViewModel.Volume = Device.GetVolume();
break;
case KeyboardHotkeyState.ResScaleUp:
GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1;
break;
case KeyboardHotkeyState.ResScaleDown:
GraphicsConfig.ResScale =
(MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1;
break;
case KeyboardHotkeyState.None:
(_keyboardInterface as AvaloniaKeyboard).Clear();
break;
@ -1031,6 +1041,14 @@ namespace Ryujinx.Ava
{
state = KeyboardHotkeyState.ToggleMute;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp))
{
state = KeyboardHotkeyState.ResScaleUp;
}
else if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown))
{
state = KeyboardHotkeyState.ResScaleDown;
}
return state;
}

View File

@ -556,5 +556,7 @@
"SettingsSelectThemeFileDialogTitle" : "Wähle ein benutzerdefiniertes Thema",
"SettingsXamlThemeFile" : "Xaml Thema-Datei",
"SettingsTabGraphicsBackend" : "Grafik-Backend",
"GraphicsBackendTooltip" : "Ändert das Grafik-Backend"
"GraphicsBackendTooltip" : "Ändert das Grafik-Backend",
"SettingsTabHotkeysResScaleUpHotkey": "Auflösung erhöhen:",
"SettingsTabHotkeysResScaleDownHotkey": "Auflösung vermindern:"
}

View File

@ -109,21 +109,21 @@
"SettingsTabSystemSystemLanguageTraditionalChinese": "Traditional Chinese",
"SettingsTabSystemSystemTimeZone": "System TimeZone:",
"SettingsTabSystemSystemTime": "System Time:",
"SettingsTabSystemEnableVsync": "Enable VSync",
"SettingsTabSystemEnablePptc": "Enable PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableFsIntegrityChecks": "Enable FS Integrity Checks",
"SettingsTabSystemEnableVsync": "VSync",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableFsIntegrityChecks": "FS Integrity Checks",
"SettingsTabSystemAudioBackend": "Audio Backend:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " - These many cause instabilities",
"SettingsTabSystemExpandDramSize": "Expand DRAM size to 6GB",
"SettingsTabSystemHacksNote": " (may cause instability)",
"SettingsTabSystemExpandDramSize": "Expand DRAM Size to 6GB",
"SettingsTabSystemIgnoreMissingServices": "Ignore Missing Services",
"SettingsTabGraphics": "Graphics",
"SettingsTabGraphicsEnhancements": "Enhancements",
"SettingsTabGraphicsEnableShaderCache": "Enable Shader Cache",
"SettingsTabGraphicsEnableShaderCache": "Shader Cache",
"SettingsTabGraphicsAnisotropicFiltering": "Anisotropic Filtering:",
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
@ -164,7 +164,7 @@
"SettingsTabLoggingOpenglLogLevelAll": "All",
"SettingsTabLoggingEnableDebugLogs": "Enable Debug Logs",
"SettingsTabInput": "Input",
"SettingsTabInputEnableDockedMode": "Enable Docked Mode",
"SettingsTabInputEnableDockedMode": "Docked Mode",
"SettingsTabInputDirectKeyboardAccess": "Direct Keyboard Access",
"SettingsButtonSave": "Save",
"SettingsButtonClose": "Close",
@ -256,8 +256,8 @@
"UserProfilesSaveProfileName": "Save Profile Name",
"UserProfilesChangeProfileImage": "Change Profile Image",
"UserProfilesAvailableUserProfiles": "Available User Profiles:",
"UserProfilesAddNewProfile": "Add New Profile",
"UserProfilesDeleteSelectedProfile": "Delete Selected Profile",
"UserProfilesAddNewProfile": "Create Profile",
"UserProfilesDeleteSelectedProfile": "Delete Selected",
"UserProfilesClose": "Close",
"ProfileImageSelectionTitle": "Profile Image Selection",
"ProfileImageSelectionHeader": "Choose a profile Image",
@ -418,52 +418,52 @@
"OrderDescending": "Descending",
"SettingsTabGraphicsFeatures": "Features",
"ErrorWindowTitle": "Error Window",
"ToggleDiscordTooltip": "Enables or disables Discord Rich Presence",
"ToggleDiscordTooltip": "Choose whether or not to display Ryujinx on your \"currently playing\" Discord activity",
"AddGameDirBoxTooltip": "Enter a game directory to add to the list",
"AddGameDirTooltip": "Add a game directory to the list",
"RemoveGameDirTooltip": "Remove selected game directory",
"CustomThemeCheckTooltip": "Enable or disable custom themes in the GUI",
"CustomThemeCheckTooltip": "Use a custom Avalonia theme for the GUI to change the appearance of the emulator menus",
"CustomThemePathTooltip": "Path to custom GUI theme",
"CustomThemeBrowseTooltip": "Browse for a custom GUI theme",
"DockModeToggleTooltip": "Enable or disable Docked Mode",
"DirectKeyboardTooltip": "Enable or disable \"direct keyboard access (HID) support\" (Provides games access to your keyboard as a text entry device)",
"DirectMouseTooltip": "Enable or disable \"direct mouse access (HID) support\" (Provides games access to your mouse as a pointing device)",
"DockModeToggleTooltip": "Docked mode makes the emulated system behave as a docked Nintendo Switch. This improves graphical fidelity in most games. Conversely, disabling this will make the emulated system behave as a handheld Nintendo Switch, reducing graphics quality.\n\nConfigure player 1 controls if planning to use docked mode; configure handheld controls if planning to use handheld mode.\n\nLeave ON if unsure.",
"DirectKeyboardTooltip": "Direct keyboard access (HID) support. Provides games access to your keyboard as a text entry device.",
"DirectMouseTooltip": "Direct mouse access (HID) support. Provides games access to your mouse as a pointing device.",
"RegionTooltip": "Change System Region",
"LanguageTooltip": "Change System Language",
"TimezoneTooltip": "Change System TimeZone",
"TimeTooltip": "Change System Time",
"VSyncToggleTooltip": "Enables or disables Vertical Sync",
"PptcToggleTooltip": "Enables or disables PPTC",
"FsIntegrityToggleTooltip": "Enables integrity checks on Game content files",
"AudioBackendTooltip": "Change Audio Backend",
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.",
"VSyncToggleTooltip": "Emulated console's Vertical Sync. Essentially a frame-limiter for the majority of games; disabling it may cause games to run at higher speed or make loading screens take longer or get stuck.\n\nCan be toggled in-game with a hotkey of your preference. We recommend doing this if you plan on disabling it.\n\nLeave ON if unsure.",
"PptcToggleTooltip": "Saves translated JIT functions so that they do not need to be translated every time the game loads.\n\nReduces stuttering and significantly speeds up boot times after the first boot of a game.\n\nLeave ON if unsure.",
"FsIntegrityToggleTooltip": "Checks for corrupt files when booting a game, and if corrupt files are detected, displays a hash error in the log.\n\nHas no impact on performance and is meant to help troubleshooting.\n\nLeave ON if unsure.",
"AudioBackendTooltip": "Changes the backend used to render audio.\n\nSDL2 is the preferred one, while OpenAL and SoundIO are used as fallbacks. Dummy will have no sound.\n\nSet to SDL2 if unsure.",
"MemoryManagerTooltip": "Change how guest memory is mapped and accessed. Greatly affects emulated CPU performance.\n\nSet to HOST UNCHECKED if unsure.",
"MemoryManagerSoftwareTooltip": "Use a software page table for address translation. Highest accuracy but slowest performance.",
"MemoryManagerHostTooltip": "Directly map memory in the host address space. Much faster JIT compilation and execution.",
"MemoryManagerUnsafeTooltip": "Directly map memory, but do not mask the address within the guest address space before access. Faster, but at the cost of safety. The guest application can access memory from anywhere in Ryujinx, so only run programs you trust with this mode.",
"DRamTooltip": "Expands the amount of memory on the emulated system from 4GB to 6GB",
"IgnoreMissingServicesTooltip": "Enable or disable ignoring missing services option",
"GraphicsBackendThreadingTooltip": "Enable Graphics Backend Multithreading",
"GalThreadingTooltip": "Executes graphics backend commands on a second thread. Allows runtime multithreading of shader compilation, reduces stuttering, and improves performance on drivers without multithreading support of their own. Slightly varying peak performance on drivers with multithreading. Ryujinx may need to be restarted to correctly disable driver built-in multithreading, or you may need to do it manually to get the best performance.",
"ShaderCacheToggleTooltip": "Enables or disables Shader Cache",
"DRamTooltip": "Increases the amount of memory on the emulated system from 4GB to 6GB.\n\nThis is only useful for higher-resolution texture packs or 4k resolution mods. Does NOT improve performance.\n\nLeave OFF if unsure.",
"IgnoreMissingServicesTooltip": "Ignores unimplemented Horizon OS services. This may help in bypassing crashes when booting certain games.\n\nLeave OFF if unsure.",
"GraphicsBackendThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"GalThreadingTooltip": "Executes graphics backend commands on a second thread.\n\nSpeeds up shader compilation, reduces stuttering, and improves performance on GPU drivers without multithreading support of their own. Slightly better performance on drivers with multithreading.\n\nSet to AUTO if unsure.",
"ShaderCacheToggleTooltip": "Saves a disk shader cache which reduces stuttering in subsequent runs.\n\nLeave ON if unsure.",
"ResolutionScaleTooltip": "Resolution Scale applied to applicable render targets",
"ResolutionScaleEntryTooltip": "Floating point resolution scale, such as 1.5. Non-integral scales are more likely to cause issues or crash.",
"AnisotropyTooltip": "Level of Anisotropic Filtering (set to Auto to use the value requested by the game)",
"AspectRatioTooltip": "Aspect Ratio applied to the renderer window.",
"ShaderDumpPathTooltip": "Graphics Shaders Dump Path",
"FileLogTooltip": "Enables or disables logging to a file on disk",
"StubLogTooltip": "Enables printing stub log messages",
"InfoLogTooltip": "Enables printing info log messages",
"WarnLogTooltip": "Enables printing warning log messages",
"ErrorLogTooltip": "Enables printing error log messages",
"TraceLogTooltip": "Enables printing trace log messages",
"GuestLogTooltip": "Enables printing guest log messages",
"FileAccessLogTooltip": "Enables printing file access log messages",
"FileLogTooltip": "Saves console logging to a log file on disk. Does not affect performance.",
"StubLogTooltip": "Prints stub log messages in the console. Does not affect performance.",
"InfoLogTooltip": "Prints info log messages in the console. Does not affect performance.",
"WarnLogTooltip": "Prints warning log messages in the console. Does not affect performance.",
"ErrorLogTooltip": "Prints error log messages in the console. Does not affect performance.",
"TraceLogTooltip": "Prints trace log messages in the console. Does not affect performance.",
"GuestLogTooltip": "Prints guest log messages in the console. Does not affect performance.",
"FileAccessLogTooltip": "Prints file access log messages in the console.",
"FSAccessLogModeTooltip": "Enables FS access log output to the console. Possible modes are 0-3",
"DeveloperOptionTooltip": "Use with care",
"OpenGlLogLevel": "Requires appropriate log levels enabled",
"DebugLogTooltip": "Enables printing debug log messages",
"LoadApplicationFileTooltip": "Open a file chooser to choose a Switch compatible file to load",
"LoadApplicationFolderTooltip": "Open a file chooser to choose a Switch compatible, unpacked application to load",
"DebugLogTooltip": "Prints debug log messages in the console.\n\nOnly use this if specifically instructed by a staff member, as it will make logs difficult to read and worsen emulator performance.",
"LoadApplicationFileTooltip": "Open a file explorer to choose a Switch compatible file to load",
"LoadApplicationFolderTooltip": "Open a file explorer to choose a Switch compatible, unpacked application to load",
"OpenRyujinxFolderTooltip": "Open Ryujinx filesystem folder",
"OpenRyujinxLogsTooltip": "Opens the folder where logs are written to",
"ExitTooltip": "Exit Ryujinx",
@ -478,8 +478,8 @@
"AboutRyujinxContributorsButtonHeader": "See All Contributors",
"SettingsTabSystemAudioVolume": "Volume: ",
"AudioVolumeTooltip": "Change Audio Volume",
"SettingsTabSystemEnableInternetAccess": "Enable Guest Internet Access",
"EnableInternetAccessTooltip": "Enables guest Internet access. If enabled, the application will behave as if the emulated Switch console was connected to the Internet. Note that in some cases, applications may still access the Internet even with this option disabled",
"SettingsTabSystemEnableInternetAccess": "Guest Internet Access/LAN Mode",
"EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.",
"GameListContextMenuManageCheatToolTip": "Manage Cheats",
"GameListContextMenuManageCheat": "Manage Cheats",
"ControllerSettingsStickRange": "Range:",
@ -494,10 +494,10 @@
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
"UpdaterDisabledWarningTitle": "Updater Disabled!",
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
"ControllerSettingsRotate90": "Rotate 90° Clockwise",
"IconSize": "Icon Size",
"IconSizeTooltip": "Change the size of game icon",
"IconSizeTooltip": "Change the size of game icons",
"MenuBarOptionsShowConsole": "Show Console",
"ShaderCachePurgeError": "Error purging shader cache at {0}: {1}",
"UserErrorNoKeys": "Keys not found",
@ -568,5 +568,14 @@
"UpdateWindowTitle": "Manage Game Updates",
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]",
"GameUpdateWindowHeading": "DLC Available for {0} [{1}]"
"UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel",
"Save": "Save",
"Discard": "Discard",
"UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:"
}

View File

@ -1,4 +1,4 @@
{
{
"MenuBarFileOpenApplet": "Abrir applet",
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "Abre el editor de Mii en modo autónomo",
"SettingsTabInputDirectMouseAccess": "Acceso directo al ratón",
@ -109,9 +109,9 @@
"SettingsTabSystemSystemLanguageTraditionalChinese": "Chino tradicional",
"SettingsTabSystemSystemTimeZone": "Zona horaria del sistema:",
"SettingsTabSystemSystemTime": "Hora del sistema:",
"SettingsTabSystemEnableVsync": "Habilitar sincronización vertical",
"SettingsTabSystemEnablePptc": "Habilitar PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableFsIntegrityChecks": "Habilitar comprobaciones de integridad FS",
"SettingsTabSystemEnableVsync": "Sincronización vertical",
"SettingsTabSystemEnablePptc": "PPTC (Profiled Persistent Translation Cache)",
"SettingsTabSystemEnableFsIntegrityChecks": "Comprobar integridad de los archivos",
"SettingsTabSystemAudioBackend": "Motor de audio:",
"SettingsTabSystemAudioBackendDummy": "Dummy",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
@ -120,10 +120,10 @@
"SettingsTabSystemHacks": "Hacks",
"SettingsTabSystemHacksNote": " - Pueden causar inestabilidad",
"SettingsTabSystemExpandDramSize": "Expandir DRAM a 6GB",
"SettingsTabSystemIgnoreMissingServices": "Ignorar falta de servicios",
"SettingsTabSystemIgnoreMissingServices": "Ignorar servicios no implementados",
"SettingsTabGraphics": "Gráficos",
"SettingsTabGraphicsEnhancements": "Mejoras",
"SettingsTabGraphicsEnableShaderCache": "Habilitar caché de sombreadores",
"SettingsTabGraphicsEnableShaderCache": "Caché de sombreadores",
"SettingsTabGraphicsAnisotropicFiltering": "Filtro anisotrópico:",
"SettingsTabGraphicsAnisotropicFilteringAuto": "Auto",
"SettingsTabGraphicsAnisotropicFiltering2x": "x2",
@ -152,6 +152,7 @@
"SettingsTabLoggingEnableInfoLogs": "Habilitar registros de Info",
"SettingsTabLoggingEnableWarningLogs": "Habilitar registros de Advertencia",
"SettingsTabLoggingEnableErrorLogs": "Habilitar registros de Error",
"SettingsTabLoggingEnableTraceLogs": "Habilitar registros de Rastro",
"SettingsTabLoggingEnableGuestLogs": "Habilitar registros de Guest",
"SettingsTabLoggingEnableFsAccessLogs": "Habilitar registros de Fs Access",
"SettingsTabLoggingFsGlobalAccessLogMode": "Modo de registros Fs Global Access:",
@ -163,7 +164,7 @@
"SettingsTabLoggingOpenglLogLevelAll": "Todo",
"SettingsTabLoggingEnableDebugLogs": "Habilitar registros de debug",
"SettingsTabInput": "Entrada",
"SettingsTabInputEnableDockedMode": "Habilitar modo acoplado (dock/TV)",
"SettingsTabInputEnableDockedMode": "Modo dock/TV",
"SettingsTabInputDirectKeyboardAccess": "Acceso directo al teclado",
"SettingsButtonSave": "Guardar",
"SettingsButtonClose": "Cerrar",
@ -312,8 +313,8 @@
"DialogUpdaterConvertFailedMessage": "No se pudo convertir la versión actual de Ryujinx.",
"DialogUpdaterCancelUpdateMessage": "¡Cancelando actualización!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "¡Ya tienes la versión más reciente de Ryujinx!",
"DialogUpdaterFailedToGetVersionMessage": "Ha ocurrido un error al intentar obtener información de versión desde AppVeyor.",
"DialogUpdaterConvertFailedAppveyorMessage": "No se pudo convertir la versión de Ryujinx recibida de AppVeyor.",
"DialogUpdaterFailedToGetVersionMessage": "Ha ocurrido un error al intentar obtener información de versión desde GitHub Release. Esto puede ocurrir cuando una nueva versión está siendo compilada por GitHub Actions. Inténtalo de nuevo en unos minutos.",
"DialogUpdaterConvertFailedGithubMessage": "No se pudo convertir la versión de Ryujinx recibida de GitHub Release.",
"DialogUpdaterDownloadingMessage": "Descargando actualización...",
"DialogUpdaterExtractionMessage": "Extrayendo actualización...",
"DialogUpdaterRenamingMessage": "Renombrando actualización...",
@ -417,49 +418,50 @@
"OrderDescending": "Descendente",
"SettingsTabGraphicsFeatures": "Funcionalidades",
"ErrorWindowTitle": "Ventana de error",
"ToggleDiscordTooltip": "Activa o desactiva la presencia enriquecida de Discord",
"ToggleDiscordTooltip": "Elige si muestras Ryujinx o no en tu actividad de Discord cuando lo estés usando",
"AddGameDirBoxTooltip": "Elige un directorio de juegos para mostrar en la ventana principal",
"AddGameDirTooltip": "Agrega un directorio de juegos a la lista",
"RemoveGameDirTooltip": "Quita el directorio seleccionado de la lista",
"CustomThemeCheckTooltip": "Activa o desactiva los temas personalizados para la interfaz",
"CustomThemePathTooltip": "Carpeta que contiene los temas personalizados para la interfaz",
"CustomThemeBrowseTooltip": "Busca un tema personalizado para la interfaz",
"DockModeToggleTooltip": "Activa o desactiva el modo TV de la Nintendo Switch",
"DockModeToggleTooltip": "El modo dock o modo TV hace que la consola emulada se comporte como una Nintendo Switch en su dock. Esto mejora la calidad gráfica en la mayoría de los juegos. Del mismo modo, si lo desactivas, el sistema emulado se comportará como una Nintendo Switch en modo portátil, reduciendo la cálidad de los gráficos.\n\nConfigura los controles de \"Jugador\" 1 si planeas jugar en modo dock/TV; configura los controles de \"Portátil\" si planeas jugar en modo portátil.\n\nActívalo si no sabes qué hacer.",
"DirectKeyboardTooltip": "Activa o desactiva \"soporte para acceso directo al teclado (HID)\" (Permite a los juegos utilizar tu teclado como entrada de texto)",
"DirectMouseTooltip": "Activa o desactiva \"soporte para acceso directo al ratón (HID)\" (Permite a los juegos utilizar tu ratón como cursor)",
"RegionTooltip": "Cambia la región del sistema",
"LanguageTooltip": "Cambia el idioma del sistema",
"TimezoneTooltip": "Cambia la zona horaria del sistema",
"TimeTooltip": "Cambia la hora del sistema",
"VSyncToggleTooltip": "Activa o desactiva la sincronización vertical",
"PptcToggleTooltip": "Activa o desactiva la caché de PPTC",
"FsIntegrityToggleTooltip": "Activa comprobaciones de integridad de los archivos de un juego",
"AudioBackendTooltip": "Cambia el motor de audio",
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.",
"VSyncToggleTooltip": "Sincronización vertical del sistema emulado. A efectos prácticos es un límite de fotogramas para la mayoría de juegos; deshabilitarlo puede hacer que los juegos se aceleren o que las pantallas de carga tarden más o se atasquen.\n\nPuedes activar y desactivar esto mientras el emulador está funcionando con un atajo de teclado a tu elección. Recomendamos hacer esto en vez de deshabilitarlo.\n\nActívalo si no sabes qué hacer.",
"PptcToggleTooltip": "Guarda funciones de JIT traducidas para que no sea necesario traducirlas cada vez que el juego carga.\n\nReduce los tirones y acelera significativamente el tiempo de inicio de los juegos después de haberlos ejecutado al menos una vez.\n\nActívalo si no sabes qué hacer.",
"FsIntegrityToggleTooltip": "Comprueba si hay archivos corruptos en los juegos que ejecutes al abrirlos, y si detecta archivos corruptos, muestra un error de Hash en los registros.\n\nEsto no tiene impacto alguno en el rendimiento y está pensado para ayudar a resolver problemas.\n\nActívalo si no sabes qué hacer.",
"AudioBackendTooltip": "Cambia el motor usado para renderizar audio.\n\nSDL2 es el preferido, mientras que OpenAL y SoundIO se usan si hay problemas con este. Dummy no produce audio.\n\nSelecciona SDL2 si no sabes qué hacer.",
"MemoryManagerTooltip": "Cambia la forma de mapear y acceder a la memoria del guest. Afecta en gran medida al rendimiento de la CPU emulada.\n\nSelecciona \"Host sin verificación\" si no sabes qué hacer.",
"MemoryManagerSoftwareTooltip": "Usa una tabla de paginación de software para traducir direcciones. Ofrece la precisión más exacta pero el rendimiento más lento.",
"MemoryManagerHostTooltip": "Mapea la memoria directamente en la dirección de espacio del host. Compilación y ejecución JIT mucho más rápida.",
"MemoryManagerUnsafeTooltip": "Mapea la memoria directamente, pero no enmascara la dirección dentro del espacio de dirección del guest antes del acceso. El modo más rápido, pero a costa de seguridad. La aplicación guest puede acceder a la memoria desde cualquier parte en Ryujinx, así que ejecuta solo programas en los que confíes cuando uses este modo.",
"DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GB a 6GB. Utilizar solo con mods 4K.",
"IgnoreMissingServicesTooltip": "Activa o desactiva un hack para ignorar servicios no implementados",
"GraphicsBackendThreadingTooltip": "Activa el multihilado del motor gráfico",
"GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Permite multihilar la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos sin necesidad de que utilicen su propio multihilado. Rendimiento máximo ligeramente variable en controladores gráficos que soporten multihilado. Ryujinx puede necesitar reiniciarse para desactivar correctamente el multihilado de tu controlador de gráficos, o puede que tengas que desactivarlo manualmente para lograr el mejor rendimiento posible.",
"ShaderCacheToggleTooltip": "Activa o desactiva la caché de sombreadores",
"DRamTooltip": "Expande la memoria DRAM del sistema emulado de 4GB a 6GB.\n\nUtilizar solo con packs de texturas HD o mods de resolución 4K. NO mejora el rendimiento.\n\nDesactívalo si no sabes qué hacer.",
"IgnoreMissingServicesTooltip": "Hack para ignorar servicios no implementados del Horizon OS. Esto puede ayudar a sobrepasar crasheos cuando inicies ciertos juegos.\n\nDesactívalo si no sabes qué hacer.",
"GraphicsBackendThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
"GalThreadingTooltip": "Ejecuta los comandos del motor gráfico en un segundo hilo. Acelera la compilación de sombreadores, reduce los tirones, y mejora el rendimiento en controladores gráficos que no realicen su propio multihilado. Rendimiento máximo ligeramente superior en controladores gráficos que soporten multihilado.\n\nSelecciona \"Auto\" si no sabes qué hacer.",
"ShaderCacheToggleTooltip": "Guarda una caché de sombreadores en disco, la cual reduce los tirones a medida que vas jugando.\n\nActívalo si no sabes qué hacer.",
"ResolutionScaleTooltip": "Escala de resolución aplicada a objetivos aplicables en el renderizado",
"ResolutionScaleEntryTooltip": "Escalado de resolución de coma flotante, como por ejemplo 1,5. Los valores no íntegros pueden causar errores gráficos o crashes.",
"AnisotropyTooltip": "Nivel de filtrado anisotrópico (selecciona Auto para utilizar el valor solicitado por el juego)",
"AspectRatioTooltip": "Relación de aspecto aplicada a la ventana de renderizado.",
"ShaderDumpPathTooltip": "Directorio en el cual se volcarán los sombreadores de los gráficos",
"FileLogTooltip": "Activa o desactiva el guardado de registros en archivos en disco",
"StubLogTooltip": "Activa mensajes de Stub en la consola",
"InfoLogTooltip": "Activa mensajes de Info en la consola",
"WarnLogTooltip": "Activa mensajes de Advertencia en la consola",
"ErrorLogTooltip": "Activa mensajes de Error en la consola",
"GuestLogTooltip": "Activa mensajes de Guest en la consola",
"FileLogTooltip": "Guarda los registros de la consola en archivos en disco. No afectan al rendimiento.",
"StubLogTooltip": "Escribe mensajes de Stub en la consola. No afectan al rendimiento.",
"InfoLogTooltip": "Escribe mensajes de Info en la consola. No afectan al rendimiento.",
"WarnLogTooltip": "Escribe mensajes de Advertencia en la consola. No afectan al rendimiento.",
"ErrorLogTooltip": "Escribe mensajes de Error en la consola. No afectan al rendimiento.",
"TraceLogTooltip": "Escribe mensajes de Rastro en la consola. No afectan al rendimiento.",
"GuestLogTooltip": "Escribe mensajes de Guest en la consola. No afectan al rendimiento.",
"FileAccessLogTooltip": "Activa mensajes de acceso a archivo en la consola",
"FSAccessLogModeTooltip": "Activa registros FS Access en la consola. Los modos posibles son entre 0 y 3",
"DeveloperOptionTooltip": "Usar con cuidado",
"OpenGlLogLevel": "Requiere activar los niveles de registro apropiados",
"DebugLogTooltip": "Activa mensajes de debug en la consola",
"DebugLogTooltip": "Escribe mensajes de debug en la consola\n\nActiva esto solo si un miembro del equipo te lo pide expresamente, pues hará que el registro sea difícil de leer y empeorará el rendimiento del emulador.",
"LoadApplicationFileTooltip": "Abre el explorador de archivos para elegir un archivo compatible con Switch para cargar",
"LoadApplicationFolderTooltip": "Abre el explorador de archivos para elegir un archivo desempaquetado y compatible con Switch para cargar",
"OpenRyujinxFolderTooltip": "Abre la carpeta de sistema de Ryujinx",
@ -473,22 +475,100 @@
"GridSize": "Tamaño de cuadrícula",
"GridSizeTooltip": "Cambia el tamaño de los objetos en la cuadrícula",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "Portugués brasileño",
"AboutRyujinxContributorsButtonHeader" : "Ver todos los contribuidores",
"AboutRyujinxContributorsButtonHeader": "Ver todos los contribuidores",
"SettingsTabSystemAudioVolume": "Volumen: ",
"AudioVolumeTooltip": "Ajusta el nivel de volumen",
"SettingsTabSystemEnableInternetAccess": "Habilitar acceso a Internet del guest",
"EnableInternetAccessTooltip": "Activa el acceso a Internet del guest. Cuando esté activo, la aplicación actuará como si la Nintendo Switch emulada estuviese conectada a Internet. Ten en cuenta que algunas aplicaciones pueden intentar acceder a Internet incluso con esta opción desactivada.",
"GameListContextMenuManageCheatToolTip" : "Activa o desactiva los cheats",
"GameListContextMenuManageCheat" : "Administrar cheats",
"ControllerSettingsStickRange" : "Alcance:",
"DialogStopEmulationTitle" : "Ryujinx - Detener emulación",
"SettingsTabSystemEnableInternetAccess": "Conectar guest a Internet/Modo LAN",
"EnableInternetAccessTooltip": "Permite a la aplicación emulada conectarse a Internet.\n\nLos juegos que tengan modo LAN podrán conectarse entre sí habilitando esta opción y estando conectados al mismo módem. Asimismo, esto permite conexiones con consolas reales.\n\nNO permite conectar con los servidores de Nintendo Online. Puede causar que ciertos juegos crasheen al intentar conectarse a sus servidores.\n\nDesactívalo si no estás seguro.",
"GameListContextMenuManageCheatToolTip": "Activa o desactiva los cheats",
"GameListContextMenuManageCheat": "Administrar cheats",
"ControllerSettingsStickRange": "Alcance:",
"DialogStopEmulationTitle": "Ryujinx - Detener emulación",
"DialogStopEmulationMessage": "¿Seguro que quieres detener la emulación actual?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "Audio",
"SettingsTabNetwork": "Red",
"SettingsTabNetworkConnection" : "Conexión de red",
"SettingsTabCpuCache" : "Caché de CPU",
"SettingsTabCpuMemory" : "Memoria de CPU",
"ControllerMotionTitle": "Motion Control Settings",
"ControllerRumbleTitle": "Rumble Settings"
"SettingsTabNetworkConnection": "Conexión de red",
"SettingsTabCpuCache": "Caché de CPU",
"SettingsTabCpuMemory": "Memoria de CPU",
"DialogUpdaterFlatpakNotSupportedMessage": "Por favor, actualiza Ryujinx a través de FlatHub.",
"UpdaterDisabledWarningTitle": "¡Actualizador deshabilitado!",
"GameListContextMenuOpenSdModsDirectory": "Abrir carpeta de mods Atmosphere",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Abre la carpeta alternativa de mods en la que puedes insertar mods de Atmosphere. Útil para mods que vengan organizados para uso en consola.",
"ControllerSettingsRotate90": "Rotar 90° en el sentido de las agujas del reloj",
"IconSize": "Tamaño de iconos",
"IconSizeTooltip": "Cambia el tamaño de los iconos de juegos",
"MenuBarOptionsShowConsole": "Mostrar consola",
"ShaderCachePurgeError": "Error al eliminar la caché en {0}: {1}",
"UserErrorNoKeys": "No se encontraron keys",
"UserErrorNoFirmware": "No se encontró firmware",
"UserErrorFirmwareParsingFailed": "Error al analizar el firmware",
"UserErrorApplicationNotFound": "No se encontró la aplicación",
"UserErrorUnknown": "Error desconocido",
"UserErrorUndefined": "Error indefinido",
"UserErrorNoKeysDescription": "Ryujinx no pudo encontrar tus 'prod.keys'.",
"UserErrorNoFirmwareDescription": "Ryujinx no pudo encontrar un firmware instalado.",
"UserErrorFirmwareParsingFailedDescription": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.",
"UserErrorApplicationNotFoundDescription": "Ryujinx no pudo encontrar una aplicación válida en ese camino.",
"UserErrorUnknownDescription": "¡Ocurrió un error desconocido!",
"UserErrorUndefinedDescription": "¡Ocurrió un error indefinido! Esto no debería pasar, por favor, ¡contacta con un desarrollador!",
"OpenSetupGuideMessage": "Abrir la guía de instalación",
"NoUpdate": "No actualizado",
"TitleUpdateVersionLabel": "Versión {0} - {1}",
"RyujinxInfo": "Ryujinx - Info",
"RyujinxConfirm": "Ryujinx - Confirmación",
"FileDialogAllTypes": "Todos los tipos",
"Never": "Nunca",
"SwkbdMinCharacters": "Debe tener al menos {0} caracteres",
"SwkbdMinRangeCharacters": "Debe tener {0}-{1} caracteres",
"SoftwareKeyboard": "Teclado de software",
"DialogControllerAppletMessagePlayerRange": "La aplicación require {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.",
"DialogControllerAppletMessage": "La aplicación require exactamente {0} jugador(es) con:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Por favor abre las opciones y reconfigura los dispositivos de entrada o presiona 'Cerrar'.",
"DialogControllerAppletDockModeSet": "Modo dock/TV activo. El control portátil también es inválido.\n\n",
"UpdaterRenaming": "Renombrando archivos viejos...",
"UpdaterRenameFailed": "El actualizador no pudo renombrar el archivo: {0}",
"UpdaterAddingFiles": "Añadiendo nuevos archivos...",
"UpdaterExtracting": "Extrayendo actualización...",
"UpdaterDownloading": "Descargando actualización...",
"Game": "Juego",
"Docked": "Dock/TV",
"Handheld": "Portátil",
"ConnectionError": "Error de conexión.",
"AboutPageDeveloperListMore": "{0} y más...",
"ApiError": "Error de API.",
"LoadingHeading": "Cargando {0}",
"CompilingPPTC": "Compilando PTC",
"CompilingShaders": "Compilando sombreadores",
"AllKeyboards": "Todos los teclados",
"OpenFileDialogTitle": "Selecciona un archivo soportado para cargar",
"OpenFolderDialogTitle": "Selecciona una carpeta con un juego desempaquetado",
"AllSupportedFormats": "Todos los formatos soportados",
"RyujinxUpdater": "Actualizador de Ryujinx",
"SettingsTabHotkeys": "Atajos de teclado",
"SettingsTabHotkeysHotkeys": "Atajos de teclado",
"SettingsTabHotkeysToggleVsyncHotkey": "Alternar la sincronización vertical:",
"SettingsTabHotkeysScreenshotHotkey": "Captura de pantalla:",
"SettingsTabHotkeysShowUiHotkey": "Mostrar interfaz:",
"SettingsTabHotkeysPauseHotkey": "Pausar:",
"SettingsTabHotkeysToggleMuteHotkey": "Silenciar:",
"ControllerMotionTitle": "Opciones de controles de movimiento",
"ControllerRumbleTitle": "Opciones de vibración",
"SettingsSelectThemeFileDialogTitle": "Selecciona un archivo de tema",
"SettingsXamlThemeFile": "Archivo de tema Xaml",
"AvatarWindowTitle": "Administrar cuentas - Avatar",
"Amiibo": "Amiibo",
"Unknown": "Desconocido",
"Usage": "Usage",
"Writable": "Escribible",
"SelectDlcDialogTitle": "Selecciona archivo(s) de DLC",
"SelectUpdateDialogTitle": "Selecciona archivo(s) de actualización",
"UserProfileWindowTitle": "Administrar perfiles de usuario",
"CheatWindowTitle": "Administrar cheats",
"DlcWindowTitle": "Administrar contenido descargable",
"UpdateWindowTitle": "Administrar actualizaciones",
"CheatWindowHeading": "Cheats disponibles para {0} [{1}]",
"DlcWindowHeading": "Contenido descargable disponible para {0} [{1}]",
"GameUpdateWindowHeading": "Actualizaciones disponibles para {0} [{1}]",
"SettingsTabHotkeysResScaleUpHotkey": "Aumentar la resolución:",
"SettingsTabHotkeysResScaleDownHotkey": "Disminuir la resolución:"
}

View File

@ -554,5 +554,7 @@
"ControllerMotionTitle": "Impostazioni dei sensori di movimento",
"ControllerRumbleTitle": "Impostazioni di vibrazione",
"SettingsSelectThemeFileDialogTitle" : "Seleziona file del tema",
"SettingsXamlThemeFile" : "File del tema xaml"
"SettingsXamlThemeFile" : "File del tema xaml",
"SettingsTabHotkeysResScaleUpHotkey": "Aumentare la risoluzione:",
"SettingsTabHotkeysResScaleDownHotkey": "Diminuire la risoluzione:"
}

View File

@ -554,5 +554,7 @@
"ControllerMotionTitle": "Configurações do controle de movimento",
"ControllerRumbleTitle": "Configurações de vibração",
"SettingsSelectThemeFileDialogTitle" : "Selecionar arquivo do tema",
"SettingsXamlThemeFile" : "Arquivo de tema Xaml"
"SettingsXamlThemeFile" : "Arquivo de tema Xaml",
"SettingsTabHotkeysResScaleUpHotkey": "Aumentar a resolução:",
"SettingsTabHotkeysResScaleDownHotkey": "Diminuir a resolução:"
}

View File

@ -554,5 +554,7 @@
"ControllerMotionTitle": "Hareket Kontrol Seçenekleri",
"ControllerRumbleTitle": "Titreşim Seçenekleri",
"SettingsSelectThemeFileDialogTitle" : "Tema Dosyası Seçin",
"SettingsXamlThemeFile" : "Xaml Tema Dosyası"
"SettingsXamlThemeFile" : "Xaml Tema Dosyası",
"SettingsTabHotkeysResScaleUpHotkey": "Çözünürlüğü artırın:",
"SettingsTabHotkeysResScaleDownHotkey": "Çözünürlüğü azaltın:"
}

View File

@ -568,5 +568,7 @@
"UpdateWindowTitle": "管理游戏更新",
"CheatWindowHeading": "适用于 {0} [{1}] 的金手指",
"DlcWindowHeading": "适用于 {0} [{1}] 的 DLC",
"GameUpdateWindowHeading": "适用于 {0} [{1}] 的更新"
"GameUpdateWindowHeading": "适用于 {0} [{1}] 的更新",
"SettingsTabHotkeysResScaleUpHotkey": "分辨率提高",
"SettingsTabHotkeysResScaleDownHotkey": "降低分辨率"
}

View File

@ -0,0 +1,572 @@
{
"MenuBarFileOpenApplet": "打開小程式",
"MenuBarFileOpenAppletOpenMiiAppletToolTip": "打開獨立的 Mii 小程式",
"SettingsTabInputDirectMouseAccess": "直通滑鼠操作",
"SettingsTabSystemMemoryManagerMode": "記憶體管理模式:",
"SettingsTabSystemMemoryManagerModeSoftware": "軟體",
"SettingsTabSystemMemoryManagerModeHost": "Host (快速)",
"SettingsTabSystemMemoryManagerModeHostUnchecked": "Host 略過檢查 (最快,但較不安全)",
"MenuBarFile": "_檔案",
"MenuBarFileOpenFromFile": "_載入檔案",
"MenuBarFileOpenUnpacked": "載入_解包後的遊戲",
"MenuBarFileOpenEmuFolder": "開啟 Ryujinx 資料夾",
"MenuBarFileOpenLogsFolder": "開啟日誌資料夾",
"MenuBarFileExit": "_退出",
"MenuBarOptions": "選項",
"MenuBarOptionsToggleFullscreen": "切換全螢幕模式",
"MenuBarOptionsStartGamesInFullscreen": "使用全螢幕模式啟動遊戲",
"MenuBarOptionsStopEmulation": "停止模擬",
"MenuBarOptionsSettings": "_設定",
"MenuBarOptionsManageUserProfiles": "_管理使用者帳號",
"MenuBarActions": "_動作",
"MenuBarOptionsSimulateWakeUpMessage": "模擬喚醒訊息",
"MenuBarActionsScanAmiibo": "掃描 Amiibo",
"MenuBarTools": "_工具",
"MenuBarToolsInstallFirmware": "安裝韌體",
"MenuBarFileToolsInstallFirmwareFromFile": "從 XCI 或 ZIP 安裝韌體",
"MenuBarFileToolsInstallFirmwareFromDirectory": "從資料夾安裝韌體",
"MenuBarHelp": "幫助",
"MenuBarHelpCheckForUpdates": "檢查更新",
"MenuBarHelpAbout": "關於",
"MenuSearch": "搜尋...",
"GameListHeaderFavorite": "收藏",
"GameListHeaderIcon": "圖示",
"GameListHeaderApplication": "名稱",
"GameListHeaderDeveloper": "開發商",
"GameListHeaderVersion": "版本",
"GameListHeaderTimePlayed": "遊玩時間",
"GameListHeaderLastPlayed": "上次遊玩",
"GameListHeaderFileExtension": "副檔名",
"GameListHeaderFileSize": "大小",
"GameListHeaderPath": "路徑",
"GameListContextMenuOpenUserSaveDirectory": "開啟使用者存檔資料夾",
"GameListContextMenuOpenUserSaveDirectoryToolTip": "開啟儲存遊戲存檔的資料夾",
"GameListContextMenuOpenUserDeviceDirectory": "開啟系統資料夾",
"GameListContextMenuOpenUserDeviceDirectoryToolTip": "開啟包含遊戲系統設定的資料夾",
"GameListContextMenuOpenUserBcatDirectory": "開啟 BCAT 資料夾",
"GameListContextMenuOpenUserBcatDirectoryToolTip": "開啟包含遊戲 BCAT 資料的資料夾",
"GameListContextMenuManageTitleUpdates": "管理遊戲更新",
"GameListContextMenuManageTitleUpdatesToolTip": "開啟更新管理視窗",
"GameListContextMenuManageDlc": "管理 DLC",
"GameListContextMenuManageDlcToolTip": "開啟 DLC 管理視窗",
"GameListContextMenuOpenModsDirectory": "開啟模組資料夾",
"GameListContextMenuOpenModsDirectoryToolTip": "開啟存放遊戲模組的資料夾",
"GameListContextMenuCacheManagement": "快取管理",
"GameListContextMenuCacheManagementPurgePptc": "清除 PPTC 快取",
"GameListContextMenuCacheManagementPurgePptcToolTip": "刪除遊戲的 PPTC 快取",
"GameListContextMenuCacheManagementPurgeShaderCache": "清除渲染器快取",
"GameListContextMenuCacheManagementPurgeShaderCacheToolTip": "刪除遊戲的渲染器快取",
"GameListContextMenuCacheManagementOpenPptcDirectory": "開啟 PPTC 資料夾",
"GameListContextMenuCacheManagementOpenPptcDirectoryToolTip": "開啟包含遊戲 PPTC 快取的資料夾",
"GameListContextMenuCacheManagementOpenShaderCacheDirectory": "開啟渲染器快取資料夾",
"GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip": "開啟包含應用程式渲染器快取的資料夾",
"GameListContextMenuExtractData": "提取資料",
"GameListContextMenuExtractDataExeFS": "ExeFS",
"GameListContextMenuExtractDataExeFSToolTip": "從遊戲的目前狀態中提取 ExeFS 分區(包含更新)",
"GameListContextMenuExtractDataRomFS": "RomFS",
"GameListContextMenuExtractDataRomFSToolTip": "從遊戲的目前狀態中提取 RomFS 分區(包含更新)",
"GameListContextMenuExtractDataLogo": "圖示",
"GameListContextMenuExtractDataLogoToolTip": "從遊戲的目前狀態中提取圖示(包含更新)",
"StatusBarGamesLoaded": "{0}/{1} 遊戲載入完成",
"StatusBarSystemVersion": "系統版本: {0}",
"Settings": "設定",
"SettingsTabGeneral": "使用者介面",
"SettingsTabGeneralGeneral": "一般",
"SettingsTabGeneralEnableDiscordRichPresence": "啟用 Discord 動態狀態展示",
"SettingsTabGeneralCheckUpdatesOnLaunch": "自動檢查更新",
"SettingsTabGeneralShowConfirmExitDialog": "顯示 \"確認離開\" 對話框",
"SettingsTabGeneralHideCursorOnIdle": "自動隱藏滑鼠",
"SettingsTabGeneralGameDirectories": "遊戲資料夾",
"SettingsTabGeneralAdd": "新增",
"SettingsTabGeneralRemove": "刪除",
"SettingsTabSystem": "系統",
"SettingsTabSystemCore": "核心",
"SettingsTabSystemSystemRegion": "系統區域:",
"SettingsTabSystemSystemRegionJapan": "日本",
"SettingsTabSystemSystemRegionUSA": "美國",
"SettingsTabSystemSystemRegionEurope": "歐洲",
"SettingsTabSystemSystemRegionAustralia": "澳洲",
"SettingsTabSystemSystemRegionChina": "中國",
"SettingsTabSystemSystemRegionKorea": "韓國",
"SettingsTabSystemSystemRegionTaiwan": "台灣",
"SettingsTabSystemSystemLanguage": "系統語言:",
"SettingsTabSystemSystemLanguageJapanese": "日語",
"SettingsTabSystemSystemLanguageAmericanEnglish": "美式英語",
"SettingsTabSystemSystemLanguageFrench": "法語",
"SettingsTabSystemSystemLanguageGerman": "德語",
"SettingsTabSystemSystemLanguageItalian": "義大利語",
"SettingsTabSystemSystemLanguageSpanish": "西班牙語",
"SettingsTabSystemSystemLanguageChinese": "中文 (中國)",
"SettingsTabSystemSystemLanguageKorean": "韓語",
"SettingsTabSystemSystemLanguageDutch": "荷蘭語",
"SettingsTabSystemSystemLanguagePortuguese": "葡萄牙語",
"SettingsTabSystemSystemLanguageRussian": "俄語",
"SettingsTabSystemSystemLanguageTaiwanese": "中文 (台灣)",
"SettingsTabSystemSystemLanguageBritishEnglish": "英式英語",
"SettingsTabSystemSystemLanguageCanadianFrench": "加拿大法語",
"SettingsTabSystemSystemLanguageLatinAmericanSpanish": "拉美西班牙語",
"SettingsTabSystemSystemLanguageSimplifiedChinese": "簡體中文 (推薦)",
"SettingsTabSystemSystemLanguageTraditionalChinese": "繁體中文 (推薦)",
"SettingsTabSystemSystemTimeZone": "系統時區:",
"SettingsTabSystemSystemTime": "系統時鐘:",
"SettingsTabSystemEnableVsync": "開啟 VSync",
"SettingsTabSystemEnablePptc": "開啟 PPTC 快取",
"SettingsTabSystemEnableFsIntegrityChecks": "開啟檔案系統完整性檢查",
"SettingsTabSystemAudioBackend": "音訊後端:",
"SettingsTabSystemAudioBackendDummy": "無",
"SettingsTabSystemAudioBackendOpenAL": "OpenAL",
"SettingsTabSystemAudioBackendSoundIO": "SoundIO",
"SettingsTabSystemAudioBackendSDL2": "SDL2",
"SettingsTabSystemHacks": "修正",
"SettingsTabSystemHacksNote": " - 會引起模擬器不穩定",
"SettingsTabSystemExpandDramSize": "將模擬記憶體大小擴充至 6GB",
"SettingsTabSystemIgnoreMissingServices": "忽略缺少的服務",
"SettingsTabGraphics": "圖形",
"SettingsTabGraphicsEnhancements": "增強",
"SettingsTabGraphicsEnableShaderCache": "啟用渲染器快取",
"SettingsTabGraphicsAnisotropicFiltering": "各向異性過濾:",
"SettingsTabGraphicsAnisotropicFilteringAuto": "自動",
"SettingsTabGraphicsAnisotropicFiltering2x": "2x",
"SettingsTabGraphicsAnisotropicFiltering4x": "4x",
"SettingsTabGraphicsAnisotropicFiltering8x": "8x",
"SettingsTabGraphicsAnisotropicFiltering16x": "16x",
"SettingsTabGraphicsResolutionScale": "解析度縮放:",
"SettingsTabGraphicsResolutionScaleCustom": "自訂 (不推薦)",
"SettingsTabGraphicsResolutionScaleNative": "原生 (720p/1080p)",
"SettingsTabGraphicsResolutionScale2x": "2x (1440p/2160p)",
"SettingsTabGraphicsResolutionScale3x": "3x (2160p/3240p)",
"SettingsTabGraphicsResolutionScale4x": "4x (2880p/4320p)",
"SettingsTabGraphicsAspectRatio": "寬高比:",
"SettingsTabGraphicsAspectRatio4x3": "4:3",
"SettingsTabGraphicsAspectRatio16x9": "16:9",
"SettingsTabGraphicsAspectRatio16x10": "16:10",
"SettingsTabGraphicsAspectRatio21x9": "21:9",
"SettingsTabGraphicsAspectRatio32x9": "32:9",
"SettingsTabGraphicsAspectRatioStretch": "拉伸至螢幕大小",
"SettingsTabGraphicsDeveloperOptions": "開發者選項",
"SettingsTabGraphicsShaderDumpPath": "圖形渲染器轉儲路徑:",
"SettingsTabLogging": "日誌",
"SettingsTabLoggingLogging": "日誌",
"SettingsTabLoggingEnableLoggingToFile": "儲存日誌為檔案",
"SettingsTabLoggingEnableStubLogs": "記錄 Stub",
"SettingsTabLoggingEnableInfoLogs": "記錄資訊",
"SettingsTabLoggingEnableWarningLogs": "記錄警告",
"SettingsTabLoggingEnableErrorLogs": "記錄錯誤",
"SettingsTabLoggingEnableTraceLogs": "記錄 Trace",
"SettingsTabLoggingEnableGuestLogs": "記錄 Guest",
"SettingsTabLoggingEnableFsAccessLogs": "記錄檔案存取",
"SettingsTabLoggingFsGlobalAccessLogMode": "記錄全域檔案存取模式:",
"SettingsTabLoggingDeveloperOptions": "開發者選項 (警告: 會降低效能)",
"SettingsTabLoggingOpenglLogLevel": "OpenGL 日誌級別:",
"SettingsTabLoggingOpenglLogLevelNone": "無",
"SettingsTabLoggingOpenglLogLevelError": "錯誤",
"SettingsTabLoggingOpenglLogLevelPerformance": "減速",
"SettingsTabLoggingOpenglLogLevelAll": "全部",
"SettingsTabLoggingEnableDebugLogs": "啟用除錯日誌",
"SettingsTabInput": "輸入",
"SettingsTabInputEnableDockedMode": "Docked 模式",
"SettingsTabInputDirectKeyboardAccess": "直通鍵盤控制",
"SettingsButtonSave": "儲存",
"SettingsButtonClose": "關閉",
"SettingsButtonApply": "套用",
"ControllerSettingsPlayer": "玩家",
"ControllerSettingsPlayer1": "玩家 1",
"ControllerSettingsPlayer2": "玩家 2",
"ControllerSettingsPlayer3": "玩家 3",
"ControllerSettingsPlayer4": "玩家 4",
"ControllerSettingsPlayer5": "玩家 5",
"ControllerSettingsPlayer6": "玩家 6",
"ControllerSettingsPlayer7": "玩家 7",
"ControllerSettingsPlayer8": "玩家 8",
"ControllerSettingsHandheld": "掌機模式",
"ControllerSettingsInputDevice": "輸入設備",
"ControllerSettingsRefresh": "更新",
"ControllerSettingsDeviceDisabled": "關閉",
"ControllerSettingsControllerType": "手把類型",
"ControllerSettingsControllerTypeHandheld": "掌機",
"ControllerSettingsControllerTypeProController": "Pro 手把",
"ControllerSettingsControllerTypeJoyConPair": "JoyCon",
"ControllerSettingsControllerTypeJoyConLeft": "左 JoyCon",
"ControllerSettingsControllerTypeJoyConRight": "右 JoyCon",
"ControllerSettingsProfile": "預設",
"ControllerSettingsProfileDefault": "預設",
"ControllerSettingsLoad": "載入",
"ControllerSettingsAdd": "建立",
"ControllerSettingsRemove": "刪除",
"ControllerSettingsButtons": "按鈕",
"ControllerSettingsButtonA": "A",
"ControllerSettingsButtonB": "B",
"ControllerSettingsButtonX": "X",
"ControllerSettingsButtonY": "Y",
"ControllerSettingsButtonPlus": "+",
"ControllerSettingsButtonMinus": "-",
"ControllerSettingsDPad": "方向鍵",
"ControllerSettingsDPadUp": "上",
"ControllerSettingsDPadDown": "下",
"ControllerSettingsDPadLeft": "左",
"ControllerSettingsDPadRight": "右",
"ControllerSettingsLStick": "左搖桿",
"ControllerSettingsLStickButton": "按下",
"ControllerSettingsLStickUp": "上",
"ControllerSettingsLStickDown": "下",
"ControllerSettingsLStickLeft": "左",
"ControllerSettingsLStickRight": "右",
"ControllerSettingsLStickStick": "桿",
"ControllerSettingsLStickInvertXAxis": "反轉 X 方向",
"ControllerSettingsLStickInvertYAxis": "反轉 Y 方向",
"ControllerSettingsLStickDeadzone": "死區:",
"ControllerSettingsRStick": "右搖桿",
"ControllerSettingsRStickButton": "按下",
"ControllerSettingsRStickUp": "上",
"ControllerSettingsRStickDown": "下",
"ControllerSettingsRStickLeft": "左",
"ControllerSettingsRStickRight": "右",
"ControllerSettingsRStickStick": "桿",
"ControllerSettingsRStickInvertXAxis": "反轉 X 方向",
"ControllerSettingsRStickInvertYAxis": "反轉 Y 方向",
"ControllerSettingsRStickDeadzone": "死區:",
"ControllerSettingsTriggersLeft": "左 Triggers",
"ControllerSettingsTriggersRight": "右 Triggers",
"ControllerSettingsTriggersButtonsLeft": "左 Triggers 鍵",
"ControllerSettingsTriggersButtonsRight": "右 Triggers 鍵",
"ControllerSettingsTriggers": "Triggers",
"ControllerSettingsTriggerL": "L",
"ControllerSettingsTriggerR": "R",
"ControllerSettingsTriggerZL": "ZL",
"ControllerSettingsTriggerZR": "ZR",
"ControllerSettingsLeftSL": "SL",
"ControllerSettingsLeftSR": "SR",
"ControllerSettingsRightSL": "SL",
"ControllerSettingsRightSR": "SR",
"ControllerSettingsExtraButtonsLeft": "左按鍵",
"ControllerSettingsExtraButtonsRight": "右按鍵",
"ControllerSettingsMisc": "其他",
"ControllerSettingsTriggerThreshold": "Triggers 閾值:",
"ControllerSettingsMotion": "體感",
"ControllerSettingsMotionUseCemuhookCompatibleMotion": "使用 CemuHook 體感協議",
"ControllerSettingsMotionControllerSlot": "手把:",
"ControllerSettingsMotionMirrorInput": "鏡像操作",
"ControllerSettingsMotionRightJoyConSlot": "右 JoyCon:",
"ControllerSettingsMotionServerHost": "伺服器 Host:",
"ControllerSettingsMotionGyroSensitivity": "陀螺儀敏感度:",
"ControllerSettingsMotionGyroDeadzone": "陀螺儀死區:",
"ControllerSettingsSave": "儲存",
"ControllerSettingsClose": "關閉",
"UserProfilesSelectedUserProfile": "選擇使用者帳號:",
"UserProfilesSaveProfileName": "儲存帳號名稱",
"UserProfilesChangeProfileImage": "更換頭像",
"UserProfilesAvailableUserProfiles": "現有的帳號:",
"UserProfilesAddNewProfile": "建立帳號",
"UserProfilesDeleteSelectedProfile": "刪除選擇的帳號",
"UserProfilesClose": "關閉",
"ProfileImageSelectionTitle": "頭像選擇",
"ProfileImageSelectionHeader": "選擇合適的頭像圖片",
"ProfileImageSelectionNote": "您可以導入自訂頭像,或從系統中選擇頭像",
"ProfileImageSelectionImportImage": "導入圖片檔案",
"ProfileImageSelectionSelectAvatar": "選擇系統頭像",
"InputDialogTitle": "輸入對話框",
"InputDialogOk": "完成",
"InputDialogCancel": "取消",
"InputDialogAddNewProfileTitle": "選擇使用者名稱",
"InputDialogAddNewProfileHeader": "請輸入帳號名稱",
"InputDialogAddNewProfileSubtext": "(最大長度: {0})",
"AvatarChoose": "選擇",
"AvatarSetBackgroundColor": "設定背景顏色",
"AvatarClose": "關閉",
"ControllerSettingsLoadProfileToolTip": "載入預設",
"ControllerSettingsAddProfileToolTip": "新增預設",
"ControllerSettingsRemoveProfileToolTip": "刪除預設",
"ControllerSettingsSaveProfileToolTip": "儲存預設",
"MenuBarFileToolsTakeScreenshot": "儲存截圖",
"MenuBarFileToolsHideUi": "隱藏 UI",
"GameListContextMenuToggleFavorite": "標記為收藏",
"GameListContextMenuToggleFavoriteToolTip": "啟用或取消收藏標記",
"SettingsTabGeneralTheme": "主題",
"SettingsTabGeneralThemeCustomTheme": "自定主題路徑",
"SettingsTabGeneralThemeBaseStyle": "主題樣式",
"SettingsTabGeneralThemeBaseStyleDark": "深色模式",
"SettingsTabGeneralThemeBaseStyleLight": "淺色模式",
"SettingsTabGeneralThemeEnableCustomTheme": "使用自訂主題介面",
"ButtonBrowse": "瀏覽",
"ControllerSettingsConfigureGeneral": "配置",
"ControllerSettingsRumble": "震動",
"ControllerSettingsRumbleStrongMultiplier": "強震動調節",
"ControllerSettingsRumbleWeakMultiplier": "弱震動調節",
"DialogMessageSaveNotAvailableMessage": "沒有{0} [{1:x16}]的遊戲存檔",
"DialogMessageSaveNotAvailableCreateSaveMessage": "是否建立該遊戲的存檔資料夾?",
"DialogConfirmationTitle": "Ryujinx - 設定",
"DialogUpdaterTitle": "Ryujinx - 更新",
"DialogErrorTitle": "Ryujinx - 錯誤",
"DialogWarningTitle": "Ryujinx - 警告",
"DialogExitTitle": "Ryujinx - 關閉",
"DialogErrorMessage": "Ryujinx 遇到了錯誤",
"DialogExitMessage": "是否關閉 Ryujinx",
"DialogExitSubMessage": "所有未儲存的進度會遺失!",
"DialogMessageCreateSaveErrorMessage": "建立特定的存檔時出錯: {0}",
"DialogMessageFindSaveErrorMessage": "查找特定的存檔時出錯: {0}",
"FolderDialogExtractTitle": "選擇要解壓到的資料夾",
"DialogNcaExtractionMessage": "提取{1}的{0}分區...",
"DialogNcaExtractionTitle": "Ryujinx - NCA分區提取",
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "提取失敗。所選檔案中不含主NCA檔案",
"DialogNcaExtractionCheckLogErrorMessage": "提取失敗。請查看日誌檔案取得詳情。",
"DialogNcaExtractionSuccessMessage": "提取成功。",
"DialogUpdaterConvertFailedMessage": "無法轉換目前 Ryujinx 版本。",
"DialogUpdaterCancelUpdateMessage": "更新取消!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "您使用的 Ryujinx 是最新版本。",
"DialogUpdaterFailedToGetVersionMessage": "嘗試從 Github 取得版本訊息時無效。可能是因為 GitHub Actions 正在編譯新版本。請過幾分鐘重試。",
"DialogUpdaterConvertFailedGithubMessage": "無法轉換從 Github 接收到的 Ryujinx 版本。",
"DialogUpdaterDownloadingMessage": "下載新版本中...",
"DialogUpdaterExtractionMessage": "正在提取更新...",
"DialogUpdaterRenamingMessage": "正在刪除舊檔案...",
"DialogUpdaterAddingFilesMessage": "安裝更新中...",
"DialogUpdaterCompleteMessage": "更新成功!",
"DialogUpdaterRestartMessage": "立即重啟 Ryujinx 完成更新?",
"DialogUpdaterArchNotSupportedMessage": "您執行的系統架構不受支援!",
"DialogUpdaterArchNotSupportedSubMessage": "(僅支援 x64 系統)",
"DialogUpdaterNoInternetMessage": "沒有連接到網路",
"DialogUpdaterNoInternetSubMessage": "請確保網路連接正常。",
"DialogUpdaterDirtyBuildMessage": "不能更新非官方版本的 Ryujinx",
"DialogUpdaterDirtyBuildSubMessage": "如果希望使用受支援的版本,請您在 https://ryujinx.org/ 下載。",
"DialogRestartRequiredMessage": "需要重啟模擬器",
"DialogThemeRestartMessage": "主題設定已儲存。需要重新啟動才能生效。",
"DialogThemeRestartSubMessage": "您是否要重啟?",
"DialogFirmwareInstallEmbeddedMessage": "要安裝遊戲內建的韌體嗎?(韌體 {0})",
"DialogFirmwareInstallEmbeddedSuccessMessage": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}.\\n模擬器現在可以執行。",
"DialogFirmwareNoFirmwareInstalledMessage": "未安裝韌體",
"DialogFirmwareInstalledMessage": "已安裝韌體{0}",
"DialogOpenSettingsWindowLabel": "打開設定視窗",
"DialogControllerAppletTitle": "控制器小視窗",
"DialogMessageDialogErrorExceptionMessage": "顯示訊息對話框時出錯: {0}",
"DialogSoftwareKeyboardErrorExceptionMessage": "顯示軟體鍵盤時出錯: {0}",
"DialogErrorAppletErrorExceptionMessage": "顯示錯誤對話框時出錯: {0}",
"DialogUserErrorDialogMessage": "{0}: {1}",
"DialogUserErrorDialogInfoMessage": "\n有關修復此錯誤的更多訊息可以遵循我們的設定指南。",
"DialogUserErrorDialogTitle": "Ryujinx 錯誤 ({0})",
"DialogAmiiboApiTitle": "Amiibo API",
"DialogAmiiboApiFailFetchMessage": "從 API 取得訊息時出錯。",
"DialogAmiiboApiConnectErrorMessage": "無法連接到 Amiibo API 伺服器。伺服器可能已關閉,或者您沒有網路連接。",
"DialogProfileInvalidProfileErrorMessage": "預設{0} 與目前輸入配置系統不相容。",
"DialogProfileDefaultProfileOverwriteErrorMessage": "默認預設無法被覆蓋",
"DialogProfileDeleteProfileTitle": "刪除預設",
"DialogProfileDeleteProfileMessage": "刪除後不可恢復,確定嗎?",
"DialogWarning": "警告",
"DialogPPTCDeletionMessage": "您即將刪除:\n\n{0}的 PPTC 快取\n\n確定嗎",
"DialogPPTCDeletionErrorMessage": "清除位於{0}的 PPTC 快取時出錯: {1}",
"DialogShaderDeletionMessage": "您即將刪除:\n\n{0}的渲染器快取\n\n確定嗎",
"DialogShaderDeletionErrorMessage": "清除位於{0}的渲染器快取時出錯: {1}",
"DialogRyujinxErrorMessage": "Ryujinx 遇到錯誤",
"DialogInvalidTitleIdErrorMessage": "UI 錯誤:所選遊戲沒有有效的標題ID",
"DialogFirmwareInstallerFirmwareNotFoundErrorMessage": "路徑{0}找不到有效的系統韌體。",
"DialogFirmwareInstallerFirmwareInstallTitle": "安裝韌體{0}",
"DialogFirmwareInstallerFirmwareInstallMessage": "將安裝{0}版本的系統。",
"DialogFirmwareInstallerFirmwareInstallSubMessage": "\n\n這將替換目前系統版本{0}。",
"DialogFirmwareInstallerFirmwareInstallConfirmMessage": "\n\n確認進行?",
"DialogFirmwareInstallerFirmwareInstallWaitMessage": "安裝韌體中...",
"DialogFirmwareInstallerFirmwareInstallSuccessMessage": "成功安裝系統版本{0}。",
"DialogUserProfileDeletionWarningMessage": "刪除後將沒有可選擇的使用者帳號",
"DialogUserProfileDeletionConfirmMessage": "是否刪除選擇的帳號",
"DialogControllerSettingsModifiedConfirmMessage": "目前的輸入預設已更新",
"DialogControllerSettingsModifiedConfirmSubMessage": "要儲存嗎?",
"DialogDlcLoadNcaErrorMessage": "{0}. 錯誤的檔案: {1}",
"DialogDlcNoDlcErrorMessage": "選擇的檔案不包含所選遊戲的 DLC",
"DialogPerformanceCheckLoggingEnabledMessage": "您啟用了跟蹤日誌,僅供開發人員使用。",
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "為了獲得最佳效能,建議停用跟蹤日誌記錄。您是否要立即停用?",
"DialogPerformanceCheckShaderDumpEnabledMessage": "您啟用了渲染器轉儲,僅供開發人員使用。",
"DialogPerformanceCheckShaderDumpEnabledConfirmMessage": "為了獲得最佳效能,建議停用渲染器轉儲。您是否要立即停用?",
"DialogLoadAppGameAlreadyLoadedMessage": "目前已載入有遊戲",
"DialogLoadAppGameAlreadyLoadedSubMessage": "請停止模擬或關閉程式,再啟動另一個遊戲。",
"DialogUpdateAddUpdateErrorMessage": "選擇的檔案不包含所選遊戲的更新!",
"DialogSettingsBackendThreadingWarningTitle": "警告 - 後端多執行緒",
"DialogSettingsBackendThreadingWarningMessage": "改變此選項後必須重啟 Ryujinx 才能生效。根據您的硬體您開啟該選項時可能需要手動停用驅動程式本身的GL多執行緒。",
"SettingsTabGraphicsFeaturesOptions": "功能",
"SettingsTabGraphicsBackendMultithreading": "後端多執行緒:",
"CommonAuto": "自動(推薦)",
"CommonOff": "關閉",
"CommonOn": "打開",
"InputDialogYes": "是",
"InputDialogNo": "否",
"DialogProfileInvalidProfileNameErrorMessage": "檔案名包含無效字元,請重試。",
"MenuBarOptionsPauseEmulation": "暫停",
"MenuBarOptionsResumeEmulation": "繼續",
"AboutUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的官網。",
"AboutDisclaimerMessage": "Ryujinx 以任何方式與 Nintendo™ 及其合作伙伴都沒有任何關聯。",
"AboutAmiiboDisclaimerMessage": "我們的 Amiibo 模擬使用了\nAmiiboAPI (www.amiiboapi.com) ",
"AboutPatreonUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Patreon 贊助頁。",
"AboutGithubUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 GitHub 儲存庫。",
"AboutDiscordUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Discord 伺服器邀請連結。",
"AboutTwitterUrlTooltipMessage": "在瀏覽器中打開 Ryujinx 的 Twitter 首頁。",
"AboutRyujinxAboutTitle": "關於:",
"AboutRyujinxAboutContent": "Ryujinx 是一款 Nintendo Switch™ 模擬器。\n您可以在 Patreon 上贊助 Ryujinx。\n關注 Twitter 或 Discord 可以取得模擬器最新動態。\n如果您對開發本軟體感興趣歡迎來 GitHub 和 Discord 加入我們!",
"AboutRyujinxMaintainersTitle": "由以下作者維護:",
"AboutRyujinxMaintainersContentTooltipMessage": "在瀏覽器中打開貢獻者的網頁",
"AboutRyujinxSupprtersTitle": "感謝 Patreon 的贊助者:",
"AmiiboSeriesLabel": "Amiibo 系列",
"AmiiboCharacterLabel": "角色",
"AmiiboScanButtonLabel": "掃描",
"AmiiboOptionsShowAllLabel": "顯示所有 Amiibo",
"AmiiboOptionsUsRandomTagLabel": "修正: 使用隨機標記的 Uuid",
"DlcManagerTableHeadingEnabledLabel": "啟用",
"DlcManagerTableHeadingTitleIdLabel": "遊戲ID",
"DlcManagerTableHeadingContainerPathLabel": "資料夾路徑",
"DlcManagerTableHeadingFullPathLabel": "完整路徑",
"DlcManagerRemoveAllButton": "全部刪除",
"MenuBarOptionsChangeLanguage": "變更語言",
"CommonSort": "排序",
"CommonShowNames": "顯示名稱",
"CommonFavorite": "收藏",
"OrderAscending": "從小到大",
"OrderDescending": "從大到小",
"SettingsTabGraphicsFeatures": "額外功能",
"ErrorWindowTitle": "錯誤視窗",
"ToggleDiscordTooltip": "啟用或關閉 Discord 動態狀態展示",
"AddGameDirBoxTooltip": "輸入要添加的遊戲資料夾",
"AddGameDirTooltip": "添加遊戲資料夾到列表中",
"RemoveGameDirTooltip": "移除選中的資料夾",
"CustomThemeCheckTooltip": "啟用或關閉自訂主題",
"CustomThemePathTooltip": "自訂主題的資料夾",
"CustomThemeBrowseTooltip": "查找自訂主題",
"DockModeToggleTooltip": "是否開啟 Switch 的 Docked 模式",
"DirectKeyboardTooltip": "是否開啟\"直連鍵盤存取(HID) 支援\"\n(部分遊戲可以使用您的鍵盤輸入文字)",
"DirectMouseTooltip": "是否開啟\"直連滑鼠存取(HID) 支援\"\n(部分遊戲可以使用您的滑鼠導航)",
"RegionTooltip": "變更系統區域",
"LanguageTooltip": "變更系統語言",
"TimezoneTooltip": "變更系統時區",
"TimeTooltip": "變更系統時鐘",
"VSyncToggleTooltip": "關閉後,部分使用動態幀率的遊戲可以超過 60Hz 更新率",
"PptcToggleTooltip": "開啟以後減少遊戲啟動時間和卡頓",
"FsIntegrityToggleTooltip": "是否檢查遊戲檔案內容的完整性",
"AudioBackendTooltip": "默認推薦SDL但每種音訊後端對各類遊戲相容性不同遇到音訊問題可以切換後端",
"MemoryManagerTooltip": "改變 Switch 記憶體映射到電腦記憶體的方式會影響CPU效能消耗",
"MemoryManagerSoftwareTooltip": "使用軟體記憶體頁管理,最精確但是速度最慢",
"MemoryManagerHostTooltip": "直接映射記憶體頁到電腦記憶體JIT效率高",
"MemoryManagerUnsafeTooltip": "直接映射記憶體頁但是不檢查記憶體溢出JIT效率最高。\nRyujinx可以存取任何位置的記憶體因而相對不安全。此模式下只應執行您信任的遊戲或軟體(即官方遊戲)",
"DRamTooltip": "擴展模擬的 Switch 記憶體為6GB某些高畫質材質模組或 4K 模組需要此選項",
"IgnoreMissingServicesTooltip": "忽略某些未實現的系統服務,少部分遊戲需要此選項才能啟動",
"GraphicsBackendThreadingTooltip": "啟用後端多執行緒",
"GalThreadingTooltip": "使用模擬器自帶的多執行緒調度,減少渲染器編譯的卡頓,並提高驅動程式的效能(尤其是缺失多執行緒的AMD)。\nNVIDIA使用者需要重啟模擬器才能停用驅動本身的多執行緒否則您需手動執行停用獲得最佳效能",
"ShaderCacheToggleTooltip": "開啟後快取渲染器到硬碟,減少遊戲卡頓",
"ResolutionScaleTooltip": "縮放渲染的解析度",
"ResolutionScaleEntryTooltip": "盡量使用如1.5的浮點倍數。非整數的倍率易引起錯誤",
"AnisotropyTooltip": "各向異性過濾等級。提高傾斜視角材質的清晰度\n('自動'使用遊戲默認指定的等級)",
"AspectRatioTooltip": "模擬器渲染視窗的寬高比",
"ShaderDumpPathTooltip": "轉儲圖形渲染器的路徑",
"FileLogTooltip": "是否儲存日誌檔案到硬碟",
"StubLogTooltip": "記錄 Stub 訊息",
"InfoLogTooltip": "記錄資訊訊息",
"WarnLogTooltip": "記錄警告訊息",
"ErrorLogTooltip": "記錄錯誤訊息",
"TraceLogTooltip": "記錄 Trace 訊息",
"GuestLogTooltip": "記錄 Guest 訊息",
"FileAccessLogTooltip": "記錄檔案存取訊息",
"FSAccessLogModeTooltip": "記錄 FS 存取訊息輸出到控制台。可選的模式是0-3",
"DeveloperOptionTooltip": "使用請謹慎",
"OpenGlLogLevel": "需要打開適當的日誌等級",
"DebugLogTooltip": "記錄Debug訊息",
"LoadApplicationFileTooltip": "選擇 Switch 支援的遊戲格式並載入",
"LoadApplicationFolderTooltip": "選擇解包後的 Switch 遊戲並載入",
"OpenRyujinxFolderTooltip": "打開 Ryujinx 系統資料夾",
"OpenRyujinxLogsTooltip": "打開日誌存放的資料夾",
"ExitTooltip": "關閉 Ryujinx",
"OpenSettingsTooltip": "打開設定視窗",
"OpenProfileManagerTooltip": "打開使用者帳號管理器",
"StopEmulationTooltip": "停止執行目前遊戲並回到選擇界面",
"CheckUpdatesTooltip": "檢查 Ryujinx 新版本",
"OpenAboutTooltip": "開啟關於視窗",
"GridSize": "網格尺寸",
"GridSizeTooltip": "調整網格模式的大小",
"SettingsTabSystemSystemLanguageBrazilianPortuguese": "巴西葡萄牙語",
"AboutRyujinxContributorsButtonHeader": "查看所有參與者",
"SettingsTabSystemAudioVolume": "音量: ",
"AudioVolumeTooltip": "調節音量",
"SettingsTabSystemEnableInternetAccess": "啟用網路連接",
"EnableInternetAccessTooltip": "開啟網路存取。此選項打開後,效果類似於 Switch 連接到網路的狀態。注意即使此選項關閉,應用程式偶爾也有可能連接到網路",
"GameListContextMenuManageCheatToolTip": "管理金手指",
"GameListContextMenuManageCheat": "管理金手指",
"ControllerSettingsStickRange": "範圍",
"DialogStopEmulationTitle": "Ryujinx - 停止模擬",
"DialogStopEmulationMessage": "是否確定停止模擬?",
"SettingsTabCpu": "CPU",
"SettingsTabAudio": "音訊",
"SettingsTabNetwork": "網路",
"SettingsTabNetworkConnection": "網路連接",
"SettingsTabCpuCache": "CPU 快取",
"SettingsTabCpuMemory": "CPU 記憶體",
"DialogUpdaterFlatpakNotSupportedMessage": "請透過 Flathub 更新 Ryujinx。",
"UpdaterDisabledWarningTitle": "更新已停用!",
"GameListContextMenuOpenSdModsDirectory": "打開 Atmosphere 模組資料夾",
"GameListContextMenuOpenSdModsDirectoryToolTip": "打開包含應用程式模組的額外 Atmosphere SD卡資料夾",
"ControllerSettingsRotate90": "順時針旋轉 90°",
"IconSize": "圖示尺寸",
"IconSizeTooltip": "變更遊戲圖示大小",
"MenuBarOptionsShowConsole": "顯示控制台",
"ShaderCachePurgeError": "清除渲染器快取時出錯: {0}: {1}",
"UserErrorNoKeys": "找不到金鑰",
"UserErrorNoFirmware": "找不到韌體",
"UserErrorFirmwareParsingFailed": "韌體解析錯誤",
"UserErrorApplicationNotFound": "找不到應用程式",
"UserErrorUnknown": "未知錯誤",
"UserErrorUndefined": "未定義錯誤",
"UserErrorNoKeysDescription": "Ryujinx 找不到 『prod.keys』 檔案",
"UserErrorNoFirmwareDescription": "Ryujinx 找不到任何已安裝的韌體",
"UserErrorFirmwareParsingFailedDescription": "Ryujinx 無法解密選擇的韌體。這通常是由於金鑰過舊。",
"UserErrorApplicationNotFoundDescription": "Ryujinx 在選中路徑找不到有效的應用程式。",
"UserErrorUnknownDescription": "發生未知錯誤!",
"UserErrorUndefinedDescription": "發生了未定義錯誤!此類錯誤不應出現,請聯絡開發人員!",
"OpenSetupGuideMessage": "打開設定教學",
"NoUpdate": "沒有新版本",
"TitleUpdateVersionLabel": "版本 {0} - {1}",
"RyujinxInfo": "Ryujinx - 訊息",
"RyujinxConfirm": "Ryujinx - 確認",
"FileDialogAllTypes": "全部類型",
"Never": "從不",
"SwkbdMinCharacters": "至少應為 {0} 個字長",
"SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
"SoftwareKeyboard": "軟體鍵盤",
"DialogControllerAppletMessagePlayerRange": "遊戲需要 {0} 個玩家()持有:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}請打開設定界面,配置手把;或者關閉視窗。",
"DialogControllerAppletMessage": "遊戲需要剛好 {0} 個玩家()持有 with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}請打開設定界面,配置手把;或者關閉視窗。",
"DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",
"UpdaterRenaming": "正在刪除舊檔案...",
"UpdaterRenameFailed": "更新過程中無法重命名檔案: {0}",
"UpdaterAddingFiles": "安裝更新中...",
"UpdaterExtracting": "正在提取更新...",
"UpdaterDownloading": "下載新版本中...",
"Game": "遊戲",
"Docked": "主機模式",
"Handheld": "掌機模式",
"ConnectionError": "連接錯誤。",
"AboutPageDeveloperListMore": "{0} 等開發者...",
"ApiError": "API 錯誤",
"LoadingHeading": "正在啟動 {0}",
"CompilingPPTC": "編譯 PPTC 快取中",
"CompilingShaders": "編譯渲染器中",
"AllKeyboards": "所有鍵盤",
"OpenFileDialogTitle": "選擇支援的檔案格式",
"OpenFolderDialogTitle": "選擇一個包含解包遊戲的資料夾",
"AllSupportedFormats": "全部支援的格式",
"RyujinxUpdater": "Ryujinx 更新程式",
"SettingsTabHotkeys": "快捷鍵",
"SettingsTabHotkeysHotkeys": "鍵盤快捷鍵",
"SettingsTabHotkeysToggleVsyncHotkey": "切換垂直同步",
"SettingsTabHotkeysScreenshotHotkey": "截圖",
"SettingsTabHotkeysShowUiHotkey": "隱藏 UI",
"SettingsTabHotkeysPauseHotkey": "暫停",
"SettingsTabHotkeysToggleMuteHotkey": "靜音",
"ControllerMotionTitle": "體感操作設定",
"ControllerRumbleTitle": "震動設定",
"SettingsSelectThemeFileDialogTitle": "選擇主題檔案",
"SettingsXamlThemeFile": "Xaml 主題檔案",
"AvatarWindowTitle": "管理帳號 - 頭像",
"Amiibo": "Amiibo",
"Unknown": "未知",
"Usage": "用途",
"Writable": "可寫入",
"SelectDlcDialogTitle": "選擇 DLC 檔案",
"SelectUpdateDialogTitle": "選擇更新檔",
"UserProfileWindowTitle": "管理使用者設定檔",
"CheatWindowTitle": "管理遊戲金手指",
"DlcWindowTitle": "管理遊戲 DLC",
"UpdateWindowTitle": "管理遊戲更新",
"CheatWindowHeading": "金手指可用於 {0} [{1}]",
"DlcWindowHeading": "DLC 可用於 {0} [{1}]",
"GameUpdateWindowHeading": "更新可用於 {0} [{1}]"
}

View File

@ -54,5 +54,6 @@
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FFFFFFFF</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
</Styles.Resources>
</Styles>

View File

@ -49,5 +49,6 @@
<Color x:Key="TextOnAccentFillColorPrimary">#FFFFFFFF</Color>
<Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
<Color x:Key="ThemeForegroundColor">#FF000000</Color>
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
</Styles.Resources>
</Styles>

View File

@ -221,6 +221,7 @@
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
</Style>
<Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="ListViewItemBackgroundSelected" ResourceKey="ThemeAccentColorBrush" />
<StaticResource x:Key="ListViewItemBackgroundPressed" ResourceKey="SystemAccentColorDark1" />
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
@ -232,7 +233,7 @@
Color="{DynamicResource SystemBaseMediumLowColor}" />
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
<SolidColorBrush x:Key="ThemeAccentBrush4" Color="{DynamicResource ThemeAccentColor4}" />

View File

@ -81,7 +81,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(
_owner,
string.Format(LocaleManager.Instance["DialogMessageCreateSaveErrorMessage"], result.ToStringWithName()));
});
@ -101,8 +100,7 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner,
string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageFindSaveErrorMessage"], result.ToStringWithName()));
});
return false;
@ -161,7 +159,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.Post(async () =>
{
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner,
string.Format(LocaleManager.Instance["DialogNcaExtractionMessage"], ncaSectionType, Path.GetFileName(titleFilePath)),
"",
"",
@ -232,7 +229,7 @@ namespace Ryujinx.Ava.Common
"Extraction failure. The main NCA was not present in the selected file");
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionMainNcaNotFoundErrorMessage"]);
});
return;
}
@ -273,7 +270,7 @@ namespace Ryujinx.Ava.Common
$"LibHac returned error code: {resultCode.Value.ErrorCode}");
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner, LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogNcaExtractionCheckLogErrorMessage"]);
});
}
else if (resultCode.Value.IsSuccess())
@ -281,7 +278,6 @@ namespace Ryujinx.Ava.Common
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateInfoDialog(
_owner,
LocaleManager.Instance["DialogNcaExtractionSuccessMessage"],
"",
LocaleManager.Instance["InputDialogOk"],
@ -298,7 +294,7 @@ namespace Ryujinx.Ava.Common
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}
}

View File

@ -7,6 +7,8 @@
Screenshot,
ShowUi,
Pause,
ToggleMute
ToggleMute,
ResScaleUp,
ResScaleDown
}
}

View File

@ -76,7 +76,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
});
return;
@ -111,7 +111,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
});
}
@ -129,7 +129,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
});
}
@ -142,7 +142,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, exception.Message);
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(mainWindow, LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdaterFailedToGetVersionMessage"]);
});
return;
@ -157,7 +157,7 @@ namespace Ryujinx.Modules
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateWarningDialog(mainWindow, LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterConvertFailedGithubMessage"], LocaleManager.Instance["DialogUpdaterCancelUpdateMessage"]);
});
return;
@ -169,7 +169,7 @@ namespace Ryujinx.Modules
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateUpdaterInfoDialog(mainWindow, LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance["DialogUpdaterAlreadyOnLatestVersionMessage"], "");
});
}
@ -550,7 +550,7 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"],
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterArchNotSupportedMessage"],
LocaleManager.Instance["DialogUpdaterArchNotSupportedSubMessage"]);
}
@ -561,7 +561,7 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterNoInternetMessage"],
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterNoInternetMessage"],
LocaleManager.Instance["DialogUpdaterNoInternetSubMessage"]);
}
@ -572,7 +572,7 @@ namespace Ryujinx.Modules
{
if (showWarnings)
{
ContentDialogHelper.CreateWarningDialog(parent, LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"],
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["DialogUpdaterDirtyBuildMessage"],
LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
}
@ -585,13 +585,11 @@ namespace Ryujinx.Modules
{
if (ReleaseInformations.IsFlatHubBuild())
{
ContentDialogHelper.CreateWarningDialog(parent,
LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterFlatpakNotSupportedMessage"]);
}
else
{
ContentDialogHelper.CreateWarningDialog(parent,
LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UpdaterDisabledWarningTitle"], LocaleManager.Instance["DialogUpdaterDirtyBuildSubMessage"]);
}
}

View File

@ -26,9 +26,10 @@
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="SPB" Version="0.0.4-build17" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
@ -118,6 +119,7 @@
<None Remove="Assets\Locales\ru_RU.json" />
<None Remove="Assets\Locales\tr_TR.json" />
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Locales\zh_TW.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\BaseDark.xaml" />
<None Remove="Assets\Styles\BaseLight.xaml" />
@ -135,6 +137,7 @@
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
</Project>

View File

@ -92,7 +92,7 @@ namespace Ryujinx.Ava.Ui.Applet
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogMessageDialogErrorExceptionMessage"], ex));
dialogCloseEvent.Set();
}
@ -126,7 +126,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex)
{
error = true;
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogSoftwareKeyboardErrorExceptionMessage"], ex));
}
finally
{
@ -181,7 +181,7 @@ namespace Ryujinx.Ava.Ui.Applet
catch (Exception ex)
{
dialogCloseEvent.Set();
await ContentDialogHelper.CreateErrorDialog(_parent, string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogErrorAppletErrorExceptionMessage"], ex));
}
});

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Applet
{
internal class ErrorAppletWindow : StyleableWindow
internal partial class ErrorAppletWindow : StyleableWindow
{
private readonly Window _owner;
private object _buttonResponse;
@ -50,8 +50,6 @@ namespace Ryujinx.Ava.Ui.Applet
public string Message { get; set; }
public StackPanel ButtonStack { get; set; }
private void AddButton(string label, object tag)
{
Dispatcher.UIThread.InvokeAsync(() =>
@ -79,11 +77,5 @@ namespace Ryujinx.Ava.Ui.Applet
return _buttonResponse;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
ButtonStack = this.FindControl<StackPanel>("ButtonStack");
}
}
}

View File

@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
internal class SwkbdAppletDialog : UserControl
internal partial class SwkbdAppletDialog : UserControl
{
private Predicate<int> _checkLength;
private int _inputMax;
@ -30,6 +30,10 @@ namespace Ryujinx.Ava.Ui.Controls
_placeholder = placeholder;
InitializeComponent();
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
}
@ -43,23 +47,9 @@ namespace Ryujinx.Ava.Ui.Controls
public string MainText { get; set; } = "";
public string SecondaryText { get; set; } = "";
public TextBlock Error { get; private set; }
public TextBox Input { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
Error = this.FindControl<TextBlock>("Error");
Input = this.FindControl<TextBox>("Input");
Input.Watermark = _placeholder;
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, SoftwareKeyboardUiArgs args)
{
ContentDialog contentDialog = window.ContentDialog;
ContentDialog contentDialog = new ContentDialog();
UserResult result = UserResult.Cancel;

View File

@ -16,7 +16,6 @@ namespace Ryujinx.Ava.Ui.Controls
private static bool _isChoiceDialogOpen;
private async static Task<UserResult> ShowContentDialog(
StyleableWindow window,
string title,
string primaryText,
string secondaryText,
@ -28,35 +27,32 @@ namespace Ryujinx.Ava.Ui.Controls
{
UserResult result = UserResult.None;
ContentDialog contentDialog = window.ContentDialog;
ContentDialog contentDialog = new ContentDialog();
await ShowDialog();
async Task ShowDialog()
{
if (contentDialog != null)
contentDialog.Title = title;
contentDialog.PrimaryButtonText = primaryButton;
contentDialog.SecondaryButtonText = secondaryButton;
contentDialog.CloseButtonText = closeButton;
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.Title = title;
contentDialog.PrimaryButtonText = primaryButton;
contentDialog.SecondaryButtonText = secondaryButton;
contentDialog.CloseButtonText = closeButton;
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
result = primaryButtonResult;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
});
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButtonResult;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Cancel;
});
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
};
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}
return result;
@ -78,35 +74,30 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult result = UserResult.None;
ContentDialog contentDialog = window.ContentDialog;
Window overlay = window;
if (contentDialog != null)
ContentDialog contentDialog = new ContentDialog
{
contentDialog.PrimaryButtonClick += DeferClose;
contentDialog.Title = title;
contentDialog.PrimaryButtonText = primaryButton;
contentDialog.SecondaryButtonText = secondaryButton;
contentDialog.CloseButtonText = closeButton;
contentDialog.Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol);
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
Title = title,
PrimaryButtonText = primaryButton,
SecondaryButtonText = secondaryButton,
CloseButtonText = closeButton,
Content = CreateDialogTextContent(primaryText, secondaryText, iconSymbol),
PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = primaryButton == LocaleManager.Instance["InputDialogYes"] ? UserResult.Yes : UserResult.Ok;
});
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
}),
};
contentDialog.SecondaryButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.No;
});
contentDialog.CloseButtonCommand = MiniCommand.Create(() =>
{
contentDialog.PrimaryButtonClick -= DeferClose;
result = UserResult.Cancel;
});
contentDialog.PrimaryButtonClick += DeferClose;
await contentDialog.ShowAsync(ContentDialogPlacement.Popup);
return result;
@ -141,7 +132,7 @@ namespace Ryujinx.Ava.Ui.Controls
if (doWhileDeferred != null)
{
await doWhileDeferred(overlay);
await doWhileDeferred(window);
deferResetEvent.Set();
}
@ -191,7 +182,6 @@ namespace Ryujinx.Ava.Ui.Controls
}
public static async Task<UserResult> CreateInfoDialog(
StyleableWindow window,
string primary,
string secondaryText,
string acceptButton,
@ -199,7 +189,6 @@ namespace Ryujinx.Ava.Ui.Controls
string title)
{
return await ShowContentDialog(
window,
title,
primary,
secondaryText,
@ -210,7 +199,6 @@ namespace Ryujinx.Ava.Ui.Controls
}
internal static async Task<UserResult> CreateConfirmationDialog(
StyleableWindow window,
string primaryText,
string secondaryText,
string acceptButtonText,
@ -219,7 +207,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult primaryButtonResult = UserResult.Yes)
{
return await ShowContentDialog(
window,
string.IsNullOrWhiteSpace(title) ? LocaleManager.Instance["DialogConfirmationTitle"] : title,
primaryText,
secondaryText,
@ -235,10 +222,9 @@ namespace Ryujinx.Ava.Ui.Controls
return new(mainText, secondaryText);
}
internal static async Task CreateUpdaterInfoDialog(StyleableWindow window, string primary, string secondaryText)
internal static async Task CreateUpdaterInfoDialog(string primary, string secondaryText)
{
await ShowContentDialog(
window,
LocaleManager.Instance["DialogUpdaterTitle"],
primary,
secondaryText,
@ -248,24 +234,9 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important);
}
internal static async Task ShowNotAvailableMessage(StyleableWindow window)
{
// Temporary placeholder for features to be added
await ShowContentDialog(
window,
"Feature Not Available",
"The selected feature is not available in this version.",
"",
"",
"",
LocaleManager.Instance["InputDialogOk"],
(int)Symbol.Important);
}
internal static async Task CreateWarningDialog(StyleableWindow window, string primary, string secondaryText)
internal static async Task CreateWarningDialog(string primary, string secondaryText)
{
await ShowContentDialog(
window,
LocaleManager.Instance["DialogWarningTitle"],
primary,
secondaryText,
@ -275,12 +246,11 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Important);
}
internal static async Task CreateErrorDialog(StyleableWindow owner, string errorMessage, string secondaryErrorMessage = "")
internal static async Task CreateErrorDialog(string errorMessage, string secondaryErrorMessage = "")
{
Logger.Error?.Print(LogClass.Application, errorMessage);
await ShowContentDialog(
owner,
LocaleManager.Instance["DialogErrorTitle"],
LocaleManager.Instance["DialogErrorMessage"],
errorMessage,
@ -290,7 +260,7 @@ namespace Ryujinx.Ava.Ui.Controls
(int)Symbol.Dismiss);
}
internal static async Task<bool> CreateChoiceDialog(StyleableWindow window, string title, string primary, string secondaryText)
internal static async Task<bool> CreateChoiceDialog(string title, string primary, string secondaryText)
{
if (_isChoiceDialogOpen)
{
@ -301,7 +271,6 @@ namespace Ryujinx.Ava.Ui.Controls
UserResult response =
await ShowContentDialog(
window,
title,
primary,
secondaryText,
@ -316,19 +285,17 @@ namespace Ryujinx.Ava.Ui.Controls
return response == UserResult.Yes;
}
internal static async Task<bool> CreateExitDialog(StyleableWindow owner)
internal static async Task<bool> CreateExitDialog()
{
return await CreateChoiceDialog(
owner,
LocaleManager.Instance["DialogExitTitle"],
LocaleManager.Instance["DialogExitMessage"],
LocaleManager.Instance["DialogExitSubMessage"]);
}
internal static async Task<bool> CreateStopEmulationDialog(StyleableWindow owner)
internal static async Task<bool> CreateStopEmulationDialog()
{
return await CreateChoiceDialog(
owner,
LocaleManager.Instance["DialogStopEmulationTitle"],
LocaleManager.Instance["DialogStopEmulationMessage"],
LocaleManager.Instance["DialogExitSubMessage"]);
@ -338,12 +305,10 @@ namespace Ryujinx.Ava.Ui.Controls
string title,
string mainText,
string subText,
StyleableWindow owner,
uint maxLength = int.MaxValue,
string input = "")
{
var result = await InputDialog.ShowInputDialog(
owner,
title,
mainText,
input,

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
public class InputDialog : UserControl
public partial class InputDialog : UserControl
{
public string Message { get; set; }
public string Input { get; set; }
@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Ui.Controls
MaxLength = maxLength;
DataContext = this;
InitializeComponent();
}
public InputDialog()
@ -33,33 +31,26 @@ namespace Ryujinx.Ava.Ui.Controls
InitializeComponent();
}
private void InitializeComponent()
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, string message,
string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
AvaloniaXamlLoader.Load(this);
}
public static async Task<(UserResult Result, string Input)> ShowInputDialog(StyleableWindow window, string title, string message, string input = "", string subMessage = "", uint maxLength = int.MaxValue)
{
ContentDialog contentDialog = window.ContentDialog;
UserResult result = UserResult.Cancel;
InputDialog content = new InputDialog(message, input = "", subMessage = "", maxLength);
if (contentDialog != null)
InputDialog content = new InputDialog(message, input, subMessage, maxLength);
ContentDialog contentDialog = new ContentDialog
{
contentDialog.Title = title;
contentDialog.PrimaryButtonText = LocaleManager.Instance["InputDialogOk"];
contentDialog.SecondaryButtonText = "";
contentDialog.CloseButtonText = LocaleManager.Instance["InputDialogCancel"];
contentDialog.Content = content;
contentDialog.PrimaryButtonCommand = MiniCommand.Create(() =>
Title = title,
PrimaryButtonText = LocaleManager.Instance["InputDialogOk"],
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["InputDialogCancel"],
Content = content,
PrimaryButtonCommand = MiniCommand.Create(() =>
{
result = UserResult.Ok;
input = content.Input;
});
await contentDialog.ShowAsync();
}
})
};
await contentDialog.ShowAsync();
return (result, input);
}

View File

@ -0,0 +1,10 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.NavigationDialogHost">
<ui:Frame HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
x:Name="ContentFrame" />
</UserControl>

View File

@ -0,0 +1,85 @@
using Avalonia;
using Avalonia.Controls;
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class NavigationDialogHost : UserControl
{
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public UserProfileViewModel ViewModel { get; set; }
public NavigationDialogHost()
{
InitializeComponent();
}
public NavigationDialogHost(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem)
{
AccountManager = accountManager;
ContentManager = contentManager;
ViewModel = new UserProfileViewModel(this);
if (contentManager.GetCurrentFirmwareVersion() != null)
{
Task.Run(() =>
{
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
});
}
InitializeComponent();
}
public void GoBack(object parameter = null)
{
if (ContentFrame.BackStack.Count > 0)
{
ContentFrame.GoBack();
}
ViewModel.LoadProfiles();
}
public void Navigate(Type sourcePageType, object parameter)
{
ContentFrame.Navigate(sourcePageType, parameter);
}
public static async Task Show(AccountManager ownerAccountManager, ContentManager ownerContentManager, VirtualFileSystem ownerVirtualFileSystem)
{
var content = new NavigationDialogHost(ownerAccountManager, ownerContentManager, ownerVirtualFileSystem);
ContentDialog contentDialog = new ContentDialog
{
Title = LocaleManager.Instance["UserProfileWindowTitle"],
PrimaryButtonText = "",
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["UserProfilesClose"],
Content = content,
Padding = new Thickness(0)
};
contentDialog.Closed += (sender, args) =>
{
content.ViewModel.Dispose();
};
await contentDialog.ShowAsync();
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
Navigate(typeof(UserSelector), this);
}
}
}

View File

@ -1,14 +1,10 @@
<Window xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner"
Title="{Locale:Locale ProfileImageSelectionTitle}"
CanResize="false">
x:Class="Ryujinx.Ava.Ui.Controls.ProfileImageSelectionDialog">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@ -32,4 +28,4 @@
</Button>
</StackPanel>
</Grid>
</Window>
</UserControl>

View File

@ -1,8 +1,10 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
@ -12,36 +14,40 @@ using Image = SixLabors.ImageSharp.Image;
namespace Ryujinx.Ava.Ui.Controls
{
public class ProfileImageSelectionDialog : StyleableWindow
public partial class ProfileImageSelectionDialog : UserControl
{
private readonly ContentManager _contentManager;
private ContentManager _contentManager;
private NavigationDialogHost _parent;
private TempProfile _profile;
public bool FirmwareFound => _contentManager.GetCurrentFirmwareVersion() != null;
public byte[] BufferImageProfile { get; set; }
public ProfileImageSelectionDialog(ContentManager contentManager)
{
_contentManager = contentManager;
DataContext = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
public ProfileImageSelectionDialog()
{
DataContext = this;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void InitializeComponent()
private void NavigatedTo(NavigationEventArgs arg)
{
AvaloniaXamlLoader.Load(this);
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
_contentManager = _parent.ContentManager;
break;
case NavigationMode.Back:
_parent.GoBack();
break;
}
DataContext = this;
}
}
private async void Import_OnClick(object sender, RoutedEventArgs e)
@ -58,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Controls
dialog.AllowMultiple = false;
string[] image = await dialog.ShowAsync(this);
string[] image = await dialog.ShowAsync(((TopLevel)_parent.GetVisualRoot()) as Window);
if (image != null)
{
@ -66,28 +72,22 @@ namespace Ryujinx.Ava.Ui.Controls
{
string imageFile = image[0];
ProcessProfileImage(File.ReadAllBytes(imageFile));
_profile.Image = ProcessProfileImage(File.ReadAllBytes(imageFile));
}
Close();
_parent.GoBack();
}
}
private async void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
private void SelectFirmwareImage_OnClick(object sender, RoutedEventArgs e)
{
if (FirmwareFound)
{
AvatarWindow window = new(_contentManager);
await window.ShowDialog(this);
BufferImageProfile = window.SelectedImage;
Close();
_parent.Navigate(typeof(AvatarWindow), (_parent, _profile));
}
}
private void ProcessProfileImage(byte[] buffer)
private static byte[] ProcessProfileImage(byte[] buffer)
{
using (Image image = Image.Load(buffer))
{
@ -97,7 +97,7 @@ namespace Ryujinx.Ava.Ui.Controls
{
image.SaveAsJpeg(streamJpg);
BufferImageProfile = streamJpg.ToArray();
return streamJpg.ToArray();
}
}
}

View File

@ -5,7 +5,7 @@ using Ryujinx.Ava.Ui.Windows;
namespace Ryujinx.Ava.Ui.Controls
{
public class UpdateWaitWindow : StyleableWindow
public partial class UpdateWaitWindow : StyleableWindow
{
public UpdateWaitWindow(string primaryText, string secondaryText) : this()
{
@ -21,15 +21,5 @@ namespace Ryujinx.Ava.Ui.Controls
this.AttachDevTools();
#endif
}
public TextBlock PrimaryText { get; private set; }
public TextBlock SecondaryText { get; private set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
PrimaryText = this.FindControl<TextBlock>("PrimaryText");
SecondaryText = this.FindControl<TextBlock>("SecondaryText");
}
}
}

View File

@ -0,0 +1,55 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Padding="0"
Margin="0"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
x:Class="Ryujinx.Ava.Ui.Controls.UserEditor">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Vertical" VerticalAlignment="Stretch" HorizontalAlignment="Left">
<Image
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Height="96" Width="96"
Name="ProfileImage"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<Button Margin="5" Content="{Locale:Locale UserProfilesChangeProfileImage}"
Name="ChangePictureButton"
Click="ChangePictureButton_Click"
HorizontalAlignment="Stretch"/>
<Button Margin="5" Content="{Locale:Locale UserProfilesSetProfileImage}"
Name="AddPictureButton"
Click="ChangePictureButton_Click"
HorizontalAlignment="Stretch"/>
</StackPanel>
<StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
Margin="5, 10">
<TextBox Name="NameBox" Width="300" Text="{Binding Name}" MaxLength="{Binding MaxProfileNameLength}"
HorizontalAlignment="Stretch" />
<TextBlock Text="{Binding UserId}" Name="IdLabel" />
</StackPanel>
<StackPanel Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Content="{Locale:Locale Save}" Name="SaveButton" Click="SaveButton_Click"/>
<Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}"
Name="CloseButton" Click="CloseButton_Click"/>
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,123 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Models;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserEditor : UserControl
{
private NavigationDialogHost _parent;
private UserProfile _profile;
private bool _isNewUser;
public TempProfile TempProfile { get; set; }
public uint MaxProfileNameLength => 0x20;
public UserEditor()
{
InitializeComponent();
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
var args = ((NavigationDialogHost parent, UserProfile profile, bool isNewUser))arg.Parameter;
_isNewUser = args.isNewUser;
if (!_isNewUser)
{
_profile = args.profile;
TempProfile = new TempProfile(_profile);
}
else
{
TempProfile = new TempProfile();
}
_parent = args.parent;
break;
}
DataContext = TempProfile;
AddPictureButton.IsVisible = _isNewUser;
IdLabel.IsVisible = !_isNewUser;
ChangePictureButton.IsVisible = !_isNewUser;
}
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
_parent?.GoBack();
}
private void SaveButton_Click(object sender, RoutedEventArgs e)
{
DataValidationErrors.ClearErrors(NameBox);
bool isInvalid = false;
if (string.IsNullOrWhiteSpace(TempProfile.Name))
{
DataValidationErrors.SetError(NameBox, new DataValidationException(LocaleManager.Instance["UserProfileEmptyNameError"]));
isInvalid = true;
}
if (TempProfile.Image == null)
{
ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance["UserProfileNoImageError"], "");
isInvalid = true;
}
if(isInvalid)
{
return;
}
if (_profile != null)
{
_profile.Name = TempProfile.Name;
_profile.Image = TempProfile.Image;
_profile.UpdateState();
_parent.AccountManager.SetUserName(_profile.UserId, _profile.Name);
_parent.AccountManager.SetUserImage(_profile.UserId, _profile.Image);
}
else if (_isNewUser)
{
_parent.AccountManager.AddUser(TempProfile.Name, TempProfile.Image);
}
else
{
return;
}
_parent?.GoBack();
}
public void SelectProfileImage()
{
_parent.Navigate(typeof(ProfileImageSelectionDialog), (_parent, TempProfile));
}
private void ChangePictureButton_Click(object sender, RoutedEventArgs e)
{
if (_profile != null || _isNewUser)
{
SelectProfileImage();
}
}
}
}

View File

@ -75,7 +75,7 @@ namespace Ryujinx.Ava.Ui.Controls
string setupButtonLabel = isInSetupGuide ? LocaleManager.Instance["OpenSetupGuideMessage"] : "";
var result = await ContentDialogHelper.CreateInfoDialog(owner,
var result = await ContentDialogHelper.CreateInfoDialog(
string.Format(LocaleManager.Instance["DialogUserErrorDialogMessage"], errorCode, GetErrorTitle(error)),
GetErrorDescription(error) + (isInSetupGuide
? LocaleManager.Instance["DialogUserErrorDialogInfoMessage"]

View File

@ -0,0 +1,90 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ryujinx.Ava.Ui.Controls.UserSelector">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5" Items="{Binding Profiles}"
DoubleTapped="ProfilesList_DoubleTapped"
SelectionChanged="SelectingItemsControl_SelectionChanged">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<flex:FlexPanel
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AlignContent="FlexStart"
JustifyContent="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
<Grid Margin="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Margin="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Height="96" Width="96"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<StackPanel
Grid.Row="1"
Height="30"
Margin="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock
HorizontalAlignment="Stretch"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Border>
<Border HorizontalAlignment="Left" VerticalAlignment="Top"
IsVisible="{Binding IsOpened}"
Background="LimeGreen"
Width="10"
Height="10"
Margin="5"
CornerRadius="5" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Center">
<Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
<Button IsEnabled="{Binding IsSelectedProfiledEditable}"
Content="{Locale:Locale UserProfilesEditProfile}" Command="{Binding EditUser}" />
<Button IsEnabled="{Binding IsSelectedProfileDeletable}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
</StackPanel>
</Grid>
</UserControl>

View File

@ -0,0 +1,79 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.ViewModels;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Controls
{
public partial class UserSelector : UserControl
{
private NavigationDialogHost _parent;
public UserProfileViewModel ViewModel { get; set; }
public UserSelector()
{
InitializeComponent();
if (Program.PreviewerDetached)
{
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, Avalonia.Interactivity.RoutingStrategies.Direct);
}
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
switch (arg.NavigationMode)
{
case NavigationMode.New:
_parent = (NavigationDialogHost)arg.Parameter;
ViewModel = _parent.ViewModel;
break;
}
DataContext = ViewModel;
}
}
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
_parent?.AccountManager?.OpenUser(ViewModel.SelectedProfile.UserId);
ViewModel.LoadProfiles();
foreach (UserProfile profile in ViewModel.Profiles)
{
profile.UpdateState();
}
}
}
}
private void SelectingItemsControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.HighlightedProfile = ViewModel.Profiles[selectedIndex];
}
}
}
}
}

View File

@ -0,0 +1,55 @@
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
namespace Ryujinx.Ava.Ui.Models
{
public class TempProfile : BaseModel
{
private readonly UserProfile _profile;
private byte[] _image = null;
private string _name = String.Empty;
private UserId _userId;
public byte[] Image
{
get => _image;
set
{
_image = value;
OnPropertyChanged();
}
}
public UserId UserId
{
get => _userId;
set
{
_userId = value;
OnPropertyChanged();
}
}
public string Name
{
get => _name;
set
{
_name = value;
OnPropertyChanged();
}
}
public TempProfile(UserProfile profile)
{
_profile = profile;
Image = profile.Image;
Name = profile.Name;
UserId = profile.UserId;
}
public TempProfile(){}
}
}

View File

@ -390,7 +390,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
return amiiboJsonString;
}
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
LocaleManager.Instance["DialogAmiiboApiFailFetchMessage"],
LocaleManager.Instance["InputDialogOk"],
"",
@ -440,7 +440,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private async void ShowInfoDialog()
{
await ContentDialogHelper.CreateInfoDialog(_owner, LocaleManager.Instance["DialogAmiiboApiTitle"],
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogAmiiboApiTitle"],
LocaleManager.Instance["DialogAmiiboApiConnectErrorMessage"],
LocaleManager.Instance["InputDialogOk"],
"",

View File

@ -327,12 +327,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void ShowMotionConfig()
{
await MotionSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
await MotionSettingsWindow.Show(this);
}
public async void ShowRumbleConfig()
{
await RumbleSettingsWindow.Show(this, _owner.GetVisualRoot() as StyleableWindow);
await RumbleSettingsWindow.Show(this);
}
private void LoadInputDriver()
@ -701,8 +701,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (InvalidOperationException)
{
Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system.");
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow,
String.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogProfileInvalidProfileErrorMessage"], ProfileName));
return;
}
@ -736,7 +736,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (ProfileName == LocaleManager.Instance["ControllerSettingsProfileDefault"])
{
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileDefaultProfileOverwriteErrorMessage"]);
return;
}
@ -769,7 +769,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
else
{
await ContentDialogHelper.CreateErrorDialog(_owner.GetVisualRoot() as StyleableWindow, LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogProfileInvalidProfileNameErrorMessage"]);
}
}
}
@ -782,7 +782,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogProfileDeleteProfileTitle"],
LocaleManager.Instance["DialogProfileDeleteProfileMessage"],
LocaleManager.Instance["InputDialogYes"],

View File

@ -535,6 +535,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public bool ShowConsoleVisible
{
get => ConsoleHelper.SetConsoleWindowStateSupported;
}
public ObservableCollection<ApplicationData> Applications
{
get => _applications;
@ -703,7 +708,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
return;
}
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
@ -970,9 +975,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void ManageProfiles()
{
UserProfileWindow window = new(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
await window.ShowDialog(_owner);
await NavigationDialogHost.Show(_owner.AccountManager, _owner.ContentManager, _owner.VirtualFileSystem);
}
public async void OpenAboutWindow()
@ -1049,8 +1052,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
});
return;
@ -1133,7 +1135,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new();
@ -1158,7 +1160,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], file.Name, e));
}
}
}
@ -1196,7 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(_owner, LocaleManager.Instance["DialogWarning"],
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>();
@ -1219,7 +1221,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogPPTCDeletionErrorMessage"], directory.Name, e));
}
}
}
@ -1232,7 +1234,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
catch (Exception e)
{
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["ShaderCachePurgeError"], file.Name, e));
}
}
}
@ -1311,12 +1313,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
});
return;
@ -1337,12 +1338,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
Task.Run(() =>
{
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
out ulong titleIdNumber))
out ulong titleIdNumber))
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogRyujinxErrorMessage"], LocaleManager.Instance["DialogInvalidTitleIdErrorMessage"]);
});
return;
@ -1401,7 +1401,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (firmwareVersion == null)
{
await ContentDialogHelper.CreateErrorDialog(_owner, string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareNotFoundErrorMessage"], filename));
return;
}
@ -1421,7 +1421,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
dialogMessage += LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
_owner,
dialogTitle,
dialogMessage,
LocaleManager.Instance["InputDialogYes"],
@ -1451,7 +1450,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(_owner, dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache.
@ -1470,7 +1469,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
waitingDialog.Close();
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
await ContentDialogHelper.CreateErrorDialog(ex.Message);
});
}
finally
@ -1491,7 +1490,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(_owner, ex.Message);
await ContentDialogHelper.CreateErrorDialog(ex.Message);
}
}

View File

@ -63,8 +63,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateInfoDialog(_owner,
LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance["DialogSettingsBackendThreadingWarningMessage"],
"",
"",
LocaleManager.Instance["InputDialogOk"],

View File

@ -1,31 +1,27 @@
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Windows;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.ViewModels
{
public class UserProfileViewModel : BaseModel, IDisposable
{
private const uint MaxProfileNameLength = 0x20;
private readonly UserProfileWindow _owner;
private readonly NavigationDialogHost _owner;
private UserProfile _selectedProfile;
private string _tempUserName;
private UserProfile _highlightedProfile;
public UserProfileViewModel()
{
Profiles = new ObservableCollection<UserProfile>();
}
public UserProfileViewModel(UserProfileWindow owner) : this()
public UserProfileViewModel(NavigationDialogHost owner) : this()
{
_owner = owner;
@ -42,12 +38,29 @@ namespace Ryujinx.Ava.Ui.ViewModels
_selectedProfile = value;
OnPropertyChanged(nameof(SelectedProfile));
OnPropertyChanged(nameof(IsSelectedProfileDeletable));
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
}
}
public bool IsSelectedProfileDeletable =>
_selectedProfile != null && _selectedProfile.UserId != AccountManager.DefaultUserId;
public bool IsHighlightedProfileEditable =>
_highlightedProfile != null;
public bool IsHighlightedProfileDeletable =>
_highlightedProfile != null && _highlightedProfile.UserId != AccountManager.DefaultUserId;
public UserProfile HighlightedProfile
{
get => _highlightedProfile;
set
{
_highlightedProfile = value;
OnPropertyChanged(nameof(HighlightedProfile));
OnPropertyChanged(nameof(IsHighlightedProfileDeletable));
OnPropertyChanged(nameof(IsHighlightedProfileEditable));
}
}
public void Dispose()
{
@ -78,64 +91,24 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
}
public async void ChooseProfileImage()
public void AddUser()
{
await SelectProfileImage();
UserProfile userProfile = null;
_owner.Navigate(typeof(UserEditor), (this._owner, userProfile, true));
}
public async Task SelectProfileImage(bool isNewUser = false)
public void EditUser()
{
ProfileImageSelectionDialog selectionDialog = new(_owner.ContentManager);
await selectionDialog.ShowDialog(_owner);
if (selectionDialog.BufferImageProfile != null)
{
if (isNewUser)
{
if (!string.IsNullOrWhiteSpace(_tempUserName))
{
_owner.AccountManager.AddUser(_tempUserName, selectionDialog.BufferImageProfile);
}
}
else if (SelectedProfile != null)
{
_owner.AccountManager.SetUserImage(SelectedProfile.UserId, selectionDialog.BufferImageProfile);
SelectedProfile.Image = selectionDialog.BufferImageProfile;
SelectedProfile = null;
}
LoadProfiles();
}
}
public async void AddUser()
{
var dlgTitle = LocaleManager.Instance["InputDialogAddNewProfileTitle"];
var dlgMainText = LocaleManager.Instance["InputDialogAddNewProfileHeader"];
var dlgSubText = string.Format(LocaleManager.Instance["InputDialogAddNewProfileSubtext"],
MaxProfileNameLength);
_tempUserName =
await ContentDialogHelper.CreateInputDialog(dlgTitle, dlgMainText, dlgSubText, _owner,
MaxProfileNameLength);
if (!string.IsNullOrWhiteSpace(_tempUserName))
{
await SelectProfileImage(true);
}
_tempUserName = String.Empty;
_owner.Navigate(typeof(UserEditor), (this._owner, _highlightedProfile ?? SelectedProfile, false));
}
public async void DeleteUser()
{
if (_selectedProfile != null)
if (_highlightedProfile != null)
{
var lastUserId = _owner.AccountManager.LastOpenedUser.UserId;
if (_selectedProfile.UserId == lastUserId)
if (_highlightedProfile.UserId == lastUserId)
{
// If we are deleting the currently open profile, then we must open something else before deleting.
var profile = Profiles.FirstOrDefault(x => x.UserId != lastUserId);
@ -144,8 +117,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(_owner,
LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUserProfileDeletionWarningMessage"]);
});
return;
@ -155,13 +127,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
}
var result =
await ContentDialogHelper.CreateConfirmationDialog(_owner,
LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogUserProfileDeletionConfirmMessage"], "",
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], "");
if (result == UserResult.Yes)
{
_owner.AccountManager.DeleteUser(_selectedProfile.UserId);
_owner.AccountManager.DeleteUser(_highlightedProfile.UserId);
}
}

View File

@ -17,43 +17,43 @@
SizeToContent="Width"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<Grid
<Grid
Margin="15"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid
Grid.Row="1"
Grid.Column="0"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image
Grid.Row="0"
Grid.RowSpan="3"
Grid.Column="0"
@ -61,21 +61,21 @@
MinWidth="50"
Margin="5,10,20,10"
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
<TextBlock
Grid.Row="0"
Grid.Column="1"
Margin="0,20,0,0"
FontSize="35"
Text="Ryujinx"
TextAlignment="Center" />
<TextBlock
<TextBlock
Grid.Row="1"
Grid.Column="1"
Margin="0,0,0,0"
FontSize="16"
Text="(REE-YOU-JINX)"
TextAlignment="Center" />
<Button
<Button
Grid.Row="2"
Grid.Column="1"
Margin="0"
@ -83,27 +83,27 @@
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.ryujinx.org/">
<TextBlock
<TextBlock
Text="www.ryujinx.org"
TextAlignment="Center"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
</Button>
</Grid>
<TextBlock
</Button>
</Grid>
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Version}"
TextAlignment="Center" />
<TextBlock
<TextBlock
Grid.Row="2"
Margin="20"
HorizontalAlignment="Center"
MaxLines="2"
Text="{locale:Locale AboutDisclaimerMessage}"
TextAlignment="Center" />
<TextBlock
<TextBlock
Name="AmiiboLabel"
Grid.Row="3"
Margin="20"
@ -112,94 +112,94 @@
PointerPressed="AmiiboLabel_OnPointerPressed"
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
TextAlignment="Center" />
<StackPanel
<StackPanel
Grid.Row="4"
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="10">
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://www.patreon.com/ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Patreon" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Button
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="GitHub" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Button
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://discordapp.com/invite/N2FmfVc">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Discord" />
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Button
</Grid>
</Button>
</StackPanel>
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
<Button
Height="65"
Background="Transparent"
Click="Button_OnClick"
Tag="https://twitter.com/RyujinxEmu">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
<TextBlock
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Center"
Text="Twitter" />
</Grid>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Border
</Grid>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Border
Grid.Row="1"
Grid.Column="1"
Width="2"
@ -207,62 +207,62 @@
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1,0,0,0">
<Separator Width="0" />
</Border>
<Grid
<Separator Width="0" />
</Border>
<Grid
Grid.Row="1"
Grid.Column="2"
Margin="20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxAboutTitle}"
TextDecorations="Underline" />
<TextBlock
<TextBlock
Grid.Row="1"
Margin="20,5,5,5"
LineHeight="20"
Text="{locale:Locale AboutRyujinxAboutContent}" />
<TextBlock
<TextBlock
Grid.Row="2"
Margin="0,10,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
TextDecorations="Underline" />
<TextBlock
<TextBlock
Grid.Row="3"
Margin="20,5,5,5"
LineHeight="20"
Text="{Binding Developers}" />
<Button
<Button
Grid.Row="4"
HorizontalAlignment="Right"
Background="Transparent"
Click="Button_OnClick"
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
<TextBlock
<TextBlock
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
TextAlignment="Right"
TextDecorations="Underline"
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
</Button>
<TextBlock
</Button>
<TextBlock
Grid.Row="5"
Margin="0,0,0,0"
FontWeight="Bold"
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
TextDecorations="Underline" />
<Border
<Border
Grid.Row="6"
Width="460"
Height="200"
@ -271,12 +271,12 @@
VerticalAlignment="Stretch"
BorderBrush="White"
BorderThickness="1">
<TextBlock
<TextBlock
Name="SupportersTextBlock"
VerticalAlignment="Top"
Text="{Binding Supporters}"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid>
</Border>
</Grid>
</Grid>
</window:StyleableWindow>

View File

@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows
{
public class AboutWindow : StyleableWindow
public partial class AboutWindow : StyleableWindow
{
public AboutWindow()
{
@ -39,15 +39,6 @@ namespace Ryujinx.Ava.Ui.Windows
public string Developers => string.Format(LocaleManager.Instance["AboutPageDeveloperListMore"], "gdkchan, Ac_K, Thog, rip in peri peri, LDj3SNuD, emmaus, Thealexbarney, Xpl0itR, GoffyDude, »jD«");
public TextBlock SupportersTextBlock { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
SupportersTextBlock = this.FindControl<TextBlock>("SupportersTextBlock");
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
if (sender is Button button)

View File

@ -7,7 +7,7 @@ using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Windows
{
public class AmiiboWindow : StyleableWindow
public partial class AmiiboWindow : StyleableWindow
{
public AmiiboWindow(bool showAll, string lastScannedAmiiboId, string titleId)
{
@ -44,11 +44,6 @@ namespace Ryujinx.Ava.Ui.Windows
public Amiibo.AmiiboApi ScannedAmiibo { get; set; }
public AmiiboWindowViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void ScanButton_Click(object sender, RoutedEventArgs e)
{
if (ViewModel.AmiiboSelectedIndex > -1)

View File

@ -1,36 +1,35 @@
<Window xmlns="https://github.com/avaloniaui"
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
x:Class="Ryujinx.Ava.Ui.Windows.AvatarWindow"
CanResize="False"
Margin="0"
Padding="0"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
WindowStartupLocation="CenterOwner"
x:CompileBindings="True"
x:DataType="viewModels:AvatarProfileViewModel"
SizeToContent="WidthAndHeight">
x:DataType="viewModels:AvatarProfileViewModel">
<Design.DataContext>
<viewModels:AvatarProfileViewModel />
</Design.DataContext>
<Window.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</Window.Resources>
<Grid Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<UserControl.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</UserControl.Resources>
<Grid Margin="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Width="600" Height="500"
<ListBox Grid.Row="1" BorderThickness="0" SelectedIndex="{Binding SelectedIndex}" Height="400"
Items="{Binding Images}" HorizontalAlignment="Stretch" VerticalAlignment="Center">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" MaxWidth="600" Margin="0" HorizontalAlignment="Center" />
<WrapPanel Orientation="Horizontal" MaxWidth="700" Margin="0" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
@ -45,9 +44,9 @@
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="10" Margin="10" HorizontalAlignment="Center">
<Button Content="{Locale:Locale AvatarChoose}" Width="200" Name="ChooseButton" Click="ChooseButton_OnClick" />
<ui:ColorPickerButton Color="{Binding BackgroundColor, Mode=TwoWay}" Name="ColorButton" />
<Button HorizontalAlignment="Right" Content="{Locale:Locale AvatarClose}" Click="CloseButton_OnClick"
<Button HorizontalAlignment="Right" Content="{Locale:Locale Discard}" Click="CloseButton_OnClick"
Name="CloseButton"
Width="200" />
</StackPanel>
</Grid>
</Window>
</UserControl>

View File

@ -1,70 +1,76 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Navigation;
using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using System;
namespace Ryujinx.Ava.Ui.Windows
{
public class AvatarWindow : StyleableWindow
public partial class AvatarWindow : UserControl
{
private NavigationDialogHost _parent;
private TempProfile _profile;
public AvatarWindow(ContentManager contentManager)
{
ContentManager = contentManager;
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
DataContext = ViewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
}
public AvatarWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
AddHandler(Frame.NavigatedToEvent, (s, e) =>
{
NavigatedTo(e);
}, RoutingStrategies.Direct);
}
private void NavigatedTo(NavigationEventArgs arg)
{
if (Program.PreviewerDetached)
{
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["AvatarWindowTitle"];
if (arg.NavigationMode == NavigationMode.New)
{
(_parent, _profile) = ((NavigationDialogHost, TempProfile))arg.Parameter;
ContentManager = _parent.ContentManager;
if (Program.PreviewerDetached)
{
ViewModel = new AvatarProfileViewModel(() => ViewModel.ReloadImages());
}
DataContext = ViewModel;
}
}
}
public ContentManager ContentManager { get; }
public byte[] SelectedImage { get; set; }
public ContentManager ContentManager { get; private set; }
internal AvatarProfileViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnClosed(EventArgs e)
{
ViewModel.Dispose();
base.OnClosed(e);
}
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
{
Close();
ViewModel.Dispose();
_parent.GoBack();
}
private void ChooseButton_OnClick(object sender, RoutedEventArgs e)
{
if (ViewModel.SelectedIndex > -1)
{
SelectedImage = ViewModel.SelectedImage;
_profile.Image = ViewModel.SelectedImage;
Close();
ViewModel.Dispose();
_parent.GoBack();
}
}
}

View File

@ -12,7 +12,7 @@ using System.Linq;
namespace Ryujinx.Ava.Ui.Windows
{
public class CheatWindow : StyleableWindow
public partial class CheatWindow : StyleableWindow
{
private readonly string _enabledCheatsPath;
public bool NoCheatsFound { get; }
@ -102,11 +102,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void Save()
{
if (NoCheatsFound)

View File

@ -24,11 +24,10 @@ using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Ui.Windows
{
public class ControllerSettingsWindow : UserControl
public partial class ControllerSettingsWindow : UserControl
{
private bool _dialogOpen;
public Grid SettingButtons { get; set; }
private ButtonKeyAssigner _currentAssigner;
internal ControllerSettingsViewModel ViewModel { get; set; }
@ -48,13 +47,6 @@ namespace Ryujinx.Ava.Ui.Windows
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
SettingButtons = this.FindControl<Grid>("SettingButtons");
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
@ -165,7 +157,6 @@ namespace Ryujinx.Ava.Ui.Windows
_dialogOpen = true;
var result = await ContentDialogHelper.CreateConfirmationDialog(
this.GetVisualRoot() as StyleableWindow,
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmMessage"],
LocaleManager.Instance["DialogControllerSettingsModifiedConfirmSubMessage"],
LocaleManager.Instance["InputDialogYes"],

View File

@ -1,7 +1,6 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LibHac.Common;
using LibHac.Fs;
@ -27,7 +26,7 @@ using Path = System.IO.Path;
namespace Ryujinx.Ava.Ui.Windows
{
public class DlcManagerWindow : StyleableWindow
public partial class DlcManagerWindow : StyleableWindow
{
private readonly List<DlcContainer> _dlcContainerList;
private readonly string _dlcJsonPath;
@ -35,7 +34,6 @@ namespace Ryujinx.Ava.Ui.Windows
public VirtualFileSystem VirtualFileSystem { get; }
public AvaloniaList<DlcModel> Dlcs { get; set; }
public Grid DlcGrid { get; private set; }
public ulong TitleId { get; }
public string TitleName { get; }
@ -47,7 +45,7 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
}
@ -72,7 +70,7 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent();
AttachDebugDevTools();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"];
LoadDlcs();
@ -84,15 +82,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools();
}
private void InitializeComponent()
{
Dlcs = new AvaloniaList<DlcModel>();
AvaloniaXamlLoader.Load(this);
DlcGrid = this.FindControl<Grid>("DlcGrid");
}
private void LoadDlcs()
{
foreach (DlcContainer dlcContainer in _dlcContainerList)
@ -129,8 +118,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
await ContentDialogHelper.CreateErrorDialog(this,
string.Format(LocaleManager.Instance[
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[
"DialogDlcLoadNcaErrorMessage"], ex.Message, containerPath));
});
}
@ -180,7 +168,7 @@ namespace Ryujinx.Ava.Ui.Windows
if (!containsDlc)
{
await ContentDialogHelper.CreateErrorDialog(this, LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
}
}
}
@ -189,7 +177,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (removeSelectedOnly)
{
Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
Dlcs.RemoveAll(Dlcs.Where(x => x.IsEnabled).ToList());
}
else
{

View File

@ -38,18 +38,6 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
<ContentControl
Grid.Row="1"
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog
Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="True"
KeyboardNavigation.IsTabStop="False" />
</ContentControl>
<StackPanel Grid.Row="0" IsVisible="False">
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
@ -123,14 +111,20 @@
Command="{ReflectionBinding ToggleFullscreen}"
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
InputGesture="F11" />
<MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
<MenuItem>
<MenuItem.Icon>
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsStartGamesInFullscreen}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{locale:Locale MenuBarOptionsShowConsole}">
<MenuItem IsVisible="{Binding ShowConsoleVisible}">
<MenuItem.Icon>
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding ShowConsole, Mode=TwoWay}"
MinWidth="250">
<TextBlock Text="{locale:Locale MenuBarOptionsShowConsole}"/>
</CheckBox>
</MenuItem.Icon>
</MenuItem>
<Separator />
@ -179,6 +173,10 @@
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_CN"
Header="Simplified Chinese" />
<MenuItem
Command="{ReflectionBinding ChangeLanguage}"
CommandParameter="zh_TW"
Header="Traditional Chinese (Taiwan)" />
</MenuItem>
<Separator />
<MenuItem

View File

@ -36,7 +36,7 @@ using InputManager = Ryujinx.Input.HLE.InputManager;
using ProgressBar = Avalonia.Controls.ProgressBar;
namespace Ryujinx.Ava.Ui.Windows
{
public class MainWindow : StyleableWindow
public partial class MainWindow : StyleableWindow
{
private bool _canUpdate;
private bool _isClosing;
@ -62,22 +62,6 @@ namespace Ryujinx.Ava.Ui.Windows
public InputManager InputManager { get; private set; }
internal RendererControl GlRenderer { get; private set; }
public ContentControl ContentFrame { get; private set; }
public TextBlock LoadStatus { get; private set; }
public TextBlock FirmwareStatus { get; private set; }
public TextBox SearchBox { get; private set; }
public ProgressBar LoadProgressBar { get; private set; }
public Menu Menu { get; private set; }
public MenuItem UpdateMenuItem { get; private set; }
public MenuItem ActionsMenuItem { get; private set; }
public GameGridView GameGrid { get; private set; }
public GameListView GameList { get; private set; }
public OffscreenTextBox HiddenTextBox { get; private set; }
public HotKeyControl FullscreenHotKey { get; private set; }
public HotKeyControl FullscreenHotKey2 { get; private set; }
public HotKeyControl DockToggleHotKey { get; private set; }
public HotKeyControl ExitHotKey { get; private set; }
public ToggleSplitButton VolumeStatus { get; set; }
internal MainWindowViewModel ViewModel { get; private set; }
public SettingsWindow SettingsWindow { get; set; }
@ -102,6 +86,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel;
InitializeComponent();
Load();
AttachDebugDevTools();
UiHandler = new AvaHostUiHandler(this);
@ -192,7 +177,9 @@ namespace Ryujinx.Ava.Ui.Windows
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledMessage"];
string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckLoggingEnabledConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result != UserResult.Yes)
{
@ -205,9 +192,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value))
{
string mainMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledMessage"];
string secondaryMessage = LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
string secondaryMessage =
LocaleManager.Instance["DialogPerformanceCheckShaderDumpEnabledConfirmMessage"];
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(this, mainMessage, secondaryMessage, LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]);
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(mainMessage, secondaryMessage,
LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
if (result != UserResult.Yes)
{
@ -231,7 +221,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
if (AppHost != null)
{
await ContentDialogHelper.CreateInfoDialog(this,
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedMessage"],
LocaleManager.Instance["DialogLoadAppGameAlreadyLoadedSubMessage"],
LocaleManager.Instance["InputDialogOk"],
@ -254,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
PrepareLoadScreen();
_mainViewContent = ContentFrame.Content as Control;
_mainViewContent = Content.Content as Control;
GlRenderer = new RendererControl(3, 3, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
AppHost = new AppHost(GlRenderer, InputManager, path, VirtualFileSystem, ContentManager, AccountManager, _userChannelPersistence, this);
@ -321,7 +311,7 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() =>
{
ContentFrame.Content = GlRenderer;
Content.Content = GlRenderer;
if (startFullscreen && WindowState != WindowState.FullScreen)
{
@ -365,9 +355,9 @@ namespace Ryujinx.Ava.Ui.Windows
Dispatcher.UIThread.InvokeAsync(() =>
{
if (ContentFrame.Content != _mainViewContent)
if (Content.Content != _mainViewContent)
{
ContentFrame.Content = _mainViewContent;
Content.Content = _mainViewContent;
}
ViewModel.ShowMenuAndStatusBar = true;
@ -501,27 +491,8 @@ namespace Ryujinx.Ava.Ui.Windows
ViewModel.IsAppletMenuActive = hasApplet;
}
private void InitializeComponent()
private void Load()
{
AvaloniaXamlLoader.Load(this);
ContentFrame = this.FindControl<ContentControl>("Content");
GameList = this.FindControl<GameListView>("GameList");
LoadStatus = this.FindControl<TextBlock>("LoadStatus");
FirmwareStatus = this.FindControl<TextBlock>("FirmwareStatus");
LoadProgressBar = this.FindControl<ProgressBar>("LoadProgressBar");
SearchBox = this.FindControl<TextBox>("SearchBox");
Menu = this.FindControl<Menu>("Menu");
UpdateMenuItem = this.FindControl<MenuItem>("UpdateMenuItem");
GameGrid = this.FindControl<GameGridView>("GameGrid");
HiddenTextBox = this.FindControl<OffscreenTextBox>("HiddenTextBox");
FullscreenHotKey = this.FindControl<HotKeyControl>("FullscreenHotKey");
FullscreenHotKey2 = this.FindControl<HotKeyControl>("FullscreenHotKey2");
DockToggleHotKey = this.FindControl<HotKeyControl>("DockToggleHotKey");
ExitHotKey = this.FindControl<HotKeyControl>("ExitHotKey");
VolumeStatus = this.FindControl<ToggleSplitButton>("VolumeStatus");
ActionsMenuItem = this.FindControl<MenuItem>("ActionsMenuItem");
VolumeStatus.Click += VolumeStatus_CheckedChanged;
GameGrid.ApplicationOpened += Application_Opened;
@ -710,7 +681,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
_isClosing = await ContentDialogHelper.CreateExitDialog(this);
_isClosing = await ContentDialogHelper.CreateExitDialog();
if (_isClosing)
{

View File

@ -9,13 +9,14 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows
{
public class MotionSettingsWindow : UserControl
public partial class MotionSettingsWindow : UserControl
{
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public MotionSettingsWindow()
{
InitializeComponent();
DataContext = _viewmodel;
}
public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
@ -36,46 +37,36 @@ namespace Ryujinx.Ava.Ui.Windows
};
InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
}
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
public static async Task Show(ControllerSettingsViewModel viewmodel)
{
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
if (contentDialog != null)
ContentDialog contentDialog = new ContentDialog
{
contentDialog.Title = LocaleManager.Instance["ControllerMotionTitle"];
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
contentDialog.SecondaryButtonText = "";
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
contentDialog.Content = content;
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewmodel.Slot;
config.EnableMotion = content._viewmodel.EnableMotion;
config.Sensitivity = content._viewmodel.Sensitivity;
config.GyroDeadzone = content._viewmodel.GyroDeadzone;
config.AltSlot = content._viewmodel.AltSlot;
config.DsuServerHost = content._viewmodel.DsuServerHost;
config.DsuServerPort = content._viewmodel.DsuServerPort;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
config.MirrorInput = content._viewmodel.MirrorInput;
};
Title = LocaleManager.Instance["ControllerMotionTitle"],
PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
Content = content
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.Slot = content._viewmodel.Slot;
config.EnableMotion = content._viewmodel.EnableMotion;
config.Sensitivity = content._viewmodel.Sensitivity;
config.GyroDeadzone = content._viewmodel.GyroDeadzone;
config.AltSlot = content._viewmodel.AltSlot;
config.DsuServerHost = content._viewmodel.DsuServerHost;
config.DsuServerPort = content._viewmodel.DsuServerPort;
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
config.MirrorInput = content._viewmodel.MirrorInput;
};
await contentDialog.ShowAsync();
}
await contentDialog.ShowAsync();
}
}
}

View File

@ -9,13 +9,14 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.Ui.Windows
{
public class RumbleSettingsWindow : UserControl
public partial class RumbleSettingsWindow : UserControl
{
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
public RumbleSettingsWindow()
{
InitializeComponent();
DataContext = _viewmodel;
}
public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
@ -24,44 +25,34 @@ namespace Ryujinx.Ava.Ui.Windows
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
{
StrongRumble = config.StrongRumble,
WeakRumble = config.WeakRumble
StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble
};
InitializeComponent();
}
private void InitializeComponent()
{
DataContext = _viewmodel;
AvaloniaXamlLoader.Load(this);
}
public static async Task Show(ControllerSettingsViewModel viewmodel, StyleableWindow window)
public static async Task Show(ControllerSettingsViewModel viewmodel)
{
ContentDialog contentDialog = window.ContentDialog;
string name = string.Empty;
RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
if (contentDialog != null)
ContentDialog contentDialog = new ContentDialog
{
contentDialog.Title = LocaleManager.Instance["ControllerRumbleTitle"];
contentDialog.PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"];
contentDialog.SecondaryButtonText = "";
contentDialog.CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"];
contentDialog.Content = content;
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewmodel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble;
};
await contentDialog.ShowAsync();
}
Title = LocaleManager.Instance["ControllerRumbleTitle"],
PrimaryButtonText = LocaleManager.Instance["ControllerSettingsSave"],
SecondaryButtonText = "",
CloseButtonText = LocaleManager.Instance["ControllerSettingsClose"],
Content = content,
};
contentDialog.PrimaryButtonClick += (sender, args) =>
{
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
config.StrongRumble = content._viewmodel.StrongRumble;
config.WeakRumble = content._viewmodel.WeakRumble;
};
await contentDialog.ShowAsync();
}
}
}

View File

@ -31,16 +31,11 @@
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1"
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<ScrollViewer Name="UiPage"
Margin="0,0,10,0"
@ -246,6 +241,22 @@
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleUpHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleUp, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center" Text="{locale:Locale SettingsTabHotkeysResScaleDownHotkey}" Width="230" />
<ToggleButton Width="90" Height="27" Checked="Button_Checked" Unchecked="Button_Unchecked">
<TextBlock
Text="{Binding KeyboardHotkeys.ResScaleDown, Mode=TwoWay, Converter={StaticResource Key}}"
TextAlignment="Center" />
</ToggleButton>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>

View File

@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Data.Converters;
@ -28,24 +29,8 @@ using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows
{
public class SettingsWindow : StyleableWindow
public partial class SettingsWindow : StyleableWindow
{
private ListBox _gameList;
private TextBox _pathBox;
private AutoCompleteBox _timeZoneBox;
private ControllerSettingsWindow _controllerSettings;
// Pages
private Control _uiPage;
private Control _inputPage;
private Control _hotkeysPage;
private Control _systemPage;
private Control _cpuPage;
private Control _graphicsPage;
private Control _audioPage;
private Control _networkPage;
private Control _loggingPage;
private NavigationView _navPanel;
private ButtonKeyAssigner _currentAssigner;
internal SettingsViewModel ViewModel { get; set; }
@ -58,6 +43,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel;
InitializeComponent();
Load();
AttachDebugDevTools();
FuncMultiValueConverter<string, string> converter = new(parts => string.Format("{0} {1} {2}", parts.ToArray()));
@ -66,7 +52,7 @@ namespace Ryujinx.Ava.Ui.Windows
tzMultiBinding.Bindings.Add(new Binding("Location"));
tzMultiBinding.Bindings.Add(new Binding("Abbreviation"));
_timeZoneBox.ValueMemberBinding = tzMultiBinding;
TimeZoneBox.ValueMemberBinding = tzMultiBinding;
}
public SettingsWindow()
@ -75,6 +61,7 @@ namespace Ryujinx.Ava.Ui.Windows
DataContext = ViewModel;
InitializeComponent();
Load();
AttachDebugDevTools();
}
@ -84,31 +71,11 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools();
}
private void InitializeComponent()
private void Load()
{
AvaloniaXamlLoader.Load(this);
_pathBox = this.FindControl<TextBox>("PathBox");
_gameList = this.FindControl<ListBox>("GameList");
_timeZoneBox = this.FindControl<AutoCompleteBox>("TimeZoneBox");
_controllerSettings = this.FindControl<ControllerSettingsWindow>("ControllerSettings");
_uiPage = this.FindControl<Control>("UiPage");
_inputPage = this.FindControl<Control>("InputPage");
_hotkeysPage = this.FindControl<Control>("HotkeysPage");
_systemPage = this.FindControl<Control>("SystemPage");
_cpuPage = this.FindControl<Control>("CpuPage");
_graphicsPage = this.FindControl<Control>("GraphicsPage");
_audioPage = this.FindControl<Control>("AudioPage");
_networkPage = this.FindControl<Control>("NetworkPage");
_loggingPage = this.FindControl<Control>("LoggingPage");
var pageGrid = this.FindControl<Grid>("Pages");
pageGrid.Children.Clear();
_navPanel = this.FindControl<NavigationView>("NavPanel");
_navPanel.SelectionChanged += NavPanelOnSelectionChanged;
_navPanel.SelectedItem = _navPanel.MenuItems.ElementAt(0);
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void Button_Checked(object sender, RoutedEventArgs e)
@ -174,31 +141,31 @@ namespace Ryujinx.Ava.Ui.Windows
switch (navitem.Tag.ToString())
{
case "UiPage":
_navPanel.Content = _uiPage;
NavPanel.Content = UiPage;
break;
case "InputPage":
_navPanel.Content = _inputPage;
NavPanel.Content = InputPage;
break;
case "HotkeysPage":
_navPanel.Content = _hotkeysPage;
NavPanel.Content = HotkeysPage;
break;
case "SystemPage":
_navPanel.Content = _systemPage;
NavPanel.Content = SystemPage;
break;
case "CpuPage":
_navPanel.Content = _cpuPage;
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
_navPanel.Content = _graphicsPage;
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
_navPanel.Content = _audioPage;
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
_navPanel.Content = _networkPage;
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
_navPanel.Content = _loggingPage;
NavPanel.Content = LoggingPage;
break;
}
}
@ -206,7 +173,7 @@ namespace Ryujinx.Ava.Ui.Windows
private async void AddButton_OnClick(object sender, RoutedEventArgs e)
{
string path = _pathBox.Text;
string path = PathBox.Text;
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path) && !ViewModel.GameDirectories.Contains(path))
{
@ -225,7 +192,7 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{
List<string> selected = new(_gameList.SelectedItems.Cast<string>());
List<string> selected = new(GameList.SelectedItems.Cast<string>());
foreach (string path in selected)
{
@ -279,7 +246,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
ViewModel.SaveSettings();
_controllerSettings?.SaveCurrentProfile();
ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window)
{
@ -289,7 +256,7 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e)
{
_controllerSettings.Dispose();
ControllerSettings.Dispose();
_currentAssigner?.Cancel();
_currentAssigner = null;
base.OnClosed(e);

View File

@ -11,7 +11,6 @@ namespace Ryujinx.Ava.Ui.Windows
{
public class StyleableWindow : Window
{
public ContentDialog ContentDialog { get; private set; }
public IBitmap IconImage { get; set; }
public StyleableWindow()
@ -26,15 +25,9 @@ namespace Ryujinx.Ava.Ui.Windows
IconImage = new Bitmap(stream);
}
public void LoadDialog()
{
ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
ContentDialog = this.FindControl<ContentDialog>("ContentDialog");
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)

View File

@ -28,14 +28,14 @@ using Avalonia.Threading;
namespace Ryujinx.Ava.Ui.Windows
{
public class TitleUpdateWindow : StyleableWindow
public partial class TitleUpdateWindow : StyleableWindow
{
private readonly string _updateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>();
public string TitleId { get; }
public string TitleName { get; }
@ -84,13 +84,6 @@ namespace Ryujinx.Ava.Ui.Windows
this.AttachDevTools();
}
private void InitializeComponent()
{
TitleUpdates = new AvaloniaList<TitleUpdateModel>();
AvaloniaXamlLoader.Load(this);
}
private void LoadUpdates()
{
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
@ -154,8 +147,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(this,
LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
});
}
}
@ -163,8 +155,7 @@ namespace Ryujinx.Ava.Ui.Windows
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(this,
string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
});
}
}

View File

@ -11,7 +11,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Ava.Ui.Windows
{
public class UpdaterWindow : StyleableWindow
public partial class UpdaterWindow : StyleableWindow
{
private readonly string _buildUrl;
private readonly MainWindow _mainWindow;
@ -36,21 +36,6 @@ namespace Ryujinx.Ava.Ui.Windows
_buildUrl = buildUrl;
}
public TextBlock MainText { get; set; }
public TextBlock SecondaryText { get; set; }
public ProgressBar ProgressBar { get; set; }
public StackPanel ButtonBox { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
MainText = this.FindControl<TextBlock>("MainText");
SecondaryText = this.FindControl<TextBlock>("SecondaryText");
ProgressBar = this.FindControl<ProgressBar>("ProgressBar");
ButtonBox = this.FindControl<StackPanel>("ButtonBox");
}
[DllImport("libc", SetLastError = true)]
private static extern int chmod(string path, uint mode);

View File

@ -1,107 +0,0 @@
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
x:Class="Ryujinx.Ava.Ui.Windows.UserProfileWindow"
xmlns:Locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
CanResize="False"
Width="850" MinHeight="550" Height="550"
WindowStartupLocation="CenterOwner"
SizeToContent="Manual"
MinWidth="600">
<Design.DataContext>
<viewModels:UserProfileViewModel />
</Design.DataContext>
<Window.Resources>
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
</Window.Resources>
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl
Focusable="False"
IsVisible="False"
KeyboardNavigation.IsTabStop="False">
<ui:ContentDialog Name="ContentDialog"
IsPrimaryButtonEnabled="True"
IsSecondaryButtonEnabled="True"
IsVisible="False" />
</ContentControl>
<TextBlock Text="{Locale:Locale UserProfilesSelectedUserProfile}" />
<Grid Grid.Row="1" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Height="96" Width="96"
Source="{Binding SelectedProfile.Image, Converter={StaticResource ByteImage}}" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="1" Spacing="10"
Margin="5, 10">
<TextBox Name="NameBox" Text="{Binding SelectedProfile.Name, Mode=OneWay}"
HorizontalAlignment="Stretch" />
<TextBlock Text="{Binding SelectedProfile.UserId}" />
</StackPanel>
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Grid.Column="2" Spacing="10"
Margin="5">
<Button Content="{Locale:Locale UserProfilesSaveProfileName}" Name="SetNameButton"
Click="SetNameButton_OnClick" />
<Button Name="SelectProfileImage" Command="{Binding ChooseProfileImage}"
Content="{Locale:Locale UserProfilesChangeProfileImage}" />
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Locale:Locale UserProfilesAvailableUserProfiles}" />
<ListBox Grid.Row="1" Margin="10" Name="ProfilesList" DoubleTapped="ProfilesList_DoubleTapped"
Items="{Binding Profiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="{DynamicResource ThemeAccentColorBrush}"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="5" MinWidth="5"
IsVisible="{Binding IsOpened}" />
<Image Grid.Column="0" Height="96" Width="96"
Source="{Binding Image, Converter={StaticResource ByteImage}}" />
<StackPanel Margin="10" Orientation="Vertical" HorizontalAlignment="Stretch"
VerticalAlignment="Center" Grid.Column="1">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding UserId}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="10,0" Spacing="10" HorizontalAlignment="Stretch">
<Button Content="{Locale:Locale UserProfilesAddNewProfile}" Command="{Binding AddUser}" />
<Button IsEnabled="{Binding IsSelectedProfileDeletable}"
Content="{Locale:Locale UserProfilesDeleteSelectedProfile}" Command="{Binding DeleteUser}" />
<Button HorizontalAlignment="Right" Content="{Locale:Locale UserProfilesClose}" Click="CloseButton_OnClick"
Name="CloseButton" />
</StackPanel>
</Grid>
</window:StyleableWindow>

View File

@ -1,102 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.ViewModels;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using System.Threading.Tasks;
using UserProfile = Ryujinx.Ava.Ui.Models.UserProfile;
namespace Ryujinx.Ava.Ui.Windows
{
public class UserProfileWindow : StyleableWindow
{
private TextBox _nameBox;
public UserProfileWindow(AccountManager accountManager, ContentManager contentManager,
VirtualFileSystem virtualFileSystem)
{
AccountManager = accountManager;
ContentManager = contentManager;
ViewModel = new UserProfileViewModel(this);
DataContext = ViewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
if (contentManager.GetCurrentFirmwareVersion() != null)
{
Task.Run(() =>
{
AvatarProfileViewModel.PreloadAvatars(contentManager, virtualFileSystem);
});
}
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
}
public UserProfileWindow()
{
ViewModel = new UserProfileViewModel();
DataContext = ViewModel;
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UserProfileWindowTitle"];
}
public AccountManager AccountManager { get; }
public ContentManager ContentManager { get; }
public UserProfileViewModel ViewModel { get; set; }
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_nameBox = this.FindControl<TextBox>("NameBox");
}
private void ProfilesList_DoubleTapped(object sender, RoutedEventArgs e)
{
if (sender is ListBox listBox)
{
int selectedIndex = listBox.SelectedIndex;
if (selectedIndex >= 0 && selectedIndex < ViewModel.Profiles.Count)
{
ViewModel.SelectedProfile = ViewModel.Profiles[selectedIndex];
AccountManager.OpenUser(ViewModel.SelectedProfile.UserId);
ViewModel.LoadProfiles();
foreach (UserProfile profile in ViewModel.Profiles)
{
profile.UpdateState();
}
}
}
}
private void CloseButton_OnClick(object sender, RoutedEventArgs e)
{
Close();
}
private void SetNameButton_OnClick(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(_nameBox.Text))
{
ViewModel.SelectedProfile.Name = _nameBox.Text;
AccountManager.SetUserName(ViewModel.SelectedProfile.UserId, _nameBox.Text);
}
}
}
}

View File

@ -7,5 +7,7 @@
public Key ShowUi { get; set; }
public Key Pause { get; set; }
public Key ToggleMute { get; set; }
public Key ResScaleUp { get; set; }
public Key ResScaleDown { get; set; }
}
}

View File

@ -208,7 +208,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
}
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
@ -262,43 +261,63 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
target.SynchronizeMemory();
target.SetData(data);
target.SignalModified();
return;
}
else if (srcCalculator.LayoutMatches(dstCalculator))
{
srcSpan.CopyTo(dstSpan); // No layout conversion has to be performed, just copy the data entirely.
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, dstSpan);
// No layout conversion has to be performed, just copy the data entirely.
memoryManager.Write(dstGpuVa + (ulong)dstBaseOffset, srcSpan);
return;
}
}
unsafe bool Convert<T>(Span<byte> dstSpan, ReadOnlySpan<byte> srcSpan) where T : unmanaged
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
if (srcLinear && dstLinear && srcBpp == dstBpp)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
// Optimized path for purely linear copies - we don't need to calculate every single byte offset,
// and we can make use of Span.CopyTo which is very very fast (even compared to pointers)
for (int y = 0; y < yCount; y++)
{
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
int srcOffset = srcCalculator.GetOffset(srcRegionX);
int dstOffset = dstCalculator.GetOffset(dstRegionX);
srcSpan.Slice(srcOffset - srcBaseOffset, xCount * srcBpp)
.CopyTo(dstSpan.Slice(dstOffset - dstBaseOffset, xCount * dstBpp));
}
}
else
{
fixed (byte* dstPtr = dstSpan, srcPtr = srcSpan)
{
byte* dstBase = dstPtr - dstBaseOffset; // Layout offset is relative to the base, so we need to subtract the span's offset.
byte* srcBase = srcPtr - srcBaseOffset;
for (int x = 0; x < xCount; x++)
for (int y = 0; y < yCount; y++)
{
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
srcCalculator.SetY(srcRegionY + y);
dstCalculator.SetY(dstRegionY + y);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
for (int x = 0; x < xCount; x++)
{
int srcOffset = srcCalculator.GetOffset(srcRegionX + x);
int dstOffset = dstCalculator.GetOffset(dstRegionX + x);
*(T*)(dstBase + dstOffset) = *(T*)(srcBase + srcOffset);
}
}
}
}
return true;
}
// OPT: This allocates a (potentially) huge temporary array and then copies an existing
// region of memory into it, data that might get overwritten entirely anyways. Ideally this should
// all be rewritten to use pooled arrays, but that gets complicated with packed data and strides
Span<byte> dstSpan = memoryManager.GetSpan(dstGpuVa + (ulong)dstBaseOffset, dstSize).ToArray();
bool _ = srcBpp switch
{
1 => Convert<byte>(dstSpan, srcSpan),

View File

@ -174,6 +174,14 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
if (info.Width == info.Height * info.Height)
{
// Possibly used for a "3D texture" drawn onto a 2D surface.
// Some games do this to generate a tone mapping LUT without rendering into 3D texture slices.
return false;
}
return true;
}

View File

@ -21,7 +21,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 1;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 3069;
private const uint CodeGenVersion = 3469;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@ -1,23 +1,21 @@
using FFmpeg.AutoGen;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Nvdec.FFmpeg
{
unsafe class FFmpegContext : IDisposable
{
private readonly AVCodec_decode _decodeFrame;
private static readonly av_log_set_callback_callback _logFunc;
private readonly FFCodec.AVCodec_decode _decodeFrame;
private static readonly FFmpegApi.av_log_set_callback_callback _logFunc;
private readonly AVCodec* _codec;
private AVPacket* _packet;
private AVCodecContext* _context;
public FFmpegContext(AVCodecID codecId)
{
_codec = ffmpeg.avcodec_find_decoder(codecId);
_codec = FFmpegApi.avcodec_find_decoder(codecId);
if (_codec == null)
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, $"Codec wasn't found. Make sure you have the {codecId} codec present in your FFmpeg installation.");
@ -25,7 +23,7 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return;
}
_context = ffmpeg.avcodec_alloc_context3(_codec);
_context = FFmpegApi.avcodec_alloc_context3(_codec);
if (_context == null)
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec context couldn't be allocated.");
@ -33,14 +31,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return;
}
if (ffmpeg.avcodec_open2(_context, _codec, null) != 0)
if (FFmpegApi.avcodec_open2(_context, _codec, null) != 0)
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Codec couldn't be opened.");
return;
}
_packet = ffmpeg.av_packet_alloc();
_packet = FFmpegApi.av_packet_alloc();
if (_packet == null)
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, "Packet couldn't be allocated.");
@ -48,52 +46,39 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
return;
}
_decodeFrame = Marshal.GetDelegateForFunctionPointer<AVCodec_decode>(_codec->decode.Pointer);
int avCodecRawVersion = FFmpegApi.avcodec_version();
int avCodecMajorVersion = avCodecRawVersion >> 16;
int avCodecMinorVersion = (avCodecRawVersion >> 8) & 0xFF;
// libavcodec 59.24 changed AvCodec to move its private API and also move the codec function to an union.
if (avCodecMajorVersion > 59 || (avCodecMajorVersion == 59 && avCodecMinorVersion > 24))
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodec*)_codec)->CodecCallback);
}
// libavcodec 59.x changed AvCodec private API layout.
else if (avCodecMajorVersion == 59)
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodec>*)_codec)->Decode);
}
// libavcodec 58.x and lower
else
{
_decodeFrame = Marshal.GetDelegateForFunctionPointer<FFCodec.AVCodec_decode>(((FFCodecLegacy<AVCodecLegacy>*)_codec)->Decode);
}
}
static FFmpegContext()
{
SetRootPath();
_logFunc = Log;
// Redirect log output.
ffmpeg.av_log_set_level(ffmpeg.AV_LOG_MAX_OFFSET);
ffmpeg.av_log_set_callback(_logFunc);
FFmpegApi.av_log_set_level(AVLog.MaxOffset);
FFmpegApi.av_log_set_callback(_logFunc);
}
private static void SetRootPath()
private static void Log(void* ptr, AVLog level, string format, byte* vl)
{
if (OperatingSystem.IsLinux())
{
// Configure FFmpeg search path
Process lddProcess = Process.Start(new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = "-c \"ldd $(which ffmpeg 2>/dev/null) | grep libavfilter\" 2>/dev/null",
UseShellExecute = false,
RedirectStandardOutput = true
});
string lddOutput = lddProcess.StandardOutput.ReadToEnd();
lddProcess.WaitForExit();
lddProcess.Close();
if (lddOutput.Contains(" => "))
{
ffmpeg.RootPath = Path.GetDirectoryName(lddOutput.Split(" => ")[1]);
}
else
{
Logger.Error?.PrintMsg(LogClass.FFmpeg, "FFmpeg wasn't found. Make sure that you have it installed and up to date.");
}
}
}
private static void Log(void* p0, int level, string format, byte* vl)
{
if (level > ffmpeg.av_log_get_level())
if (level > FFmpegApi.av_log_get_level())
{
return;
}
@ -102,65 +87,67 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
byte* lineBuffer = stackalloc byte[lineSize];
int printPrefix = 1;
ffmpeg.av_log_format_line(p0, level, format, vl, lineBuffer, lineSize, &printPrefix);
FFmpegApi.av_log_format_line(ptr, level, format, vl, lineBuffer, lineSize, &printPrefix);
string line = Marshal.PtrToStringAnsi((IntPtr)lineBuffer).Trim();
switch (level)
{
case ffmpeg.AV_LOG_PANIC:
case ffmpeg.AV_LOG_FATAL:
case ffmpeg.AV_LOG_ERROR:
case AVLog.Panic:
case AVLog.Fatal:
case AVLog.Error:
Logger.Error?.Print(LogClass.FFmpeg, line);
break;
case ffmpeg.AV_LOG_WARNING:
case AVLog.Warning:
Logger.Warning?.Print(LogClass.FFmpeg, line);
break;
case ffmpeg.AV_LOG_INFO:
case AVLog.Info:
Logger.Info?.Print(LogClass.FFmpeg, line);
break;
case ffmpeg.AV_LOG_VERBOSE:
case ffmpeg.AV_LOG_DEBUG:
case ffmpeg.AV_LOG_TRACE:
case AVLog.Verbose:
case AVLog.Debug:
Logger.Debug?.Print(LogClass.FFmpeg, line);
break;
case AVLog.Trace:
Logger.Trace?.Print(LogClass.FFmpeg, line);
break;
}
}
public int DecodeFrame(Surface output, ReadOnlySpan<byte> bitstream)
{
ffmpeg.av_frame_unref(output.Frame);
FFmpegApi.av_frame_unref(output.Frame);
int result;
int gotFrame;
fixed (byte* ptr = bitstream)
{
_packet->data = ptr;
_packet->size = bitstream.Length;
_packet->Data = ptr;
_packet->Size = bitstream.Length;
result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
}
if (gotFrame == 0)
{
ffmpeg.av_frame_unref(output.Frame);
FFmpegApi.av_frame_unref(output.Frame);
// If the frame was not delivered, it was probably delayed.
// Get the next delayed frame by passing a 0 length packet.
_packet->data = null;
_packet->size = 0;
_packet->Data = null;
_packet->Size = 0;
result = _decodeFrame(_context, output.Frame, &gotFrame, _packet);
// We need to set B frames to 0 as we already consumed all delayed frames.
// This prevents the decoder from trying to return a delayed frame next time.
_context->has_b_frames = 0;
_context->HasBFrames = 0;
}
ffmpeg.av_packet_unref(_packet);
FFmpegApi.av_packet_unref(_packet);
if (gotFrame == 0)
{
ffmpeg.av_frame_unref(output.Frame);
FFmpegApi.av_frame_unref(output.Frame);
return -1;
}
@ -172,14 +159,14 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
{
fixed (AVPacket** ppPacket = &_packet)
{
ffmpeg.av_packet_free(ppPacket);
FFmpegApi.av_packet_free(ppPacket);
}
ffmpeg.avcodec_close(_context);
FFmpegApi.avcodec_close(_context);
fixed (AVCodecContext** ppContext = &_context)
{
ffmpeg.avcodec_free_context(ppContext);
FFmpegApi.avcodec_free_context(ppContext);
}
}
}

View File

@ -1,4 +1,4 @@
using FFmpeg.AutoGen;
using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video;
using System;

View File

@ -0,0 +1,25 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodec
{
#pragma warning disable CS0649
public unsafe byte* Name;
public unsafe byte* LongName;
public int Type;
public AVCodecID Id;
public int Capabilities;
public byte MaxLowRes;
public unsafe AVRational* SupportedFramerates;
public IntPtr PixFmts;
public IntPtr SupportedSamplerates;
public IntPtr SampleFmts;
// Deprecated
public unsafe ulong* ChannelLayouts;
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
#pragma warning restore CS0649
}
}

View File

@ -0,0 +1,171 @@
using Ryujinx.Common.Memory;
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodecContext
{
#pragma warning disable CS0649
public unsafe IntPtr AvClass;
public int LogLevelOffset;
public int CodecType;
public unsafe AVCodecLegacy* Codec;
public AVCodecID CodecId;
public uint CodecTag;
public IntPtr PrivData;
public IntPtr Internal;
public IntPtr Opaque;
public long BitRate;
public int BitRateTolerance;
public int GlobalQuality;
public int CompressionLevel;
public int Flags;
public int Flags2;
public IntPtr ExtraData;
public int ExtraDataSize;
public AVRational TimeBase;
public int TicksPerFrame;
public int Delay;
public int Width;
public int Height;
public int CodedWidth;
public int CodedHeight;
public int GopSize;
public int PixFmt;
public IntPtr DrawHorizBand;
public IntPtr GetFormat;
public int MaxBFrames;
public float BQuantFactor;
public float BQuantOffset;
public int HasBFrames;
public float IQuantFactor;
public float IQuantOffset;
public float LumiMasking;
public float TemporalCplxMasking;
public float SpatialCplxMasking;
public float PMasking;
public float DarkMasking;
public int SliceCount;
public IntPtr SliceOffset;
public AVRational SampleAspectRatio;
public int MeCmp;
public int MeSubCmp;
public int MbCmp;
public int IldctCmp;
public int DiaSize;
public int LastPredictorCount;
public int MePreCmp;
public int PreDiaSize;
public int MeSubpelQuality;
public int MeRange;
public int SliceFlags;
public int MbDecision;
public IntPtr IntraMatrix;
public IntPtr InterMatrix;
public int IntraDcPrecision;
public int SkipTop;
public int SkipBottom;
public int MbLmin;
public int MbLmax;
public int BidirRefine;
public int KeyintMin;
public int Refs;
public int Mv0Threshold;
public int ColorPrimaries;
public int ColorPrc;
public int Colorspace;
public int ColorRange;
public int ChromaSampleLocation;
public int Slices;
public int FieldOrder;
public int SampleRate;
public int Channels;
public int SampleFmt;
public int FrameSize;
public int FrameNumber;
public int BlockAlign;
public int CutOff;
public ulong ChannelLayout;
public ulong RequestChannelLayout;
public int AudioServiceType;
public int RequestSampleFmt;
public IntPtr GetBuffer2;
public float QCompress;
public float QBlur;
public int QMin;
public int QMax;
public int MaxQdiff;
public int RcBufferSize;
public int RcOverrideCount;
public IntPtr RcOverride;
public long RcMaxRate;
public long RcMinRate;
public float RcMax_available_vbv_use;
public float RcMin_vbv_overflow_use;
public int RcInitialBufferOccupancy;
public int Trellis;
public IntPtr StatsOut;
public IntPtr StatsIn;
public int WorkaroundBugs;
public int StrictStdCompliance;
public int ErrorConcealment;
public int Debug;
public int ErrRecognition;
public long ReorderedOpaque;
public IntPtr HwAccel;
public IntPtr HwAccelContext;
public Array8<ulong> Error;
public int DctAlgo;
public int IdctAlgo;
public int BitsPerCodedSample;
public int BitsPerRawSample;
public int LowRes;
public int ThreadCount;
public int ThreadType;
public int ActiveThreadType;
public int ThreadSafeCallbacks;
public IntPtr Execute;
public IntPtr Execute2;
public int NsseWeight;
public int Profile;
public int Level;
public int SkipLoopFilter;
public int SkipIdct;
public int SkipFrame;
public IntPtr SubtitleHeader;
public int SubtitleHeaderSize;
public int InitialPadding;
public AVRational Framerate;
public int SwPixFmt;
public AVRational PktTimebase;
public IntPtr CodecDescriptor;
public long PtsCorrectionNumFaultyPts;
public long PtsCorrectionNumFaultyDts;
public long PtsCorrectionLastPts;
public long PtsCorrectionLastDts;
public IntPtr SubCharenc;
public int SubCharencMode;
public int SkipAlpha;
public int SeekPreroll;
public int DebugMv;
public IntPtr ChromaIntraMatrix;
public IntPtr DumpSeparator;
public IntPtr CodecWhitelist;
public uint Properties;
public IntPtr CodedSideData;
public int NbCodedSideData;
public IntPtr HwFramesCtx;
public int SubTextFormat;
public int TrailingPadding;
public long MaxPixels;
public IntPtr HwDeviceCtx;
public int HwAccelFlags;
public int applyCropping;
public int ExtraHwFrames;
public int DiscardDamagedPercentage;
public long MaxSamples;
public int ExportSideData;
public IntPtr GetEncodeBuffer;
#pragma warning restore CS0649
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
enum AVCodecID
{
AV_CODEC_ID_H264 = 27,
AV_CODEC_ID_VP8 = 139,
}
}

View File

@ -0,0 +1,26 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVCodecLegacy
{
#pragma warning disable CS0649
public unsafe byte* Name;
public unsafe byte* LongName;
public int Type;
public AVCodecID Id;
public int Capabilities;
public byte MaxLowRes;
public unsafe AVRational* SupportedFramerates;
public IntPtr PixFmts;
public IntPtr SupportedSamplerates;
public IntPtr SampleFmts;
// Deprecated
public unsafe ulong* ChannelLayouts;
public unsafe IntPtr PrivClass;
public IntPtr Profiles;
public unsafe byte* WrapperName;
public IntPtr ChLayouts;
#pragma warning restore CS0649
}
}

View File

@ -0,0 +1,37 @@
using Ryujinx.Common.Memory;
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVFrame
{
#pragma warning disable CS0649
public Array8<IntPtr> Data;
public Array8<int> LineSize;
public IntPtr ExtendedData;
public int Width;
public int Height;
public int NumSamples;
public int Format;
public int KeyFrame;
public int PictureType;
public AVRational SampleAspectRatio;
public long Pts;
public long PktDts;
public AVRational TimeBase;
public int CodedPictureNumber;
public int DisplayPictureNumber;
public int Quality;
public IntPtr Opaque;
public int RepeatPicture;
public int InterlacedFrame;
public int TopFieldFirst;
public int PaletteHasChanged;
public long ReorderedOpaque;
public int SampleRate;
public ulong ChannelLayout;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@ -0,0 +1,15 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
enum AVLog
{
Panic = 0,
Fatal = 8,
Error = 16,
Warning = 24,
Info = 32,
Verbose = 40,
Debug = 48,
Trace = 56,
MaxOffset = 64
}
}

View File

@ -0,0 +1,26 @@
using System;
using AVBufferRef = System.IntPtr;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct AVPacket
{
#pragma warning disable CS0649
public unsafe AVBufferRef *Buf;
public long Pts;
public long Dts;
public unsafe byte* Data;
public int Size;
public int StreamIndex;
public int Flags;
public IntPtr SizeData;
public int SizeDataElems;
public long Duration;
public long Position;
public IntPtr Opaque;
public unsafe AVBufferRef *OpaqueRef;
public AVRational TimeBase;
#pragma warning restore CS0649
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
public struct AVRational
{
public int Numerator;
public int Denominator;
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct FFCodec
{
public unsafe delegate int AVCodec_decode(AVCodecContext* avctx, void* outdata, int* got_frame_ptr, AVPacket* avpkt);
#pragma warning disable CS0649
public AVCodec Base;
public int CapsInternalOrCbType;
public int PrivDataSize;
public IntPtr UpdateThreadContext;
public IntPtr UpdateThreadContextForUser;
public IntPtr Defaults;
public IntPtr InitStaticData;
public IntPtr Init;
public IntPtr CodecCallback;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@ -0,0 +1,23 @@
using System;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
struct FFCodecLegacy<T> where T: struct
{
#pragma warning disable CS0649
public T Base;
public uint CapsInternalOrCbType;
public int PrivDataSize;
public IntPtr UpdateThreadContext;
public IntPtr UpdateThreadContextForUser;
public IntPtr Defaults;
public IntPtr InitStaticData;
public IntPtr Init;
public IntPtr EncodeSub;
public IntPtr Encode2;
public IntPtr Decode;
#pragma warning restore CS0649
// NOTE: There is more after, but the layout kind of changed a bit and we don't need more than this. This is safe as we only manipulate this behind a reference.
}
}

View File

@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Nvdec.FFmpeg.Native
{
static class FFmpegApi
{
public const string AvCodecLibraryName = "avcodec";
public const string AvUtilLibraryName = "avutil";
private static readonly Dictionary<string, (int, int)> _librariesWhitelist = new Dictionary<string, (int, int)>
{
{ AvCodecLibraryName, (58, 59) },
{ AvUtilLibraryName, (56, 57) }
};
private static string FormatLibraryNameForCurrentOs(string libraryName, int version)
{
if (OperatingSystem.IsWindows())
{
return $"{libraryName}-{version}.dll";
}
else if (OperatingSystem.IsLinux())
{
return $"lib{libraryName}.so.{version}";
}
else if (OperatingSystem.IsMacOS())
{
return $"lib{libraryName}.{version}.dylib";
}
else
{
throw new NotImplementedException($"Unsupported OS for FFmpeg: {RuntimeInformation.RuntimeIdentifier}");
}
}
private static bool TryLoadWhitelistedLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle)
{
handle = IntPtr.Zero;
if (_librariesWhitelist.TryGetValue(libraryName, out var value))
{
(int minVersion, int maxVersion) = value;
for (int version = maxVersion; version >= minVersion; version--)
{
if (NativeLibrary.TryLoad(FormatLibraryNameForCurrentOs(libraryName, version), assembly, searchPath, out handle))
{
return true;
}
}
}
return false;
}
static FFmpegApi()
{
NativeLibrary.SetDllImportResolver(typeof(FFmpegApi).Assembly, (name, assembly, path) =>
{
IntPtr handle;
if (name == AvUtilLibraryName && TryLoadWhitelistedLibrary(AvUtilLibraryName, assembly, path, out handle))
{
return handle;
}
else if (name == AvCodecLibraryName && TryLoadWhitelistedLibrary(AvCodecLibraryName, assembly, path, out handle))
{
return handle;
}
return IntPtr.Zero;
});
}
public unsafe delegate void av_log_set_callback_callback(void* a0, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string a2, byte* a3);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVFrame* av_frame_alloc();
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_frame_unref(AVFrame* frame);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_free(AVFrame* frame);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_set_level(AVLog level);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_set_callback(av_log_set_callback_callback callback);
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVLog av_log_get_level();
[DllImport(AvUtilLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_log_format_line(void* ptr, AVLog level, [MarshalAs(UnmanagedType.LPUTF8Str)] string fmt, byte* vl, byte* line, int lineSize, int* printPrefix);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVCodec* avcodec_find_decoder(AVCodecID id);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVCodecContext* avcodec_alloc_context3(AVCodec* codec);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_open2(AVCodecContext* avctx, AVCodec* codec, void **options);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_close(AVCodecContext* avctx);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void avcodec_free_context(AVCodecContext** avctx);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern AVPacket* av_packet_alloc();
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_packet_unref(AVPacket* pkt);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern void av_packet_free(AVPacket** pkt);
[DllImport(AvCodecLibraryName, CallingConvention = CallingConvention.Cdecl)]
internal static unsafe extern int avcodec_version();
}
}

View File

@ -1,14 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FFmpeg.AutoGen" Version="4.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj" />

View File

@ -1,4 +1,4 @@
using FFmpeg.AutoGen;
using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video;
using System;
@ -11,31 +11,31 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
public int RequestedWidth { get; }
public int RequestedHeight { get; }
public Plane YPlane => new Plane((IntPtr)Frame->data[0], Stride * Height);
public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight);
public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight);
public Plane YPlane => new Plane((IntPtr)Frame->Data[0], Stride * Height);
public Plane UPlane => new Plane((IntPtr)Frame->Data[1], UvStride * UvHeight);
public Plane VPlane => new Plane((IntPtr)Frame->Data[2], UvStride * UvHeight);
public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive;
public FrameField Field => Frame->InterlacedFrame != 0 ? FrameField.Interlaced : FrameField.Progressive;
public int Width => Frame->width;
public int Height => Frame->height;
public int Stride => Frame->linesize[0];
public int Width => Frame->Width;
public int Height => Frame->Height;
public int Stride => Frame->LineSize[0];
public int UvWidth => (Width + 1) >> 1;
public int UvHeight => (Height + 1) >> 1;
public int UvStride => Frame->linesize[1];
public int UvStride => Frame->LineSize[1];
public Surface(int width, int height)
{
RequestedWidth = width;
RequestedHeight = height;
Frame = ffmpeg.av_frame_alloc();
Frame = FFmpegApi.av_frame_alloc();
}
public void Dispose()
{
ffmpeg.av_frame_unref(Frame);
ffmpeg.av_free(Frame);
FFmpegApi.av_frame_unref(Frame);
FFmpegApi.av_free(Frame);
}
}
}

View File

@ -1,4 +1,4 @@
using FFmpeg.AutoGen;
using Ryujinx.Graphics.Nvdec.FFmpeg.Native;
using Ryujinx.Graphics.Video;
using System;

View File

@ -92,7 +92,11 @@ namespace Ryujinx.Graphics.Shader.Decoders
pushOpInfo.Consumers.Add(rightBlock, local);
}
rightBlock.SyncTargets.Union(SyncTargets);
foreach ((ulong key, SyncTarget value) in SyncTargets)
{
rightBlock.SyncTargets.Add(key, value);
}
SyncTargets.Clear();
// Move push ops.

View File

@ -340,7 +340,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
{
InstConditional condOp = new InstConditional(op.RawOpCode);
if (op.Name == InstName.Exit && condOp.Ccc != Ccc.T)
if ((op.Name == InstName.Bra || op.Name == InstName.Exit) && condOp.Ccc != Ccc.T)
{
return false;
}
@ -672,6 +672,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
// Make sure we found the correct address,
// the push and pop instruction types must match, so:
// - BRK can only consume addresses pushed by PBK.
// - CONT can only consume addresses pushed by PCNT.
// - SYNC can only consume addresses pushed by SSY.
if (found)
{

View File

@ -104,12 +104,23 @@ namespace Ryujinx.Graphics.Shader.Instructions
return;
}
// TODO: Figure out how this is supposed to work in the
// presence of other condition codes.
if (op.Ccc == Ccc.T)
{
context.Return();
}
else
{
Operand cond = GetCondition(context, op.Ccc, IrConsts.False);
// If the condition is always false, we don't need to do anything.
if (cond.Type != OperandType.Constant || cond.Value != IrConsts.False)
{
Operand lblSkip = Label();
context.BranchIfFalse(lblSkip, cond);
context.Return();
context.MarkLabel(lblSkip);
}
}
}
public static void Kil(EmitterContext context)
@ -250,7 +261,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
private static Operand GetCondition(EmitterContext context, Ccc cond)
private static Operand GetCondition(EmitterContext context, Ccc cond, int defaultCond = IrConsts.True)
{
// TODO: More condition codes, figure out how they work.
switch (cond)
@ -263,7 +274,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return context.BitwiseNot(GetZF());
}
return Const(IrConsts.True);
return Const(defaultCond);
}
}
}

View File

@ -45,12 +45,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (isFP64)
{
return context.PackDouble2x32(
context.Config.CreateCbuf(cbufSlot, cbufOffset),
context.Config.CreateCbuf(cbufSlot, cbufOffset + 1));
Cbuf(cbufSlot, cbufOffset),
Cbuf(cbufSlot, cbufOffset + 1));
}
else
{
return context.Config.CreateCbuf(cbufSlot, cbufOffset);
return Cbuf(cbufSlot, cbufOffset);
}
}

View File

@ -300,6 +300,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
if (operand.Type != OperandType.LocalVariable)
{
if (operand.Type == OperandType.ConstantBuffer)
{
Config.SetUsedConstantBuffer(operand.GetCbufSlot());
}
return new AstOperand(operand);
}

View File

@ -68,7 +68,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = config.CreateCbuf(0, GetStorageCbOffset(config.Stage, storageIndex));
Operand baseAddrLow = Cbuf(0, GetStorageCbOffset(config.Stage, storageIndex));
Operand baseAddrTrunc = Local();
@ -152,7 +152,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
Operand addrLow = operation.GetSource(0);
Operand baseAddrLow = config.CreateCbuf(0, UbeBaseOffset + storageIndex * StorageDescSize);
Operand baseAddrLow = Cbuf(0, UbeBaseOffset + storageIndex * StorageDescSize);
Operand baseAddrTrunc = Local();

View File

@ -45,6 +45,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
if (!(node.Value is Operation operation) || isUnused)
{
if (node.Value is PhiNode phi && !isUnused)
{
isUnused = PropagatePhi(phi);
}
if (isUnused)
{
RemoveNode(block, node);
@ -101,6 +106,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
{
// Propagate copy source operand to all uses of
// the destination operand.
Operand dest = copyOp.Dest;
Operand src = copyOp.GetSource(0);
@ -118,6 +124,53 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
}
}
private static bool PropagatePhi(PhiNode phi)
{
// If all phi sources are the same, we can propagate it and remove the phi.
Operand firstSrc = phi.GetSource(0);
for (int index = 1; index < phi.SourcesCount; index++)
{
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
{
return false;
}
}
// All sources are equal, we can propagate the value.
Operand dest = phi.Dest;
INode[] uses = dest.UseOps.ToArray();
foreach (INode useNode in uses)
{
for (int index = 0; index < useNode.SourcesCount; index++)
{
if (useNode.GetSource(index) == dest)
{
useNode.SetSource(index, firstSrc);
}
}
}
return true;
}
private static bool IsSameOperand(Operand x, Operand y)
{
if (x.Type != y.Type || x.Value != y.Value)
{
return false;
}
return x.Type == OperandType.Attribute ||
x.Type == OperandType.AttributePerPatch ||
x.Type == OperandType.Constant ||
x.Type == OperandType.ConstantBuffer;
}
private static bool PropagatePack(Operation packOp)
{
// Propagate pack source operands to uses by unpack

View File

@ -75,9 +75,9 @@ namespace Ryujinx.Graphics.Shader.Translation
int cbOffset = GetStorageCbOffset(config.Stage, slot);
Operand baseAddrLow = config.CreateCbuf(0, cbOffset);
Operand baseAddrHigh = config.CreateCbuf(0, cbOffset + 1);
Operand size = config.CreateCbuf(0, cbOffset + 2);
Operand baseAddrLow = Cbuf(0, cbOffset);
Operand baseAddrHigh = Cbuf(0, cbOffset + 1);
Operand size = Cbuf(0, cbOffset + 2);
Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow);
Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow);
@ -164,7 +164,7 @@ namespace Ryujinx.Graphics.Shader.Translation
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
bool isCoordNormalized = !isBindless && config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
if (!hasInvalidOffset && isCoordNormalized)
{

View File

@ -360,12 +360,6 @@ namespace Ryujinx.Graphics.Shader.Translation
UsedFeatures |= flags;
}
public Operand CreateCbuf(int slot, int offset)
{
SetUsedConstantBuffer(slot);
return OperandHelper.Cbuf(slot, offset);
}
public void SetUsedConstantBuffer(int slot)
{
_usedConstantBuffers |= 1 << slot;

View File

@ -184,18 +184,35 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
public LinuxError Receive(out int receiveSize, Span<byte> buffer, BsdSocketFlags flags)
{
LinuxError result;
bool shouldBlockAfterOperation = false;
try
{
if (Blocking && flags.HasFlag(BsdSocketFlags.DontWait))
{
Blocking = false;
shouldBlockAfterOperation = true;
}
receiveSize = Socket.Receive(buffer, ConvertBsdSocketFlags(flags));
return LinuxError.SUCCESS;
result = LinuxError.SUCCESS;
}
catch (SocketException exception)
{
receiveSize = -1;
return WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
result = WinSockHelper.ConvertError((WsaError)exception.ErrorCode);
}
if (shouldBlockAfterOperation)
{
Blocking = true;
}
return result;
}
public LinuxError ReceiveFrom(out int receiveSize, Span<byte> buffer, int size, BsdSocketFlags flags, out IPEndPoint remoteEndPoint)
@ -304,7 +321,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EOPNOTSUPP;
}
int value = MemoryMarshal.Read<int>(optionValue);
int value = optionValue.Length >= 4 ? MemoryMarshal.Read<int>(optionValue) : MemoryMarshal.Read<byte>(optionValue);
if (option == BsdSocketOption.SoLinger)
{

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build7" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
</ItemGroup>
<ItemGroup>

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Ui.Common.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 38;
public const int CurrentVersion = 39;
/// <summary>
/// Version of the configuration file format
@ -80,7 +80,7 @@ namespace Ryujinx.Ui.Common.Configuration
/// Enables printing error log messages
/// </summary>
public bool LoggingEnableError { get; set; }
/// <summary>
/// Enables printing trace log messages
/// </summary>

View File

@ -310,7 +310,7 @@ namespace Ryujinx.Ui.Common.Configuration
EnableDockedMode = new ReactiveObject<bool>();
EnableDockedMode.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableDockedMode));
EnablePtc = new ReactiveObject<bool>();
EnablePtc.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnablePtc));
EnablePtc.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnablePtc));
EnableInternetAccess = new ReactiveObject<bool>();
EnableInternetAccess.Event += static (sender, e) => LogValueChange(sender, e, nameof(EnableInternetAccess));
EnableFsIntegrityChecks = new ReactiveObject<bool>();
@ -648,7 +648,9 @@ namespace Ryujinx.Ui.Common.Configuration
ToggleMute = Key.F2,
Screenshot = Key.F8,
ShowUi = Key.F4,
Pause = Key.F5
Pause = Key.F5,
ResScaleUp = Key.Unbound,
ResScaleDown = Key.Unbound
};
Hid.InputConfig.Value = new List<InputConfig>
{
@ -967,7 +969,7 @@ namespace Ryujinx.Ui.Common.Configuration
if (configurationFileFormat.Version < 29)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 29.");
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
ToggleVsync = Key.Tab,
@ -1096,6 +1098,22 @@ namespace Ryujinx.Ui.Common.Configuration
configurationFileUpdated = true;
}
if (configurationFileFormat.Version < 39)
{
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 39.");
configurationFileFormat.Hotkeys = new KeyboardHotkeys
{
ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
Screenshot = configurationFileFormat.Hotkeys.Screenshot,
ShowUi = configurationFileFormat.Hotkeys.ShowUi,
Pause = configurationFileFormat.Hotkeys.Pause,
ToggleMute = configurationFileFormat.Hotkeys.ToggleMute,
ResScaleUp = Key.Unbound,
ResScaleDown = Key.Unbound
};
}
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;

View File

@ -19,7 +19,7 @@
<ItemGroup>
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
<PackageReference Include="GtkSharp.Dependencies" Version="1.1.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="4.4.0-build9" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build10" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
<PackageReference Include="OpenTK.Graphics" Version="4.7.2" />
<PackageReference Include="SPB" Version="0.0.4-build17" />

View File

@ -6,6 +6,7 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Graphics.Gpu;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.GAL.Multithreading;
using Ryujinx.Input;
@ -33,6 +34,7 @@ namespace Ryujinx.Ui
private const int SwitchPanelWidth = 1280;
private const int SwitchPanelHeight = 720;
private const int TargetFps = 60;
private const float MaxResolutionScale = 4.0f; // Max resolution hotkeys can scale to before wrapping.
public ManualResetEvent WaitEvent { get; set; }
public NpadManager NpadManager { get; }
@ -618,6 +620,19 @@ namespace Ryujinx.Ui
}
}
if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp) &&
!_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleUp))
{
GraphicsConfig.ResScale = GraphicsConfig.ResScale % MaxResolutionScale + 1;
}
if (currentHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown) &&
!_prevHotkeyState.HasFlag(KeyboardHotkeyState.ResScaleDown))
{
GraphicsConfig.ResScale =
(MaxResolutionScale + GraphicsConfig.ResScale - 2) % MaxResolutionScale + 1;
}
_prevHotkeyState = currentHotkeyState;
}
@ -648,7 +663,9 @@ namespace Ryujinx.Ui
Screenshot = 1 << 1,
ShowUi = 1 << 2,
Pause = 1 << 3,
ToggleMute = 1 << 4
ToggleMute = 1 << 4,
ResScaleUp = 1 << 5,
ResScaleDown = 1 << 6
}
private KeyboardHotkeyState GetHotkeyState()
@ -680,6 +697,16 @@ namespace Ryujinx.Ui
state |= KeyboardHotkeyState.ToggleMute;
}
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleUp))
{
state |= KeyboardHotkeyState.ResScaleUp;
}
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ResScaleDown))
{
state |= KeyboardHotkeyState.ResScaleDown;
}
return state;
}
}

View File

@ -1509,7 +1509,7 @@
</child>
<child>
<object class="GtkCheckButton" id="_internetToggle">
<property name="label" translatable="yes">Enable guest Internet access</property>
<property name="label" translatable="yes">Enable Guest Internet Access</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -1643,7 +1643,7 @@
</child>
<child>
<object class="GtkRadioButton" id="_mmHostUnsafe">
<property name="label" translatable="yes">Host unchecked (fastest, unsafe)</property>
<property name="label" translatable="yes">Host Unchecked (fastest, unsafe)</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -1725,7 +1725,7 @@
<property name="can-focus">False</property>
<property name="halign">start</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes"> - These may cause instability</property>
<property name="label" translatable="yes"> (may cause instability)</property>
</object>
<packing>
<property name="expand">False</property>
@ -1749,7 +1749,7 @@
<property name="orientation">vertical</property>
<child>
<object class="GtkCheckButton" id="_expandRamToggle">
<property name="label" translatable="yes">Expand DRAM size to 6GB</property>
<property name="label" translatable="yes">Expand DRAM Size to 6GB</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>