UI: Actions Menu (Part 2 of 4) → XCI Trimmer (#146)

Ayyyy, welcome to the UI: Actions Menu → XCI Trimmer PR!

Let's keep it up!

This is the second PR in a series aimed at delivering the largest overhaul and improvements to the `Actions` menu yet.

This second PR introduces visual improvements to the `XCI Trimmer` and also fixes some bugs.

### GENERAL:
* **Renamed:** `XCI Trimmer` files to use the capitalised XCI instead of Xci
    * Files were inconsistently named with either Xci or XCI. As such, they were all renamed to use XCI.

### LOCALISATION:
* **Fractured:** More locales
    * `Common_Search.json` - search-related locales
    * `Common_Sort.json` - sorting fields, ascending/descending locales
    * `Dialog_XCITrimmer.json` - XCI trimmer dialogs
    * `GameListContextMenu.json` - all UI dialogs related to the game list menu (right-click game)
    * `XCITrimmer.json` - all UI-related locales for the XCI Trimmer window
* **Added:** Additional entry to `StatusBar.json`
* **Added:** `Simulate Wake-Up Message` to `MenuBar_Actions.json` (previously did not exist there, for some reason).

**NOTE:** `Common_Search.json` and `Common_Sort.json` were not fully populated, but many more locales will be added and cleaned up in future PRs.

### XCI TRIMMER:
* **Renamed:** `Trim XCI Files` menu → `XCI Trimmer`
* **XCI Trimmer (Window):**
    * **Renamed:** Window title from `XCI File Trimmer` → `XCI Trimmer`
    * **Renamed:** `X of Y Title(s) Selected` → `XCI Selected: X/Y`
        * `Z Displayed` was renamed to `Displayed: Z` and, as usual, appears only when searching for particular XCIs in the search bar.
    * **Fixed:** Counting bug
        * Pressing `Select Shown` continuously would increment the amount of XCIs selected to infinity (e.g. if 5 XCIs are in the list, then you could get 1201312 of 5 Title(s) Selected). Furthermore, when you were trimming a specific number of XCIs (not the all XCIs) at once, it would show that its trimming all titles from the list (e.g. "Trimming 5 Titles" when only 1 was being trimmed).
    * **Changed:** Button structure
        * Previous structure included 2 buttons: `Select Shown` and `Deselect Shown`. Initially, they were renamed to `Select All` and `Deselect All`, as that sounded more natural. However, after further consideration, they were both removed and replaced by a single button, whose action and label would change depending on the state it was in:
            * If no XCIs were selected → `Select All`
            * If at least one XCI is selected →`Clear Selection`
                * `Clear Selection` was used instead of Deselect All, because some translations were quite awkward.
                * Furthermore, this reflects the way the button works: you are not "deselecting all" when only 2 out of 5 XCIs are selected - you are clearing that particular selection of XCIs.
    * **Improved:** Button and Control Layout (above list)
        * Sorting dropdown was expanded a bit further (150 default to 170 width), as some locales would get cutoff. This will be further adjusted in a future PR, which will tackle all of Ryujinx's sorting features.
        * Added a new sorting: Trim Status
        * See images for visual comparison.
    * **Improved:** XCI List Layout
        * The entire list was revamped to be more modern and cleaner to look at.
        * Instead of displaying the file status (Trimmed, Untrimmed, Partial, Failed, Unknown), it now shows icons: Checkmark (Trimmed), Cross (Untrimmed), Wrench (Partial), Exclamation Mark (Failed), Question mark (everything else/Unknown), No Icon (when it's not XCI).
            * Furthermore, it shows a Sync icon when performing the trimming/untrimming operation.
            * Additionally, it shows the said status when you hover above the icons.
        * Save X MB and Saved Y MB were both removed and replaced with just the amount and a percentage value (how much you save per file).
        * See images for visual comparison.
        * NOTE: Icons are still under consideration and may be changed in the future.
    * **Improved:** Bottom Savings Display
        * Instead of showing `Potential Savings`" and `Actual Savings`, the bottom row now shows the amount `Saved` AND the amount `Remaining`.

### GAME LIST CONTEXT MENU (Right-Click Game):
* **Renamed:** `Check & Trim XCI File` → `Trim XCI`
    * The option already performs a check regardless.
*  **Remove:** `Trim XCI` Tooltip
    * The user can press the option and will be confronted with a dialog, which explains what the feature does. Furthermore, the action itself is reversible at any moment (i.e. before trimming and after trimming in the XCI Trimmer).
        * It is planned to either add a message that tells the user they can untrim the XCI in the XCI Trimmer, or to make the option work both ways (Trim & Untrim). This is still under consideration, but, if chosen to be implemented, it will be a part of the Game List Context Menu PR.
* **Added:** IsVisible parameter to `Trim XCI`
    * Instead of displaying `Trim XCI` for every single game (NSP, NSO, etc.), it will only be displayed for XCIs (as they are the only ones that can be trimmed). It is still _enabled_ only if an XCI is untrimmed.
* **Improved:** `Trim XCI` Dialog
    * Some minor visual adjustment and organisation improvements.
    * **Changed:** Value formatting for "File Size", "Game Size", and "Space Savings"
        * Previously they were very long numbers, expressed in MB, with all the decimals. Instead of displaying them as such, they were shortened to display the same amount as they do in the XCI Trimmer window already.
    * All further dialogs will be improved in a separate PR, because they desperately need consistency.

### STATUS BAR:
* **Adjusted:** Formatting of XCI Trimming Status on status bar
    * **Forced:** Filename without extension (as we are already trimming an XCI already).
    * **Fixed:** Trimming Position when Playtime is not available
    * **Fixed:** Progress bar position (will be further adjusted in a Status Bar PR…boy there will be a lot of PRs, which is good)

_If there are any features or changes that you wish to be implemented, please comment down below and I'll be happy to accommodate!_

Reviewed-on: https://git.ryujinx.app/projects/Ryubing/pulls/146
This commit is contained in:
_Neo_
2026-06-27 02:11:31 +00:00
committed by sh0inx
parent 54e9b40cd7
commit 8b2d1382a4
26 changed files with 1466 additions and 1458 deletions

View File

@@ -0,0 +1,29 @@
{
"Locales": [
{
"ID": "SearchWatermark",
"Translations": {
"ar_SA": "بحث",
"de_DE": "Suche",
"el_GR": "Αναζήτηση",
"en_US": "Search",
"es_ES": "Buscar",
"fr_FR": "Rechercher",
"he_IL": "חפש",
"it_IT": "Cerca",
"ja_JP": "検索",
"ko_KR": "찾기",
"no_NO": "Søk",
"pl_PL": "Wyszukaj",
"pt_BR": "Buscar",
"ru_RU": "Поиск",
"sv_SE": "Sök",
"th_TH": "ค้นหา",
"tr_TR": "Ara",
"uk_UA": "Пошук",
"zh_CN": "搜索",
"zh_TW": "搜尋"
}
}
]
}

View File

@@ -0,0 +1,129 @@
{
"Locales": [
{
"ID": "NameLabel",
"Translations": {
"ar_SA": "الاسم",
"de_DE": "",
"el_GR": "Όνομα",
"en_US": "Name",
"es_ES": "Nombre",
"fr_FR": "Nom",
"he_IL": "שם",
"it_IT": "Nome",
"ja_JP": "名称",
"ko_KR": "이름",
"no_NO": "Navn",
"pl_PL": "Nazwa",
"pt_BR": "Nome",
"ru_RU": "Название",
"sv_SE": "Namn",
"th_TH": "ชื่อ",
"tr_TR": "İsim",
"uk_UA": "Назва",
"zh_CN": "名称",
"zh_TW": "名稱"
}
},
{
"ID": "SavingsLabel",
"Translations": {
"ar_SA": "التوفير",
"de_DE": "Einsparung",
"el_GR": "Εξοικονόμηση",
"en_US": "Savings",
"es_ES": "Ahorro",
"fr_FR": "Économies",
"he_IL": "חיסכון",
"it_IT": "Risparmio",
"ja_JP": "節約",
"ko_KR": "절약",
"no_NO": "Besparelse",
"pl_PL": "Oszczędność",
"pt_BR": "Economia",
"ru_RU": "Экономия",
"sv_SE": "Besparing",
"th_TH": "การประหยัด",
"tr_TR": "Tasarruf",
"uk_UA": "Економія",
"zh_CN": "节省",
"zh_TW": "節省"
}
},
{
"ID": "TrimStatusLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Trim Status",
"es_ES": "Estado del recorte",
"fr_FR": "État de réduction",
"he_IL": "",
"it_IT": "Stato della riduzione",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Статус обрезки",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Статус обрізки",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "OrderAscending",
"Translations": {
"ar_SA": "تصاعدي",
"de_DE": "Aufsteigend",
"el_GR": "Αύξουσα",
"en_US": "Ascending",
"es_ES": "Ascendente",
"fr_FR": "Croissant",
"he_IL": "סדר עולה",
"it_IT": "Crescente",
"ja_JP": "昇順",
"ko_KR": "오름차순",
"no_NO": "Stigende",
"pl_PL": "Rosnąco",
"pt_BR": "Ascendente",
"ru_RU": "По Возрастанию",
"sv_SE": "Stigande",
"th_TH": "จากน้อยไปมาก",
"tr_TR": "Artan",
"uk_UA": "За зростанням",
"zh_CN": "升序",
"zh_TW": "從小到大"
}
},
{
"ID": "OrderDescending",
"Translations": {
"ar_SA": "تنازلي",
"de_DE": "Absteigend",
"el_GR": "Φθίνουσα",
"en_US": "Descending",
"es_ES": "Descendente",
"fr_FR": "Décroissant",
"he_IL": "סדר יורד",
"it_IT": "Decrescente",
"ja_JP": "降順",
"ko_KR": "내림차순",
"no_NO": "Synkende",
"pl_PL": "Malejąco",
"pt_BR": "Descendente",
"ru_RU": "По Убыванию",
"sv_SE": "Fallande",
"th_TH": "จากมากไปน้อย",
"tr_TR": "Azalan",
"uk_UA": "За спаданням",
"zh_CN": "降序",
"zh_TW": "從大到小"
}
}
]
}

View File

@@ -0,0 +1,304 @@
{
"Locales": [
{
"ID": "PrimaryMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Removes unused space from the XCI to reduce its file size.",
"es_ES": "Elimina el espacio no utilizado del XCI para reducir su tamaño.",
"fr_FR": "Supprime lespace inutilisé du XCI afin de réduire sa taille.",
"he_IL": "",
"it_IT": "Rimuove lo spazio inutilizzato dall'XCI per ridurne le dimensioni.",
"ja_JP": "",
"ko_KR": "XCI에서 사용되지 않는 공간을 제거하여 파일 크기를 줄입니다.",
"no_NO": "Fjerner ubrukt plass fra XCI-filen for å redusere filstørrelsen.",
"pl_PL": "Usuwa nieużywane miejsce z pliku XCI, aby zmniejszyć jego rozmiar.",
"pt_BR": "Remove o espaço não utilizado do XCI para reduzir seu tamanho.",
"ru_RU": "Удаляет неиспользуемое пространство из XCI, уменьшая размер файла.",
"sv_SE": "Tar bort oanvänt utrymme från XCI-filen för att minska filstorleken.",
"th_TH": "ลบพื้นที่ที่ไม่ได้ใช้งานออกจาก XCI เพื่อลดขนาดไฟล์",
"tr_TR": "",
"uk_UA": "Видаляє невикористаний простір із XCI, зменшуючи розмір файлу.",
"zh_CN": "移除 XCI 中未使用的空间以减小文件大小。",
"zh_TW": "移除 XCI 中未使用的空間以減少檔案大小。"
}
},
{
"ID": "SecondaryMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "File: {0:n} MB • Game: {1:n} MB\n\nSavings: {2:n} MB",
"es_ES": "Archivo: {0:n} MB • Juego: {1:n} MB\n\nAhorro: {2:n} MB",
"fr_FR": "Fichier: {0:n} Mo • Jeu: {1:n} Mo\n\nÉconomies: {2:n} Mo",
"he_IL": "",
"it_IT": "File: {0:n} MB • Gioco: {1:n} MB\n\nRisparmio: {2:n} MB",
"ja_JP": "",
"ko_KR": "파일: {0:n} MB • 게임: {1:n} MB\n\n절약: {2:n} MB",
"no_NO": "Fil: {0:n} MB • Spill: {1:n} MB\n\nBesparelse: {2:n} MB",
"pl_PL": "Plik: {0:n} MB • Gra: {1:n} MB\n\nOszczędności: {2:n} MB",
"pt_BR": "Arquivo: {0:n} MB • Jogo: {1:n} MB\n\nEconomia: {2:n} MB",
"ru_RU": "Файл: {0:n} МБ • Игра: {1:n} МБ\n\nЭкономия: {2:n} МБ",
"sv_SE": "Fil: {0:n} MB • Spel: {1:n} MB\n\nBesparing: {2:n} MB",
"th_TH": "ไฟล์: {0:n} MB • เกม: {1:n} MB\n\nการประหยัด: {2:n} MB",
"tr_TR": "",
"uk_UA": "Файл: {0:n} МБ • Гра: {1:n} МБ\n\nЕкономія: {2:n} МБ",
"zh_CN": "文件: {0:n} MB • 游戏: {1:n} MB\n\n节省: {2:n} MB",
"zh_TW": "檔案: {0:n} MB • 遊戲: {1:n} MB\n\n節省: {2:n} MB"
}
},
{
"ID": "NoTrimNecessaryMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI does not require trimming. Check logs for details.",
"es_ES": "El XCI no necesita ser recortado. Verifica los logs para detalles.",
"fr_FR": "Le XCI na pas besoin dêtre réduit. Référez-vous aux journaux pour détails.",
"he_IL": "",
"it_IT": "Non è necessario ridurre la dimensione del XCI. Controlla i log per dettagli.",
"ja_JP": "",
"ko_KR": "XCI는 트리밍할 필요가 없습니다. 자세한 내용은 로그를 확인.",
"no_NO": "XCI trenger ikke å trimmes. Sjekk loggene for detaljer.",
"pl_PL": "XCI nie wymaga przycinania. Sprawdź dzienniki, aby uzyskać szczegóły.",
"pt_BR": "O XCI não precisa ser reduzido. Verifique os logs para detalhes.",
"ru_RU": "XCI не требует обрезки. Проверьте логи для подробностей.",
"sv_SE": "XCI behöver inte optimeras. Kontrollera loggen för detaljer.",
"th_TH": "XCI ไม่จำเป็นต้องถูกตัดแต่ง โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
"tr_TR": "",
"uk_UA": "XCI не потребує обрізання. Перевірте журнали для отримання деталей.",
"zh_CN": "XCI 不需要被瘦身。查看日志以获得更多细节。",
"zh_TW": "XCI 不需要修剪。檢查日誌以取得更多資訊。"
}
},
{
"ID": "NoUntrimPossibleMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI cannot be untrimmed. Check logs for details.",
"es_ES": "El recorte del XCI no puede ser deshecho. Verifica los registros para detalles.",
"fr_FR": "Le XCI ne peut être restauré. Référez-vous aux journaux pour détails.",
"he_IL": "",
"it_IT": "XCI non può essere ripristinato. Controlla i log per dettagli.",
"ja_JP": "",
"ko_KR": "XCI는 복원할 수 없습니다. 자세한 내용은 로그를 확인.",
"no_NO": "XCI kan ikke gjenopprettes. Sjekk loggene for detaljer.",
"pl_PL": "XCI nie może zostać przywrócone. Sprawdź dzienniki, aby uzyskać szczegóły.",
"pt_BR": "XCI não pode ser desfeito. Verifique os logs para detalhes.",
"ru_RU": "XCI не может быть восстановлен. Проверьте журналы для подробностей.",
"sv_SE": "XCI kan inte återställas. Kontrollera loggen för detaljer.",
"th_TH": "ไม่สามารถคืนค่า XCI ได้ โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
"tr_TR": "",
"uk_UA": "XCI не можна відновити. Перевірте журнали для деталей.",
"zh_CN": "XCI 不能恢复。查看日志以获取详情。",
"zh_TW": "XCI 無法恢復。檢查日誌以取得詳情。"
}
},
{
"ID": "ReadOnlyFileCannotFixMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI is read-only and could not be made writable. Check logs for details.",
"es_ES": "XCI es solo lectura y no se puede escribir. Verifica los registros para detalles.",
"fr_FR": "XCI en lecture seule et n'a pas pu être rendu écrivable. Référez-vous aux journaux pour détails.",
"he_IL": "",
"it_IT": "XCI è solo lettura e non può essere scritto. Controlla i log per dettagli.",
"ja_JP": "",
"ko_KR": "XCI 파일은 읽기 전용이며 쓰기 불가. 로그를 확인하십시오.",
"no_NO": "XCI er skrivebeskyttet og kunne ikke gjøres skrivbar. Sjekk loggene for detaljer.",
"pl_PL": "XCI jest tylko do odczytu i nie można zapisać. Sprawdź logi dla szczegółów.",
"pt_BR": "XCI é somente leitura e não pode ser gravado. Verifique os logs para detalhes.",
"ru_RU": "XCI только для чтения и не стал доступен для записи. Проверьте журналы для подробностей.",
"sv_SE": "XCI-filen är skrivskyddad och kunde inte göras skrivbar. Kontrollera loggen för mer information",
"th_TH": "XCI เป็นอ่านอย่างเดียวและไม่สามารถเขียนได้ ตรวจสอบบันทึกสำหรับรายละเอียด",
"tr_TR": "",
"uk_UA": "XCI тільки для читання і не можна записати. Перевірте логи для деталей.",
"zh_CN": "XCI 只读,无法写入。查看日志以获取详情。",
"zh_TW": "XCI 檔案唯讀,無法寫入。檢查日誌以取得詳情。"
}
},
{
"ID": "SizeChangedMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI size changed since last scan. Ensure the file is not being written to and try again.",
"es_ES": "El tamaño de XCI ha cambiado desde que fue escaneado. Verifica que no se esté escribiendo al archivo y vuelve a intentarlo.",
"fr_FR": "La taille de XCI a changé depuis son analyse. Vérifiez que le fichier nest pas en cours décriture, puis réessayez.",
"he_IL": "",
"it_IT": "La dimensione di XCI è cambiata da quando è stato scansionato. Controlla che il file non sia scritto e riprova.",
"ja_JP": "",
"ko_KR": "XCI 크기가 스캔 후 변경되었습니다. 파일이 쓰여지고 있지 않은지 확인하고 다시 시도하세요.",
"no_NO": "XCI har endret størrelse siden den ble skannet. Kontroller at det ikke skrives til filen, og prøv på nytt.",
"pl_PL": "Rozmiar XCI zmienił się od momentu zeskanowania. Sprawdź, czy plik nie jest zapisywany, a następnie spróbuj ponownie.",
"pt_BR": "O tamanho de XCI mudou desde que foi escaneado. Verifique se o arquivo não está sendo gravado e tente novamente.",
"ru_RU": "Размер XCI изменился после сканирования. Проверьте, не записывается ли файл, и попробуйте снова.",
"sv_SE": "XCI har ändrats i storlek sedan den lästes av. Kontrollera att filen inte skrivs till och försök igen.",
"th_TH": "ขนาด XCI เปลี่ยนไปตั้งแต่การสแกนครั้งล่าสุด ตรวจสอบว่าไฟล์ไม่ได้ถูกเขียน และลองใหม่",
"tr_TR": "",
"uk_UA": "Розмір XCI змінився з моменту сканування. Перевірте, чи не записується файл, та спробуйте знову.",
"zh_CN": "XCI 在扫描后大小发生了变化。请检查文件是否未被写入,然后重试。",
"zh_TW": "XCI 檔案大小自上次掃描以來已經改變。請檢查檔案是否未被寫入,然後再嘗試。"
}
},
{
"ID": "FreeSpaceCheckFailedMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI has data in the free space area. It is not safe to trim.",
"es_ES": "XCI tiene datos en el área de espacio libre. No es seguro recortarlo.",
"fr_FR": "XCI contient des données dans la zone d'espace libre. Il n'est pas sûr de le réduire.",
"he_IL": "",
"it_IT": "XCI contiene dati nell'area di spazio libero. Non è sicuro ridurre la sua dimensione.",
"ja_JP": "",
"ko_KR": "XCI 파일에 여유 공간 영역에 데이터가 있으므로 트리밍하는 것이 안전하지 않습니다.",
"no_NO": "XCI har data i den ledige plassen. Det er ikke trygt å trimme den.",
"pl_PL": "XCI zawiera dane w obszarze wolnego miejsca. Nie jest bezpieczne go przycinać.",
"pt_BR": "XCI tem dados na área de espaço livre. Não é seguro reduzi-lo.",
"ru_RU": "XCI содержит данные в свободной области. Его обрезка небезопасна.",
"sv_SE": "XCI har data i det lediga utrymmet. Det är inte säkert att optimera.",
"th_TH": "XCI มีข้อมูลในพื้นที่ว่าง จึงไม่ปลอดภัยที่จะทำการตัดแต่ง",
"tr_TR": "",
"uk_UA": "XCI містить дані в зоні вільного простору. Тому обрізка небезпечна.",
"zh_CN": "XCI 文件的空闲区域内有数据。不能安全瘦身。",
"zh_TW": "XCI 檔案有數據儲存於空閒區域。修剪不安全。"
}
},
{
"ID": "InvalidDataMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI contains invalid data. Check logs for details.",
"es_ES": "XCI contiene datos inválidos. Lee el registro para detalles.",
"fr_FR": "XCI contient des données invalides. Référez-vous aux journaux pour détails.",
"he_IL": "",
"it_IT": "XCI contiene dati non validi. Controlla i log per dettagli.",
"ja_JP": "",
"ko_KR": "XCI 파일에 유효하지 않은 데이터가 포함되어 있습니다. 로그를 확인하세요.",
"no_NO": "XCI-filen inneholder ugyldige data. Sjekk loggene for detaljer.",
"pl_PL": "XCI zawiera nieprawidłowe dane. Sprawdź dzienniki, aby uzyskać szczegóły.",
"pt_BR": "XCI contém dados inválidos. Verifique os logs para detalhes.",
"ru_RU": "XCI содержит недопустимые данные. Проверьте журналы для подробностей.",
"sv_SE": "XCI-filen innehåller ogiltig data. Kontrollera loggen för detaljer.",
"th_TH": "XCI มีข้อมูลที่ไม่ถูกต้อง โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
"tr_TR": "",
"uk_UA": "XCI містить недійсні дані. Перевірте журнали для деталей.",
"zh_CN": "XCI 文件含有无效数据。查看日志以获得更多细节。",
"zh_TW": "XCI 檔案帶有無效的數據。檢查日誌以取得更多資訊"
}
},
{
"ID": "WriteErrorMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI could not be opened for writing. Check logs for details.",
"es_ES": "XCI no se puede abrir para escribir. Lee el registro para detalles.",
"fr_FR": "XCI n'a pas pu être ouvert pour écriture. Consultez les journaux pour détails.",
"he_IL": "",
"it_IT": "XCI non può essere aperto in scrittura. Controlla i log per dettagli.",
"ja_JP": "",
"ko_KR": "XCI를 쓰기 위해 열 수 없습니다. 로그를 확인하세요.",
"no_NO": "XCI kunne ikke åpnes for skriving. Sjekk loggene for detaljer.",
"pl_PL": "Nie można otworzyć XCI do zapisu. Sprawdź dzienniki, aby uzyskać szczegóły.",
"pt_BR": "XCI não pôde ser aberto para gravação. Verifique os logs para detalhes.",
"ru_RU": "Не удалось открыть XCI для записи. Проверьте журналы для подробностей.",
"sv_SE": "XCI kunde inte öppnas för skrivning. Kontrollera loggen för detaljer.",
"th_TH": "ไม่สามารถเปิด XCI เพื่อเขียนข้อมูลได้ โปรดตรวจสอบบันทึกสำหรับรายละเอียด",
"tr_TR": "",
"uk_UA": "Не вдалося відкрити XCI для запису. Перевірте журнали для деталей.",
"zh_CN": "XCI 不能写入。查看日志以获得更多细节。",
"zh_TW": "XCI 無法開啟以進行寫入。請檢查日誌以取得更多資訊。"
}
},
{
"ID": "TrimFailedMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Failed to trim XCI.",
"es_ES": "El recorte del XCI falló.",
"fr_FR": "La réduction du XCI a échoué.",
"he_IL": "",
"it_IT": "Riduzione del XCI fallita.",
"ja_JP": "",
"ko_KR": "XCI 트리밍에 실패했습니다.",
"no_NO": "Trimming av XCI mislyktes.",
"pl_PL": "Nie udało się przyciąć XCI.",
"pt_BR": "A redução do XCI falhou.",
"ru_RU": "Обрезка XCI не удалась.",
"sv_SE": "Optimering av XCI misslyckades.",
"th_TH": "การตัดแต่ง XCI ล้มเหลว",
"tr_TR": "",
"uk_UA": "Не вдалося обрізати XCI.",
"zh_CN": "XCI 瘦身失败。",
"zh_TW": "修剪 XCI 失敗。"
}
},
{
"ID": "TrimCancelledMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "The operation was cancelled.",
"es_ES": "La operación fue cancelada.",
"fr_FR": "L'opération a été annulée.",
"he_IL": "",
"it_IT": "L'operazione è stata annullata.",
"ja_JP": "",
"ko_KR": "작업이 취소되었습니다.",
"no_NO": "Operasjonen ble avbrutt.",
"pl_PL": "",
"pt_BR": "A operação foi cancelada.",
"ru_RU": "Операция была отменена.",
"sv_SE": "Åtgärden avbröts.",
"th_TH": "การดำเนินการถูกยกเลิกแล้ว.",
"tr_TR": "",
"uk_UA": "Операцію перервано.",
"zh_CN": "操作已取消。",
"zh_TW": "操作已取消。"
}
},
{
"ID": "NoOperationPerformedMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "No operation was performed.",
"es_ES": "No se realizó ninguna operación.",
"fr_FR": "Aucune opération n'a été effectuée.",
"he_IL": "",
"it_IT": "Non è stata effettuata alcuna operazione.",
"ja_JP": "",
"ko_KR": "작업이 수행되지 않았습니다.",
"no_NO": "Ingen operasjon ble utført.",
"pl_PL": "",
"pt_BR": "Nenhuma operação foi realizada.",
"ru_RU": "Операция не была выполнена.",
"sv_SE": "Ingen åtgärd genomfördes.",
"th_TH": "ไม่มีการดำเนินการใด ๆ",
"tr_TR": "",
"uk_UA": "Операцію не було виконано.",
"zh_CN": "未执行任何操作。",
"zh_TW": "沒有執行任何操作。"
}
}
]
}

