mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 23:49:44 +08:00
[GUI] Add inline per-game settings and unify options styling (#728)
* [GUI] Add inline per-game settings and unify options styling * [GUI] Update options styling & add scrollable area * [GUI] Remove focus and pointover styles for options
This commit is contained in:
@@ -31,6 +31,8 @@ cascade order so individual launcher views do not redefine global visuals.
|
|||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Inputs.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Inputs.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Console.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Console.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Library.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Library.axaml" />
|
||||||
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/OptionsNav.axaml" />
|
||||||
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Options.axaml" />
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/Options.axaml" />
|
||||||
|
<StyleInclude Source="avares://SharpEmu.GUI/Themes/Styles/GameOptions.axaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ إضافة مجلد ألعاب",
|
"Library.Empty.AddFolder": "+ إضافة مجلد ألعاب",
|
||||||
|
|
||||||
"Library.Loading": "جارٍ تحميل المكتبة...",
|
"Library.Loading": "جارٍ تحميل المكتبة...",
|
||||||
|
"Library.Stat.Version": "الإصدار",
|
||||||
|
"Library.Stat.Installed": "المثبت",
|
||||||
|
"Library.Stat.TitleId": "معرّف اللعبة",
|
||||||
|
"Common.Back": "رجوع",
|
||||||
|
|
||||||
"Options.General": "عام",
|
"Options.General": "عام",
|
||||||
|
"Options.Logging": "التسجيل",
|
||||||
"Options.Section.Emulation": "المحاكاة",
|
"Options.Section.Emulation": "المحاكاة",
|
||||||
"Options.Section.Logging": "التسجيل",
|
"Options.Section.Logging": "التسجيل",
|
||||||
"Options.Section.Launcher": "المُشغِّل",
|
"Options.Section.Launcher": "المُشغِّل",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "إعادة رفع أسطح الضيف التي تعيد كتابتها شيفرة المعالج الخاصة باللعبة.\nاتركه مغلقًا عادة. شغّله للألعاب التي لا تصل أسطحها المرسومة بالمعالج إلى الشاشة.\nيكلّف أداءً ويسبب مشاكل في بعض الألعاب مثل GTA V.",
|
"Options.Env.GuestImageCpuSync.Desc": "إعادة رفع أسطح الضيف التي تعيد كتابتها شيفرة المعالج الخاصة باللعبة.\nاتركه مغلقًا عادة. شغّله للألعاب التي لا تصل أسطحها المرسومة بالمعالج إلى الشاشة.\nيكلّف أداءً ويسبب مشاكل في بعض الألعاب مثل GTA V.",
|
||||||
"Common.Save": "حفظ",
|
"Common.Save": "حفظ",
|
||||||
"Common.Cancel": "إلغاء",
|
"Common.Cancel": "إلغاء",
|
||||||
"PerGame.Title": "إعدادات خاصة باللعبة — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "الصفوف غير المحددة ترث الإعدادات الافتراضية العامة.",
|
|
||||||
"PerGame.EnvToggles.Label": "مفاتيح البيئة",
|
|
||||||
"PerGame.EnvToggles.Desc": "تجاوز المجموعة العامة من مفاتيح SHARPEMU_* لهذه اللعبة.",
|
|
||||||
"Options.About": "حول",
|
"Options.About": "حول",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "الكود المصدري والمشكلات وتطوير المشروع.",
|
"About.Github.Desc": "الكود المصدري والمشكلات وتطوير المشروع.",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Adicionar pasta do jogo",
|
"Library.Empty.AddFolder": "+ Adicionar pasta do jogo",
|
||||||
|
|
||||||
"Library.Loading": "Carregando biblioteca…",
|
"Library.Loading": "Carregando biblioteca…",
|
||||||
|
"Library.Stat.Version": "Versão",
|
||||||
|
"Library.Stat.Installed": "Instalado",
|
||||||
|
"Library.Stat.TitleId": "ID do título",
|
||||||
|
"Common.Back": "Voltar",
|
||||||
|
|
||||||
"Options.General": "Opções Gerais",
|
"Options.General": "Opções Gerais",
|
||||||
|
"Options.Logging": "Logs",
|
||||||
"Options.Env.Tab": "Ambiente",
|
"Options.Env.Tab": "Ambiente",
|
||||||
"Options.Section.Environment": "VARIÁVEIS DE AMBIENTE",
|
"Options.Section.Environment": "VARIÁVEIS DE AMBIENTE",
|
||||||
"Options.Env.Desc": "Variáveis de ambiente passadas ao emulador durante a inicialização.",
|
"Options.Env.Desc": "Variáveis de ambiente passadas ao emulador durante a inicialização.",
|
||||||
@@ -151,10 +156,6 @@
|
|||||||
"Options.Env.LogIo.Desc": "Registra no console a abertura e leitura de arquivos e a resolução de caminhos.\nUse quando um jogo não encontrar seus arquivos de dados durante a inicialização.",
|
"Options.Env.LogIo.Desc": "Registra no console a abertura e leitura de arquivos e a resolução de caminhos.\nUse quando um jogo não encontrar seus arquivos de dados durante a inicialização.",
|
||||||
"Common.Save": "Salvar",
|
"Common.Save": "Salvar",
|
||||||
"Common.Cancel": "Cancelar",
|
"Common.Cancel": "Cancelar",
|
||||||
"PerGame.Title": "Configurações por jogo — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "As linhas desmarcadas herdam os padrões globais.",
|
|
||||||
"PerGame.EnvToggles.Label": "Variáveis de ambiente",
|
|
||||||
"PerGame.EnvToggles.Desc": "Substitui o conjunto global de opções SHARPEMU_* para este jogo.",
|
|
||||||
"About.Github.LatestCommitLabel": "Último commit",
|
"About.Github.LatestCommitLabel": "Último commit",
|
||||||
"About.Github.LatestCommitDescription": "Último commit na branch main",
|
"About.Github.LatestCommitDescription": "Último commit na branch main",
|
||||||
"Updater.Auto.Label": "Verificar atualizações ao iniciar",
|
"Updater.Auto.Label": "Verificar atualizações ao iniciar",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Spielordner hinzufügen",
|
"Library.Empty.AddFolder": "+ Spielordner hinzufügen",
|
||||||
|
|
||||||
"Library.Loading": "Bibliothek wird geladen…",
|
"Library.Loading": "Bibliothek wird geladen…",
|
||||||
|
"Library.Stat.Version": "Version",
|
||||||
|
"Library.Stat.Installed": "Installiert",
|
||||||
|
"Library.Stat.TitleId": "Titel-ID",
|
||||||
|
"Common.Back": "Zurück",
|
||||||
|
|
||||||
"Options.General": "Allgemein",
|
"Options.General": "Allgemein",
|
||||||
|
"Options.Logging": "Protokollierung",
|
||||||
"Options.Section.Emulation": "EMULATION",
|
"Options.Section.Emulation": "EMULATION",
|
||||||
"Options.Section.Logging": "PROTOKOLLIERUNG",
|
"Options.Section.Logging": "PROTOKOLLIERUNG",
|
||||||
"Options.Section.Launcher": "LAUNCHER",
|
"Options.Section.Launcher": "LAUNCHER",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "Gast-Oberflächen neu hochladen, die der eigene CPU-Code des Spiels überschreibt.\nNormalerweise aus lassen. Für Titel aktivieren, deren CPU-gezeichnete Oberflächen nie auf dem Bildschirm erscheinen.\nKostet Leistung und verursacht bei einigen Titeln wie GTA V Regressionen.",
|
"Options.Env.GuestImageCpuSync.Desc": "Gast-Oberflächen neu hochladen, die der eigene CPU-Code des Spiels überschreibt.\nNormalerweise aus lassen. Für Titel aktivieren, deren CPU-gezeichnete Oberflächen nie auf dem Bildschirm erscheinen.\nKostet Leistung und verursacht bei einigen Titeln wie GTA V Regressionen.",
|
||||||
"Common.Save": "Speichern",
|
"Common.Save": "Speichern",
|
||||||
"Common.Cancel": "Abbrechen",
|
"Common.Cancel": "Abbrechen",
|
||||||
"PerGame.Title": "Spielspezifische Einstellungen — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Nicht angehakte Zeilen übernehmen die globalen Standardwerte.",
|
|
||||||
"PerGame.EnvToggles.Label": "Umgebungsschalter",
|
|
||||||
"PerGame.EnvToggles.Desc": "Die globalen SHARPEMU_*-Schalter für dieses Spiel überschreiben.",
|
|
||||||
"Options.About": "Über",
|
"Options.About": "Über",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "Quellcode, Issues und Projektentwicklung.",
|
"About.Github.Desc": "Quellcode, Issues und Projektentwicklung.",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Tilføj spilmappe",
|
"Library.Empty.AddFolder": "+ Tilføj spilmappe",
|
||||||
|
|
||||||
"Library.Loading": "Indlæser bibliotek…",
|
"Library.Loading": "Indlæser bibliotek…",
|
||||||
|
"Library.Stat.Version": "Version",
|
||||||
|
"Library.Stat.Installed": "Installeret",
|
||||||
|
"Library.Stat.TitleId": "Titel-id",
|
||||||
|
"Common.Back": "Tilbage",
|
||||||
|
|
||||||
"Options.General": "Generelt",
|
"Options.General": "Generelt",
|
||||||
|
"Options.Logging": "Logging",
|
||||||
"Options.Section.Emulation": "EMULERING",
|
"Options.Section.Emulation": "EMULERING",
|
||||||
"Options.Section.Logging": "LOGGING",
|
"Options.Section.Logging": "LOGGING",
|
||||||
"Options.Section.Launcher": "LAUNCHER",
|
"Options.Section.Launcher": "LAUNCHER",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "Genindlæs gæsteoverflader, som spillets egen CPU-kode omskriver.\nLad den være slået fra normalt. Slå til for titler, hvis CPU-tegnede overflader aldrig når skærmen.\nKoster ydeevne og giver regressioner i nogle titler, såsom GTA V.",
|
"Options.Env.GuestImageCpuSync.Desc": "Genindlæs gæsteoverflader, som spillets egen CPU-kode omskriver.\nLad den være slået fra normalt. Slå til for titler, hvis CPU-tegnede overflader aldrig når skærmen.\nKoster ydeevne og giver regressioner i nogle titler, såsom GTA V.",
|
||||||
"Common.Save": "Gem",
|
"Common.Save": "Gem",
|
||||||
"Common.Cancel": "Annuller",
|
"Common.Cancel": "Annuller",
|
||||||
"PerGame.Title": "Indstillinger pr. spil — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Umarkerede rækker arver de globale standardværdier.",
|
|
||||||
"PerGame.EnvToggles.Label": "Miljøkontakter",
|
|
||||||
"PerGame.EnvToggles.Desc": "Tilsidesæt det globale sæt SHARPEMU_*-kontakter for dette spil.",
|
|
||||||
"Options.About": "Om",
|
"Options.About": "Om",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "Kildekode, issues og projektudvikling.",
|
"About.Github.Desc": "Kildekode, issues og projektudvikling.",
|
||||||
|
|||||||
@@ -24,8 +24,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Add game folder",
|
"Library.Empty.AddFolder": "+ Add game folder",
|
||||||
|
|
||||||
"Library.Loading": "Loading library…",
|
"Library.Loading": "Loading library…",
|
||||||
|
"Library.Stat.Version": "Version",
|
||||||
|
"Library.Stat.Installed": "Installed",
|
||||||
|
"Library.Stat.TitleId": "Title ID",
|
||||||
|
"Common.Back": "Back",
|
||||||
|
|
||||||
"Options.General": "General",
|
"Options.General": "General",
|
||||||
|
"Options.Logging": "Logging",
|
||||||
"Options.Env.Tab": "Environment",
|
"Options.Env.Tab": "Environment",
|
||||||
"Options.Section.Environment": "ENVIRONMENT VARIABLES",
|
"Options.Section.Environment": "ENVIRONMENT VARIABLES",
|
||||||
"Options.Env.Desc": "Switches passed to the emulator as environment variables at launch.",
|
"Options.Env.Desc": "Switches passed to the emulator as environment variables at launch.",
|
||||||
@@ -117,12 +122,6 @@
|
|||||||
"Common.Save": "Save",
|
"Common.Save": "Save",
|
||||||
"Common.Cancel": "Cancel",
|
"Common.Cancel": "Cancel",
|
||||||
|
|
||||||
"PerGame.Title": "Per-game settings — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Unchecked rows inherit the global defaults.",
|
|
||||||
"PerGame.Tab.General": "General",
|
|
||||||
"PerGame.Tab.Graphics": "Graphics",
|
|
||||||
"PerGame.EnvToggles.Label": "Environment toggles",
|
|
||||||
"PerGame.EnvToggles.Desc": "Override the global set of SHARPEMU_* switches for this game.",
|
|
||||||
|
|
||||||
"Console.Title": "CONSOLE",
|
"Console.Title": "CONSOLE",
|
||||||
"Console.SearchWatermark": "Search...",
|
"Console.SearchWatermark": "Search...",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Añadir carpeta de juegos",
|
"Library.Empty.AddFolder": "+ Añadir carpeta de juegos",
|
||||||
|
|
||||||
"Library.Loading": "Cargando biblioteca…",
|
"Library.Loading": "Cargando biblioteca…",
|
||||||
|
"Library.Stat.Version": "Versión",
|
||||||
|
"Library.Stat.Installed": "Instalado",
|
||||||
|
"Library.Stat.TitleId": "ID del título",
|
||||||
|
"Common.Back": "Volver",
|
||||||
|
|
||||||
"Options.General": "General",
|
"Options.General": "General",
|
||||||
|
"Options.Logging": "Logs",
|
||||||
"Options.Section.Emulation": "EMULACIÓN",
|
"Options.Section.Emulation": "EMULACIÓN",
|
||||||
"Options.Section.Logging": "LOGS",
|
"Options.Section.Logging": "LOGS",
|
||||||
"Options.Section.Launcher": "LAUNCHER",
|
"Options.Section.Launcher": "LAUNCHER",
|
||||||
@@ -153,10 +158,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "Volver a subir las superficies del invitado que reescribe el propio código de CPU del juego.\nDejar desactivado normalmente. Activar en títulos cuyas superficies dibujadas por CPU nunca llegan a la pantalla.\nCuesta rendimiento y causa regresiones en algunos títulos, como GTA V.",
|
"Options.Env.GuestImageCpuSync.Desc": "Volver a subir las superficies del invitado que reescribe el propio código de CPU del juego.\nDejar desactivado normalmente. Activar en títulos cuyas superficies dibujadas por CPU nunca llegan a la pantalla.\nCuesta rendimiento y causa regresiones en algunos títulos, como GTA V.",
|
||||||
"Common.Save": "Guardar",
|
"Common.Save": "Guardar",
|
||||||
"Common.Cancel": "Cancelar",
|
"Common.Cancel": "Cancelar",
|
||||||
"PerGame.Title": "Ajustes por juego — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Las filas sin marcar heredan los valores globales.",
|
|
||||||
"PerGame.EnvToggles.Label": "Variables de entorno",
|
|
||||||
"PerGame.EnvToggles.Desc": "Sustituir el conjunto global de opciones SHARPEMU_* para este juego.",
|
|
||||||
"Updater.Auto.Label": "Buscar actualizaciones al iniciar",
|
"Updater.Auto.Label": "Buscar actualizaciones al iniciar",
|
||||||
"Updater.Auto.Desc": "Consulta GitHub sin retrasar el arranque.",
|
"Updater.Auto.Desc": "Consulta GitHub sin retrasar el arranque.",
|
||||||
"Updater.Label": "Actualizaciones",
|
"Updater.Label": "Actualizaciones",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Ajouter un dossier de jeux",
|
"Library.Empty.AddFolder": "+ Ajouter un dossier de jeux",
|
||||||
|
|
||||||
"Library.Loading": "Chargement de la bibliothèque…",
|
"Library.Loading": "Chargement de la bibliothèque…",
|
||||||
|
"Library.Stat.Version": "Version",
|
||||||
|
"Library.Stat.Installed": "Installé",
|
||||||
|
"Library.Stat.TitleId": "ID du titre",
|
||||||
|
"Common.Back": "Retour",
|
||||||
|
|
||||||
"Options.General": "Général",
|
"Options.General": "Général",
|
||||||
|
"Options.Logging": "Journalisation",
|
||||||
"Options.Env.Tab": "Environnement",
|
"Options.Env.Tab": "Environnement",
|
||||||
"Options.Section.Environment": "VARIABLES D’ENVIRONNEMENT",
|
"Options.Section.Environment": "VARIABLES D’ENVIRONNEMENT",
|
||||||
"Options.Env.Desc": "Paramètres passés à l’émulateur comme variables d’environnement au lancement.",
|
"Options.Env.Desc": "Paramètres passés à l’émulateur comme variables d’environnement au lancement.",
|
||||||
@@ -151,10 +156,6 @@
|
|||||||
"Options.Env.LogIo.Desc": "Journaliser l’ouverture et la lecture des fichiers ainsi que la résolution des chemins dans la console.\nÀ utiliser quand un jeu ne trouve pas ses fichiers de données au démarrage.",
|
"Options.Env.LogIo.Desc": "Journaliser l’ouverture et la lecture des fichiers ainsi que la résolution des chemins dans la console.\nÀ utiliser quand un jeu ne trouve pas ses fichiers de données au démarrage.",
|
||||||
"Common.Save": "Enregistrer",
|
"Common.Save": "Enregistrer",
|
||||||
"Common.Cancel": "Annuler",
|
"Common.Cancel": "Annuler",
|
||||||
"PerGame.Title": "Paramètres par jeu — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Les lignes non cochées héritent des valeurs globales par défaut.",
|
|
||||||
"PerGame.EnvToggles.Label": "Variables d’environnement",
|
|
||||||
"PerGame.EnvToggles.Desc": "Remplacer l’ensemble global des options SHARPEMU_* pour ce jeu.",
|
|
||||||
"About.Github.LatestCommitLabel": "Dernier commit",
|
"About.Github.LatestCommitLabel": "Dernier commit",
|
||||||
"About.Github.LatestCommitDescription": "Dernier commit sur la branche main",
|
"About.Github.LatestCommitDescription": "Dernier commit sur la branche main",
|
||||||
"Updater.Auto.Label": "Vérifier les mises à jour au démarrage",
|
"Updater.Auto.Label": "Vérifier les mises à jour au démarrage",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Játékmappa hozzáadása",
|
"Library.Empty.AddFolder": "+ Játékmappa hozzáadása",
|
||||||
|
|
||||||
"Library.Loading": "Könyvtár betöltése",
|
"Library.Loading": "Könyvtár betöltése",
|
||||||
|
"Library.Stat.Version": "Verzió",
|
||||||
|
"Library.Stat.Installed": "Telepítve",
|
||||||
|
"Library.Stat.TitleId": "Címazonosító",
|
||||||
|
"Common.Back": "Vissza",
|
||||||
|
|
||||||
"Options.General": "Általános",
|
"Options.General": "Általános",
|
||||||
|
"Options.Logging": "Logolás",
|
||||||
"Options.Env.Tab": "Környezet",
|
"Options.Env.Tab": "Környezet",
|
||||||
"Options.Section.Environment": "KÖRNYEZETI VÁLTOZÓK",
|
"Options.Section.Environment": "KÖRNYEZETI VÁLTOZÓK",
|
||||||
"Options.Env.Desc": "Indításkor környezeti változóként az emulátorhoz átadott kapcsolók.",
|
"Options.Env.Desc": "Indításkor környezeti változóként az emulátorhoz átadott kapcsolók.",
|
||||||
@@ -151,10 +156,6 @@
|
|||||||
"Options.Env.LogIo.Desc": "A fájlmegnyitások, olvasások és útvonal-feloldások naplózása a konzolra.\nAkkor használd, ha egy játék indításkor nem találja az adatfájljait.",
|
"Options.Env.LogIo.Desc": "A fájlmegnyitások, olvasások és útvonal-feloldások naplózása a konzolra.\nAkkor használd, ha egy játék indításkor nem találja az adatfájljait.",
|
||||||
"Common.Save": "Mentés",
|
"Common.Save": "Mentés",
|
||||||
"Common.Cancel": "Mégse",
|
"Common.Cancel": "Mégse",
|
||||||
"PerGame.Title": "Játékonkénti beállítások — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "A be nem jelölt sorok a globális alapértelmezéseket öröklik.",
|
|
||||||
"PerGame.EnvToggles.Label": "Környezeti kapcsolók",
|
|
||||||
"PerGame.EnvToggles.Desc": "A globális SHARPEMU_* kapcsolókészlet felülírása ennél a játéknál.",
|
|
||||||
"About.Github.LatestCommitLabel": "Legutóbbi commit",
|
"About.Github.LatestCommitLabel": "Legutóbbi commit",
|
||||||
"About.Github.LatestCommitDescription": "A main ág legutóbbi commitja",
|
"About.Github.LatestCommitDescription": "A main ág legutóbbi commitja",
|
||||||
"Updater.Auto.Label": "Frissítések keresése indításkor",
|
"Updater.Auto.Label": "Frissítések keresése indításkor",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Aggiungi cartella giochi",
|
"Library.Empty.AddFolder": "+ Aggiungi cartella giochi",
|
||||||
|
|
||||||
"Library.Loading": "Caricamento libreria…",
|
"Library.Loading": "Caricamento libreria…",
|
||||||
|
"Library.Stat.Version": "Versione",
|
||||||
|
"Library.Stat.Installed": "Installato",
|
||||||
|
"Library.Stat.TitleId": "ID titolo",
|
||||||
|
"Common.Back": "Indietro",
|
||||||
|
|
||||||
"Options.General": "Generale",
|
"Options.General": "Generale",
|
||||||
|
"Options.Logging": "Log",
|
||||||
"Options.Section.Emulation": "EMULAZIONE",
|
"Options.Section.Emulation": "EMULAZIONE",
|
||||||
"Options.Section.Logging": "LOG",
|
"Options.Section.Logging": "LOG",
|
||||||
"Options.Section.Launcher": "LAUNCHER",
|
"Options.Section.Launcher": "LAUNCHER",
|
||||||
@@ -148,10 +153,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "Ricarica le superfici guest riscritte dal codice CPU del gioco.\nLasciare disattivato normalmente. Attivare per i titoli le cui superfici disegnate dalla CPU non raggiungono mai lo schermo.\nCosta prestazioni e causa regressioni in alcuni titoli, come GTA V.",
|
"Options.Env.GuestImageCpuSync.Desc": "Ricarica le superfici guest riscritte dal codice CPU del gioco.\nLasciare disattivato normalmente. Attivare per i titoli le cui superfici disegnate dalla CPU non raggiungono mai lo schermo.\nCosta prestazioni e causa regressioni in alcuni titoli, come GTA V.",
|
||||||
"Common.Save": "Salva",
|
"Common.Save": "Salva",
|
||||||
"Common.Cancel": "Annulla",
|
"Common.Cancel": "Annulla",
|
||||||
"PerGame.Title": "Impostazioni per gioco — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Le righe non selezionate ereditano i valori globali.",
|
|
||||||
"PerGame.EnvToggles.Label": "Variabili d'ambiente",
|
|
||||||
"PerGame.EnvToggles.Desc": "Sovrascrivi l'insieme globale delle opzioni SHARPEMU_* per questo gioco.",
|
|
||||||
"Options.About": "Informazioni",
|
"Options.About": "Informazioni",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "Codice sorgente, issue e sviluppo del progetto.",
|
"About.Github.Desc": "Codice sorgente, issue e sviluppo del progetto.",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ ゲームフォルダーを追加",
|
"Library.Empty.AddFolder": "+ ゲームフォルダーを追加",
|
||||||
|
|
||||||
"Library.Loading": "ライブラリを読み込み中…",
|
"Library.Loading": "ライブラリを読み込み中…",
|
||||||
|
"Library.Stat.Version": "バージョン",
|
||||||
|
"Library.Stat.Installed": "インストール済み",
|
||||||
|
"Library.Stat.TitleId": "タイトルID",
|
||||||
|
"Common.Back": "戻る",
|
||||||
|
|
||||||
"Options.General": "一般",
|
"Options.General": "一般",
|
||||||
|
"Options.Logging": "ロギング",
|
||||||
"Options.Section.Emulation": "エミュレーション",
|
"Options.Section.Emulation": "エミュレーション",
|
||||||
"Options.Section.Logging": "ロギング",
|
"Options.Section.Logging": "ロギング",
|
||||||
"Options.Section.Launcher": "ランチャー",
|
"Options.Section.Launcher": "ランチャー",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "ゲーム自身の CPU コードが書き換えるゲスト表面を再アップロードします。\n通常はオフのままにしてください。CPU で描画した表面が画面に反映されないタイトルで有効にします。\n性能を犠牲にし、GTA V など一部のタイトルでは不具合が生じます。",
|
"Options.Env.GuestImageCpuSync.Desc": "ゲーム自身の CPU コードが書き換えるゲスト表面を再アップロードします。\n通常はオフのままにしてください。CPU で描画した表面が画面に反映されないタイトルで有効にします。\n性能を犠牲にし、GTA V など一部のタイトルでは不具合が生じます。",
|
||||||
"Common.Save": "保存",
|
"Common.Save": "保存",
|
||||||
"Common.Cancel": "キャンセル",
|
"Common.Cancel": "キャンセル",
|
||||||
"PerGame.Title": "ゲームごとの設定 — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "チェックされていない行はグローバルの既定値を継承します。",
|
|
||||||
"PerGame.EnvToggles.Label": "環境スイッチ",
|
|
||||||
"PerGame.EnvToggles.Desc": "このゲームに対してグローバルのSHARPEMU_*スイッチを上書きします。",
|
|
||||||
"Options.About": "情報",
|
"Options.About": "情報",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "ソースコード、Issue、プロジェクトの開発。",
|
"About.Github.Desc": "ソースコード、Issue、プロジェクトの開発。",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ 게임 폴더 추가",
|
"Library.Empty.AddFolder": "+ 게임 폴더 추가",
|
||||||
|
|
||||||
"Library.Loading": "라이브러리 불러오는 중…",
|
"Library.Loading": "라이브러리 불러오는 중…",
|
||||||
|
"Library.Stat.Version": "버전",
|
||||||
|
"Library.Stat.Installed": "설치됨",
|
||||||
|
"Library.Stat.TitleId": "타이틀 ID",
|
||||||
|
"Common.Back": "뒤로",
|
||||||
|
|
||||||
"Options.General": "일반",
|
"Options.General": "일반",
|
||||||
|
"Options.Logging": "로깅",
|
||||||
"Options.Section.Emulation": "에뮬레이션",
|
"Options.Section.Emulation": "에뮬레이션",
|
||||||
"Options.Section.Logging": "로깅",
|
"Options.Section.Logging": "로깅",
|
||||||
"Options.Section.Launcher": "런처",
|
"Options.Section.Launcher": "런처",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "게임의 자체 CPU 코드가 다시 쓰는 게스트 표면을 다시 업로드합니다.\n평소에는 꺼 두세요. CPU로 그린 표면이 화면에 나타나지 않는 타이틀에서 켜세요.\n성능을 소모하며 GTA V 등 일부 타이틀에서는 문제가 생깁니다.",
|
"Options.Env.GuestImageCpuSync.Desc": "게임의 자체 CPU 코드가 다시 쓰는 게스트 표면을 다시 업로드합니다.\n평소에는 꺼 두세요. CPU로 그린 표면이 화면에 나타나지 않는 타이틀에서 켜세요.\n성능을 소모하며 GTA V 등 일부 타이틀에서는 문제가 생깁니다.",
|
||||||
"Common.Save": "저장",
|
"Common.Save": "저장",
|
||||||
"Common.Cancel": "취소",
|
"Common.Cancel": "취소",
|
||||||
"PerGame.Title": "게임별 설정 — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "선택하지 않은 항목은 전역 기본값을 따릅니다.",
|
|
||||||
"PerGame.EnvToggles.Label": "환경 스위치",
|
|
||||||
"PerGame.EnvToggles.Desc": "이 게임에 대해 전역 SHARPEMU_* 스위치 설정을 재정의합니다.",
|
|
||||||
"Options.About": "정보",
|
"Options.About": "정보",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "소스 코드, 이슈, 프로젝트 개발.",
|
"About.Github.Desc": "소스 코드, 이슈, 프로젝트 개발.",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Gamemap toevoegen",
|
"Library.Empty.AddFolder": "+ Gamemap toevoegen",
|
||||||
|
|
||||||
"Library.Loading": "Bibliotheek laden…",
|
"Library.Loading": "Bibliotheek laden…",
|
||||||
|
"Library.Stat.Version": "Versie",
|
||||||
|
"Library.Stat.Installed": "Geïnstalleerd",
|
||||||
|
"Library.Stat.TitleId": "Titel-ID",
|
||||||
|
"Common.Back": "Terug",
|
||||||
|
|
||||||
"Options.General": "Algemeen",
|
"Options.General": "Algemeen",
|
||||||
|
"Options.Logging": "Logging",
|
||||||
"Options.Section.Emulation": "EMULATIE",
|
"Options.Section.Emulation": "EMULATIE",
|
||||||
"Options.Section.Logging": "LOGGING",
|
"Options.Section.Logging": "LOGGING",
|
||||||
"Options.Section.Launcher": "LAUNCHER",
|
"Options.Section.Launcher": "LAUNCHER",
|
||||||
@@ -143,10 +148,6 @@
|
|||||||
"Options.Env.GuestImageCpuSync.Desc": "Gastoppervlakken opnieuw uploaden die de eigen CPU-code van de game herschrijft.\nNormaal uit laten. Inschakelen voor titels waarvan de door de CPU getekende oppervlakken nooit het scherm bereiken.\nKost prestaties en veroorzaakt regressies in sommige titels, zoals GTA V.",
|
"Options.Env.GuestImageCpuSync.Desc": "Gastoppervlakken opnieuw uploaden die de eigen CPU-code van de game herschrijft.\nNormaal uit laten. Inschakelen voor titels waarvan de door de CPU getekende oppervlakken nooit het scherm bereiken.\nKost prestaties en veroorzaakt regressies in sommige titels, zoals GTA V.",
|
||||||
"Common.Save": "Opslaan",
|
"Common.Save": "Opslaan",
|
||||||
"Common.Cancel": "Annuleren",
|
"Common.Cancel": "Annuleren",
|
||||||
"PerGame.Title": "Instellingen per game — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Niet-aangevinkte rijen erven de globale standaardwaarden.",
|
|
||||||
"PerGame.EnvToggles.Label": "Omgevingsschakelaars",
|
|
||||||
"PerGame.EnvToggles.Desc": "Overschrijf de globale set SHARPEMU_*-schakelaars voor deze game.",
|
|
||||||
"Options.About": "Over",
|
"Options.About": "Over",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "Broncode, issues en projectontwikkeling.",
|
"About.Github.Desc": "Broncode, issues en projectontwikkeling.",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Adicionar pasta de jogos",
|
"Library.Empty.AddFolder": "+ Adicionar pasta de jogos",
|
||||||
|
|
||||||
"Library.Loading": "A carregar biblioteca…",
|
"Library.Loading": "A carregar biblioteca…",
|
||||||
|
"Library.Stat.Version": "Versão",
|
||||||
|
"Library.Stat.Installed": "Instalado",
|
||||||
|
"Library.Stat.TitleId": "ID do título",
|
||||||
|
"Common.Back": "Voltar",
|
||||||
|
|
||||||
"Options.General": "Geral",
|
"Options.General": "Geral",
|
||||||
|
"Options.Logging": "Registos",
|
||||||
"Options.Env.Tab": "Ambiente",
|
"Options.Env.Tab": "Ambiente",
|
||||||
"Options.Section.Environment": "VARIÁVEIS DE AMBIENTE",
|
"Options.Section.Environment": "VARIÁVEIS DE AMBIENTE",
|
||||||
"Options.Env.Desc": "Switches passados ao emulador como variáveis de ambiente no arranque.",
|
"Options.Env.Desc": "Switches passados ao emulador como variáveis de ambiente no arranque.",
|
||||||
@@ -151,10 +156,6 @@
|
|||||||
"Options.Env.LogIo.Desc": "Registar na consola a abertura e leitura de ficheiros e a resolução de caminhos.\nUtilize quando um jogo não encontrar os seus ficheiros de dados durante o arranque.",
|
"Options.Env.LogIo.Desc": "Registar na consola a abertura e leitura de ficheiros e a resolução de caminhos.\nUtilize quando um jogo não encontrar os seus ficheiros de dados durante o arranque.",
|
||||||
"Common.Save": "Guardar",
|
"Common.Save": "Guardar",
|
||||||
"Common.Cancel": "Cancelar",
|
"Common.Cancel": "Cancelar",
|
||||||
"PerGame.Title": "Definições por jogo — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "As linhas não assinaladas herdam as predefinições globais.",
|
|
||||||
"PerGame.EnvToggles.Label": "Variáveis de ambiente",
|
|
||||||
"PerGame.EnvToggles.Desc": "Substituir o conjunto global de opções SHARPEMU_* para este jogo.",
|
|
||||||
"About.Github.LatestCommitLabel": "Último commit",
|
"About.Github.LatestCommitLabel": "Último commit",
|
||||||
"About.Github.LatestCommitDescription": "Último commit no ramo main",
|
"About.Github.LatestCommitDescription": "Último commit no ramo main",
|
||||||
"Updater.Auto.Label": "Procurar atualizações no arranque",
|
"Updater.Auto.Label": "Procurar atualizações no arranque",
|
||||||
|
|||||||
@@ -24,8 +24,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Добавить папку с играми",
|
"Library.Empty.AddFolder": "+ Добавить папку с играми",
|
||||||
|
|
||||||
"Library.Loading": "Загрузка библиотеки…",
|
"Library.Loading": "Загрузка библиотеки…",
|
||||||
|
"Library.Stat.Version": "Версия",
|
||||||
|
"Library.Stat.Installed": "Установлено",
|
||||||
|
"Library.Stat.TitleId": "ID игры",
|
||||||
|
"Common.Back": "Назад",
|
||||||
|
|
||||||
"Options.General": "Основные",
|
"Options.General": "Основные",
|
||||||
|
"Options.Logging": "Логгирование",
|
||||||
"Options.Env.Tab": "Окружение",
|
"Options.Env.Tab": "Окружение",
|
||||||
"Options.Section.Environment": "ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ",
|
"Options.Section.Environment": "ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ",
|
||||||
"Options.Env.Desc": "Параметры, передаваемые эмулятору как переменные окружения при запуске.",
|
"Options.Env.Desc": "Параметры, передаваемые эмулятору как переменные окружения при запуске.",
|
||||||
@@ -117,12 +122,6 @@
|
|||||||
"Common.Save": "Сохранить",
|
"Common.Save": "Сохранить",
|
||||||
"Common.Cancel": "Отмена",
|
"Common.Cancel": "Отмена",
|
||||||
|
|
||||||
"PerGame.Title": "Настройки игры — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "Неотмеченные строки наследуют глобальные настройки.",
|
|
||||||
"PerGame.Tab.General": "Основные",
|
|
||||||
"PerGame.Tab.Graphics": "Графика",
|
|
||||||
"PerGame.EnvToggles.Label": "Переключатели окружения",
|
|
||||||
"PerGame.EnvToggles.Desc": "Переопределить глобальный набор переключателей SHARPEMU_* для этой игры.",
|
|
||||||
|
|
||||||
"Console.Title": "КОНСОЛЬ",
|
"Console.Title": "КОНСОЛЬ",
|
||||||
"Console.SearchWatermark": "Поиск...",
|
"Console.SearchWatermark": "Поиск...",
|
||||||
|
|||||||
@@ -23,8 +23,13 @@
|
|||||||
"Library.Empty.AddFolder": "+ Oyun klasörü ekle",
|
"Library.Empty.AddFolder": "+ Oyun klasörü ekle",
|
||||||
|
|
||||||
"Library.Loading": "Kütüphane yükleniyor…",
|
"Library.Loading": "Kütüphane yükleniyor…",
|
||||||
|
"Library.Stat.Version": "Sürüm",
|
||||||
|
"Library.Stat.Installed": "Yüklü",
|
||||||
|
"Library.Stat.TitleId": "Başlık kimliği",
|
||||||
|
"Common.Back": "Geri",
|
||||||
|
|
||||||
"Options.General": "Genel",
|
"Options.General": "Genel",
|
||||||
|
"Options.Logging": "Günlükleme",
|
||||||
"Options.Section.Emulation": "EMÜLASYON",
|
"Options.Section.Emulation": "EMÜLASYON",
|
||||||
"Options.Section.Logging": "GÜNLÜKLEME",
|
"Options.Section.Logging": "GÜNLÜKLEME",
|
||||||
"Options.Section.Launcher": "BAŞLATICI",
|
"Options.Section.Launcher": "BAŞLATICI",
|
||||||
@@ -177,12 +182,6 @@
|
|||||||
"Options.DefaultProfile.Desc": "Oyun metin girisi istediginde kullanilacak ad. Varsayilan deger Sharp'tir.",
|
"Options.DefaultProfile.Desc": "Oyun metin girisi istediginde kullanilacak ad. Varsayilan deger Sharp'tir.",
|
||||||
"Common.Save": "Kaydet",
|
"Common.Save": "Kaydet",
|
||||||
"Common.Cancel": "İptal",
|
"Common.Cancel": "İptal",
|
||||||
"PerGame.Title": "Oyuna özel ayarlar — {0} ({1})",
|
|
||||||
"PerGame.InheritNote": "İşaretlenmemiş satırlar genel varsayılanları kullanır.",
|
|
||||||
"PerGame.Tab.General": "Genel",
|
|
||||||
"PerGame.Tab.Graphics": "Grafik",
|
|
||||||
"PerGame.EnvToggles.Label": "Ortam anahtarları",
|
|
||||||
"PerGame.EnvToggles.Desc": "Bu oyun için genel SHARPEMU_* anahtar kümesini geçersiz kıl.",
|
|
||||||
"Options.About": "Hakkında",
|
"Options.About": "Hakkında",
|
||||||
"About.Github.Label": "GitHub",
|
"About.Github.Label": "GitHub",
|
||||||
"About.Github.Desc": "Kaynak kodu, hata kayıtları ve proje geliştirme.",
|
"About.Github.Desc": "Kaynak kodu, hata kayıtları ve proje geliştirme.",
|
||||||
|
|||||||
@@ -0,0 +1,493 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using SharpEmu.Libs.VideoOut;
|
||||||
|
|
||||||
|
namespace SharpEmu.GUI;
|
||||||
|
|
||||||
|
/// <summary>Inline per-game settings navigation, loading and persistence.</summary>
|
||||||
|
public partial class MainWindow
|
||||||
|
{
|
||||||
|
private static readonly string[] GameEnvironmentToggleNames =
|
||||||
|
[
|
||||||
|
"SHARPEMU_BTHID_UNAVAILABLE",
|
||||||
|
"SHARPEMU_DISABLE_IMPORT_LOOP_GUARD",
|
||||||
|
"SHARPEMU_WRITABLE_APP0",
|
||||||
|
"SHARPEMU_VK_VALIDATION",
|
||||||
|
"SHARPEMU_DUMP_SPIRV",
|
||||||
|
"SHARPEMU_LOG_DIRECT_MEMORY",
|
||||||
|
"SHARPEMU_LOG_IO",
|
||||||
|
"SHARPEMU_LOG_NP",
|
||||||
|
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||||
|
];
|
||||||
|
|
||||||
|
private readonly List<string> _gameEnvironmentPassthrough = new();
|
||||||
|
private IReadOnlyList<HostDisplayOption> _gameHostDisplays = [];
|
||||||
|
private bool _isGameSettingsOpen;
|
||||||
|
private bool _isLoadingGameSettings;
|
||||||
|
private bool _updatingGameHostDisplayOptions;
|
||||||
|
private int _gameOptionsIndicatorIndex;
|
||||||
|
private int _gameOptionsSectionIndex;
|
||||||
|
private string? _gameSettingsTitleId;
|
||||||
|
|
||||||
|
private void WireGameOptions()
|
||||||
|
{
|
||||||
|
GameSettingsButton.Click += (_, _) => OpenSelectedGameSettings();
|
||||||
|
|
||||||
|
GameLogLevelBox.ItemsSource = _logLevelChoices;
|
||||||
|
GameWindowModeBox.ItemsSource = _windowModeChoices;
|
||||||
|
GameScalingModeBox.ItemsSource = _scalingModeChoices;
|
||||||
|
GameHdrModeBox.ItemsSource = _hdrModeChoices;
|
||||||
|
|
||||||
|
var navigationButtons = GameOptionsNavigationButtons();
|
||||||
|
for (var index = 0; index < navigationButtons.Length; index++)
|
||||||
|
{
|
||||||
|
var section = index;
|
||||||
|
navigationButtons[index].Click += (_, _) =>
|
||||||
|
{
|
||||||
|
if (section < GameOptionsSectionPanels().Length)
|
||||||
|
{
|
||||||
|
SetGameOptionsSection(section);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CloseGameSettings();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
navigationButtons[index].PointerEntered += (_, _) =>
|
||||||
|
{
|
||||||
|
if (_isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
SetGameOptionsNavigationIndicator(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
navigationButtons[index].GotFocus += (_, _) =>
|
||||||
|
{
|
||||||
|
if (_isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
SetGameOptionsNavigationIndicator(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
GameOptionsNavHost.PointerExited += (_, _) =>
|
||||||
|
{
|
||||||
|
if (_isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
SetGameOptionsNavigationIndicator(_gameOptionsSectionIndex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GameOptionsLaunchButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
CloseGameSettings();
|
||||||
|
LaunchSelected();
|
||||||
|
};
|
||||||
|
GameOptionsOpenFolderButton.Click += (_, _) => OpenSelectedGameFolder();
|
||||||
|
GameOptionsCopyPathButton.Click += async (_, _) =>
|
||||||
|
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.Path);
|
||||||
|
GameOptionsCopyTitleIdButton.Click += async (_, _) =>
|
||||||
|
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.TitleId);
|
||||||
|
GameOptionsRemoveButton.Click += (_, _) =>
|
||||||
|
{
|
||||||
|
CloseGameSettings();
|
||||||
|
RemoveSelectedFromLibrary();
|
||||||
|
};
|
||||||
|
|
||||||
|
GameStrictToggle.IsCheckedChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameLogLevelBox.SelectionChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameTraceImportsBox.ValueChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameLogToFileToggle.IsCheckedChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameWindowModeBox.SelectionChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameDisplayBox.SelectionChanged += (_, _) => OnGameHostDisplayChanged();
|
||||||
|
GameResolutionBox.SelectionChanged += (_, _) => OnGameHostResolutionChanged();
|
||||||
|
GameRefreshRateBox.SelectionChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameScalingModeBox.SelectionChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameVSyncToggle.IsCheckedChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
GameHdrModeBox.SelectionChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
foreach (var (_, toggle) in GameEnvironmentToggles())
|
||||||
|
{
|
||||||
|
toggle.IsCheckedChanged += (_, _) => PersistOpenGameSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
SetGameOptionsSection(0, animateIndicator: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSelectedGameSettings()
|
||||||
|
{
|
||||||
|
if (GameList.SelectedItem is not GameEntry game)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(game.TitleId))
|
||||||
|
{
|
||||||
|
AppendConsoleLine(
|
||||||
|
"[GUI][WARN] Per-game settings require a title ID, which this game does not have.",
|
||||||
|
WarningLineBrush);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameSettingsTitleId = game.TitleId;
|
||||||
|
GameOptionsOverlay.DataContext = game;
|
||||||
|
LoadGameSettings(game.TitleId);
|
||||||
|
SetGameOptionsSection(0, animateIndicator: false);
|
||||||
|
GameOptionsLaunchButton.IsEnabled = !_isRunning;
|
||||||
|
GameOptionsCopyTitleIdButton.IsEnabled =
|
||||||
|
!string.IsNullOrWhiteSpace(game.TitleId);
|
||||||
|
|
||||||
|
_isGameSettingsOpen = true;
|
||||||
|
SetGameOptionsOpenClass(BackdropLayer, active: true);
|
||||||
|
SetGameOptionsOpenClass(CarouselHost, active: true);
|
||||||
|
SetGameOptionsOpenClass(LibrarySelectedDetails, active: true);
|
||||||
|
SetGameOptionsOpenClass(GameOptionsOverlay, active: true);
|
||||||
|
GameList.IsHitTestVisible = false;
|
||||||
|
LibraryToolbar.IsHitTestVisible = false;
|
||||||
|
GameOptionsOverlay.IsHitTestVisible = true;
|
||||||
|
GameOptionsGeneralNav.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseGameSettings(bool restoreLibrary = true)
|
||||||
|
{
|
||||||
|
if (!_isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isGameSettingsOpen = false;
|
||||||
|
SetGameOptionsNavigationIndicator(_gameOptionsIndicatorIndex, animate: false);
|
||||||
|
_gameSettingsTitleId = null;
|
||||||
|
_gameEnvironmentPassthrough.Clear();
|
||||||
|
SetGameOptionsOpenClass(BackdropLayer, active: false);
|
||||||
|
SetGameOptionsOpenClass(CarouselHost, active: false);
|
||||||
|
SetGameOptionsOpenClass(LibrarySelectedDetails, active: false);
|
||||||
|
SetGameOptionsOpenClass(GameOptionsOverlay, active: false);
|
||||||
|
GameOptionsOverlay.IsHitTestVisible = false;
|
||||||
|
GameOptionsOverlay.DataContext = null;
|
||||||
|
GameList.IsHitTestVisible = true;
|
||||||
|
LibraryToolbar.IsHitTestVisible = true;
|
||||||
|
|
||||||
|
if (restoreLibrary && _activePageIndex == 0)
|
||||||
|
{
|
||||||
|
GameList.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadGameSettings(string titleId)
|
||||||
|
{
|
||||||
|
var effective = EffectiveLaunchSettings.Resolve(
|
||||||
|
_settings,
|
||||||
|
PerGameSettings.Load(titleId));
|
||||||
|
|
||||||
|
_isLoadingGameSettings = true;
|
||||||
|
_updatingGameHostDisplayOptions = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GameStrictToggle.IsChecked = effective.StrictDynlibResolution;
|
||||||
|
GameLogLevelBox.SelectedItem = FindChoice(
|
||||||
|
_logLevelChoices,
|
||||||
|
effective.LogLevel,
|
||||||
|
"Info");
|
||||||
|
GameTraceImportsBox.Value = Math.Clamp(effective.ImportTraceLimit, 0, 4096);
|
||||||
|
GameLogToFileToggle.IsChecked = effective.LogToFile;
|
||||||
|
GameWindowModeBox.SelectedItem = FindChoice(
|
||||||
|
_windowModeChoices,
|
||||||
|
effective.WindowMode,
|
||||||
|
"Windowed");
|
||||||
|
GameScalingModeBox.SelectedItem = FindChoice(
|
||||||
|
_scalingModeChoices,
|
||||||
|
effective.ScalingMode,
|
||||||
|
"Fit");
|
||||||
|
GameVSyncToggle.IsChecked = effective.VSync;
|
||||||
|
GameHdrModeBox.SelectedItem = FindChoice(
|
||||||
|
_hdrModeChoices,
|
||||||
|
effective.HdrMode,
|
||||||
|
"Auto");
|
||||||
|
|
||||||
|
_gameHostDisplays = HostDisplayOptions.BuildDisplays(
|
||||||
|
HostDisplayCatalog.Query(),
|
||||||
|
effective.DisplayIndex);
|
||||||
|
GameDisplayBox.ItemsSource = _gameHostDisplays;
|
||||||
|
var display = HostDisplayOptions.SelectDisplay(
|
||||||
|
_gameHostDisplays,
|
||||||
|
effective.DisplayIndex);
|
||||||
|
GameDisplayBox.SelectedItem = display;
|
||||||
|
PopulateGameHostModes(
|
||||||
|
display,
|
||||||
|
effective.Resolution,
|
||||||
|
effective.RefreshRate);
|
||||||
|
|
||||||
|
_gameEnvironmentPassthrough.Clear();
|
||||||
|
foreach (var entry in effective.EnvironmentToggles)
|
||||||
|
{
|
||||||
|
if (!IsKnownGameEnvironmentEntry(entry))
|
||||||
|
{
|
||||||
|
_gameEnvironmentPassthrough.Add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (name, toggle) in GameEnvironmentToggles())
|
||||||
|
{
|
||||||
|
toggle.IsChecked = IsEnvironmentEnabled(
|
||||||
|
effective.EnvironmentToggles,
|
||||||
|
name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updatingGameHostDisplayOptions = false;
|
||||||
|
_isLoadingGameSettings = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistOpenGameSettings()
|
||||||
|
{
|
||||||
|
if (!_isGameSettingsOpen ||
|
||||||
|
_isLoadingGameSettings ||
|
||||||
|
_updatingGameHostDisplayOptions ||
|
||||||
|
string.IsNullOrWhiteSpace(_gameSettingsTitleId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = new PerGameSettings
|
||||||
|
{
|
||||||
|
LogLevel = SelectedComboText(GameLogLevelBox, "Info"),
|
||||||
|
ImportTraceLimit = (int)(GameTraceImportsBox.Value ?? 0),
|
||||||
|
StrictDynlibResolution = GameStrictToggle.IsChecked == true,
|
||||||
|
LogToFile = GameLogToFileToggle.IsChecked == true,
|
||||||
|
WindowMode = SelectedComboText(GameWindowModeBox, "Windowed"),
|
||||||
|
Resolution = SelectedComboText(GameResolutionBox, "1920x1080"),
|
||||||
|
DisplayIndex = GameDisplayBox.SelectedItem is HostDisplayOption display
|
||||||
|
? display.Index
|
||||||
|
: 0,
|
||||||
|
RefreshRate = SelectedGameRefreshRate(),
|
||||||
|
ScalingMode = SelectedComboText(GameScalingModeBox, "Fit"),
|
||||||
|
VSync = GameVSyncToggle.IsChecked == true,
|
||||||
|
HdrMode = SelectedComboText(GameHdrModeBox, "Auto"),
|
||||||
|
EnvironmentToggles = BuildGameEnvironmentEntries(),
|
||||||
|
};
|
||||||
|
settings.RemoveInheritedValues(_settings);
|
||||||
|
settings.Save(_gameSettingsTitleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameHostDisplayChanged()
|
||||||
|
{
|
||||||
|
if (_isLoadingGameSettings ||
|
||||||
|
_updatingGameHostDisplayOptions ||
|
||||||
|
GameDisplayBox.SelectedItem is not HostDisplayOption display)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updatingGameHostDisplayOptions = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PopulateGameHostModes(
|
||||||
|
display,
|
||||||
|
GameResolutionBox.SelectedItem as string ?? "1920x1080",
|
||||||
|
SelectedGameRefreshRate());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updatingGameHostDisplayOptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistOpenGameSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameHostResolutionChanged()
|
||||||
|
{
|
||||||
|
if (_isLoadingGameSettings ||
|
||||||
|
_updatingGameHostDisplayOptions ||
|
||||||
|
GameDisplayBox.SelectedItem is not HostDisplayOption display)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updatingGameHostDisplayOptions = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PopulateGameRefreshRates(
|
||||||
|
display,
|
||||||
|
GameResolutionBox.SelectedItem as string,
|
||||||
|
SelectedGameRefreshRate());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_updatingGameHostDisplayOptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistOpenGameSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateGameHostModes(
|
||||||
|
HostDisplayOption display,
|
||||||
|
string selectedResolution,
|
||||||
|
int selectedRefreshRate)
|
||||||
|
{
|
||||||
|
var resolutions = HostDisplayOptions.BuildResolutions(display, selectedResolution);
|
||||||
|
GameResolutionBox.ItemsSource = resolutions;
|
||||||
|
GameResolutionBox.SelectedItem = resolutions.FirstOrDefault(resolution =>
|
||||||
|
string.Equals(resolution, selectedResolution, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? resolutions[0];
|
||||||
|
PopulateGameRefreshRates(
|
||||||
|
display,
|
||||||
|
GameResolutionBox.SelectedItem as string,
|
||||||
|
selectedRefreshRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PopulateGameRefreshRates(
|
||||||
|
HostDisplayOption display,
|
||||||
|
string? resolution,
|
||||||
|
int selectedRefreshRate)
|
||||||
|
{
|
||||||
|
var refreshRates = HostDisplayOptions.BuildRefreshRates(
|
||||||
|
display,
|
||||||
|
resolution,
|
||||||
|
selectedRefreshRate,
|
||||||
|
Localization.Instance.Get("Options.RefreshRate.Automatic"));
|
||||||
|
GameRefreshRateBox.ItemsSource = refreshRates;
|
||||||
|
GameRefreshRateBox.SelectedItem = refreshRates.FirstOrDefault(
|
||||||
|
refreshRate => refreshRate.Value == selectedRefreshRate) ?? refreshRates[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int SelectedGameRefreshRate() =>
|
||||||
|
GameRefreshRateBox.SelectedItem is HostRefreshRateOption refreshRate
|
||||||
|
? refreshRate.Value
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
private List<string> BuildGameEnvironmentEntries()
|
||||||
|
{
|
||||||
|
var entries = new List<string>(_gameEnvironmentPassthrough);
|
||||||
|
foreach (var (name, toggle) in GameEnvironmentToggles())
|
||||||
|
{
|
||||||
|
if (toggle.IsChecked == true)
|
||||||
|
{
|
||||||
|
entries.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalizedChoice FindChoice(
|
||||||
|
IEnumerable<LocalizedChoice> choices,
|
||||||
|
string value,
|
||||||
|
string fallback) =>
|
||||||
|
choices.FirstOrDefault(choice =>
|
||||||
|
string.Equals(choice.Value, value, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? choices.First(choice =>
|
||||||
|
string.Equals(choice.Value, fallback, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
private static bool IsEnvironmentEnabled(
|
||||||
|
IEnumerable<string> entries,
|
||||||
|
string name)
|
||||||
|
{
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
var parts = entry.Split('=', 2, StringSplitOptions.TrimEntries);
|
||||||
|
if (!string.Equals(parts[0], name, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.Length == 1 || parts[1] != "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsKnownGameEnvironmentEntry(string entry)
|
||||||
|
{
|
||||||
|
var name = entry.Split('=', 2, StringSplitOptions.TrimEntries)[0];
|
||||||
|
return GameEnvironmentToggleNames.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGameOptionsSection(int section, bool animateIndicator = true)
|
||||||
|
{
|
||||||
|
var buttons = GameOptionsSectionButtons();
|
||||||
|
var panels = GameOptionsSectionPanels();
|
||||||
|
section = Math.Clamp(section, 0, buttons.Length - 1);
|
||||||
|
_gameOptionsSectionIndex = section;
|
||||||
|
SetGameOptionsNavigationIndicator(section, animateIndicator);
|
||||||
|
|
||||||
|
for (var index = 0; index < buttons.Length; index++)
|
||||||
|
{
|
||||||
|
var active = index == section;
|
||||||
|
SetActiveClass(buttons[index], active);
|
||||||
|
SetOptionsPanelInteraction(panels[index], active);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetActiveClass(GameOptionsBackNav, active: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetGameOptionsNavigationIndicator(int section, bool animate = true)
|
||||||
|
{
|
||||||
|
var buttons = GameOptionsNavigationButtons();
|
||||||
|
_gameOptionsIndicatorIndex = Math.Clamp(section, 0, buttons.Length - 1);
|
||||||
|
var button = buttons[_gameOptionsIndicatorIndex];
|
||||||
|
MoveNavigationIndicator(
|
||||||
|
GameOptionsNavIndicator,
|
||||||
|
GameOptionsNavHost,
|
||||||
|
button,
|
||||||
|
_gameOptionsIndicatorIndex,
|
||||||
|
animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button[] GameOptionsNavigationButtons() =>
|
||||||
|
[
|
||||||
|
GameOptionsGeneralNav,
|
||||||
|
GameOptionsLoggingNav,
|
||||||
|
GameOptionsRenderingNav,
|
||||||
|
GameOptionsEnvironmentNav,
|
||||||
|
GameOptionsBackNav,
|
||||||
|
];
|
||||||
|
|
||||||
|
private Button[] GameOptionsSectionButtons() =>
|
||||||
|
[
|
||||||
|
GameOptionsGeneralNav,
|
||||||
|
GameOptionsLoggingNav,
|
||||||
|
GameOptionsRenderingNav,
|
||||||
|
GameOptionsEnvironmentNav,
|
||||||
|
];
|
||||||
|
|
||||||
|
private Control[] GameOptionsSectionPanels() =>
|
||||||
|
[
|
||||||
|
GameOptionsGeneralPanel,
|
||||||
|
GameOptionsLoggingPanel,
|
||||||
|
GameOptionsRenderingPanel,
|
||||||
|
GameOptionsEnvironmentPanel,
|
||||||
|
];
|
||||||
|
|
||||||
|
private (string Name, ToggleSwitch Toggle)[] GameEnvironmentToggles() =>
|
||||||
|
[
|
||||||
|
("SHARPEMU_BTHID_UNAVAILABLE", GameEnvBthidToggle),
|
||||||
|
("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD", GameEnvLoopGuardToggle),
|
||||||
|
("SHARPEMU_WRITABLE_APP0", GameEnvWritableApp0Toggle),
|
||||||
|
("SHARPEMU_VK_VALIDATION", GameEnvVkValidationToggle),
|
||||||
|
("SHARPEMU_DUMP_SPIRV", GameEnvDumpSpirvToggle),
|
||||||
|
("SHARPEMU_LOG_DIRECT_MEMORY", GameEnvLogDirectMemoryToggle),
|
||||||
|
("SHARPEMU_LOG_IO", GameEnvLogIoToggle),
|
||||||
|
("SHARPEMU_LOG_NP", GameEnvLogNpToggle),
|
||||||
|
("SHARPEMU_GUEST_IMAGE_CPU_SYNC", GameEnvGuestImageCpuSyncToggle),
|
||||||
|
];
|
||||||
|
|
||||||
|
private static void SetGameOptionsOpenClass(Control control, bool active)
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
{
|
||||||
|
if (!control.Classes.Contains("gameOptionsOpen"))
|
||||||
|
{
|
||||||
|
control.Classes.Add("gameOptionsOpen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
control.Classes.Remove("gameOptionsOpen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,11 +26,22 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid x:Name="RootLayout" RowDefinitions="Auto,*,Auto">
|
<Grid x:Name="RootLayout" RowDefinitions="Auto,*">
|
||||||
|
|
||||||
<!-- Selected-game backdrop: key art behind the main content, dimmed by
|
<!-- Selected-game backdrop: key art behind the main content, dimmed by
|
||||||
a scrim so tiles and text stay readable. Fades on selection. -->
|
a scrim so tiles and text stay readable. Fades on selection. -->
|
||||||
<Panel Grid.Row="1" ClipToBounds="True">
|
<Panel x:Name="BackdropLayer"
|
||||||
|
Grid.Row="1"
|
||||||
|
Classes="backdropLayer"
|
||||||
|
ClipToBounds="True"
|
||||||
|
RenderTransformOrigin="50%,50%">
|
||||||
|
<Panel.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<TransformOperationsTransition Property="RenderTransform"
|
||||||
|
Duration="0:0:0.72"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Panel.Transitions>
|
||||||
<Image x:Name="BackdropImage" Stretch="UniformToFill" Opacity="0"
|
<Image x:Name="BackdropImage" Stretch="UniformToFill" Opacity="0"
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<Image.Transitions>
|
<Image.Transitions>
|
||||||
@@ -42,16 +53,15 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<Border>
|
<Border>
|
||||||
<Border.Background>
|
<Border.Background>
|
||||||
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
|
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
|
||||||
<GradientStop Offset="0" Color="#730D1017" />
|
<GradientStop Offset="0" Color="#730C0C0C" />
|
||||||
<GradientStop Offset="0.55" Color="#BF0D1017" />
|
<GradientStop Offset="0.55" Color="#BF0C0C0C" />
|
||||||
<GradientStop Offset="1" Color="#EB0D1017" />
|
<GradientStop Offset="1" Color="#EB0C0C0C" />
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
</Border.Background>
|
</Border.Background>
|
||||||
</Border>
|
</Border>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
<!-- Title bar; hidden in fullscreen (F11) along with the status bar, so
|
<!-- Title bar; hidden in fullscreen (F11) so the game gets the whole screen. -->
|
||||||
the game gets the whole screen. -->
|
|
||||||
<Grid x:Name="TitleBar"
|
<Grid x:Name="TitleBar"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Height="44"
|
Height="44"
|
||||||
@@ -96,6 +106,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
<Grid x:Name="MainContent" Grid.Row="1" Margin="32,24,32,20" RowDefinitions="Auto,*,Auto">
|
<Grid x:Name="MainContent" Grid.Row="1" Margin="32,24,32,20" RowDefinitions="Auto,*,Auto">
|
||||||
|
<!-- Settings use the same opaque neutral surface as contextual
|
||||||
|
per-game settings, keeping both views in one visual system -->
|
||||||
|
<Border x:Name="OptionsPageSurface"
|
||||||
|
Grid.RowSpan="3"
|
||||||
|
Margin="-32,-24,-32,-20"
|
||||||
|
Background="{StaticResource GameOptionsMenuBrush}"
|
||||||
|
IsVisible="False" />
|
||||||
|
|
||||||
<!-- Library / Options page switcher, with the library toolbar sharing
|
<!-- Library / Options page switcher, with the library toolbar sharing
|
||||||
the same row on the right. Plain buttons (not TabItem) so there is
|
the same row on the right. Plain buttons (not TabItem) so there is
|
||||||
@@ -135,8 +152,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<!-- Library page. Covers use a horizontal virtualized rail so the
|
<!-- Library page. Covers use a horizontal virtualized rail so the
|
||||||
number of realized images stays bounded for large libraries. -->
|
number of realized images stays bounded for large libraries. -->
|
||||||
<Grid x:Name="LibraryPage" Margin="0,46,0,0" RowDefinitions="188,*">
|
<Grid x:Name="LibraryPage" Margin="0,46,0,0" RowDefinitions="188,*">
|
||||||
<ListBox x:Name="GameList" Classes="tileGrid" Background="Transparent"
|
<Panel x:Name="CarouselHost"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
|
Classes="carouselHost"
|
||||||
|
RenderTransformOrigin="50%,50%">
|
||||||
|
<Panel.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<TransformOperationsTransition Property="RenderTransform"
|
||||||
|
Duration="0:0:0.32"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
<DoubleTransition Property="Opacity"
|
||||||
|
Duration="0:0:0.18"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Panel.Transitions>
|
||||||
|
<ListBox x:Name="GameList" Classes="tileGrid" Background="Transparent"
|
||||||
SelectionMode="Single" Padding="4,0,28,0">
|
SelectionMode="Single" Padding="4,0,28,0">
|
||||||
<ListBox.ContextMenu>
|
<ListBox.ContextMenu>
|
||||||
<ContextMenu x:Name="GameContextMenu" Placement="Pointer">
|
<ContextMenu x:Name="GameContextMenu" Placement="Pointer">
|
||||||
@@ -249,11 +279,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.DataTemplates>
|
</ListBox.DataTemplates>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
</Panel>
|
||||||
|
|
||||||
<!-- Selected-game identity and actions share one detail surface so
|
<!-- Selected-game identity and actions share one detail surface so
|
||||||
the selected title and metadata are never rendered twice. -->
|
the selected title and metadata are never rendered twice. -->
|
||||||
<StackPanel x:Name="LibrarySelectedDetails"
|
<StackPanel x:Name="LibrarySelectedDetails"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
Classes="selectedDetailsHost"
|
||||||
x:DataType="local:GameEntry"
|
x:DataType="local:GameEntry"
|
||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
IsVisible="False"
|
IsVisible="False"
|
||||||
@@ -262,6 +294,14 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Margin="4,8,0,0"
|
Margin="4,8,0,0"
|
||||||
Spacing="18">
|
Spacing="18">
|
||||||
|
<StackPanel.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<DoubleTransition Property="Opacity"
|
||||||
|
Duration="0:0:0.16" />
|
||||||
|
<TransformOperationsTransition Property="RenderTransform"
|
||||||
|
Duration="0:0:0.18" />
|
||||||
|
</Transitions>
|
||||||
|
</StackPanel.Transitions>
|
||||||
<TextBlock Text="{Binding Name}"
|
<TextBlock Text="{Binding Name}"
|
||||||
FontSize="42"
|
FontSize="42"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
@@ -282,25 +322,36 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
Foreground="{StaticResource MutedBrush}" />
|
Foreground="{StaticResource MutedBrush}" />
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Grid Width="360"
|
<StackPanel Orientation="Horizontal"
|
||||||
ColumnDefinitions="*,*"
|
Spacing="12"
|
||||||
ColumnSpacing="8"
|
|
||||||
HorizontalAlignment="Left">
|
HorizontalAlignment="Left">
|
||||||
<Button x:Name="LaunchButton"
|
<Button x:Name="LaunchButton"
|
||||||
Grid.Column="0"
|
Classes="playButton"
|
||||||
Classes="accent"
|
|
||||||
Content="Launch"
|
Content="Launch"
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
IsEnabled="False" />
|
IsEnabled="False" />
|
||||||
<ToggleButton x:Name="ConsoleToggle"
|
<Button x:Name="GameSettingsButton"
|
||||||
Grid.Column="1"
|
Classes="optionsCircle"
|
||||||
Classes="ghost"
|
ToolTip.Tip="{Binding [Library.Context.GameSettings],
|
||||||
Padding="22,10"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Content="{Binding [Launch.Console],
|
|
||||||
Source={x:Static local:Localization.Instance},
|
Source={x:Static local:Localization.Instance},
|
||||||
x:CompileBindings=False}" />
|
x:CompileBindings=False}"
|
||||||
</Grid>
|
AutomationProperties.Name="{Binding [Library.Context.GameSettings],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="more_horiz" />
|
||||||
|
</Button>
|
||||||
|
<ToggleButton x:Name="ConsoleToggle"
|
||||||
|
Classes="optionsCircle"
|
||||||
|
ToolTip.Tip="{Binding [Launch.Console],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
AutomationProperties.Name="{Binding [Launch.Console],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="terminal" />
|
||||||
|
</ToggleButton>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
@@ -328,6 +379,569 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Contextual per-game settings use the same layered reveal as the
|
||||||
|
original UI branch while keeping the current launch settings. -->
|
||||||
|
<Grid x:Name="GameOptionsOverlay"
|
||||||
|
Classes="gameOptionsOverlay"
|
||||||
|
x:DataType="local:GameEntry"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
RowDefinitions="1*,3*"
|
||||||
|
IsHitTestVisible="False">
|
||||||
|
<Grid.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<DoubleTransition Property="Opacity"
|
||||||
|
Duration="0:0:0.2"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
<TransformOperationsTransition Property="RenderTransform"
|
||||||
|
Duration="0:0:0.38"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Grid.Transitions>
|
||||||
|
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Background="Transparent">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Margin="-32,0,-32,-20"
|
||||||
|
Background="{StaticResource GameOptionsMenuBrush}" />
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Height="1"
|
||||||
|
Margin="-32,0,-32,0"
|
||||||
|
ZIndex="1"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
BoxShadow="0 18 42 0 #A6000000" />
|
||||||
|
|
||||||
|
<!-- Anchor the cover to the actual menu-surface boundary so it
|
||||||
|
overlaps both the key art and the settings surface -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Width="180"
|
||||||
|
Height="180"
|
||||||
|
Margin="52,-152,0,0"
|
||||||
|
ZIndex="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
BoxShadow="0 26 54 0 #D1000000">
|
||||||
|
<Border Classes="coverClip"
|
||||||
|
Background="{Binding PlaceholderBrush}">
|
||||||
|
<Panel>
|
||||||
|
<TextBlock Text="{Binding Initials}"
|
||||||
|
FontSize="28"
|
||||||
|
FontWeight="Medium"
|
||||||
|
Foreground="#A6FFFFFF"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsVisible="{Binding !HasCover}" />
|
||||||
|
<Image Source="{Binding Cover}"
|
||||||
|
Stretch="UniformToFill"
|
||||||
|
IsVisible="{Binding HasCover}" />
|
||||||
|
</Panel>
|
||||||
|
</Border>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border Padding="260,18,32,24">
|
||||||
|
<Grid ColumnDefinitions="*,Auto"
|
||||||
|
ColumnSpacing="34">
|
||||||
|
<StackPanel Spacing="3"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Name}"
|
||||||
|
FontSize="42"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
<TextBlock Text="{Binding Path}"
|
||||||
|
MaxWidth="620"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{StaticResource SecondaryTextBrush}"
|
||||||
|
Opacity="0.8"
|
||||||
|
TextTrimming="CharacterEllipsis" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="34"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<StackPanel Spacing="4"
|
||||||
|
IsVisible="{Binding HasVersion}">
|
||||||
|
<TextBlock Classes="gameStatLabel"
|
||||||
|
Text="{Binding [Library.Stat.Version],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
<TextBlock Classes="gameStatValue"
|
||||||
|
FontSize="15"
|
||||||
|
Text="{Binding VersionText}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="4">
|
||||||
|
<TextBlock Classes="gameStatLabel"
|
||||||
|
Text="{Binding [Library.Stat.Installed],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
<TextBlock Classes="gameStatValue"
|
||||||
|
FontSize="15"
|
||||||
|
Text="{Binding SizeText}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Spacing="4"
|
||||||
|
IsVisible="{Binding HasTitleId}">
|
||||||
|
<TextBlock Classes="gameStatLabel"
|
||||||
|
Text="{Binding [Library.Stat.TitleId],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
<TextBlock Classes="gameStatValue"
|
||||||
|
FontSize="15"
|
||||||
|
Text="{Binding TitleId}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1"
|
||||||
|
Margin="32,12,32,8"
|
||||||
|
ColumnDefinitions="220,*"
|
||||||
|
ColumnSpacing="54">
|
||||||
|
<Grid Margin="0,62,0,0"
|
||||||
|
RowDefinitions="Auto,*">
|
||||||
|
<Button x:Name="GameOptionsLaunchButton"
|
||||||
|
Classes="gameOptionsLaunch">
|
||||||
|
<TextBlock VerticalAlignment="Center"
|
||||||
|
Text="{Binding [Library.Context.Launch],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ScrollViewer x:Name="GameOptionsNavScroll"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0,12,0,0"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Hidden"
|
||||||
|
BringIntoViewOnFocusChange="True">
|
||||||
|
<ScrollViewer.Styles>
|
||||||
|
<Style Selector="ScrollBar">
|
||||||
|
<Setter Property="IsHitTestVisible" Value="False" />
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
</ScrollViewer.Styles>
|
||||||
|
<Grid x:Name="GameOptionsNavHost"
|
||||||
|
Background="Transparent"
|
||||||
|
VerticalAlignment="Top">
|
||||||
|
<Border x:Name="GameOptionsNavIndicator"
|
||||||
|
Classes="optionsNavIndicator"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
VerticalAlignment="Top" />
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<Button x:Name="GameOptionsGeneralNav"
|
||||||
|
Classes="optionsNav active"
|
||||||
|
AutomationProperties.Name="{Binding [Options.General],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<Grid ColumnDefinitions="36,*">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="materialSymbol optionsNavIcon"
|
||||||
|
Text="tune" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="optionsNavLabel"
|
||||||
|
Text="{Binding [Options.General],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsLoggingNav"
|
||||||
|
Classes="optionsNav"
|
||||||
|
AutomationProperties.Name="{Binding [Options.Logging],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<Grid ColumnDefinitions="36,*">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="materialSymbol optionsNavIcon"
|
||||||
|
Text="terminal" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="optionsNavLabel"
|
||||||
|
Text="{Binding [Options.Logging],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsRenderingNav"
|
||||||
|
Classes="optionsNav"
|
||||||
|
AutomationProperties.Name="{Binding [Options.Graphics],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<Grid ColumnDefinitions="36,*">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="materialSymbol optionsNavIcon"
|
||||||
|
Text="monitor" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="optionsNavLabel"
|
||||||
|
Text="{Binding [Options.Graphics],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsEnvironmentNav"
|
||||||
|
Classes="optionsNav"
|
||||||
|
AutomationProperties.Name="{Binding [Options.Env.Tab],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<Grid ColumnDefinitions="36,*">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="materialSymbol optionsNavIcon"
|
||||||
|
Text="deployed_code" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="optionsNavLabel"
|
||||||
|
Text="{Binding [Options.Env.Tab],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsBackNav"
|
||||||
|
Classes="optionsNav"
|
||||||
|
AutomationProperties.Name="{Binding [Common.Back],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<Grid ColumnDefinitions="36,*">
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Classes="materialSymbol optionsNavIcon"
|
||||||
|
Text="arrow_back" />
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Classes="optionsNavLabel"
|
||||||
|
Text="{Binding [Common.Back],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}" />
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Column="1"
|
||||||
|
Margin="0,36,0,0">
|
||||||
|
<ScrollViewer x:Name="GameOptionsGeneralPanel"
|
||||||
|
Classes="optionsSectionPanel active">
|
||||||
|
<StackPanel Spacing="12">
|
||||||
|
<Border Classes="optionsGroup">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<local:SettingRow x:Name="GameStrictRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.Strict.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.Strict.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameStrictToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<WrapPanel Margin="0,12,0,0"
|
||||||
|
ItemWidth="260"
|
||||||
|
ItemHeight="54"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button x:Name="GameOptionsOpenFolderButton"
|
||||||
|
Classes="gameAction"
|
||||||
|
Margin="0,0,10,10">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="11">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="folder_open" />
|
||||||
|
<TextBlock Text="{Binding [Library.Context.OpenFolder],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsCopyPathButton"
|
||||||
|
Classes="gameAction"
|
||||||
|
Margin="0,0,10,10">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="11">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="content_copy" />
|
||||||
|
<TextBlock Text="{Binding [Library.Context.CopyPath],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsCopyTitleIdButton"
|
||||||
|
Classes="gameAction"
|
||||||
|
Margin="0,0,10,10">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="11">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="tag" />
|
||||||
|
<TextBlock Text="{Binding [Library.Context.CopyTitleId],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
<Button x:Name="GameOptionsRemoveButton"
|
||||||
|
Classes="gameAction dangerAction"
|
||||||
|
Margin="0,0,10,10">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="11">
|
||||||
|
<TextBlock Classes="materialSymbol"
|
||||||
|
Text="delete" />
|
||||||
|
<TextBlock Text="{Binding [Library.Context.Remove],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
</Button>
|
||||||
|
</WrapPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<ScrollViewer x:Name="GameOptionsLoggingPanel"
|
||||||
|
Classes="optionsSectionPanel">
|
||||||
|
<StackPanel>
|
||||||
|
<Border Classes="optionsGroup">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<local:SettingRow x:Name="GameLogLevelRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.LogLevel.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.LogLevel.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameLogLevelBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameTraceImportsRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.TraceImports.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.TraceImports.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<settingsControls:SplitNumericUpDown x:Name="GameTraceImportsBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
Minimum="0"
|
||||||
|
Maximum="4096"
|
||||||
|
Increment="16"
|
||||||
|
Value="0"
|
||||||
|
FormatString="0"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameLogToFileRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.LogToFile.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.LogToFile.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameLogToFileToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<ScrollViewer x:Name="GameOptionsRenderingPanel"
|
||||||
|
Classes="optionsSectionPanel">
|
||||||
|
<StackPanel>
|
||||||
|
<Border Classes="optionsGroup">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<local:SettingRow x:Name="GameWindowModeRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.WindowMode.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.WindowMode.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameWindowModeBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameResolutionRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.Resolution.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.Resolution.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameResolutionBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameDisplayRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.Display.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.Display.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameDisplayBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
Width="260"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameRefreshRateRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.RefreshRate.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.RefreshRate.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameRefreshRateBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameScalingModeRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.Scaling.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.Scaling.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameScalingModeBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameVSyncRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.VSync.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.VSync.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameVSyncToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
|
||||||
|
<local:SettingRow x:Name="GameHdrModeRow"
|
||||||
|
Classes="optionRow"
|
||||||
|
Label="{Binding [Options.Hdr.Label],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}"
|
||||||
|
Description="{Binding [Options.Hdr.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ComboBox x:Name="GameHdrModeBox"
|
||||||
|
Classes="optionValue"
|
||||||
|
ItemTemplate="{StaticResource LocalizedChoiceTemplate}"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</local:SettingRow>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<ScrollViewer x:Name="GameOptionsEnvironmentPanel"
|
||||||
|
Classes="optionsSectionPanel">
|
||||||
|
<StackPanel>
|
||||||
|
<Border Classes="optionsGroup">
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_BTHID_UNAVAILABLE"
|
||||||
|
Description="{Binding [Options.Env.Bthid.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvBthidToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_DISABLE_IMPORT_LOOP_GUARD"
|
||||||
|
Description="{Binding [Options.Env.LoopGuard.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvLoopGuardToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_WRITABLE_APP0"
|
||||||
|
Description="{Binding [Options.Env.WritableApp0.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvWritableApp0Toggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_VK_VALIDATION"
|
||||||
|
Description="{Binding [Options.Env.VkValidation.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvVkValidationToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_DUMP_SPIRV"
|
||||||
|
Description="{Binding [Options.Env.DumpSpirv.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvDumpSpirvToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_LOG_DIRECT_MEMORY"
|
||||||
|
Description="{Binding [Options.Env.LogDirectMemory.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvLogDirectMemoryToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_LOG_IO"
|
||||||
|
Description="{Binding [Options.Env.LogIo.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvLogIoToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_LOG_NP"
|
||||||
|
Description="{Binding [Options.Env.LogNp.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvLogNpToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
<local:SettingRow Classes="optionRow"
|
||||||
|
Label="SHARPEMU_GUEST_IMAGE_CPU_SYNC"
|
||||||
|
Description="{Binding [Options.Env.GuestImageCpuSync.Desc],
|
||||||
|
Source={x:Static local:Localization.Instance},
|
||||||
|
x:CompileBindings=False}">
|
||||||
|
<ToggleSwitch x:Name="GameEnvGuestImageCpuSyncToggle"
|
||||||
|
Classes="optionToggle" />
|
||||||
|
</local:SettingRow>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<!-- Options page: stable local navigation keeps each settings group
|
<!-- Options page: stable local navigation keeps each settings group
|
||||||
focused while preserving every setting from the current launcher -->
|
focused while preserving every setting from the current launcher -->
|
||||||
<Grid x:Name="OptionsPage"
|
<Grid x:Name="OptionsPage"
|
||||||
@@ -343,19 +957,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
Classes="optionsNavIndicator"
|
Classes="optionsNavIndicator"
|
||||||
Height="54"
|
Height="54"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
IsHitTestVisible="False">
|
IsHitTestVisible="False" />
|
||||||
<Border.RenderTransform>
|
|
||||||
<TranslateTransform>
|
|
||||||
<TranslateTransform.Transitions>
|
|
||||||
<Transitions>
|
|
||||||
<DoubleTransition Property="Y"
|
|
||||||
Duration="0:0:0.16"
|
|
||||||
Easing="CubicEaseOut" />
|
|
||||||
</Transitions>
|
|
||||||
</TranslateTransform.Transitions>
|
|
||||||
</TranslateTransform>
|
|
||||||
</Border.RenderTransform>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Button x:Name="OptionsGeneralNav" Classes="optionsNav active">
|
<Button x:Name="OptionsGeneralNav" Classes="optionsNav active">
|
||||||
@@ -595,7 +1197,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="LatestCommitDescription"
|
<TextBlock x:Name="LatestCommitDescription"
|
||||||
Text="{Binding [About.Github.LatestCommitDescription], Source={x:Static local:Localization.Instance}}"
|
Text="{Binding [About.Github.LatestCommitDescription], Source={x:Static local:Localization.Instance}}"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Foreground="{StaticResource MutedBrush}"
|
Foreground="{StaticResource SettingsDescriptionBrush}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
@@ -620,7 +1222,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="UpdateStatusText"
|
<TextBlock x:Name="UpdateStatusText"
|
||||||
Text="Current build: dev"
|
Text="Current build: dev"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Foreground="{StaticResource MutedBrush}"
|
Foreground="{StaticResource SettingsDescriptionBrush}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
@@ -642,7 +1244,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="GithubDesc"
|
<TextBlock x:Name="GithubDesc"
|
||||||
Text="{Binding [About.Github.Desc], Source={x:Static local:Localization.Instance}}"
|
Text="{Binding [About.Github.Desc], Source={x:Static local:Localization.Instance}}"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Foreground="{StaticResource MutedBrush}"
|
Foreground="{StaticResource SettingsDescriptionBrush}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1"
|
<Button Grid.Column="1"
|
||||||
@@ -664,7 +1266,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<TextBlock x:Name="DiscordServerDesc"
|
<TextBlock x:Name="DiscordServerDesc"
|
||||||
Text="{Binding [About.Discord.Desc], Source={x:Static local:Localization.Instance}}"
|
Text="{Binding [About.Discord.Desc], Source={x:Static local:Localization.Instance}}"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
Foreground="{StaticResource MutedBrush}"
|
Foreground="{StaticResource SettingsDescriptionBrush}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<TextBlock Grid.Column="1"
|
<TextBlock Grid.Column="1"
|
||||||
@@ -877,20 +1479,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Status bar -->
|
|
||||||
<Grid x:Name="StatusBar" Grid.Row="2" Height="32" Background="{StaticResource ChromeBrush}"
|
|
||||||
ColumnDefinitions="*,Auto">
|
|
||||||
<TextBlock x:Name="EmulatorPathText" Grid.Column="0" Text="Emulator: locating…" FontSize="11"
|
|
||||||
Foreground="{StaticResource FaintBrush}" VerticalAlignment="Center" Margin="16,0"
|
|
||||||
TextTrimming="CharacterEllipsis" />
|
|
||||||
<TextBlock x:Name="StatusBarRight" Grid.Column="1" Text="" FontSize="11"
|
|
||||||
Foreground="{StaticResource FaintBrush}" VerticalAlignment="Center" Margin="16,0" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Portable resize hit targets for the frameless desktop window. They
|
<!-- Portable resize hit targets for the frameless desktop window. They
|
||||||
are disabled while maximized or fullscreen in code-behind. -->
|
are disabled while maximized or fullscreen in code-behind. -->
|
||||||
<Panel x:Name="ResizeHandles"
|
<Panel x:Name="ResizeHandles"
|
||||||
Grid.RowSpan="3"
|
Grid.RowSpan="2"
|
||||||
ZIndex="1000"
|
ZIndex="1000"
|
||||||
IsVisible="False">
|
IsVisible="False">
|
||||||
<Border Tag="North"
|
<Border Tag="North"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Animation.Easings;
|
||||||
using Avalonia.Automation;
|
using Avalonia.Automation;
|
||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@@ -12,6 +13,7 @@ using Avalonia.Media;
|
|||||||
using Avalonia.Media.Imaging;
|
using Avalonia.Media.Imaging;
|
||||||
using Avalonia.Platform;
|
using Avalonia.Platform;
|
||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
|
using Avalonia.Rendering.Composition;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.VisualTree;
|
||||||
using SharpEmu.Core.Cpu;
|
using SharpEmu.Core.Cpu;
|
||||||
@@ -33,6 +35,8 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
private const int MaxConsoleLines = 4000;
|
private const int MaxConsoleLines = 4000;
|
||||||
private const int MaxConsoleLinesPerFlush = 500;
|
private const int MaxConsoleLinesPerFlush = 500;
|
||||||
|
private static readonly TimeSpan NavigationIndicatorAnimationDuration =
|
||||||
|
TimeSpan.FromMilliseconds(240);
|
||||||
|
|
||||||
private static readonly IBrush DefaultLineBrush = new SolidColorBrush(Color.Parse("#C7CFDE"));
|
private static readonly IBrush DefaultLineBrush = new SolidColorBrush(Color.Parse("#C7CFDE"));
|
||||||
private static readonly IBrush DimLineBrush = new SolidColorBrush(Color.Parse("#6B7488"));
|
private static readonly IBrush DimLineBrush = new SolidColorBrush(Color.Parse("#6B7488"));
|
||||||
@@ -217,6 +221,7 @@ public partial class MainWindow : Window
|
|||||||
OptionsTabButton.Click += (_, _) => SetActivePage(1);
|
OptionsTabButton.Click += (_, _) => SetActivePage(1);
|
||||||
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
|
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
|
||||||
WireOptionsNavigation();
|
WireOptionsNavigation();
|
||||||
|
WireGameOptions();
|
||||||
|
|
||||||
// The settings page edits _settings live, so a launch started while
|
// The settings page edits _settings live, so a launch started while
|
||||||
// it is open already uses the new values.
|
// it is open already uses the new values.
|
||||||
@@ -288,9 +293,9 @@ public partial class MainWindow : Window
|
|||||||
CtxLaunch.Click += (_, _) => LaunchSelected();
|
CtxLaunch.Click += (_, _) => LaunchSelected();
|
||||||
CtxOpenFolder.Click += (_, _) => OpenSelectedGameFolder();
|
CtxOpenFolder.Click += (_, _) => OpenSelectedGameFolder();
|
||||||
CtxCopyPath.Click += async (_, _) =>
|
CtxCopyPath.Click += async (_, _) =>
|
||||||
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.Path, "Clipboard.Path");
|
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.Path);
|
||||||
CtxCopyTitleId.Click += async (_, _) =>
|
CtxCopyTitleId.Click += async (_, _) =>
|
||||||
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.TitleId, "Clipboard.TitleId");
|
await CopyToClipboardAsync((GameList.SelectedItem as GameEntry)?.TitleId);
|
||||||
CtxGameSettings.Click += (_, _) => OpenSelectedGameSettings();
|
CtxGameSettings.Click += (_, _) => OpenSelectedGameSettings();
|
||||||
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
|
CtxRemove.Click += (_, _) => RemoveSelectedFromLibrary();
|
||||||
|
|
||||||
@@ -340,6 +345,11 @@ public partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
if (index == _activePageIndex)
|
if (index == _activePageIndex)
|
||||||
{
|
{
|
||||||
|
if (index == 0 && _isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
CloseGameSettings();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,12 +358,25 @@ public partial class MainWindow : Window
|
|||||||
_settings.Save(); // leaving the Options page
|
_settings.Save(); // leaving the Options page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
CloseGameSettings(restoreLibrary: false);
|
||||||
|
}
|
||||||
|
|
||||||
_activePageIndex = index;
|
_activePageIndex = index;
|
||||||
SetActiveClass(LibraryTabButton, index == 0);
|
SetActiveClass(LibraryTabButton, index == 0);
|
||||||
SetActiveClass(OptionsTabButton, index == 1);
|
SetActiveClass(OptionsTabButton, index == 1);
|
||||||
LibraryPage.IsVisible = index == 0;
|
LibraryPage.IsVisible = index == 0;
|
||||||
LibraryToolbar.IsVisible = index == 0;
|
LibraryToolbar.IsVisible = index == 0;
|
||||||
|
OptionsPageSurface.IsVisible = index == 1;
|
||||||
OptionsPage.IsVisible = index == 1;
|
OptionsPage.IsVisible = index == 1;
|
||||||
|
|
||||||
|
if (index == 1)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(
|
||||||
|
() => SetOptionsNavigationIndicator(_optionsSectionIndex, animate: false),
|
||||||
|
DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetActiveClass(Button button, bool active)
|
private static void SetActiveClass(Button button, bool active)
|
||||||
@@ -446,17 +469,69 @@ public partial class MainWindow : Window
|
|||||||
active ? KeyboardNavigationMode.Continue : KeyboardNavigationMode.None);
|
active ? KeyboardNavigationMode.Continue : KeyboardNavigationMode.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetOptionsNavigationIndicator(int section)
|
private void SetOptionsNavigationIndicator(int section, bool animate = true)
|
||||||
{
|
{
|
||||||
if (OptionsNavIndicator.RenderTransform is not TranslateTransform transform)
|
var buttons = OptionsNavigationButtons();
|
||||||
|
var button = buttons[Math.Clamp(section, 0, buttons.Length - 1)];
|
||||||
|
MoveNavigationIndicator(
|
||||||
|
OptionsNavIndicator,
|
||||||
|
OptionsNavHost,
|
||||||
|
button,
|
||||||
|
section,
|
||||||
|
animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureNavigationIndicatorAnimation(Border indicator)
|
||||||
|
{
|
||||||
|
if (ElementComposition.GetElementVisual(indicator) is not { } visual)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buttons = OptionsNavigationButtons();
|
var translationAnimation = visual.Compositor.CreateVector3KeyFrameAnimation();
|
||||||
var button = buttons[Math.Clamp(section, 0, buttons.Length - 1)];
|
translationAnimation.Duration = NavigationIndicatorAnimationDuration;
|
||||||
transform.Y = button.TranslatePoint(default, OptionsNavHost)?.Y
|
translationAnimation.Target = nameof(CompositionVisual.Translation);
|
||||||
|
translationAnimation.InsertExpressionKeyFrame(
|
||||||
|
1f,
|
||||||
|
"this.FinalValue",
|
||||||
|
new SineEaseInOut());
|
||||||
|
|
||||||
|
var animations = visual.Compositor.CreateImplicitAnimationCollection();
|
||||||
|
animations[nameof(CompositionVisual.Translation)] = translationAnimation;
|
||||||
|
visual.ImplicitAnimations = animations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MoveNavigationIndicator(
|
||||||
|
Border indicator,
|
||||||
|
Control host,
|
||||||
|
Button button,
|
||||||
|
int section,
|
||||||
|
bool animate = true)
|
||||||
|
{
|
||||||
|
if (ElementComposition.GetElementVisual(indicator) is not { } visual)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetY = button.TranslatePoint(default, host)?.Y
|
||||||
?? section * button.Bounds.Height;
|
?? section * button.Bounds.Height;
|
||||||
|
|
||||||
|
if (!animate)
|
||||||
|
{
|
||||||
|
visual.ImplicitAnimations = null;
|
||||||
|
visual.StopAnimation(nameof(CompositionVisual.Translation));
|
||||||
|
}
|
||||||
|
else if (visual.ImplicitAnimations is null)
|
||||||
|
{
|
||||||
|
ConfigureNavigationIndicatorAnimation(indicator);
|
||||||
|
}
|
||||||
|
|
||||||
|
visual.Translation = new Vector3D(0, targetY, 0);
|
||||||
|
|
||||||
|
if (!animate)
|
||||||
|
{
|
||||||
|
ConfigureNavigationIndicatorAnimation(indicator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Github http client config ----
|
// ---- Github http client config ----
|
||||||
@@ -581,7 +656,7 @@ public partial class MainWindow : Window
|
|||||||
SetActivePage(1);
|
SetActivePage(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_activePageIndex != 0)
|
if (_activePageIndex != 0 || _isGameSettingsOpen)
|
||||||
{
|
{
|
||||||
_previousPadButtons = pad.Buttons;
|
_previousPadButtons = pad.Buttons;
|
||||||
return;
|
return;
|
||||||
@@ -798,6 +873,13 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private void OnKeyDown(object sender, KeyEventArgs args)
|
private void OnKeyDown(object sender, KeyEventArgs args)
|
||||||
{
|
{
|
||||||
|
if (args.Key == Key.Escape && _isGameSettingsOpen)
|
||||||
|
{
|
||||||
|
CloseGameSettings();
|
||||||
|
args.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.Key == Key.F11 && !_isRunning)
|
if (args.Key == Key.F11 && !_isRunning)
|
||||||
{
|
{
|
||||||
WindowState = WindowState == WindowState.FullScreen
|
WindowState = WindowState == WindowState.FullScreen
|
||||||
@@ -910,11 +992,6 @@ public partial class MainWindow : Window
|
|||||||
titleBar.IsVisible = !isFullscreen;
|
titleBar.IsVisible = !isFullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StatusBar is { } statusBar)
|
|
||||||
{
|
|
||||||
statusBar.IsVisible = !isFullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ResizeHandles is { } handles)
|
if (ResizeHandles is { } handles)
|
||||||
{
|
{
|
||||||
handles.IsVisible = CanResize && WindowState == WindowState.Normal;
|
handles.IsVisible = CanResize && WindowState == WindowState.Normal;
|
||||||
@@ -1302,9 +1379,6 @@ public partial class MainWindow : Window
|
|||||||
? Path.GetFullPath(found)
|
? Path.GetFullPath(found)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
EmulatorPathText.Text = _emulatorExePath is not null
|
|
||||||
? Localization.Instance.Format("Status.EmulatorPath", _emulatorExePath)
|
|
||||||
: Localization.Instance.Get("Status.EmulatorNotFound");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Game library ----
|
// ---- Game library ----
|
||||||
@@ -1379,11 +1453,6 @@ public partial class MainWindow : Window
|
|||||||
var excluded = new HashSet<string>(_settings.ExcludedGames, GameLibraryPath.Comparer);
|
var excluded = new HashSet<string>(_settings.ExcludedGames, GameLibraryPath.Comparer);
|
||||||
_libraryWatcher.Watch(folders);
|
_libraryWatcher.Watch(folders);
|
||||||
var showLoadingState = showProgress && _allGames.Count == 0;
|
var showLoadingState = showProgress && _allGames.Count == 0;
|
||||||
if (showProgress)
|
|
||||||
{
|
|
||||||
StatusBarRight.Text = Localization.Instance.Get("Status.ScanningLibrary");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLoadingState)
|
if (showLoadingState)
|
||||||
{
|
{
|
||||||
EmptyState.IsVisible = false;
|
EmptyState.IsVisible = false;
|
||||||
@@ -1404,12 +1473,6 @@ public partial class MainWindow : Window
|
|||||||
LoadingState.IsVisible = false;
|
LoadingState.IsVisible = false;
|
||||||
LoadGameDetailsInBackground(reconciliation.CoversToLoad, reconciliation.Games);
|
LoadGameDetailsInBackground(reconciliation.CoversToLoad, reconciliation.Games);
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
if (showProgress)
|
|
||||||
{
|
|
||||||
StatusBarRight.Text = folders.Length == 0
|
|
||||||
? Localization.Instance.Get("Status.AddFolderPrompt")
|
|
||||||
: Localization.Instance.Format("Status.LibraryScanned", games.Count, folders.Length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1806,24 +1869,6 @@ public partial class MainWindow : Window
|
|||||||
CtxGameSettings.IsEnabled = !string.IsNullOrWhiteSpace(game.TitleId);
|
CtxGameSettings.IsEnabled = !string.IsNullOrWhiteSpace(game.TitleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenSelectedGameSettings()
|
|
||||||
{
|
|
||||||
if (GameList.SelectedItem is not GameEntry game)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(game.TitleId))
|
|
||||||
{
|
|
||||||
AppendConsoleLine(
|
|
||||||
"[GUI][WARN] Per-game settings require a title ID, which this game does not have.",
|
|
||||||
WarningLineBrush);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = new PerGameSettingsDialog(game.TitleId, game.Name, _settings).ShowDialog(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpenSelectedGameFolder()
|
private void OpenSelectedGameFolder()
|
||||||
{
|
{
|
||||||
if (GameList.SelectedItem is not GameEntry game)
|
if (GameList.SelectedItem is not GameEntry game)
|
||||||
@@ -1854,12 +1899,13 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
StatusBarRight.Text = Localization.Instance.Format("Status.CouldNotOpenFolder", ex.Message);
|
AppendConsoleLine(
|
||||||
|
Localization.Instance.Format("Status.CouldNotOpenFolder", ex.Message),
|
||||||
|
WarningLineBrush);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Copies <paramref name="text"/> and reports it via <paramref name="whatKey"/>, e.g. "Clipboard.Path".</summary>
|
private async Task CopyToClipboardAsync(string? text)
|
||||||
private async Task CopyToClipboardAsync(string? text, string whatKey)
|
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text) || Clipboard is null)
|
if (string.IsNullOrEmpty(text) || Clipboard is null)
|
||||||
{
|
{
|
||||||
@@ -1867,7 +1913,6 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
await Clipboard.SetTextAsync(text);
|
await Clipboard.SetTextAsync(text);
|
||||||
StatusBarRight.Text = Localization.Instance.Format("Status.CopiedToClipboard", Localization.Instance.Get(whatKey));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveSelectedFromLibrary()
|
private void RemoveSelectedFromLibrary()
|
||||||
@@ -1887,7 +1932,6 @@ public partial class MainWindow : Window
|
|||||||
string.Equals(g.Path, game.Path, GameLibraryPath.Comparison));
|
string.Equals(g.Path, game.Path, GameLibraryPath.Comparison));
|
||||||
GameList.SelectedItem = null;
|
GameList.SelectedItem = null;
|
||||||
RefreshVisibleGames();
|
RefreshVisibleGames();
|
||||||
StatusBarRight.Text = Localization.Instance.Format("Status.RemovedFromLibrary", game.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshVisibleGames(IReadOnlySet<GameEntry>? backgroundsChanged = null)
|
private void RefreshVisibleGames(IReadOnlySet<GameEntry>? backgroundsChanged = null)
|
||||||
@@ -2197,7 +2241,6 @@ public partial class MainWindow : Window
|
|||||||
_runningGameName = displayName;
|
_runningGameName = displayName;
|
||||||
_runningGameTitleId = resolvedTitleId;
|
_runningGameTitleId = resolvedTitleId;
|
||||||
_runningSinceUnixSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
_runningSinceUnixSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
StatusBarRight.Text = Localization.Instance.Format("Status.Running", displayName);
|
|
||||||
UpdateRunButtons();
|
UpdateRunButtons();
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
|
|
||||||
@@ -2250,7 +2293,6 @@ public partial class MainWindow : Window
|
|||||||
_emulator.Stop();
|
_emulator.Stop();
|
||||||
_runningGameName = null;
|
_runningGameName = null;
|
||||||
_runningGameTitleId = null;
|
_runningGameTitleId = null;
|
||||||
StatusBarRight.Text = Localization.Instance.Get("Status.Stopping");
|
|
||||||
UpdateDiscordPresence();
|
UpdateDiscordPresence();
|
||||||
ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
|
ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
|
||||||
Console.Error.WriteLine("[GUI][INFO] Waiting for the SDL game process to exit.");
|
Console.Error.WriteLine("[GUI][INFO] Waiting for the SDL game process to exit.");
|
||||||
@@ -2317,7 +2359,6 @@ public partial class MainWindow : Window
|
|||||||
brush);
|
brush);
|
||||||
CloseFileLogSoon();
|
CloseFileLogSoon();
|
||||||
|
|
||||||
StatusBarRight.Text = Localization.Instance.Get("Status.Idle");
|
|
||||||
_runningGameName = null;
|
_runningGameName = null;
|
||||||
_runningGameTitleId = null;
|
_runningGameTitleId = null;
|
||||||
UpdateRunButtons();
|
UpdateRunButtons();
|
||||||
@@ -2470,6 +2511,9 @@ public partial class MainWindow : Window
|
|||||||
LaunchButton.IsEnabled = GameList.SelectedItem is GameEntry;
|
LaunchButton.IsEnabled = GameList.SelectedItem is GameEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameSettingsButton.IsEnabled =
|
||||||
|
GameList.SelectedItem is GameEntry game &&
|
||||||
|
!string.IsNullOrWhiteSpace(game.TitleId);
|
||||||
OpenFileButton.IsEnabled = !_isRunning;
|
OpenFileButton.IsEnabled = !_isRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,74 @@ public sealed class PerGameSettings
|
|||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes values that already match the global configuration so the
|
||||||
|
/// remaining object contains only effective per-game overrides.
|
||||||
|
/// </summary>
|
||||||
|
internal void RemoveInheritedValues(GuiSettings global)
|
||||||
|
{
|
||||||
|
if (string.Equals(LogLevel, global.LogLevel, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
LogLevel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImportTraceLimit == global.ImportTraceLimit)
|
||||||
|
{
|
||||||
|
ImportTraceLimit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrictDynlibResolution == global.StrictDynlibResolution)
|
||||||
|
{
|
||||||
|
StrictDynlibResolution = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LogToFile == global.LogToFile)
|
||||||
|
{
|
||||||
|
LogToFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(WindowMode, global.WindowMode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
WindowMode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(Resolution, global.Resolution, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Resolution = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DisplayIndex == global.DisplayIndex)
|
||||||
|
{
|
||||||
|
DisplayIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RefreshRate == global.RefreshRate)
|
||||||
|
{
|
||||||
|
RefreshRate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(ScalingMode, global.ScalingMode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ScalingMode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VSync == global.VSync)
|
||||||
|
{
|
||||||
|
VSync = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(HdrMode, global.HdrMode, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
HdrMode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EnvironmentToggles is { } environmentToggles &&
|
||||||
|
EnvironmentEntriesEqual(environmentToggles, global.EnvironmentToggles))
|
||||||
|
{
|
||||||
|
EnvironmentToggles = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Save(string titleId)
|
public void Save(string titleId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(titleId))
|
if (string.IsNullOrWhiteSpace(titleId))
|
||||||
@@ -130,6 +198,35 @@ public sealed class PerGameSettings
|
|||||||
|
|
||||||
return trimmed.Length == 0 ? "UNKNOWN" : trimmed;
|
return trimmed.Length == 0 ? "UNKNOWN" : trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool EnvironmentEntriesEqual(
|
||||||
|
IEnumerable<string> left,
|
||||||
|
IEnumerable<string> right) =>
|
||||||
|
NormalizeEnvironmentEntries(left).SetEquals(NormalizeEnvironmentEntries(right));
|
||||||
|
|
||||||
|
private static HashSet<string> NormalizeEnvironmentEntries(IEnumerable<string> entries)
|
||||||
|
{
|
||||||
|
var normalized = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
var parts = entry.Split('=', 2, StringSplitOptions.TrimEntries);
|
||||||
|
if (parts.Length == 0 || string.IsNullOrWhiteSpace(parts[0]))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.Length == 2 && parts[1] == "0")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.Add(parts.Length == 2 && parts[1] != "1"
|
||||||
|
? $"{parts[0]}={parts[1]}"
|
||||||
|
: parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record EffectiveLaunchSettings(
|
public sealed record EffectiveLaunchSettings(
|
||||||
|
|||||||
@@ -1,426 +0,0 @@
|
|||||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Layout;
|
|
||||||
using Avalonia.Media;
|
|
||||||
using SharpEmu.Libs.VideoOut;
|
|
||||||
|
|
||||||
namespace SharpEmu.GUI;
|
|
||||||
|
|
||||||
public sealed class PerGameSettingsDialog : Window
|
|
||||||
{
|
|
||||||
private static readonly string[] LogLevels =
|
|
||||||
{ "Trace", "Debug", "Info", "Warning", "Error", "Critical" };
|
|
||||||
private static readonly string[] WindowModes = { "Windowed", "Borderless", "Exclusive" };
|
|
||||||
private static readonly string[] ScalingModes = { "Fit", "Cover", "Stretch", "Integer" };
|
|
||||||
private static readonly string[] HdrModes = { "Auto", "On", "Off" };
|
|
||||||
|
|
||||||
private static readonly string[] EnvToggles =
|
|
||||||
{
|
|
||||||
"SHARPEMU_BTHID_UNAVAILABLE",
|
|
||||||
"SHARPEMU_DISABLE_IMPORT_LOOP_GUARD",
|
|
||||||
"SHARPEMU_WRITABLE_APP0",
|
|
||||||
"SHARPEMU_VK_VALIDATION",
|
|
||||||
"SHARPEMU_DUMP_SPIRV",
|
|
||||||
"SHARPEMU_LOG_DIRECT_MEMORY",
|
|
||||||
"SHARPEMU_LOG_IO",
|
|
||||||
"SHARPEMU_LOG_NP",
|
|
||||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly string _titleId;
|
|
||||||
private IReadOnlyList<HostDisplayOption> _hostDisplays = [];
|
|
||||||
private bool _updatingHostDisplayOptions;
|
|
||||||
|
|
||||||
private readonly SettingRow _logLevelRow;
|
|
||||||
private readonly ComboBox _logLevel = new() { ItemsSource = LogLevels, Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _traceRow;
|
|
||||||
private readonly NumericUpDown _trace = new()
|
|
||||||
{
|
|
||||||
Minimum = 0, Maximum = 4096, Increment = 16, Width = 160, FormatString = "0",
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly SettingRow _strictRow;
|
|
||||||
private readonly ToggleSwitch _strict = new();
|
|
||||||
|
|
||||||
private readonly SettingRow _logToFileRow;
|
|
||||||
private readonly ToggleSwitch _logToFile = new();
|
|
||||||
|
|
||||||
private readonly SettingRow _windowModeRow;
|
|
||||||
private readonly ComboBox _windowMode = new() { ItemsSource = WindowModes, Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _resolutionRow;
|
|
||||||
private readonly ComboBox _resolution = new() { Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _displayIndexRow;
|
|
||||||
private readonly ComboBox _displayIndex = new() { Width = 240 };
|
|
||||||
|
|
||||||
private readonly SettingRow _refreshRateRow;
|
|
||||||
private readonly ComboBox _refreshRate = new() { Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _scalingModeRow;
|
|
||||||
private readonly ComboBox _scalingMode = new() { ItemsSource = ScalingModes, Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _vsyncRow;
|
|
||||||
private readonly ToggleSwitch _vsync = new();
|
|
||||||
|
|
||||||
private readonly SettingRow _hdrModeRow;
|
|
||||||
private readonly ComboBox _hdrMode = new() { ItemsSource = HdrModes, Width = 160 };
|
|
||||||
|
|
||||||
private readonly SettingRow _envRow;
|
|
||||||
private readonly StackPanel _envList = new() { Orientation = Orientation.Vertical, Spacing = 8, Margin = new(0, 4, 0, 0) };
|
|
||||||
private readonly List<(string Name, ToggleSwitch Box)> _envBoxes = new();
|
|
||||||
|
|
||||||
public PerGameSettingsDialog(string titleId, string displayName, GuiSettings global)
|
|
||||||
{
|
|
||||||
_titleId = titleId;
|
|
||||||
var loc = Localization.Instance;
|
|
||||||
|
|
||||||
Title = loc.Format("PerGame.Title", displayName, titleId);
|
|
||||||
Width = 520;
|
|
||||||
MaxHeight = 720;
|
|
||||||
SizeToContent = SizeToContent.Height;
|
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
|
||||||
CanResize = false;
|
|
||||||
|
|
||||||
Background = new SolidColorBrush(Color.Parse("#0D1017"));
|
|
||||||
|
|
||||||
_strict.OnContent = _logToFile.OnContent = _vsync.OnContent = loc.Get("Common.On");
|
|
||||||
_strict.OffContent = _logToFile.OffContent = _vsync.OffContent = loc.Get("Common.Off");
|
|
||||||
|
|
||||||
_logLevelRow = Row(loc.Get("Options.LogLevel.Label"), loc.Get("Options.LogLevel.Desc"), _logLevel);
|
|
||||||
_traceRow = Row(loc.Get("Options.TraceImports.Label"), loc.Get("Options.TraceImports.Desc"), _trace);
|
|
||||||
_strictRow = Row(loc.Get("Options.Strict.Label"), loc.Get("Options.Strict.Desc"), _strict);
|
|
||||||
_logToFileRow = Row(loc.Get("Options.LogToFile.Label"), loc.Get("Options.LogToFile.Desc"), _logToFile);
|
|
||||||
_windowModeRow = Row(loc.Get("Options.WindowMode.Label"), loc.Get("Options.WindowMode.Desc"), _windowMode);
|
|
||||||
_resolutionRow = Row(loc.Get("Options.Resolution.Label"), loc.Get("Options.Resolution.Desc"), _resolution);
|
|
||||||
_displayIndexRow = Row(loc.Get("Options.Display.Label"), loc.Get("Options.Display.Desc"), _displayIndex);
|
|
||||||
_refreshRateRow = Row(loc.Get("Options.RefreshRate.Label"), loc.Get("Options.RefreshRate.Desc"), _refreshRate);
|
|
||||||
_scalingModeRow = Row(loc.Get("Options.Scaling.Label"), loc.Get("Options.Scaling.Desc"), _scalingMode);
|
|
||||||
_vsyncRow = Row(loc.Get("Options.VSync.Label"), loc.Get("Options.VSync.Desc"), _vsync);
|
|
||||||
_hdrModeRow = Row(loc.Get("Options.Hdr.Label"), loc.Get("Options.Hdr.Desc"), _hdrMode);
|
|
||||||
_envRow = new SettingRow
|
|
||||||
{
|
|
||||||
Label = loc.Get("PerGame.EnvToggles.Label"),
|
|
||||||
Description = loc.Get("PerGame.EnvToggles.Desc"),
|
|
||||||
ShowOverride = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var name in EnvToggles)
|
|
||||||
{
|
|
||||||
var box = new ToggleSwitch { OnContent = name, OffContent = name };
|
|
||||||
_envBoxes.Add((name, box));
|
|
||||||
_envList.Children.Add(box);
|
|
||||||
}
|
|
||||||
|
|
||||||
var general = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(0, 12, 0, 0) };
|
|
||||||
general.Children.Add(Card(loc.Get("Options.Section.Emulation"), _strictRow));
|
|
||||||
general.Children.Add(Card(loc.Get("Options.Section.Logging"), _logLevelRow, _traceRow, _logToFileRow));
|
|
||||||
general.Children.Add(Card(loc.Get("Options.Section.Environment"), _envRow, _envList));
|
|
||||||
|
|
||||||
var graphics = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(0, 12, 0, 0) };
|
|
||||||
graphics.Children.Add(Card(
|
|
||||||
loc.Get("Options.Section.Display"),
|
|
||||||
_windowModeRow,
|
|
||||||
_resolutionRow,
|
|
||||||
_displayIndexRow,
|
|
||||||
_refreshRateRow,
|
|
||||||
_scalingModeRow,
|
|
||||||
_vsyncRow,
|
|
||||||
_hdrModeRow));
|
|
||||||
|
|
||||||
var content = new StackPanel { Orientation = Orientation.Vertical, Spacing = 12, Margin = new(16) };
|
|
||||||
content.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = loc.Get("PerGame.InheritNote"),
|
|
||||||
Foreground = new SolidColorBrush(Color.Parse("#8B94A7")),
|
|
||||||
FontSize = 12,
|
|
||||||
});
|
|
||||||
content.Children.Add(new TabControl
|
|
||||||
{
|
|
||||||
ItemsSource = new[]
|
|
||||||
{
|
|
||||||
new TabItem { Header = loc.Get("PerGame.Tab.General"), Content = general },
|
|
||||||
new TabItem { Header = loc.Get("PerGame.Tab.Graphics"), Content = graphics },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
var save = new Button { Content = loc.Get("Common.Save"), Classes = { "accent" } };
|
|
||||||
var cancel = new Button { Content = loc.Get("Common.Cancel"), Classes = { "ghost" } };
|
|
||||||
save.Click += (_, _) => { Persist(); Close(); };
|
|
||||||
cancel.Click += (_, _) => Close();
|
|
||||||
|
|
||||||
var buttonBar = new Border
|
|
||||||
{
|
|
||||||
BorderBrush = new SolidColorBrush(Color.Parse("#8B94A7")) { Opacity = 0.25 },
|
|
||||||
BorderThickness = new Thickness(0, 1, 0, 0),
|
|
||||||
Padding = new(16),
|
|
||||||
Child = new StackPanel
|
|
||||||
{
|
|
||||||
Orientation = Orientation.Horizontal,
|
|
||||||
Spacing = 8,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Right,
|
|
||||||
Children = { cancel, save },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var root = new Grid { RowDefinitions = new RowDefinitions("*,Auto") };
|
|
||||||
var scroller = new ScrollViewer { Content = content };
|
|
||||||
Grid.SetRow(scroller, 0);
|
|
||||||
Grid.SetRow(buttonBar, 1);
|
|
||||||
root.Children.Add(scroller);
|
|
||||||
root.Children.Add(buttonBar);
|
|
||||||
Content = root;
|
|
||||||
|
|
||||||
_displayIndex.SelectionChanged += (_, _) => OnHostDisplayChanged();
|
|
||||||
_resolution.SelectionChanged += (_, _) => OnHostResolutionChanged();
|
|
||||||
LoadValues(global);
|
|
||||||
_envRow.PropertyChanged += (_, e) =>
|
|
||||||
{
|
|
||||||
if (e.Property == SettingRow.IsOverriddenProperty)
|
|
||||||
{
|
|
||||||
_envList.IsEnabled = _envRow.IsOverridden;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
_envList.IsEnabled = _envRow.IsOverridden;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SettingRow Row(string label, string description, Control value) => new()
|
|
||||||
{
|
|
||||||
Label = label,
|
|
||||||
Description = description,
|
|
||||||
ShowOverride = true,
|
|
||||||
Content = value,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Border Card(string title, params Control[] rows)
|
|
||||||
{
|
|
||||||
var stack = new StackPanel { Orientation = Orientation.Vertical, Spacing = 14 };
|
|
||||||
stack.Children.Add(new TextBlock { Text = title, Classes = { "sectionTitle" } });
|
|
||||||
foreach (var row in rows)
|
|
||||||
{
|
|
||||||
stack.Children.Add(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
var card = new Border { Child = stack };
|
|
||||||
card.Classes.Add("card");
|
|
||||||
return card;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadValues(GuiSettings global)
|
|
||||||
{
|
|
||||||
var existing = PerGameSettings.Load(_titleId);
|
|
||||||
var displayIndex = Math.Max(0, existing?.DisplayIndex ?? global.DisplayIndex);
|
|
||||||
var resolution = existing?.Resolution ?? global.Resolution;
|
|
||||||
var refreshRate = Math.Clamp(existing?.RefreshRate ?? global.RefreshRate, 0, 1000);
|
|
||||||
|
|
||||||
_updatingHostDisplayOptions = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_hostDisplays = HostDisplayOptions.BuildDisplays(HostDisplayCatalog.Query(), displayIndex);
|
|
||||||
_displayIndex.ItemsSource = _hostDisplays;
|
|
||||||
var display = HostDisplayOptions.SelectDisplay(_hostDisplays, displayIndex);
|
|
||||||
_displayIndex.SelectedItem = display;
|
|
||||||
PopulateHostModes(display, resolution, refreshRate);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_updatingHostDisplayOptions = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logLevel.SelectedItem = Array.IndexOf(LogLevels, global.LogLevel) >= 0 ? global.LogLevel : "Info";
|
|
||||||
_trace.Value = global.ImportTraceLimit;
|
|
||||||
_strict.IsChecked = global.StrictDynlibResolution;
|
|
||||||
_logToFile.IsChecked = global.LogToFile;
|
|
||||||
_windowMode.SelectedItem = ChoiceOrDefault(WindowModes, global.WindowMode, "Windowed");
|
|
||||||
_scalingMode.SelectedItem = ChoiceOrDefault(ScalingModes, global.ScalingMode, "Fit");
|
|
||||||
_vsync.IsChecked = global.VSync;
|
|
||||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, global.HdrMode, "Auto");
|
|
||||||
foreach (var (name, box) in _envBoxes)
|
|
||||||
{
|
|
||||||
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing.LogLevel is { } level && Array.IndexOf(LogLevels, level) >= 0)
|
|
||||||
{
|
|
||||||
_logLevelRow.IsOverridden = true;
|
|
||||||
_logLevel.SelectedItem = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing.ImportTraceLimit is { } t) { _traceRow.IsOverridden = true; _trace.Value = t; }
|
|
||||||
if (existing.StrictDynlibResolution is { } s) { _strictRow.IsOverridden = true; _strict.IsChecked = s; }
|
|
||||||
if (existing.LogToFile is { } l) { _logToFileRow.IsOverridden = true; _logToFile.IsChecked = l; }
|
|
||||||
if (existing.WindowMode is { } windowMode && WindowModes.Contains(windowMode, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_windowModeRow.IsOverridden = true;
|
|
||||||
_windowMode.SelectedItem = ChoiceOrDefault(WindowModes, windowMode, "Windowed");
|
|
||||||
}
|
|
||||||
if (existing.Resolution is not null)
|
|
||||||
{
|
|
||||||
_resolutionRow.IsOverridden = true;
|
|
||||||
}
|
|
||||||
if (existing.DisplayIndex is not null)
|
|
||||||
{
|
|
||||||
_displayIndexRow.IsOverridden = true;
|
|
||||||
}
|
|
||||||
if (existing.RefreshRate is not null)
|
|
||||||
{
|
|
||||||
_refreshRateRow.IsOverridden = true;
|
|
||||||
}
|
|
||||||
if (existing.ScalingMode is { } scalingMode && ScalingModes.Contains(scalingMode, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_scalingModeRow.IsOverridden = true;
|
|
||||||
_scalingMode.SelectedItem = ChoiceOrDefault(ScalingModes, scalingMode, "Fit");
|
|
||||||
}
|
|
||||||
if (existing.VSync is { } vsync)
|
|
||||||
{
|
|
||||||
_vsyncRow.IsOverridden = true;
|
|
||||||
_vsync.IsChecked = vsync;
|
|
||||||
}
|
|
||||||
if (existing.HdrMode is { } hdrMode && HdrModes.Contains(hdrMode, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_hdrModeRow.IsOverridden = true;
|
|
||||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, hdrMode, "Auto");
|
|
||||||
}
|
|
||||||
if (existing.EnvironmentToggles is { } env)
|
|
||||||
{
|
|
||||||
_envRow.IsOverridden = true;
|
|
||||||
foreach (var (name, box) in _envBoxes)
|
|
||||||
{
|
|
||||||
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ChoiceOrDefault(string[] choices, string? value, string fallback) =>
|
|
||||||
choices.FirstOrDefault(choice => string.Equals(choice, value, StringComparison.OrdinalIgnoreCase)) ?? fallback;
|
|
||||||
|
|
||||||
private void OnHostDisplayChanged()
|
|
||||||
{
|
|
||||||
if (_updatingHostDisplayOptions || _displayIndex.SelectedItem is not HostDisplayOption display)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updatingHostDisplayOptions = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PopulateHostModes(
|
|
||||||
display,
|
|
||||||
_resolution.SelectedItem as string ?? "1920x1080",
|
|
||||||
SelectedRefreshRate());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_updatingHostDisplayOptions = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnHostResolutionChanged()
|
|
||||||
{
|
|
||||||
if (_updatingHostDisplayOptions || _displayIndex.SelectedItem is not HostDisplayOption display)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedRefreshRate = SelectedRefreshRate();
|
|
||||||
_updatingHostDisplayOptions = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
PopulateRefreshRates(display, _resolution.SelectedItem as string, selectedRefreshRate);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_updatingHostDisplayOptions = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateHostModes(
|
|
||||||
HostDisplayOption display,
|
|
||||||
string selectedResolution,
|
|
||||||
int selectedRefreshRate)
|
|
||||||
{
|
|
||||||
var resolutions = HostDisplayOptions.BuildResolutions(display, selectedResolution);
|
|
||||||
_resolution.ItemsSource = resolutions;
|
|
||||||
_resolution.SelectedItem = resolutions.FirstOrDefault(resolution =>
|
|
||||||
string.Equals(resolution, selectedResolution, StringComparison.OrdinalIgnoreCase)) ?? resolutions[0];
|
|
||||||
PopulateRefreshRates(display, _resolution.SelectedItem as string, selectedRefreshRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateRefreshRates(
|
|
||||||
HostDisplayOption display,
|
|
||||||
string? resolution,
|
|
||||||
int selectedRefreshRate)
|
|
||||||
{
|
|
||||||
var rates = HostDisplayOptions.BuildRefreshRates(
|
|
||||||
display,
|
|
||||||
resolution,
|
|
||||||
selectedRefreshRate,
|
|
||||||
Localization.Instance.Get("Options.RefreshRate.Automatic"));
|
|
||||||
_refreshRate.ItemsSource = rates;
|
|
||||||
_refreshRate.SelectedItem = rates.FirstOrDefault(rate => rate.Value == selectedRefreshRate) ?? rates[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private int SelectedRefreshRate() =>
|
|
||||||
_refreshRate.SelectedItem is HostRefreshRateOption refreshRate ? refreshRate.Value : 0;
|
|
||||||
|
|
||||||
private void Persist()
|
|
||||||
{
|
|
||||||
var settings = new PerGameSettings
|
|
||||||
{
|
|
||||||
LogLevel = _logLevelRow.IsOverridden ? _logLevel.SelectedItem as string : null,
|
|
||||||
ImportTraceLimit = _traceRow.IsOverridden ? (int)(_trace.Value ?? 0) : null,
|
|
||||||
StrictDynlibResolution = _strictRow.IsOverridden ? _strict.IsChecked == true : null,
|
|
||||||
LogToFile = _logToFileRow.IsOverridden ? _logToFile.IsChecked == true : null,
|
|
||||||
WindowMode = _windowModeRow.IsOverridden ? _windowMode.SelectedItem as string : null,
|
|
||||||
Resolution = _resolutionRow.IsOverridden ? _resolution.SelectedItem as string : null,
|
|
||||||
DisplayIndex = _displayIndexRow.IsOverridden && _displayIndex.SelectedItem is HostDisplayOption display
|
|
||||||
? display.Index
|
|
||||||
: null,
|
|
||||||
RefreshRate = _refreshRateRow.IsOverridden ? SelectedRefreshRate() : null,
|
|
||||||
ScalingMode = _scalingModeRow.IsOverridden ? _scalingMode.SelectedItem as string : null,
|
|
||||||
VSync = _vsyncRow.IsOverridden ? _vsync.IsChecked == true : null,
|
|
||||||
HdrMode = _hdrModeRow.IsOverridden ? _hdrMode.SelectedItem as string : null,
|
|
||||||
EnvironmentToggles = _envRow.IsOverridden ? BuildEnvironmentEntries() : null,
|
|
||||||
};
|
|
||||||
settings.Save(_titleId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> BuildEnvironmentEntries()
|
|
||||||
{
|
|
||||||
return _envBoxes
|
|
||||||
.Where(entry => entry.Box.IsChecked == true)
|
|
||||||
.Select(entry => entry.Name)
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsEnvironmentEnabled(
|
|
||||||
IEnumerable<string> entries,
|
|
||||||
string name,
|
|
||||||
bool defaultValue)
|
|
||||||
{
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
|
||||||
if (string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(entry, name + "=1", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Presenters;
|
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Data;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
|
||||||
namespace SharpEmu.GUI;
|
namespace SharpEmu.GUI;
|
||||||
@@ -18,17 +16,9 @@ public sealed class SettingRow : ContentControl
|
|||||||
public static readonly StyledProperty<string?> DescriptionProperty =
|
public static readonly StyledProperty<string?> DescriptionProperty =
|
||||||
AvaloniaProperty.Register<SettingRow, string?>(nameof(Description));
|
AvaloniaProperty.Register<SettingRow, string?>(nameof(Description));
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> ShowOverrideProperty =
|
|
||||||
AvaloniaProperty.Register<SettingRow, bool>(nameof(ShowOverride));
|
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> IsOverriddenProperty =
|
|
||||||
AvaloniaProperty.Register<SettingRow, bool>(
|
|
||||||
nameof(IsOverridden), defaultBindingMode: BindingMode.TwoWay);
|
|
||||||
|
|
||||||
public static readonly StyledProperty<FontFamily?> LabelFontFamilyProperty =
|
public static readonly StyledProperty<FontFamily?> LabelFontFamilyProperty =
|
||||||
AvaloniaProperty.Register<SettingRow, FontFamily?>(nameof(LabelFontFamily));
|
AvaloniaProperty.Register<SettingRow, FontFamily?>(nameof(LabelFontFamily));
|
||||||
|
|
||||||
private ContentPresenter? _slot;
|
|
||||||
private TextBlock? _label;
|
private TextBlock? _label;
|
||||||
|
|
||||||
public string? Label
|
public string? Label
|
||||||
@@ -43,18 +33,6 @@ public sealed class SettingRow : ContentControl
|
|||||||
set => SetValue(DescriptionProperty, value);
|
set => SetValue(DescriptionProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShowOverride
|
|
||||||
{
|
|
||||||
get => GetValue(ShowOverrideProperty);
|
|
||||||
set => SetValue(ShowOverrideProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsOverridden
|
|
||||||
{
|
|
||||||
get => GetValue(IsOverriddenProperty);
|
|
||||||
set => SetValue(IsOverriddenProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FontFamily? LabelFontFamily
|
public FontFamily? LabelFontFamily
|
||||||
{
|
{
|
||||||
get => GetValue(LabelFontFamilyProperty);
|
get => GetValue(LabelFontFamilyProperty);
|
||||||
@@ -64,20 +42,14 @@ public sealed class SettingRow : ContentControl
|
|||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
_slot = e.NameScope.Find<ContentPresenter>("PART_Slot");
|
|
||||||
_label = e.NameScope.Find<TextBlock>("PART_Label");
|
_label = e.NameScope.Find<TextBlock>("PART_Label");
|
||||||
UpdateSlotEnabled();
|
|
||||||
UpdateLabelFont();
|
UpdateLabelFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(change);
|
base.OnPropertyChanged(change);
|
||||||
if (change.Property == ShowOverrideProperty || change.Property == IsOverriddenProperty)
|
if (change.Property == LabelFontFamilyProperty)
|
||||||
{
|
|
||||||
UpdateSlotEnabled();
|
|
||||||
}
|
|
||||||
else if (change.Property == LabelFontFamilyProperty)
|
|
||||||
{
|
{
|
||||||
UpdateLabelFont();
|
UpdateLabelFont();
|
||||||
}
|
}
|
||||||
@@ -90,12 +62,4 @@ public sealed class SettingRow : ContentControl
|
|||||||
_label.FontFamily = family;
|
_label.FontFamily = family;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSlotEnabled()
|
|
||||||
{
|
|
||||||
if (_slot is not null)
|
|
||||||
{
|
|
||||||
_slot.IsEnabled = !ShowOverride || IsOverridden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
Contextual per-game options reveal, actions, and selected-game motion.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style Selector="TextBlock.gameStatLabel">
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="FontWeight" Value="Normal" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
<Setter Property="Opacity" Value="0.75" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.gameStatValue">
|
||||||
|
<Setter Property="FontSize" Value="18" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Panel.backdropLayer">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(0px) scale(1)" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Panel.carouselHost">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(0px) scale(1)" />
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="StackPanel.selectedDetailsHost">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(0px)" />
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Grid.gameOptionsOverlay">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(96px)" />
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Panel.backdropLayer.gameOptionsOpen">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(-92px) scale(1.04)" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Panel.carouselHost.gameOptionsOpen">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(-18px) scale(1)" />
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="StackPanel.selectedDetailsHost.gameOptionsOpen">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(-28px)" />
|
||||||
|
<Setter Property="Opacity" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Grid.gameOptionsOverlay.gameOptionsOpen">
|
||||||
|
<Setter Property="RenderTransform" Value="translateY(0px)" />
|
||||||
|
<Setter Property="Opacity" Value="1" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.playButton">
|
||||||
|
<Setter Property="MinHeight" Value="54" />
|
||||||
|
<Setter Property="MinWidth" Value="200" />
|
||||||
|
<Setter Property="Padding" Value="34,13" />
|
||||||
|
<Setter Property="CornerRadius" Value="999" />
|
||||||
|
<Setter Property="Background" Value="White" />
|
||||||
|
<Setter Property="Foreground" Value="#111111" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="FontSize" Value="16" />
|
||||||
|
<Setter Property="FontWeight" Value="Bold" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.playButton:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.playButton.danger">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.playButton.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="{StaticResource DangerHoverBrush}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsCircle, ToggleButton.optionsCircle">
|
||||||
|
<Setter Property="Width" Value="54" />
|
||||||
|
<Setter Property="Height" Value="54" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="CornerRadius" Value="27" />
|
||||||
|
<Setter Property="Background" Value="#12FFFFFF" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsCircle:pointerover /template/ ContentPresenter#PART_ContentPresenter,
|
||||||
|
ToggleButton.optionsCircle:pointerover /template/ ContentPresenter#PART_ContentPresenter,
|
||||||
|
ToggleButton.optionsCircle:checked /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#24FFFFFF" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameOptionsLaunch">
|
||||||
|
<Setter Property="Width" Value="220" />
|
||||||
|
<Setter Property="Height" Value="46" />
|
||||||
|
<Setter Property="Padding" Value="20,0" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="CornerRadius" Value="999" />
|
||||||
|
<Setter Property="Background" Value="White" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="#111111" />
|
||||||
|
<Setter Property="FontSize" Value="15" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameOptionsLaunch /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="BoxShadow" Value="0 0 0 0 Transparent" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameOptionsLaunch:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#EAF3FF" />
|
||||||
|
<Setter Property="Foreground" Value="#111111" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameAction">
|
||||||
|
<Setter Property="Width" Value="250" />
|
||||||
|
<Setter Property="Height" Value="48" />
|
||||||
|
<Setter Property="Padding" Value="16,0" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||||
|
<Setter Property="CornerRadius" Value="10" />
|
||||||
|
<Setter Property="Background" Value="#0FFFFFFF" />
|
||||||
|
<Setter Property="BorderBrush" Value="#14FFFFFF" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameAction:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#1AFFFFFF" />
|
||||||
|
<Setter Property="BorderBrush" Value="#28FFFFFF" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameAction.dangerAction">
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource DangerBrush}" />
|
||||||
|
<Setter Property="BorderBrush" Value="#48FF646E" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.gameAction.dangerAction:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#1FFF646E" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource DangerBrush}" />
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
@@ -1,108 +1,12 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright (C) 2026 SharpEmu Emulator Project
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
SPDX-License-Identifier: GPL-2.0-or-later
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
Options page navigation, section transitions and content layout
|
Options page section transitions and content layout
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<Styles xmlns="https://github.com/avaloniaui"
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:SharpEmu.GUI"
|
|
||||||
xmlns:settingsControls="using:SharpEmu.GUI.Controls.Settings">
|
xmlns:settingsControls="using:SharpEmu.GUI.Controls.Settings">
|
||||||
<Style Selector="Border.optionsNavSurface">
|
|
||||||
<Setter Property="Width" Value="244" />
|
|
||||||
<Setter Property="Padding" Value="0" />
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Top" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Border.optionsNavIndicator">
|
|
||||||
<Setter Property="Height" Value="54" />
|
|
||||||
<Setter Property="Margin" Value="0,3,0,0" />
|
|
||||||
<Setter Property="CornerRadius" Value="12" />
|
|
||||||
<Setter Property="Background" Value="#13FFFFFF" />
|
|
||||||
<Setter Property="BorderBrush" Value="#E6FFFFFF" />
|
|
||||||
<Setter Property="BorderThickness" Value="2" />
|
|
||||||
<Setter Property="BoxShadow" Value="0 8 24 0 #24000000" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav">
|
|
||||||
<Setter Property="Height" Value="61" />
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
||||||
<Setter Property="Padding" Value="16,0" />
|
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
||||||
<Setter Property="CornerRadius" Value="12" />
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
|
||||||
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
|
||||||
<Setter Property="FontSize" Value="14" />
|
|
||||||
<Setter Property="FontWeight" Value="Medium" />
|
|
||||||
<Setter Property="FocusAdorner" Value="{x:Null}" />
|
|
||||||
<Setter Property="RenderTransform" Value="none" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav /template/ ContentPresenter#PART_ContentPresenter">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderBrush" Value="Transparent" />
|
|
||||||
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav:pointerover /template/ ContentPresenter#PART_ContentPresenter,
|
|
||||||
Button.optionsNav:focus-visible /template/ ContentPresenter#PART_ContentPresenter,
|
|
||||||
Button.optionsNav.active /template/ ContentPresenter#PART_ContentPresenter">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderBrush" Value="Transparent" />
|
|
||||||
<Setter Property="Foreground" Value="White" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav:pressed">
|
|
||||||
<Setter Property="RenderTransform" Value="none" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="TextBlock.optionsNavIcon">
|
|
||||||
<Setter Property="Width" Value="20" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
|
||||||
<Setter Property="Padding" Value="0,0,0,5" />
|
|
||||||
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
|
||||||
<Setter Property="Transitions">
|
|
||||||
<Transitions>
|
|
||||||
<BrushTransition Property="Foreground"
|
|
||||||
Duration="0:0:0.18"
|
|
||||||
Easing="CubicEaseOut" />
|
|
||||||
</Transitions>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="TextBlock.optionsNavLabel">
|
|
||||||
<Setter Property="FontSize" Value="14" />
|
|
||||||
<Setter Property="FontWeight" Value="Medium" />
|
|
||||||
<Setter Property="LineHeight" Value="20" />
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
<Setter Property="Padding" Value="0,0,0,4" />
|
|
||||||
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
|
||||||
<Setter Property="Transitions">
|
|
||||||
<Transitions>
|
|
||||||
<BrushTransition Property="Foreground"
|
|
||||||
Duration="0:0:0.18"
|
|
||||||
Easing="CubicEaseOut" />
|
|
||||||
</Transitions>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav:pointerover TextBlock.optionsNavIcon,
|
|
||||||
Button.optionsNav:focus-visible TextBlock.optionsNavIcon,
|
|
||||||
Button.optionsNav.active TextBlock.optionsNavIcon">
|
|
||||||
<Setter Property="Foreground" Value="White" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Button.optionsNav:pointerover TextBlock.optionsNavLabel,
|
|
||||||
Button.optionsNav:focus-visible TextBlock.optionsNavLabel,
|
|
||||||
Button.optionsNav.active TextBlock.optionsNavLabel">
|
|
||||||
<Setter Property="Foreground" Value="White" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="Border.optionsContentSurface">
|
<Style Selector="Border.optionsContentSurface">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
@@ -147,11 +51,6 @@ Options page navigation, section transitions and content layout
|
|||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="local|SettingRow.optionRow:focus-within /template/ Border#PART_RowSurface">
|
|
||||||
<Setter Property="Background" Value="#0DFFFFFF" />
|
|
||||||
<Setter Property="BorderBrush" Value="#22FFFFFF" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style Selector="ComboBox.optionValue, settingsControls|SplitNumericUpDown.optionValue, TextBox.optionValue">
|
<Style Selector="ComboBox.optionValue, settingsControls|SplitNumericUpDown.optionValue, TextBox.optionValue">
|
||||||
<Setter Property="Width" Value="190" />
|
<Setter Property="Width" Value="190" />
|
||||||
<Setter Property="MinHeight" Value="44" />
|
<Setter Property="MinHeight" Value="44" />
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<!--
|
||||||
|
Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
Shared navigation for global and per-game options
|
||||||
|
-->
|
||||||
|
|
||||||
|
<Styles xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Style Selector="Border.optionsNavSurface">
|
||||||
|
<Setter Property="Width" Value="244" />
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Top" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Border.optionsNavIndicator">
|
||||||
|
<Setter Property="Height" Value="54" />
|
||||||
|
<Setter Property="Margin" Value="0,3,0,0" />
|
||||||
|
<Setter Property="CornerRadius" Value="12" />
|
||||||
|
<Setter Property="Background" Value="#13FFFFFF" />
|
||||||
|
<Setter Property="BorderBrush" Value="#E6FFFFFF" />
|
||||||
|
<Setter Property="BorderThickness" Value="2" />
|
||||||
|
<Setter Property="BoxShadow" Value="0 8 24 0 #24000000" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsNav">
|
||||||
|
<Setter Property="Height" Value="61" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="Padding" Value="16,0" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="CornerRadius" Value="12" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="FocusAdorner" Value="{x:Null}" />
|
||||||
|
<Setter Property="RenderTransform" Value="none" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsNav /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsNav:pointerover /template/ ContentPresenter#PART_ContentPresenter,
|
||||||
|
Button.optionsNav:focus-visible /template/ ContentPresenter#PART_ContentPresenter,
|
||||||
|
Button.optionsNav.active /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderBrush" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsNav:pressed">
|
||||||
|
<Setter Property="RenderTransform" Value="none" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.optionsNavIcon">
|
||||||
|
<Setter Property="Width" Value="20" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||||
|
<Setter Property="Padding" Value="0,0,0,5" />
|
||||||
|
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
||||||
|
<Setter Property="Transitions">
|
||||||
|
<Transitions>
|
||||||
|
<BrushTransition Property="Foreground"
|
||||||
|
Duration="0:0:0.18"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBlock.optionsNavLabel">
|
||||||
|
<Setter Property="FontSize" Value="14" />
|
||||||
|
<Setter Property="FontWeight" Value="Medium" />
|
||||||
|
<Setter Property="LineHeight" Value="20" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Padding" Value="0,0,0,4" />
|
||||||
|
<Setter Property="Foreground" Value="#8FFFFFFF" />
|
||||||
|
<Setter Property="Transitions">
|
||||||
|
<Transitions>
|
||||||
|
<BrushTransition Property="Foreground"
|
||||||
|
Duration="0:0:0.18"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="Button.optionsNav:pointerover TextBlock.optionsNavIcon,
|
||||||
|
Button.optionsNav:focus-visible TextBlock.optionsNavIcon,
|
||||||
|
Button.optionsNav.active TextBlock.optionsNavIcon,
|
||||||
|
Button.optionsNav:pointerover TextBlock.optionsNavLabel,
|
||||||
|
Button.optionsNav:focus-visible TextBlock.optionsNavLabel,
|
||||||
|
Button.optionsNav.active TextBlock.optionsNavLabel">
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
</Style>
|
||||||
|
</Styles>
|
||||||
@@ -12,11 +12,22 @@ Control theme for the shared launcher settings row.
|
|||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Border x:Name="PART_RowSurface"
|
<Border x:Name="PART_RowSurface"
|
||||||
|
Classes="settingRowSurface"
|
||||||
Background="#06FFFFFF"
|
Background="#06FFFFFF"
|
||||||
BorderBrush="#0AFFFFFF"
|
BorderBrush="#0AFFFFFF"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
CornerRadius="14"
|
CornerRadius="14"
|
||||||
Padding="18,12">
|
Padding="18,12">
|
||||||
|
<Border.Transitions>
|
||||||
|
<Transitions>
|
||||||
|
<BrushTransition Property="Background"
|
||||||
|
Duration="0:0:0.18"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
<BrushTransition Property="BorderBrush"
|
||||||
|
Duration="0:0:0.18"
|
||||||
|
Easing="CubicEaseOut" />
|
||||||
|
</Transitions>
|
||||||
|
</Border.Transitions>
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<StackPanel VerticalAlignment="Center" Spacing="3" Margin="0,0,18,0">
|
<StackPanel VerticalAlignment="Center" Spacing="3" Margin="0,0,18,0">
|
||||||
<TextBlock x:Name="PART_Label"
|
<TextBlock x:Name="PART_Label"
|
||||||
@@ -28,7 +39,7 @@ Control theme for the shared launcher settings row.
|
|||||||
<TextBlock Text="{TemplateBinding Description}"
|
<TextBlock Text="{TemplateBinding Description}"
|
||||||
FontSize="11"
|
FontSize="11"
|
||||||
FontWeight="Normal"
|
FontWeight="Normal"
|
||||||
Foreground="{StaticResource MutedBrush}"
|
Foreground="{StaticResource SettingsDescriptionBrush}"
|
||||||
LineHeight="16"
|
LineHeight="16"
|
||||||
MaxWidth="690"
|
MaxWidth="690"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
@@ -37,20 +48,9 @@ Control theme for the shared launcher settings row.
|
|||||||
IsVisible="{Binding Description, RelativeSource={RelativeSource TemplatedParent},
|
IsVisible="{Binding Description, RelativeSource={RelativeSource TemplatedParent},
|
||||||
Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Grid.Column="1"
|
<ContentPresenter Grid.Column="1"
|
||||||
Orientation="Horizontal"
|
|
||||||
Spacing="12"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ToggleSwitch OnContent="Override"
|
|
||||||
OffContent="Override"
|
|
||||||
MinWidth="0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
IsVisible="{TemplateBinding ShowOverride}"
|
|
||||||
IsChecked="{Binding IsOverridden, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
|
|
||||||
<ContentPresenter x:Name="PART_Slot"
|
|
||||||
Content="{TemplateBinding Content}"
|
Content="{TemplateBinding Content}"
|
||||||
VerticalAlignment="Center" />
|
VerticalAlignment="Center" />
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
|
|||||||
@@ -10,25 +10,23 @@ Shared colors and brushes used throughout the launcher.
|
|||||||
|
|
||||||
<Color x:Key="SystemAccentColor">#7C5CFC</Color>
|
<Color x:Key="SystemAccentColor">#7C5CFC</Color>
|
||||||
|
|
||||||
<LinearGradientBrush x:Key="BgBrush" StartPoint="0%,0%" EndPoint="100%,100%">
|
<SolidColorBrush x:Key="BgBrush" Color="#0C0C0C" />
|
||||||
<GradientStop Offset="0" Color="#12151F" />
|
<SolidColorBrush x:Key="ChromeBrush" Color="#070707" />
|
||||||
<GradientStop Offset="0.55" Color="#0D1017" />
|
<SolidColorBrush x:Key="CardBrush" Color="#161616" />
|
||||||
<GradientStop Offset="1" Color="#0B0D14" />
|
<SolidColorBrush x:Key="CardBorderBrush" Color="#12FFFFFF" />
|
||||||
</LinearGradientBrush>
|
<SolidColorBrush x:Key="ElevatedBrush" Color="#1E1E1E" />
|
||||||
|
<SolidColorBrush x:Key="GameOptionsMenuBrush" Color="#1B1B1B" />
|
||||||
<SolidColorBrush x:Key="ChromeBrush" Color="#090C12" />
|
<SolidColorBrush x:Key="TextBrush" Color="#F7F8FB" />
|
||||||
<SolidColorBrush x:Key="CardBrush" Color="#141924" />
|
<SolidColorBrush x:Key="SecondaryTextBrush" Color="#C0C5CF" />
|
||||||
<SolidColorBrush x:Key="CardBorderBrush" Color="#232B3A" />
|
<SolidColorBrush x:Key="MutedBrush" Color="#777F8E" />
|
||||||
<SolidColorBrush x:Key="ElevatedBrush" Color="#1B2230" />
|
<SolidColorBrush x:Key="SettingsDescriptionBrush" Color="#8A8A8A" />
|
||||||
<SolidColorBrush x:Key="TextBrush" Color="#E8ECF4" />
|
<SolidColorBrush x:Key="FaintBrush" Color="#4D5461" />
|
||||||
<SolidColorBrush x:Key="MutedBrush" Color="#8B94A7" />
|
|
||||||
<SolidColorBrush x:Key="FaintBrush" Color="#5A6478" />
|
|
||||||
<SolidColorBrush x:Key="AccentBrush" Color="#7C5CFC" />
|
<SolidColorBrush x:Key="AccentBrush" Color="#7C5CFC" />
|
||||||
<SolidColorBrush x:Key="AccentHoverBrush" Color="#8F73FF" />
|
<SolidColorBrush x:Key="AccentHoverBrush" Color="#8F73FF" />
|
||||||
<SolidColorBrush x:Key="DangerBrush" Color="#E5484D" />
|
<SolidColorBrush x:Key="DangerBrush" Color="#FF646E" />
|
||||||
<SolidColorBrush x:Key="DangerHoverBrush" Color="#F2555A" />
|
<SolidColorBrush x:Key="DangerHoverBrush" Color="#FF858D" />
|
||||||
<SolidColorBrush x:Key="SuccessBrush" Color="#46C46B" />
|
<SolidColorBrush x:Key="SuccessBrush" Color="#66F2A3" />
|
||||||
<SolidColorBrush x:Key="InfoBrush" Color="#58A6FF" />
|
<SolidColorBrush x:Key="InfoBrush" Color="#64A7FF" />
|
||||||
<SolidColorBrush x:Key="TileHoverBrush" Color="#1A2130" />
|
<SolidColorBrush x:Key="TileHoverBrush" Color="#0BFFFFFF" />
|
||||||
<SolidColorBrush x:Key="TileSelectedBrush" Color="#212A3F" />
|
<SolidColorBrush x:Key="TileSelectedBrush" Color="#12FFFFFF" />
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -57,4 +57,86 @@ public sealed class PerGameSettingsTests
|
|||||||
Assert.NotNull(settings);
|
Assert.NotNull(settings);
|
||||||
Assert.Equal(["SHARPEMU_TRACE", "SHARPEMU_NO_JIT"], settings.EnvironmentToggles);
|
Assert.Equal(["SHARPEMU_TRACE", "SHARPEMU_NO_JIT"], settings.EnvironmentToggles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveInheritedValues_AllMatchingValues_ProducesEmptySettings()
|
||||||
|
{
|
||||||
|
var global = new GuiSettings
|
||||||
|
{
|
||||||
|
LogLevel = "Info",
|
||||||
|
ImportTraceLimit = 32,
|
||||||
|
StrictDynlibResolution = true,
|
||||||
|
LogToFile = false,
|
||||||
|
WindowMode = "Borderless",
|
||||||
|
Resolution = "2560x1440",
|
||||||
|
DisplayIndex = 1,
|
||||||
|
RefreshRate = 144,
|
||||||
|
ScalingMode = "Fit",
|
||||||
|
VSync = true,
|
||||||
|
HdrMode = "Auto",
|
||||||
|
EnvironmentToggles = ["SHARPEMU_LOG_IO", "SHARPEMU_VK_VALIDATION"],
|
||||||
|
};
|
||||||
|
var perGame = new PerGameSettings
|
||||||
|
{
|
||||||
|
LogLevel = "info",
|
||||||
|
ImportTraceLimit = 32,
|
||||||
|
StrictDynlibResolution = true,
|
||||||
|
LogToFile = false,
|
||||||
|
WindowMode = "borderless",
|
||||||
|
Resolution = "2560x1440",
|
||||||
|
DisplayIndex = 1,
|
||||||
|
RefreshRate = 144,
|
||||||
|
ScalingMode = "fit",
|
||||||
|
VSync = true,
|
||||||
|
HdrMode = "auto",
|
||||||
|
EnvironmentToggles = ["SHARPEMU_VK_VALIDATION=1", "sharpemu_log_io"],
|
||||||
|
};
|
||||||
|
|
||||||
|
perGame.RemoveInheritedValues(global);
|
||||||
|
|
||||||
|
Assert.True(perGame.IsEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveInheritedValues_DifferentValues_RemainOverrides()
|
||||||
|
{
|
||||||
|
var global = new GuiSettings
|
||||||
|
{
|
||||||
|
LogLevel = "Info",
|
||||||
|
Resolution = "1920x1080",
|
||||||
|
VSync = true,
|
||||||
|
EnvironmentToggles = ["SHARPEMU_LOG_IO"],
|
||||||
|
};
|
||||||
|
var perGame = new PerGameSettings
|
||||||
|
{
|
||||||
|
LogLevel = "Debug",
|
||||||
|
Resolution = "2560x1440",
|
||||||
|
VSync = false,
|
||||||
|
EnvironmentToggles = ["SHARPEMU_VK_VALIDATION"],
|
||||||
|
};
|
||||||
|
|
||||||
|
perGame.RemoveInheritedValues(global);
|
||||||
|
|
||||||
|
Assert.Equal("Debug", perGame.LogLevel);
|
||||||
|
Assert.Equal("2560x1440", perGame.Resolution);
|
||||||
|
Assert.False(perGame.VSync);
|
||||||
|
Assert.Equal(["SHARPEMU_VK_VALIDATION"], perGame.EnvironmentToggles);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RemoveInheritedValues_DisabledEnvironmentEntry_MatchesMissingEntry()
|
||||||
|
{
|
||||||
|
var global = new GuiSettings
|
||||||
|
{
|
||||||
|
EnvironmentToggles = ["SHARPEMU_LOG_IO=0"],
|
||||||
|
};
|
||||||
|
var perGame = new PerGameSettings
|
||||||
|
{
|
||||||
|
EnvironmentToggles = [],
|
||||||
|
};
|
||||||
|
|
||||||
|
perGame.RemoveInheritedValues(global);
|
||||||
|
|
||||||
|
Assert.Null(perGame.EnvironmentToggles);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user