View File

@@ -0,0 +1,29 @@
{
"Locales": [
{
"ID": "TrimXCIButton",
"Translations": {
"ar_SA": "تقليم XCI",
"de_DE": "Zuschneiden der XCI",
"el_GR": "Κοπή XCI",
"en_US": "Trim XCI",
"es_ES": "Recortar XCI",
"fr_FR": "Réduire le XCI",
"he_IL": "חתוך XCI",
"it_IT": "Riduci il XCI",
"ja_JP": "XCIをトリム",
"ko_KR": "XCI 트림",
"no_NO": "Trim XCI-filen",
"pl_PL": "Przytnij XCI",
"pt_BR": "Reduzir o XCI",
"ru_RU": "Обрезать XCI",
"sv_SE": "Optimera XCI",
"th_TH": "ลดขนาด XCI",
"tr_TR": "XCI'yi Kırp",
"uk_UA": "Нарізка XCI",
"zh_CN": "精简 XCI",
"zh_TW": "修剪 XCI"
}
}
]
}

View File

@@ -228,26 +228,26 @@
{ {
"ID": "XCITrimmerButton", "ID": "XCITrimmerButton",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "XCI-Dateien trimmen", "de_DE": "XCI-Trimmer",
"el_GR": "", "el_GR": "",
"en_US": "Trim XCI Files", "en_US": "XCI Trimmer",
"es_ES": "Recortar Archivos XCI", "es_ES": "Recortador de XCI",
"fr_FR": "Réduire les Fichiers XCI", "fr_FR": "Réducteur de XCI",
"he_IL": "", "he_IL": "",
"it_IT": "Riduci dimensioni dei file XCI", "it_IT": "Trimmer XCI",
"ja_JP": "", "ja_JP": "",
"ko_KR": "XCI 파일 트리머", "ko_KR": "XCI 파일 트리머",
"no_NO": "Trim XCI-filer", "no_NO": "Trim XCI-filer",
"pl_PL": "", "pl_PL": "Przycinacz XCI",
"pt_BR": "Reduzir Arquivos XCI", "pt_BR": "Cortador de XCI",
"ru_RU": "Обрезать XCI файлы", "ru_RU": "Триммер XCI",
"sv_SE": "Optimera XCI-filer", "sv_SE": "XCI-trimmer",
"th_TH": "ตัดแต่งไฟล์ XCI", "th_TH": "",
"tr_TR": "", "tr_TR": "XCI Kesici",
"uk_UA": "Обрізати XCI файли", "uk_UA": "Тример XCI",
"zh_CN": "瘦身 XCI 文件", "zh_CN": "",
"zh_TW": "修剪 XCI 檔案" "zh_TW": ""
} }
}, },
{ {
@@ -450,6 +450,31 @@
"zh_TW": "移除 Skylander" "zh_TW": "移除 Skylander"
} }
}, },
{
"ID": "SimulateWakeUpMessageButton",
"Translations": {
"ar_SA": "محاكاة رسالة الاستيقاظ",
"de_DE": "Aufwachnachricht simulieren",
"el_GR": "Προσομοίωση Μηνύματος Αφύπνισης",
"en_US": "Simulate Wake-Up Message",
"es_ES": "Simular Mensaje de Reactivación",
"fr_FR": "Simuler un Message de Réveil",
"he_IL": "דמה הודעת השכמה",
"it_IT": "Simula messaggio di risveglio",
"ja_JP": "スリープ復帰メッセージをシミュレート",
"ko_KR": "절전 모드 해제 메시지 시뮬레이션",
"no_NO": "Simuler oppvåknings-melding",
"pl_PL": "Symuluj wiadomość wybudzania",
"pt_BR": "Simular Mensagem de Acordar o Console",
"ru_RU": "Имитировать сообщение пробуждения",
"sv_SE": "Simulera uppvakningsmeddelande",
"th_TH": "จำลองการปลุกอุปกรณ์ให้ทำงาน",
"tr_TR": "Uyandırma Mesajı Simüle Et",
"uk_UA": "Симулювати повідомлення про пробудження",
"zh_CN": "模拟唤醒消息",
"zh_TW": "模擬喚醒訊息"
}
},
{ {
"ID": "TakeScreenshotButton", "ID": "TakeScreenshotButton",
"Translations": { "Translations": {

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,32 @@
{ {
"Locales": [ "Locales": [
{
"ID": "TrimmingXCILabel",
"Translations": {
"ar_SA": "جاري تقليم: {0}",
"de_DE": "Schneide: {0}",
"el_GR": "Κόβει το: {0}",
"en_US": "Trimming: {0}",
"es_ES": "Recortando: {0}",
"fr_FR": "Réduction de: {0}",
"he_IL": "חיתוך: {0}",
"it_IT": "Riduzione di: {0}",
"ja_JP": "{0} をトリミング中:",
"ko_KR": "{0} 트리밍:",
"no_NO": "Trimming av: {0}",
"pl_PL": "Przycinanie: {0}",
"pt_BR": "Reduzindo: {0}",
"ru_RU": "Обрезка: {0}",
"sv_SE": "Trimmar: {0}",
"th_TH": "กำลังตัด: {0}",
"tr_TR": "{0} Kısaltılıyor:",
"uk_UA": "Обрізка: {0}",
"zh_CN": "正在修剪: {0}",
"zh_TW": "正在修剪: {0}"
}
},
{ {
"ID": "FirmwareVersion", "ID": "FirmwareVersionLabel",
"Translations": { "Translations": {
"ar_SA": "", "ar_SA": "",
"de_DE": "", "de_DE": "",

View File

@@ -0,0 +1,429 @@
{
"Locales": [
{
"ID": "StatusCountLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI Selected: {0}/{1}",
"es_ES": "XCI Seleccionados: {0}/{1}",
"fr_FR": "XCI Sélectionnés : {0}/{1}",
"he_IL": "",
"it_IT": "XCI Selezionati: {0}/{1}",
"ja_JP": "",
"ko_KR": "XCI 선택됨: {0}/{1}",
"no_NO": "XCI Valgt: {0}/{1}",
"pl_PL": "XCI Wybrane: {0}/{1}",
"pt_BR": "XCI Selecionados: {0}/{1}",
"ru_RU": "Выбрано XCI: {0}/{1}",
"sv_SE": "XCI Valda: {0}/{1}",
"th_TH": "XCI เลือกแล้ว: {0}/{1}",
"tr_TR": "",
"uk_UA": "Вибрано XCI: {0}/{1}",
"zh_CN": "XCI 已选: {0}/{1}",
"zh_TW": "XCI 已選擇: {0}/{1}"
}
},
{
"ID": "StatusCountWithFilterLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "XCI Selected: {0}/{1} (Displayed: {2})",
"es_ES": "XCI Seleccionados: {0}/{1} (Mostrados: {2})",
"fr_FR": "XCI Sélectionnés : {0}/{1} (Affichés : {2})",
"he_IL": "",
"it_IT": "XCI Selezionati: {0}/{1} (Visualizzati: {2})",
"ja_JP": "",
"ko_KR": "XCI 선택됨: {0}/{1} (표시됨: {2})",
"no_NO": "XCI Valgt: {0}/{1} (Vises: {2})",
"pl_PL": "XCI Wybrane: {0}/{1} (Wyświetlone: {2})",
"pt_BR": "XCI Selecionados: {0}/{1} (Exibidos: {2})",
"ru_RU": "Выбрано XCI: {0}/{1} (Отображается: {2})",
"sv_SE": "XCI Valda: {0}/{1} (Visas: {2})",
"th_TH": "XCI เลือกแล้ว: {0}/{1} (แสดง: {2})",
"tr_TR": "",
"uk_UA": "Вибрано XCI: {0}/{1} (Відображається: {2})",
"zh_CN": "XCI 已选: {0}/{1}(显示: {2}",
"zh_TW": "XCI 已選擇: {0}/{1}(顯示: {2}"
}
},
{
"ID": "StatusTrimmingLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Trimming: {0}/{1}...",
"es_ES": "Recortando: {0}/{1}...",
"fr_FR": "Réduction : {0}/{1}...",
"he_IL": "",
"it_IT": "Riduzione: {0}/{1}...",
"ja_JP": "",
"ko_KR": "트리밍: {0}/{1}...",
"no_NO": "Trimmer: {0}/{1}...",
"pl_PL": "",
"pt_BR": "Recortando: {0}/{1}...",
"ru_RU": "Обрезка: {0}/{1}...",
"sv_SE": "Trimmar: {0}/{1}...",
"th_TH": "กำลังตัดแต่ง: {0}/{1}...",
"tr_TR": "",
"uk_UA": "Обрізання: {0}/{1}...",
"zh_CN": "正在修剪:{0}/{1}...",
"zh_TW": "正在修剪:{0}/{1}..."
}
},
{
"ID": "StatusUntrimmingLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Untrimming {0}...",
"es_ES": "Deshaciendo el recorte {0}...",
"fr_FR": "Restauration de {0}...",
"he_IL": "",
"it_IT": "Ripristino di {0}...",
"ja_JP": "",
"ko_KR": "{0} 복원 중...",
"no_NO": "Gjenoppretter {0}...",
"pl_PL": "",
"pt_BR": "Restaurando {0}...",
"ru_RU": "Восстановление {0}...",
"sv_SE": "Återställer {0}...",
"th_TH": "กำลังกู้คืน {0}...",
"tr_TR": "",
"uk_UA": "Відновлення {0}...",
"zh_CN": "正在恢复 {0}...",
"zh_TW": "正在還原 {0}..."
}
},
{
"ID": "SelectAllButton",
"Translations": {
"ar_SA": "اختر الكل",
"de_DE": "Alles auswählen",
"el_GR": "Επιλογή όλων",
"en_US": "Select All",
"es_ES": "Seleccionar Todo",
"fr_FR": "Sélectionner Tout",
"he_IL": "בחר הכל",
"it_IT": "Seleziona tutto",
"ja_JP": "すべて選択",
"ko_KR": "모두 선택",
"no_NO": "Velg alle",
"pl_PL": "Zaznacz wszystko",
"pt_BR": "Selecionar tudo",
"ru_RU": "Выбрать все",
"sv_SE": "Markera alla",
"th_TH": "เลือกทั้งหมด",
"tr_TR": "Hepsini seç",
"uk_UA": "Вибрати все",
"zh_CN": "选择全部",
"zh_TW": "選擇全部"
}
},
{
"ID": "ClearSelectionButton",
"Translations": {
"ar_SA": "مسح التحديد",
"de_DE": "Auswahl aufheben",
"el_GR": "Εκκαθάριση επιλογής",
"en_US": "Clear Selection",
"es_ES": "Borrar selección",
"fr_FR": "Effacer la sélection",
"he_IL": "נקה בחירה",
"it_IT": "Cancella selezione",
"ja_JP": "選択をクリア",
"ko_KR": "선택 해제",
"no_NO": "Fjern utvalg",
"pl_PL": "Wyczyść zaznaczenie",
"pt_BR": "Limpar seleção",
"ru_RU": "Очистить выделение",
"sv_SE": "Rensa markering",
"th_TH": "ล้างการเลือก",
"tr_TR": "Seçimi temizle",
"uk_UA": "Очистити вибір",
"zh_CN": "清除选择",
"zh_TW": "清除選擇"
}
},
{
"ID": "TrimmedLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Trimmed",
"es_ES": "Recortado",
"fr_FR": "Réduit",
"he_IL": "",
"it_IT": "Dim. ridotta",
"ja_JP": "",
"ko_KR": "트리밍됨",
"no_NO": "Trimmet",
"pl_PL": "",
"pt_BR": "Reduzido",
"ru_RU": "Обрезан",
"sv_SE": "Optimerad",
"th_TH": "ตัดแต่งแล้ว",
"tr_TR": "",
"uk_UA": "Обрізані",
"zh_CN": "经过瘦身的",
"zh_TW": "已修剪"
}
},
{
"ID": "UntrimmedLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Untrimmed",
"es_ES": "Sin Recortar",
"fr_FR": "Non Réduit",
"he_IL": "",
"it_IT": "Dim. originale",
"ja_JP": "",
"ko_KR": "트리밍되지 않음",
"no_NO": "Ikke trimmet",
"pl_PL": "",
"pt_BR": "Não Reduzido",
"ru_RU": "Не обрезан",
"sv_SE": "Orörd",
"th_TH": "ยังไม่ได้ตัดแต่ง",
"tr_TR": "",
"uk_UA": "Необрізані",
"zh_CN": "没有瘦身的",
"zh_TW": "未修剪"
}
},
{
"ID": "PartialLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Partial",
"es_ES": "Parcial",
"fr_FR": "Partiel",
"he_IL": "",
"it_IT": "Parziale",
"ja_JP": "",
"ko_KR": "일부",
"no_NO": "Delvis",
"pl_PL": "",
"pt_BR": "Parcial",
"ru_RU": "Частично",
"sv_SE": "Delvis",
"th_TH": "ยังไม่สมบูรณ์",
"tr_TR": "",
"uk_UA": "Часткові",
"zh_CN": "分区",
"zh_TW": "部分"
}
},
{
"ID": "FailedLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Failed",
"es_ES": "Fallido",
"fr_FR": "Échoué",
"he_IL": "",
"it_IT": "Fallito",
"ja_JP": "",
"ko_KR": "실패",
"no_NO": "Mislyktes",
"pl_PL": "",
"pt_BR": "Falhou",
"ru_RU": "Ошибка",
"sv_SE": "Misslyckades",
"th_TH": "ล้มเหลว",
"tr_TR": "",
"uk_UA": "Невдача",
"zh_CN": "失败",
"zh_TW": "失敗"
}
},
{
"ID": "UnknownLabel",
"Translations": {
"ar_SA": "مجهول",
"de_DE": "Unbekannt",
"el_GR": "Άγνωστο",
"en_US": "Unknown",
"es_ES": "Desconocido",
"fr_FR": "Inconnu",
"he_IL": "לא ידוע",
"it_IT": "Sconosciuto",
"ja_JP": "不明",
"ko_KR": "알 수 없음",
"no_NO": "Ukjent",
"pl_PL": "Nieznany",
"pt_BR": "Desconhecido",
"ru_RU": "Неизвестно",
"sv_SE": "Okänd",
"th_TH": "ไม่รู้จัก",
"tr_TR": "Bilinmeyen",
"uk_UA": "Невідомо",
"zh_CN": "未知",
"zh_TW": "未知"
}
},
{
"ID": "CalculatedSavingsLabel",
"Translations": {
"ar_SA": null,
"de_DE": null,
"el_GR": null,
"en_US": "{0} MB ({1}%)",
"es_ES": null,
"fr_FR": "{0} Mo ({1} %)",
"he_IL": null,
"it_IT": null,
"ja_JP": "{0}MB{1}%",
"ko_KR": "{0}MB ({1}%)",
"no_NO": null,
"pl_PL": null,
"pt_BR": null,
"ru_RU": "{0} МБ ({1}%)",
"sv_SE": null,
"th_TH": null,
"tr_TR": null,
"uk_UA": "{0} МБ ({1}%)",
"zh_CN": "{0} MB{1}%",
"zh_TW": "{0} MB{1}%"
}
},
{
"ID": "SavedLabel",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Saved:",
"es_ES": "Ahorrado:",
"fr_FR": "Économies :",
"he_IL": "",
"it_IT": "Risparmiato:",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "Zaoszczędzone:",
"pt_BR": "Economizado:",
"ru_RU": "Сохранено:",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Збережено:",
"zh_CN": "",
"zh_TW": ""
}
},
{
"ID": "RemainingLabel",
"Translations": {
"ar_SA": "المتبقية:",
"de_DE": "Verbleibend:",
"el_GR": "Απομένουν:",
"en_US": "Remaining:",
"es_ES": "Restante:",
"fr_FR": "Restant :",
"he_IL": "נותרו:",
"it_IT": "Rimanenti:",
"ja_JP": "残り:",
"ko_KR": "남은:",
"no_NO": "Gjenstående:",
"pl_PL": "Pozostało:",
"pt_BR": "Restante:",
"ru_RU": "Осталось:",
"sv_SE": "Kvar:",
"th_TH": "เหลือ:",
"tr_TR": "Kalan:",
"uk_UA": "Залишилося:",
"zh_CN": "剩余:",
"zh_TW": "剩餘:"
}
},
{
"ID": "MBLabel",
"Translations": {
"ar_SA": null,
"de_DE": null,
"el_GR": null,
"en_US": "{0} MB",
"es_ES": null,
"fr_FR": "{0} Mo",
"he_IL": null,
"it_IT": null,
"ja_JP": "{0}MB",
"ko_KR": "{0}MB",
"no_NO": null,
"pl_PL": null,
"pt_BR": null,
"ru_RU": "{0} МБ",
"sv_SE": null,
"th_TH": null,
"tr_TR": null,
"uk_UA": "{0} МБ",
"zh_CN": null,
"zh_TW": null
}
},
{
"ID": "TrimButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Trim",
"es_ES": "Recortar",
"fr_FR": "Réduire",
"he_IL": "",
"it_IT": "Riduci",
"ja_JP": "",
"ko_KR": "트림",
"no_NO": "",
"pl_PL": "Przytnij",
"pt_BR": "Reduzir",
"ru_RU": "Обрезать",
"sv_SE": "Trimma",
"th_TH": "ตัด",
"tr_TR": "Kırp",
"uk_UA": "Обрізати",
"zh_CN": "瘦身",
"zh_TW": "修剪"
}
},
{
"ID": "UntrimButton",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Untrim",
"es_ES": "Desrecortar",
"fr_FR": "Dé-Réduire",
"he_IL": "",
"it_IT": "Ripristina",
"ja_JP": "",
"ko_KR": "언트림",
"no_NO": "Utrim",
"pl_PL": "",
"pt_BR": "Restaurar",
"ru_RU": "Восстановить",
"sv_SE": "Avoptimera",
"th_TH": "กู้คืน",
"tr_TR": "",
"uk_UA": "Відновити",
"zh_CN": "取消精简",
"zh_TW": "反修剪"
}
}
]
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Systems.AppLibrary; using Ryujinx.Ava.Systems.AppLibrary;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@@ -11,6 +12,7 @@ namespace Ryujinx.Ava.Common.Models
bool Untrimmable, bool Untrimmable,
long PotentialSavingsB, long PotentialSavingsB,
long CurrentSavingsB, long CurrentSavingsB,
long OriginalSizeB,
int? PercentageProgress, int? PercentageProgress,
XCIFileTrimmer.OperationOutcome ProcessingOutcome) XCIFileTrimmer.OperationOutcome ProcessingOutcome)
{ {
@@ -25,31 +27,57 @@ namespace Ryujinx.Ava.Common.Models
trimmer.CanBeUntrimmed, trimmer.CanBeUntrimmed,
trimmer.DiskSpaceSavingsB, trimmer.DiskSpaceSavingsB,
trimmer.DiskSpaceSavedB, trimmer.DiskSpaceSavedB,
applicationData.FileSize,
null, null,
XCIFileTrimmer.OperationOutcome.Undetermined XCIFileTrimmer.OperationOutcome.Undetermined
); );
} }
public bool IsFailed public bool IsFailed =>
ProcessingOutcome is not XCIFileTrimmer.OperationOutcome.Undetermined
and not XCIFileTrimmer.OperationOutcome.Successful;
public string StatusText
{ {
get get
{ {
return ProcessingOutcome is not XCIFileTrimmer.OperationOutcome.Undetermined and if (IsFailed)
not XCIFileTrimmer.OperationOutcome.Successful; return LocaleManager.Instance[LocaleKeys.XCITrimmer_FailedLabel];
return ProcessingOutcome switch
{
XCIFileTrimmer.OperationOutcome.Successful =>
CurrentSavingsB > 0
? LocaleManager.Instance[LocaleKeys.XCITrimmer_UntrimmedLabel]
: LocaleManager.Instance[LocaleKeys.XCITrimmer_TrimmedLabel],
XCIFileTrimmer.OperationOutcome.Undetermined =>
Trimmable && Untrimmable
? LocaleManager.Instance[LocaleKeys.XCITrimmer_PartialLabel]
: Trimmable
? LocaleManager.Instance[LocaleKeys.XCITrimmer_UntrimmedLabel]
: Untrimmable
? LocaleManager.Instance[LocaleKeys.XCITrimmer_TrimmedLabel]
: LocaleManager.Instance[LocaleKeys.XCITrimmer_UnknownLabel],
_ => LocaleManager.Instance[LocaleKeys.XCITrimmer_UnknownLabel]
};
} }
} }
public bool HasStatusDetail =>
ProcessingOutcome != XCIFileTrimmer.OperationOutcome.Undetermined;
public virtual bool Equals(XCITrimmerFileModel obj) public virtual bool Equals(XCITrimmerFileModel obj)
{ {
if (obj == null) if (obj is null)
return false; return false;
return this.Path == obj.Path; return Path == obj.Path;
}
public override int GetHashCode()
{
return this.Path.GetHashCode();
} }
public override int GetHashCode() => Path.GetHashCode();
} }
} }

View File

@@ -26,9 +26,9 @@ namespace Ryujinx.Ava.Common
internal class TrimmerWindow : Ryujinx.Common.Logging.XCIFileTrimmerLog internal class TrimmerWindow : Ryujinx.Common.Logging.XCIFileTrimmerLog
{ {
private readonly XciTrimmerViewModel _viewModel; private readonly XCITrimmerViewModel _viewModel;
public TrimmerWindow(XciTrimmerViewModel viewModel) public TrimmerWindow(XCITrimmerViewModel viewModel)
{ {
_viewModel = viewModel; _viewModel = viewModel;
} }

View File

@@ -103,10 +103,10 @@
<MenuItem <MenuItem
Command="{Binding TrimXci}" Command="{Binding TrimXci}"
CommandParameter="{Binding}" CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuTrimXCI}" Header="{ext:Locale GameListContextMenu_TrimXCIButton}"
IsEnabled="{Binding TrimXCIEnabled}" IsEnabled="{Binding TrimXCIEnabled}"
Icon="{ext:Icon fa-solid fa-scissors}" IsVisible="{Binding IsXCIFile}"
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" /> Icon="{ext:Icon fa-solid fa-scissors}" />
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon fa-solid fa-memory}"> <MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon fa-solid fa-memory}">
<MenuItem <MenuItem
Command="{Binding PurgePtcCache}" Command="{Binding PurgePtcCache}"

View File

@@ -12,32 +12,57 @@ namespace Ryujinx.Ava.UI.Helpers
internal class XCITrimmerFileSpaceSavingsConverter : IValueConverter internal class XCITrimmerFileSpaceSavingsConverter : IValueConverter
{ {
private const long _bytesPerMB = 1024 * 1024; private const long _bytesPerMB = 1024 * 1024;
public static readonly XCITrimmerFileSpaceSavingsConverter Instance = new(); public static readonly XCITrimmerFileSpaceSavingsConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (value is UnsetValueType) if (value == null || value == AvaloniaProperty.UnsetValue)
{
return BindingOperations.DoNothing; return BindingOperations.DoNothing;
}
if (!targetType.IsAssignableFrom(typeof(string)))
{
return null;
}
if (value is not XCITrimmerFileModel app) if (value is not XCITrimmerFileModel app)
{
return null; return null;
long originalSize = app.OriginalSizeB;
long currentSavings = app.CurrentSavingsB;
long potentialSavings = app.PotentialSavingsB;
if (originalSize <= 0)
{
return GetFormattedString(app, 0, 0);
} }
long mbValue = 0;
double percentage = 0;
if (currentSavings > 0)
{
mbValue = (currentSavings / _bytesPerMB).CoerceAtLeast(0);
percentage = (currentSavings / (double)originalSize) * 100;
}
else if (potentialSavings > 0)
{
mbValue = (potentialSavings / _bytesPerMB).CoerceAtLeast(0);
percentage = (potentialSavings / (double)originalSize) * 100;
}
return GetFormattedString(app, mbValue, percentage);
}
private string GetFormattedString(XCITrimmerFileModel app, long mb, double percentage)
{
// Round percentage to 1 decimal place
double roundedPercentage = Math.Round(percentage, 1);
if (app.CurrentSavingsB < app.PotentialSavingsB) if (app.CurrentSavingsB < app.PotentialSavingsB)
{ {
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, ((app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB).CoerceAtLeast(0)); return LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.XCITrimmer_CalculatedSavingsLabel, mb, roundedPercentage);
} }
else else
{ {
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, (app.CurrentSavingsB / _bytesPerMB).CoerceAtLeast(0)); return LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.XCITrimmer_CalculatedSavingsLabel, mb, roundedPercentage);
} }
} }

View File

@@ -1,7 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale; using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Models; using Ryujinx.Ava.Common.Models;
using System; using System;
using System.Globalization; using System.Globalization;
@@ -16,26 +16,30 @@ namespace Ryujinx.Ava.UI.Helpers
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
if (value is UnsetValueType) if (value is UnsetValueType)
{
return BindingOperations.DoNothing; return BindingOperations.DoNothing;
}
if (!targetType.IsAssignableFrom(typeof(string)))
{
return null;
}
if (value is not XCITrimmerFileModel app) if (value is not XCITrimmerFileModel app)
{ return default(Symbol);
return null;
}
return app.PercentageProgress != null ? String.Empty : bool isProcessing = app.PercentageProgress != null;
app.ProcessingOutcome is not OperationOutcome.Successful and not OperationOutcome.Undetermined ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusFailedLabel] :
app.Trimmable & app.Untrimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusPartialLabel] : if (isProcessing)
app.Trimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusTrimmableLabel] : return Symbol.Sync;
app.Untrimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusUntrimmableLabel] :
String.Empty; if (app.ProcessingOutcome is not OperationOutcome.Successful
and not OperationOutcome.Undetermined)
return Symbol.ImportantFilled;
if (app.Trimmable && app.Untrimmable)
return Symbol.Repair;
if (app.Trimmable)
return Symbol.Clear;
if (app.Untrimmable)
return Symbol.Checkmark;
return Symbol.Help;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

View File

@@ -9,17 +9,15 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
public string LocalizedText => opOutcome switch public string LocalizedText => opOutcome switch
{ {
OperationOutcome.NoTrimNecessary => LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary], OperationOutcome.NoTrimNecessary => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_NoTrimNecessaryMessage],
OperationOutcome.NoUntrimPossible => LocaleManager.Instance[LocaleKeys.TrimXCIFileNoUntrimPossible], OperationOutcome.NoUntrimPossible => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_NoUntrimPossibleMessage],
OperationOutcome.ReadOnlyFileCannotFix => LocaleManager.Instance[ OperationOutcome.ReadOnlyFileCannotFix => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_ReadOnlyFileCannotFixMessage],
LocaleKeys.TrimXCIFileReadOnlyFileCannotFix], OperationOutcome.FreeSpaceCheckFailed => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_FreeSpaceCheckFailedMessage],
OperationOutcome.FreeSpaceCheckFailed => LocaleManager.Instance[ OperationOutcome.InvalidXCIFile => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_InvalidDataMessage],
LocaleKeys.TrimXCIFileFreeSpaceCheckFailed], OperationOutcome.FileIOWriteError => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_WriteErrorMessage],
OperationOutcome.InvalidXCIFile => LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile], OperationOutcome.FileSizeChanged => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_SizeChangedMessage],
OperationOutcome.FileIOWriteError => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError], OperationOutcome.Cancelled => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_TrimCancelledMessage],
OperationOutcome.FileSizeChanged => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged], OperationOutcome.Undetermined => LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_NoOperationPerformedMessage],
OperationOutcome.Cancelled => LocaleManager.Instance[LocaleKeys.TrimXCIFileCancelled],
OperationOutcome.Undetermined => LocaleManager.Instance[LocaleKeys.TrimXCIFileFileUndertermined],
_ => null _ => null
}; };
} }

View File

@@ -496,6 +496,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo; public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
public bool IsXCIFile => Path.GetExtension(SelectedApplication.Path)?.ToLower() == ".xci";
public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id); public bool HasDlc => ApplicationLibrary.HasDlcs(SelectedApplication.Id);
public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder && public bool OpenUserSaveDirectoryEnabled => SelectedApplication.HasControlHolder &&
@@ -790,7 +792,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite],
ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel], ApplicationSort.TitleId => LocaleManager.Instance[LocaleKeys.DlcManagerTableHeadingTitleIdLabel],
ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication], ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.Common_Sort_NameLabel],
ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListSortDeveloper], ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListSortDeveloper],
ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListSortLastPlayed], ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListSortLastPlayed],
ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListSortTimePlayed], ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListSortTimePlayed],
@@ -1935,13 +1937,13 @@ namespace Ryujinx.Ava.UI.ViewModels
if (version != null) if (version != null)
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersion, version.VersionString); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersionLabel, version.VersionString);
hasApplet = version.Major > 3; hasApplet = version.Major > 3;
} }
else else
{ {
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersion, "NaN"); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_FirmwareVersionLabel, "NaN");
} }
IsAppletMenuActive = hasApplet; IsAppletMenuActive = hasApplet;
@@ -2317,7 +2319,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (notifyUser != null) if (notifyUser != null)
{ {
await ContentDialogHelper.CreateWarningDialog( await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileFailedPrimaryText], LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_TrimFailedMessage],
notifyUser notifyUser
); );
} }
@@ -2343,18 +2345,18 @@ namespace Ryujinx.Ava.UI.ViewModels
if (trimmer.CanBeTrimmed) if (trimmer.CanBeTrimmed)
{ {
double savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0; int savings = (int)Math.Round((double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0);
double currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0; int currentFileSize = (int)Math.Round((double)trimmer.FileSizeB / 1024.0 / 1024.0);
double cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0; int cartDataSize = (int)Math.Round((double)trimmer.DataSizeB / 1024.0 / 1024.0);
string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue( string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings); LocaleKeys.Dialog_XCITrimmer_SecondaryMessage, currentFileSize.ToString("0"), cartDataSize.ToString("0"), savings.ToString("0"));
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText], LocaleManager.Instance[LocaleKeys.Dialog_XCITrimmer_PrimaryMessage],
secondaryText, secondaryText,
LocaleManager.Instance[LocaleKeys.Continue], LocaleManager.Instance[LocaleKeys.Continue],
LocaleManager.Instance[LocaleKeys.Cancel], LocaleManager.Instance[LocaleKeys.Cancel],
LocaleManager.Instance[LocaleKeys.GameListContextMenuTrimXCI] LocaleManager.Instance[LocaleKeys.GameListContextMenu_TrimXCIButton]
); );
if (result == UserResult.Yes) if (result == UserResult.Yes)
@@ -2364,8 +2366,8 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
StatusBarProgressStatusText = StatusBarProgressStatusText =
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBar_TrimmingXCILabel,
Path.GetFileName(filename)); Path.GetFileNameWithoutExtension(filename));
StatusBarProgressStatusVisible = true; StatusBarProgressStatusVisible = true;
StatusBarProgressMaximum = 1; StatusBarProgressMaximum = 1;
StatusBarProgressValue = 0; StatusBarProgressValue = 0;

View File

@@ -16,7 +16,7 @@ using static Ryujinx.Common.Utilities.XCIFileTrimmer;
namespace Ryujinx.Ava.UI.ViewModels namespace Ryujinx.Ava.UI.ViewModels
{ {
public class XciTrimmerViewModel : BaseModel public class XCITrimmerViewModel : BaseModel
{ {
private const long BytesPerMb = 1024 * 1024; private const long BytesPerMb = 1024 * 1024;
@@ -29,11 +29,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public enum SortField public enum SortField
{ {
Name, Name,
Saved Savings,
Status
} }
private const string _FileExtXCI = "XCI"; private const string _fileExtXCI = "XCI";
private readonly Ryujinx.Common.Logging.XCIFileTrimmerLog _logger; private readonly Ryujinx.Common.Logging.XCIFileTrimmerLog _logger;
private ApplicationLibrary ApplicationLibrary => _mainWindowViewModel.ApplicationLibrary; private ApplicationLibrary ApplicationLibrary => _mainWindowViewModel.ApplicationLibrary;
private Optional<XCITrimmerFileModel> _processingApplication = null; private Optional<XCITrimmerFileModel> _processingApplication = null;
@@ -45,8 +45,11 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _search; private string _search;
private ProcessingMode _processingMode; private ProcessingMode _processingMode;
private SortField _sortField = SortField.Name; private SortField _sortField = SortField.Name;
private int _processingCurrent;
private int _processingTotal;
public XciTrimmerViewModel(MainWindowViewModel mainWindowViewModel)
public XCITrimmerViewModel(MainWindowViewModel mainWindowViewModel)
{ {
_logger = new XCITrimmerLog.TrimmerWindow(this); _logger = new XCITrimmerLog.TrimmerWindow(this);
_mainWindowViewModel = mainWindowViewModel; _mainWindowViewModel = mainWindowViewModel;
@@ -56,7 +59,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadXCIApplications() private void LoadXCIApplications()
{ {
IEnumerable<ApplicationData> apps = ApplicationLibrary.Applications.Items IEnumerable<ApplicationData> apps = ApplicationLibrary.Applications.Items
.Where(app => app.FileExtension == _FileExtXCI); .Where(app => app.FileExtension == _fileExtXCI);
foreach (ApplicationData xciApp in apps) foreach (ApplicationData xciApp in apps)
AddOrUpdateXCITrimmerFile(CreateXCITrimmerFile(xciApp.Path)); AddOrUpdateXCITrimmerFile(CreateXCITrimmerFile(xciApp.Path));
@@ -64,11 +67,9 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationsChanged(); ApplicationsChanged();
} }
private XCITrimmerFileModel CreateXCITrimmerFile( private XCITrimmerFileModel CreateXCITrimmerFile(string path, OperationOutcome operationOutcome = OperationOutcome.Undetermined)
string path,
OperationOutcome operationOutcome = OperationOutcome.Undetermined)
{ {
ApplicationData xciApp = ApplicationLibrary.Applications.Items.First(app => app.FileExtension == _FileExtXCI && app.Path == path); ApplicationData xciApp = ApplicationLibrary.Applications.Items.First(app => app.FileExtension == _fileExtXCI && app.Path == path);
return XCITrimmerFileModel.FromApplicationData(xciApp, _logger) with { ProcessingOutcome = operationOutcome }; return XCITrimmerFileModel.FromApplicationData(xciApp, _logger) with { ProcessingOutcome = operationOutcome };
} }
@@ -90,11 +91,15 @@ namespace Ryujinx.Ava.UI.ViewModels
SortAndFilter(); SortAndFilter();
} }
public bool AnySelected =>
_selectedXCIFiles.Count > 0;
private void SortingChanged() private void SortingChanged()
{ {
OnPropertiesChanged( OnPropertiesChanged(
nameof(IsSortedByName), nameof(IsSortedByName),
nameof(IsSortedBySaved), nameof(IsSortedBySavings),
nameof(IsSortedByStatus),
nameof(SortingAscending), nameof(SortingAscending),
nameof(SortingField), nameof(SortingField),
nameof(SortingFieldName)); nameof(SortingFieldName));
@@ -114,6 +119,7 @@ namespace Ryujinx.Ava.UI.ViewModels
nameof(Status), nameof(Status),
nameof(PotentialSavings), nameof(PotentialSavings),
nameof(ActualSavings), nameof(ActualSavings),
nameof(SavingsDifference),
nameof(CanTrim), nameof(CanTrim),
nameof(CanUntrim)); nameof(CanUntrim));
@@ -123,16 +129,30 @@ namespace Ryujinx.Ava.UI.ViewModels
private void SelectionChanged(bool displayedChanged = true) private void SelectionChanged(bool displayedChanged = true)
{ {
OnPropertiesChanged( OnPropertyChanged(nameof(Status));
nameof(Status), OnPropertyChanged(nameof(CanTrim));
nameof(CanTrim), OnPropertyChanged(nameof(CanUntrim));
nameof(CanUntrim), OnPropertyChanged(nameof(SelectedXCIFiles));
nameof(SelectedXCIFiles)); OnPropertyChanged(nameof(AnySelected));
OnPropertyChanged(nameof(SelectToggleText));
if (displayedChanged) if (displayedChanged)
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles)); OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
} }
public void ToggleSelect()
{
if (AnySelected)
DeselectAll();
else
SelectAll();
}
public string SelectToggleText =>
AnySelected
? LocaleManager.Instance[LocaleKeys.XCITrimmer_ClearSelectionButton]
: LocaleManager.Instance[LocaleKeys.XCITrimmer_SelectAllButton];
private void ProcessingChanged() private void ProcessingChanged()
{ {
OnPropertiesChanged( OnPropertiesChanged(
@@ -167,6 +187,14 @@ namespace Ryujinx.Ava.UI.ViewModels
(processingMode == ProcessingMode.Trimming && xci.Trimmable) (processingMode == ProcessingMode.Trimming && xci.Trimmable)
)).ToList(); )).ToList();
_processingTotal = toProcess.Count;
_processingCurrent = 0;
Dispatcher.UIThread.Post(() =>
{
OnPropertyChanged(nameof(Status));
});
List<XCITrimmerFileModel> viewsSaved = DisplayedXCIFiles.ToList(); List<XCITrimmerFileModel> viewsSaved = DisplayedXCIFiles.ToList();
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
@@ -219,6 +247,12 @@ namespace Ryujinx.Ava.UI.ViewModels
ProcessingApplication = null; ProcessingApplication = null;
}); });
} }
_processingCurrent++;
Dispatcher.UIThread.Post(() =>
{
OnPropertyChanged(nameof(Status));
});
} }
} }
finally finally
@@ -226,9 +260,20 @@ namespace Ryujinx.Ava.UI.ViewModels
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
_displayedXCIFiles.AddOrReplaceMatching(_allXCIFiles, viewsSaved); _displayedXCIFiles.AddOrReplaceMatching(_allXCIFiles, viewsSaved);
_selectedXCIFiles.AddOrReplaceMatching(_allXCIFiles, toProcess);
Processing = false; Processing = false;
ApplicationsChanged(); ApplicationsChanged();
_selectedXCIFiles.Clear();
foreach (var processed in toProcess)
{
var updated = _allXCIFiles.FirstOrDefault(x => x.Path == processed.Path);
if (updated != null)
_selectedXCIFiles.Add(updated);
}
SelectionChanged();
}); });
} }
}) })
@@ -254,9 +299,9 @@ namespace Ryujinx.Ava.UI.ViewModels
private class CompareXCITrimmerFiles : IComparer<XCITrimmerFileModel> private class CompareXCITrimmerFiles : IComparer<XCITrimmerFileModel>
{ {
private readonly XciTrimmerViewModel _viewModel; private readonly XCITrimmerViewModel _viewModel;
public CompareXCITrimmerFiles(XciTrimmerViewModel ViewModel) public CompareXCITrimmerFiles(XCITrimmerViewModel ViewModel)
{ {
_viewModel = ViewModel; _viewModel = ViewModel;
} }
@@ -270,9 +315,13 @@ namespace Ryujinx.Ava.UI.ViewModels
case SortField.Name: case SortField.Name:
result = x.Name.CompareTo(y.Name); result = x.Name.CompareTo(y.Name);
break; break;
case SortField.Saved: case SortField.Savings:
result = x.PotentialSavingsB.CompareTo(y.PotentialSavingsB); result = x.PotentialSavingsB.CompareTo(y.PotentialSavingsB);
break; break;
case SortField.Status:
result = x.CurrentSavingsB.CompareTo(y.CurrentSavingsB);
break;
} }
if (!_viewModel.SortingAscending) if (!_viewModel.SortingAscending)
@@ -312,15 +361,16 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public void SelectDisplayed() public void SelectAll()
{ {
SelectedXCIFiles.Clear();
SelectedXCIFiles.AddRange(DisplayedXCIFiles); SelectedXCIFiles.AddRange(DisplayedXCIFiles);
SelectionChanged(); SelectionChanged();
} }
public void DeselectDisplayed() public void DeselectAll()
{ {
SelectedXCIFiles.RemoveMany(DisplayedXCIFiles); SelectedXCIFiles.Clear();
SelectionChanged(); SelectionChanged();
} }
@@ -426,16 +476,23 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
return _processingMode switch return _processingMode switch
{ {
ProcessingMode.Trimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusTrimming], DisplayedXCIFiles.Count), ProcessingMode.Trimming => string.Format(
ProcessingMode.Untrimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusUntrimming], DisplayedXCIFiles.Count), LocaleManager.Instance[LocaleKeys.XCITrimmer_StatusTrimmingLabel],
_processingCurrent,
_processingTotal),
ProcessingMode.Untrimming => string.Format(
LocaleManager.Instance[LocaleKeys.XCITrimmer_StatusUntrimmingLabel],
_processingCurrent,
_processingTotal),
_ => string.Empty _ => string.Empty
}; };
} }
else else
{ {
return string.IsNullOrEmpty(Search) ? return string.IsNullOrEmpty(Search) ?
string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCount], SelectedXCIFiles.Count, AllXCIFiles.Count) : string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmer_StatusCountLabel], SelectedXCIFiles.Count, AllXCIFiles.Count) :
string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCountWithFilter], SelectedXCIFiles.Count, AllXCIFiles.Count, DisplayedXCIFiles.Count); string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmer_StatusCountWithFilterLabel], SelectedXCIFiles.Count, AllXCIFiles.Count, DisplayedXCIFiles.Count);
} }
} }
} }
@@ -466,8 +523,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
return SortingField switch return SortingField switch
{ {
SortField.Name => LocaleManager.Instance[LocaleKeys.XCITrimmerSortName], SortField.Name => LocaleManager.Instance[LocaleKeys.Common_Sort_NameLabel],
SortField.Saved => LocaleManager.Instance[LocaleKeys.XCITrimmerSortSaved], SortField.Savings => LocaleManager.Instance[LocaleKeys.Common_Sort_SavingsLabel],
SortField.Status => LocaleManager.Instance[LocaleKeys.Common_Sort_TrimStatusLabel],
_ => string.Empty, _ => string.Empty,
}; };
} }
@@ -488,11 +546,13 @@ namespace Ryujinx.Ava.UI.ViewModels
get => _sortField == SortField.Name; get => _sortField == SortField.Name;
} }
public bool IsSortedBySaved public bool IsSortedBySavings
{ {
get => _sortField == SortField.Saved; get => _sortField == SortField.Savings;
} }
public bool IsSortedByStatus => _sortField == SortField.Status;
public AvaloniaList<XCITrimmerFileModel> SelectedXCIFiles public AvaloniaList<XCITrimmerFileModel> SelectedXCIFiles
{ {
get => _selectedXCIFiles; get => _selectedXCIFiles;
@@ -517,7 +577,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
get get
{ {
return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / BytesPerMb)); return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmer_MBLabel], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / BytesPerMb));
} }
} }
@@ -525,7 +585,19 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
get get
{ {
return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / BytesPerMb)); return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmer_MBLabel], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / BytesPerMb));
}
}
public string SavingsDifference
{
get
{
long potentialSavings = AllXCIFiles.Sum(xci => xci.PotentialSavingsB);
long actualSavings = AllXCIFiles.Sum(xci => xci.CurrentSavingsB);
long differenceMb = (potentialSavings - actualSavings) / BytesPerMb;
return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmer_MBLabel], differenceMb);
} }
} }

View File

@@ -63,7 +63,7 @@
MinHeight="29" MinHeight="29"
MaxHeight="29" MaxHeight="29"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Watermark="{ext:Locale Search}" Watermark="{ext:Locale Common_Search_SearchWatermark}"
Text="{Binding Search}" /> Text="{Binding Search}" />
</Grid> </Grid>
</Panel> </Panel>

View File

@@ -46,7 +46,7 @@
MinHeight="27" MinHeight="27"
MaxHeight="27" MaxHeight="27"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Watermark="{ext:Locale Search}" Watermark="{ext:Locale Common_Search_SearchWatermark}"
Text="{Binding Search}" /> Text="{Binding Search}" />
</Grid> </Grid>
</Panel> </Panel>

View File

@@ -0,0 +1,185 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Dialog.XCITrimmerView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.Common.Models"
Width="700"
Height="600"
x:DataType="viewModels:XCITrimmerViewModel"
Focusable="True"
mc:Ignorable="d">
<Grid Margin="25,10,25,0" RowDefinitions="Auto,Auto,*,Auto,Auto">
<Panel Margin="0,0,0,10" Grid.Row="0">
<TextBlock Text="{Binding Status}" />
</Panel>
<Grid Margin="0,0,0,10" Grid.Row="1" IsVisible="{Binding !Processing}" ColumnDefinitions="Auto,*">
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
<Button Margin="0,0,10,0" MinWidth="90" Click="ToggleSelect">
<TextBlock Text="{Binding SelectToggleText}" />
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<DropDownButton Width="170" HorizontalAlignment="Right" VerticalAlignment="Center" Content="{Binding SortingFieldName}">
<DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel Margin="0" HorizontalAlignment="Stretch" Orientation="Vertical">
<StackPanel>
<RadioButton Checked="Sort_Checked" Content="{ext:Locale Common_Sort_NameLabel}" GroupName="Sort" IsChecked="{Binding IsSortedByName, Mode=OneTime}" Tag="Name" />
<RadioButton Checked="Sort_Checked" Content="{ext:Locale Common_Sort_SavingsLabel}" GroupName="Sort" IsChecked="{Binding IsSortedBySavings, Mode=OneTime}" Tag="Savings" />
<RadioButton Checked="Sort_Checked" Content="{ext:Locale Common_Sort_TrimStatusLabel}" GroupName="Sort" IsChecked="{Binding IsSortedByStatus, Mode=OneTime}" Tag="Status" />
</StackPanel>
<Border Width="60" Height="2" Margin="5" HorizontalAlignment="Stretch" BorderBrush="White" BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton Checked="Order_Checked" Content="{ext:Locale Common_Sort_OrderAscending}" GroupName="Order" IsChecked="{Binding SortingAscending, Mode=OneTime}" Tag="Ascending" />
<RadioButton Checked="Order_Checked" Content="{ext:Locale Common_Sort_OrderDescending}" GroupName="Order" IsChecked="{Binding !SortingAscending, Mode=OneTime}" Tag="Descending" />
</StackPanel>
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
<TextBox
Width="200"
MaxWidth="200"
Margin="5,0,0,0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Watermark="{ext:Locale Common_Search_SearchWatermark}"
Text="{Binding Search}" />
</StackPanel>
</Grid>
<Border
Grid.Row="2"
Margin="0,0,0,20"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"
CornerRadius="5"
Padding="2.5">
<ListBox
AutoScrollToSelectedItem="{Binding Processing}"
SelectedItem="{Binding NullableProcessingApplication}"
SelectionMode="Multiple, Toggle"
Background="Transparent"
SelectionChanged="OnSelectionChanged"
SelectedItems="{Binding SelectedDisplayedXCIFiles, Mode=OneWay}"
ItemsSource="{Binding DisplayedXCIFiles}"
IsEnabled="{Binding !Processing}">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.Styles>
<ListBox.DataTemplates>
<DataTemplate DataType="models:XCITrimmerFileModel">
<Grid ColumnDefinitions="Auto,*,Auto">
<ui:SymbolIcon
Grid.Column="0"
Margin="10,0,20,0"
Width="15"
Height="15"
FontSize="15"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Symbol="{Binding Converter={x:Static helpers:XCITrimmerFileStatusConverter.Instance}}">
<ToolTip.Tip>
<StackPanel>
<TextBlock
Text="{Binding StatusText}" />
<TextBlock
MaxLines="5"
MaxWidth="200"
TextWrapping="Wrap"
IsVisible="{Binding HasStatusDetail}"
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileStatusDetailConverter.Instance}}" />
</StackPanel>
</ToolTip.Tip>
</ui:SymbolIcon>
<TextBlock
Grid.Column="1"
Margin="0,0,10,0"
VerticalAlignment="Center"
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Name}" />
<Grid
Grid.Column="2"
MinWidth="120"
ColumnDefinitions="*">
<ProgressBar
Height="10"
Margin="10,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="5"
IsVisible="{Binding $parent[UserControl].DataContext.Processing}"
Maximum="100"
Minimum="0"
Value="{Binding PercentageProgress}" />
<TextBlock
Margin="10,0,10,0"
FlowDirection="LeftToRight"
HorizontalAlignment="Right"
VerticalAlignment="Center"
MaxLines="1"
IsVisible="{Binding !$parent[UserControl].DataContext.Processing}"
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileSpaceSavingsConverter.Instance}}" />
</Grid>
</Grid>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox>
</Border>
<StackPanel Grid.Row="3" Margin="0,0,0,20" Spacing="5" FlowDirection="LeftToRight" HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Horizontal">
<TextBlock Classes="h1" HorizontalAlignment="Center" VerticalAlignment="Center" MaxLines="1" Text="{ext:Locale XCITrimmer_SavedLabel}" />
<TextBlock Classes="h1" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Regular" MaxLines="1" Text="{Binding ActualSavings}" />
<TextBlock Classes="h1" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Regular" MaxLines="1" Text="•" />
<TextBlock Classes="h1" HorizontalAlignment="Center" VerticalAlignment="Center" MaxLines="1" Text="{ext:Locale XCITrimmer_RemainingLabel}" />
<TextBlock Classes="h1" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Regular" MaxLines="1" Text="{Binding SavingsDifference}" />
</StackPanel>
<Panel Grid.Row="4" Margin="0,10,0,0" HorizontalAlignment="Stretch">
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Left">
<Button Name="TrimButton" MinWidth="90" Click="Trim" IsEnabled="{Binding CanTrim}">
<TextBlock Text="{ext:Locale XCITrimmer_TrimButton}" />
</Button>
<Button Name="UntrimButton" MinWidth="90" Click="Untrim" IsEnabled="{Binding CanUntrim}">
<TextBlock Text="{ext:Locale XCITrimmer_UntrimButton}" />
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Right">
<Button Name="CancellingButton" MinWidth="90" Click="Cancel" IsEnabled="False">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="Processing" />
<Binding Path="Cancel" />
</MultiBinding>
</Button.IsVisible>
<TextBlock Text="{ext:Locale InputDialogCancelling}" />
</Button>
<Button Name="CancelButton" MinWidth="90" Click="Cancel">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="Processing" />
<Binding Path="!Cancel" />
</MultiBinding>
</Button.IsVisible>
<TextBlock Text="{ext:Locale InputDialogCancel}" />
</Button>
<Button Name="CloseButton" MinWidth="90" Click="Close" IsVisible="{Binding !Processing}">
<TextBlock Text="{ext:Locale SettingsButtonClose}" />
</Button>
</StackPanel>
</Grid>
</Panel>
</Grid>
</UserControl>

View File

@@ -11,13 +11,19 @@ using System.Threading.Tasks;
namespace Ryujinx.Ava.UI.Views.Dialog namespace Ryujinx.Ava.UI.Views.Dialog
{ {
public partial class XciTrimmerView : RyujinxControl<XciTrimmerViewModel> public partial class XCITrimmerView : RyujinxControl<XCITrimmerViewModel>
{ {
public XciTrimmerView() public XCITrimmerView()
{ {
InitializeComponent(); InitializeComponent();
} }
private void ToggleSelect(object sender, RoutedEventArgs e)
{
if (DataContext is XCITrimmerViewModel vm)
vm.ToggleSelect();
}
public static async Task Show() public static async Task Show()
{ {
ContentDialog contentDialog = new() ContentDialog contentDialog = new()
@@ -25,11 +31,11 @@ namespace Ryujinx.Ava.UI.Views.Dialog
PrimaryButtonText = string.Empty, PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty, SecondaryButtonText = string.Empty,
CloseButtonText = string.Empty, CloseButtonText = string.Empty,
Content = new XciTrimmerView Content = new XCITrimmerView
{ {
ViewModel = new XciTrimmerViewModel(RyujinxApp.MainWindow.ViewModel) ViewModel = new XCITrimmerViewModel(RyujinxApp.MainWindow.ViewModel)
}, },
Title = LocaleManager.Instance[LocaleKeys.XCITrimmerWindowTitle] Title = LocaleManager.Instance[LocaleKeys.MenuBar_Actions_XCITrimmerButton]
}; };
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>()); Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
@@ -63,7 +69,7 @@ namespace Ryujinx.Ava.UI.Views.Dialog
public void Sort_Checked(object sender, RoutedEventArgs args) public void Sort_Checked(object sender, RoutedEventArgs args)
{ {
if (sender is RadioButton { Tag: string sortField }) if (sender is RadioButton { Tag: string sortField })
ViewModel.SortingField = Enum.Parse<XciTrimmerViewModel.SortField>(sortField); ViewModel.SortingField = Enum.Parse<XCITrimmerViewModel.SortField>(sortField);
} }
public void Order_Checked(object sender, RoutedEventArgs args) public void Order_Checked(object sender, RoutedEventArgs args)

View File

@@ -1,311 +0,0 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Views.Dialog.XciTrimmerView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:models="clr-namespace:Ryujinx.Ava.Common.Models"
Width="700"
Height="600"
x:DataType="viewModels:XciTrimmerViewModel"
Focusable="True"
mc:Ignorable="d">
<Grid Margin="20 0 20 0" RowDefinitions="Auto,Auto,*,Auto,Auto">
<Panel
Margin="10 10 10 10"
Grid.Row="0">
<TextBlock Text="{Binding Status}" />
</Panel>
<Panel
Margin="0 0 10 10"
IsVisible="{Binding !Processing}"
Grid.Row="1">
<Grid ColumnDefinitions="Auto,*,Auto">
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
<DropDownButton
Width="150"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{Binding SortingFieldName}">
<DropDownButton.Flyout>
<Flyout Placement="Bottom">
<StackPanel
Margin="0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<StackPanel>
<RadioButton
Checked="Sort_Checked"
Content="{ext:Locale XCITrimmerSortName}"
GroupName="Sort"
IsChecked="{Binding IsSortedByName, Mode=OneTime}"
Tag="Name" />
<RadioButton
Checked="Sort_Checked"
Content="{ext:Locale XCITrimmerSortSaved}"
GroupName="Sort"
IsChecked="{Binding IsSortedBySaved, Mode=OneTime}"
Tag="Saved" />
</StackPanel>
<Border
Width="60"
Height="2"
Margin="5"
HorizontalAlignment="Stretch"
BorderBrush="White"
BorderThickness="0,1,0,0">
<Separator Height="0" HorizontalAlignment="Stretch" />
</Border>
<RadioButton
Checked="Order_Checked"
Content="{ext:Locale OrderAscending}"
GroupName="Order"
IsChecked="{Binding SortingAscending, Mode=OneTime}"
Tag="Ascending" />
<RadioButton
Checked="Order_Checked"
Content="{ext:Locale OrderDescending}"
GroupName="Order"
IsChecked="{Binding !SortingAscending, Mode=OneTime}"
Tag="Descending" />
</StackPanel>
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
</StackPanel>
<TextBox
Grid.Column="1"
MinHeight="29"
MaxHeight="29"
Margin="5 0 5 0"
HorizontalAlignment="Stretch"
Watermark="{ext:Locale Search}"
Text="{Binding Search}" />
<StackPanel
Grid.Column="2"
Orientation="Horizontal">
<Button
Name="SelectDisplayedButton"
MinWidth="90"
Margin="5"
Command="{Binding SelectDisplayed}">
<TextBlock Text="{ext:Locale XCITrimmerSelectDisplayed}" />
</Button>
<Button
Name="DeselectDisplayedButton"
MinWidth="90"
Margin="5"
Command="{Binding DeselectDisplayed}">
<TextBlock Text="{ext:Locale XCITrimmerDeselectDisplayed}" />
</Button>
</StackPanel>
</Grid>
</Panel>
<Border
Grid.Row="2"
Margin="0 0 0 10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"
CornerRadius="5"
Padding="2.5">
<ListBox
AutoScrollToSelectedItem="{Binding Processing}"
SelectedItem="{Binding NullableProcessingApplication}"
SelectionMode="Multiple, Toggle"
Background="Transparent"
SelectionChanged="OnSelectionChanged"
SelectedItems="{Binding SelectedDisplayedXCIFiles, Mode=OneWay}"
ItemsSource="{Binding DisplayedXCIFiles}"
IsEnabled="{Binding !Processing}">
<ListBox.DataTemplates>
<DataTemplate
DataType="models:XCITrimmerFileModel">
<Panel Margin="10">
<Grid ColumnDefinitions="65*,35*">
<TextBlock
Grid.Column="0"
Margin="10 0 10 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="2"
TextWrapping="Wrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Name}">
</TextBlock>
<Grid Grid.Column="1" ColumnDefinitions="45*,55*">
<ProgressBar
Height="10"
Margin="10 0 10 0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="5"
IsVisible="{Binding $parent[UserControl].((viewModels:XciTrimmerViewModel)DataContext).Processing}"
Maximum="100"
Minimum="0"
Value="{Binding PercentageProgress}" />
<TextBlock
Grid.Column="0"
Margin="10 0 10 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="1"
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileStatusConverter.Instance}}">
<ToolTip.Tip>
<StackPanel
IsVisible="{Binding IsFailed}">
<TextBlock
Classes="h1"
Text="{ext:Locale XCITrimmerTitleStatusFailed}" />
<TextBlock
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileStatusDetailConverter.Instance}}"
MaxLines="5"
MaxWidth="200"
MaxHeight="100"
TextTrimming="None"
TextWrapping="Wrap"/>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
<TextBlock
Grid.Column="1"
Margin="10 0 10 0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="1"
Text="{Binding ., Converter={x:Static helpers:XCITrimmerFileSpaceSavingsConverter.Instance}}">>
</TextBlock>
</Grid>
</Grid>
</Panel>
</DataTemplate>
</ListBox.DataTemplates>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
</Style>
</ListBox.Styles>
</ListBox>
</Border>
<Border
Grid.Row="3"
Margin="0 0 0 10"
HorizontalAlignment="Stretch"
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
BorderThickness="1"
CornerRadius="5"
Padding="2.5">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<TextBlock
Grid.Column="0"
Grid.Row="0"
Classes="h1"
Margin="5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
MaxLines="1"
Text="{ext:Locale XCITrimmerPotentialSavings}" />
<TextBlock
Grid.Column="0"
Grid.Row="1"
Classes="h1"
Margin="5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
MaxLines="1"
Text="{ext:Locale XCITrimmerActualSavings}" />
<TextBlock
Grid.Column="1"
Grid.Row="0"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="1"
Text="{Binding PotentialSavings}" />
<TextBlock
Grid.Column="1"
Grid.Row="1"
Margin="5"
HorizontalAlignment="Left"
VerticalAlignment="Center"
MaxLines="1"
Text="{Binding ActualSavings}" />
</Grid>
</Border>
<Panel
Grid.Row="4"
HorizontalAlignment="Stretch">
<Grid ColumnDefinitions="*,Auto">
<StackPanel
Grid.Column="0"
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Left">
<Button
Name="TrimButton"
MinWidth="90"
Margin="5"
Click="Trim"
IsEnabled="{Binding CanTrim}">
<TextBlock Text="{ext:Locale XCITrimmerTrim}" />
</Button>
<Button
Name="UntrimButton"
MinWidth="90"
Margin="5"
Click="Untrim"
IsEnabled="{Binding CanUntrim}">
<TextBlock Text="{ext:Locale XCITrimmerUntrim}" />
</Button>
</StackPanel>
<StackPanel
Grid.Column="1"
Orientation="Horizontal"
Spacing="10"
HorizontalAlignment="Right">
<Button
Name="CancellingButton"
MinWidth="90"
Margin="5"
Click="Cancel"
IsEnabled="False">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="Processing" />
<Binding Path="Cancel" />
</MultiBinding>
</Button.IsVisible>
<TextBlock Text="{ext:Locale InputDialogCancelling}" />
</Button>
<Button
Name="CancelButton"
MinWidth="90"
Margin="5"
Click="Cancel">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="Processing" />
<Binding Path="!Cancel" />
</MultiBinding>
</Button.IsVisible>
<TextBlock Text="{ext:Locale InputDialogCancel}" />
</Button>
<Button
Name="CloseButton"
MinWidth="90"
Margin="5"
Click="Close"
IsVisible="{Binding !Processing}">
<TextBlock Text="{ext:Locale InputDialogClose}" />
</Button>
</StackPanel>
</Grid>
</Panel>
</Grid>
</UserControl>

View File

@@ -163,7 +163,7 @@
IsEnabled="{Binding HasSkylander}" /> IsEnabled="{Binding HasSkylander}" />
<MenuItem <MenuItem
Command="{Binding SimulateWakeUpMessage}" Command="{Binding SimulateWakeUpMessage}"
Header="{ext:Locale MenuBarOptionsSimulateWakeUpMessage}" Header="{ext:Locale MenuBar_Actions_SimulateWakeUpMessageButton}"
Icon="{ext:Icon fa-solid fa-sun}" Icon="{ext:Icon fa-solid fa-sun}"
InputGesture="Ctrl + M" /> InputGesture="Ctrl + M" />
<Separator /> <Separator />
@@ -216,7 +216,7 @@
<MenuItem Header="{ext:Locale MenuBar_Actions_ToolsLabel}" Icon="{ext:Icon fa-solid fa-toolbox}"> <MenuItem Header="{ext:Locale MenuBar_Actions_ToolsLabel}" Icon="{ext:Icon fa-solid fa-toolbox}">
<MenuItem <MenuItem
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" /> Name="MiiAppletMenuItem" Header="{ext:Locale MenuBar_Actions_MiiEditorButton}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" />
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBar_Actions_XCITrimmerButton}" Icon="{ext:Icon fa-solid fa-scissors}" /> <MenuItem Name="XCITrimmerMenuItem" Header="{ext:Locale MenuBar_Actions_XCITrimmerButton}" Icon="{ext:Icon fa-solid fa-scissors}" />
</MenuItem> </MenuItem>
</MenuItem> </MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}"> <MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">

View File

@@ -43,7 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Main
ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume()); ResumeEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Resume());
StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted()); StopEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.ShowExitPrompt().OrCompleted());
RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation()); RestartEmulationMenuItem.Command = Commands.Create(() => ViewModel.RestartEmulation());
XciTrimmerMenuItem.Command = Commands.Create(XciTrimmerView.Show); XCITrimmerMenuItem.Command = Commands.Create(XCITrimmerView.Show);
AboutWindowMenuItem.Command = Commands.Create(AboutView.Show); AboutWindowMenuItem.Command = Commands.Create(AboutView.Show);
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityListWindow.Show()); CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityListWindow.Show());
LdnGameListMenuItem.Command = Commands.Create(() => LdnGamesListWindow.Show()); LdnGameListMenuItem.Command = Commands.Create(() => LdnGamesListWindow.Show());

View File

@@ -50,33 +50,37 @@
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}" IsVisible="{Binding EnableNonGameRunningControls}"
Text="{ext:Locale StatusBarGamesLoaded}" /> Text="{ext:Locale StatusBarGamesLoaded}" />
<controls:MiniVerticalSeparator Grid.Column="2" IsVisible="{Binding ShowTotalTimePlayed}" />
<TextBlock <TextBlock
Name="StatusBarProgressStatus"
Grid.Column="2"
MinWidth="200"
Margin="10,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding StatusBarProgressStatusVisible}"
Text="{Binding StatusBarProgressStatusText}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="3" Grid.Column="3"
MinWidth="200"
Height="6"
VerticalAlignment="Center"
Margin="0, 0, 5, 0"
Foreground="{DynamicResource SystemAccentColorLight2}"
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
<controls:MiniVerticalSeparator Grid.Column="4" IsVisible="{Binding ShowTotalTimePlayed}" />
<TextBlock
Grid.Column="5"
Margin="5,0,5,0" Margin="5,0,5,0"
VerticalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding ShowTotalTimePlayed}" IsVisible="{Binding ShowTotalTimePlayed}"
Text="{ext:Locale GameListLabelTotalTimePlayed}"> Text="{ext:Locale GameListLabelTotalTimePlayed}">
</TextBlock> </TextBlock>
<controls:MiniVerticalSeparator Grid.Column="4" IsVisible="{Binding StatusBarVisible}" />
<controls:MiniVerticalSeparator Grid.Column="4" IsVisible="{Binding StatusBarProgressStatusVisible}" />
<TextBlock
Name="StatusBarProgressStatus"
Grid.Column="5"
Margin="5,0,0,0"
VerticalAlignment="Center"
IsVisible="{Binding StatusBarProgressStatusVisible}"
Text="{Binding StatusBarProgressStatusText}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
MaxWidth="1000" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="6"
MinWidth="100"
Height="6"
VerticalAlignment="Center"
Margin="5,0,5,0"
Foreground="{DynamicResource SystemAccentColorLight2}"
IsVisible="{Binding StatusBarVisible}"
Maximum="{Binding StatusBarProgressMaximum}"
Value="{Binding StatusBarProgressValue}" />
</Grid> </Grid>
</StackPanel> </StackPanel>
<StackPanel <StackPanel
@@ -329,7 +333,7 @@
Margin="5, 0, 0, 0" Margin="5, 0, 0, 0"
HorizontalAlignment="Right" HorizontalAlignment="Right"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{ext:Locale StatusBar_FirmwareVersion}" /> Text="{ext:Locale StatusBar_FirmwareVersionLabel}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -95,7 +95,7 @@
Tag="Favorite" /> Tag="Favorite" />
<RadioButton <RadioButton
Checked="Sort_Checked" Checked="Sort_Checked"
Content="{ext:Locale GameListHeaderApplication}" Content="{ext:Locale Common_Sort_NameLabel}"
GroupName="Sort" GroupName="Sort"
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}" IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
Tag="Title" /> Tag="Title" />
@@ -153,13 +153,13 @@
</Border> </Border>
<RadioButton <RadioButton
Checked="Order_Checked" Checked="Order_Checked"
Content="{ext:Locale OrderAscending}" Content="{ext:Locale Common_Sort_OrderAscending}"
GroupName="Order" GroupName="Order"
IsChecked="{Binding IsAscending, Mode=OneTime}" IsChecked="{Binding IsAscending, Mode=OneTime}"
Tag="Ascending" /> Tag="Ascending" />
<RadioButton <RadioButton
Checked="Order_Checked" Checked="Order_Checked"
Content="{ext:Locale OrderDescending}" Content="{ext:Locale Common_Sort_OrderDescending}"
GroupName="Order" GroupName="Order"
IsChecked="{Binding !IsAscending, Mode=OneTime}" IsChecked="{Binding !IsAscending, Mode=OneTime}"
Tag="Descending" /> Tag="Descending" />

View File

@@ -33,7 +33,7 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
MinWidth="100"> MinWidth="100">
<ComboBoxItem <ComboBoxItem
Content="{ext:Locale Name}" /> Content="{ext:Locale Common_Sort_NameLabel}" />
<ComboBoxItem <ComboBoxItem
Content="{ext:Locale Size}" /> Content="{ext:Locale Size}" />
<ComboBox.Styles> <ComboBox.Styles>
@@ -46,9 +46,9 @@
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left"
MinWidth="150"> MinWidth="150">
<ComboBoxItem <ComboBoxItem
Content="{ext:Locale OrderAscending}" /> Content="{ext:Locale Common_Sort_OrderAscending}" />
<ComboBoxItem <ComboBoxItem
Content="{ext:Locale OrderDescending}" /> Content="{ext:Locale Common_Sort_OrderDescending}" />
<ComboBox.Styles> <ComboBox.Styles>
<Style Selector="ContentControl#ContentPresenter"> <Style Selector="ContentControl#ContentPresenter">
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
@@ -60,7 +60,7 @@
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="10,0, 0, 0" ColumnDefinitions="Auto,*"> Margin="10,0, 0, 0" ColumnDefinitions="Auto,*">
<TextBlock Text="{ext:Locale Search}" VerticalAlignment="Center" /> <TextBlock Text="{ext:Locale Common_Search_SearchWatermark}" VerticalAlignment="Center" />
<TextBox <TextBox
Margin="10,0,0,0" Margin="10,0,0,0"
Grid.Column="1" Grid.Column="1"