Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
365ae2d0cb | ||
|
|
5b6e09b926 | ||
|
|
7072647611 | ||
|
|
30cf4826f8 | ||
|
|
ca47fc1f89 | ||
|
|
16a2c84edd | ||
|
|
df5e27dd06 | ||
|
|
d95226f8c0 | ||
|
|
164fec5b73 | ||
|
|
8dad96f39f | ||
|
|
2244e73c53 | ||
|
|
456cb02d2a | ||
|
|
f310704472 | ||
|
|
96e5a7081b | ||
|
|
1c93e18822 | ||
|
|
ac152454f0 | ||
|
|
7851f7f400 | ||
|
|
2b561dd438 | ||
|
|
3545f557fc | ||
|
|
8dfb9b9ba6 | ||
|
|
7cf36cd25f | ||
|
|
c53692022b | ||
|
|
0f3b7da0b2 | ||
|
|
e22daefb08 |
@@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"[Applet Mode]": "[Applet-Modus]",
|
"[Applet Mode]": " | Applet Modus |",
|
||||||
"No Internet": "Keine Internetverbindung",
|
"No Internet": "Kein Internet",
|
||||||
"Files": "Dateien",
|
"Files": "Dateien",
|
||||||
"Apps": "Apps",
|
"Apps": "hb-Apps",
|
||||||
"Store": "Store",
|
"Store": "hb-Store",
|
||||||
"Menu": "Menü",
|
"Menu": "Menü",
|
||||||
"Options": "Optionen",
|
"Options": "Optionen",
|
||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Back": "Zurück",
|
"Back": "Zurück",
|
||||||
"Select": "Auswählen",
|
"Select": "Auswählen",
|
||||||
"Open": "Öffnen",
|
"Open": "Öffne",
|
||||||
"Launch": "Starten",
|
"Launch": "Starte",
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"Install": "Installieren",
|
"Install": "Installieren",
|
||||||
"Delete": "Löschen",
|
"Delete": "Löschen",
|
||||||
"Restart": "Neustart",
|
"Restart": "Neustart",
|
||||||
"Changelog": "Changelog",
|
"Changelog": "Neuerungen",
|
||||||
"Details": "Details",
|
"Details": "Details",
|
||||||
"Update": "Update",
|
"Update": "Update",
|
||||||
"Remove": "Entfernen",
|
"Remove": "Entfernen",
|
||||||
@@ -23,62 +23,62 @@
|
|||||||
"Download": "Download",
|
"Download": "Download",
|
||||||
"Next Page": "Nächste Seite",
|
"Next Page": "Nächste Seite",
|
||||||
"Prev Page": "Vorherige Seite",
|
"Prev Page": "Vorherige Seite",
|
||||||
"Unstar": "Favorit entfernen",
|
"Unstar": "Kein Favorit",
|
||||||
"Star": "Favorit",
|
"Star": "Favorit",
|
||||||
"System memory": "System-Speicher",
|
"System memory": "NAND Systemspeicher",
|
||||||
"microSD card": "microSD-Karte",
|
"microSD card": "SD-Karte",
|
||||||
"Sd": "SD",
|
"Sd": "SD-Karte | Root-Verzeichnis",
|
||||||
"Image System memory": "System-Speicher Bild",
|
"Image System memory": "Album | NAND Systemspeicher",
|
||||||
"Image microSD card": "microSD-Karten Bild",
|
"Image microSD card": "Album | SD-Karte",
|
||||||
"Slow": "Langsam",
|
"Slow": "Niedrig",
|
||||||
"Normal": "Normal",
|
"Normal": "Mittel",
|
||||||
"Fast": "Schnell",
|
"Fast": "Hoch",
|
||||||
"Yes": "Ja",
|
"Yes": "Ja",
|
||||||
"No": "Nein",
|
"No": "Nein",
|
||||||
"Enabled": "Aktiviert",
|
"Enabled": "An",
|
||||||
"Disabled": "Deaktiviert",
|
"Disabled": "Aus",
|
||||||
|
|
||||||
"Sort By": "Sortieren nach",
|
"Sort By": "Sortierung",
|
||||||
"Sort Options": "Sortieroptionen",
|
"Sort Options": " Sortierung | Optionen",
|
||||||
"Filter": "Filter",
|
"Filter": "Rubrik",
|
||||||
"Sort": "Sortieren",
|
"Sort": "Sortiert nach",
|
||||||
"Order": "Reihenfolge",
|
"Order": "Anordnung",
|
||||||
"Search": "Suchen",
|
"Search": "Suchen",
|
||||||
"Updated": "Aktualisiert",
|
"Updated": "zuletzt aktualisiert",
|
||||||
"Updated (Star)": "Aktualisiert (Favoriten)",
|
"Updated (Star)": "Favorit | zuletzt aktualisiert",
|
||||||
"Downloads": "Downloads",
|
"Downloads": "Downloads",
|
||||||
"Size": "Größe",
|
"Size": "Größe",
|
||||||
"Size (Star)": "Größe (Favoriten)",
|
"Size (Star)": "Favorit | Größe",
|
||||||
"Alphabetical": "Alphabetisch",
|
"Alphabetical": "Name",
|
||||||
"Alphabetical (Star)": "Alphabetisch (Favoriten)",
|
"Alphabetical (Star)": "Favorit | Name",
|
||||||
"Likes": "Likes",
|
"Likes": "Beliebtheit",
|
||||||
"ID": "ID",
|
"ID": "Theme | Paket ID",
|
||||||
"Descending": "Absteigend",
|
"Descending": "Absteigend ↓",
|
||||||
"Descending (down)": "Absteigend",
|
"Descending (down)": "Absteigend ↓",
|
||||||
"Desc": "Abst.",
|
"Desc": " ↓",
|
||||||
"Ascending": "Aufsteigend",
|
"Ascending": "Aufsteigend ↑",
|
||||||
"Ascending (Up)": "Aufsteigend",
|
"Ascending (Up)": "Aufsteigend ↑",
|
||||||
"Asc": "Aufst.",
|
"Asc": " ↑",
|
||||||
|
|
||||||
"Menu Options": "Menü-Optionen",
|
"Menu Options": " Menü | Optionen",
|
||||||
"Theme": "Theme",
|
"Theme": "Themes",
|
||||||
"Theme Options": "Theme-Optionen",
|
"Theme Options": " Themes | Optionen",
|
||||||
"Select Theme": "Theme auswählen",
|
"Select Theme": "Theme wählen",
|
||||||
"Shuffle": "Zufällig",
|
"Shuffle": "Zufällig",
|
||||||
"Music": "Musik",
|
"Music": "Musik",
|
||||||
"12 Hour Time": "",
|
"12 Hour Time": "12-Std Zeitformat",
|
||||||
"Network": "Netzwerk",
|
"Network": "Konnektivität",
|
||||||
"Network Options": "Netzwerk-Optionen",
|
"Network Options": "Konnektivität | Optionen",
|
||||||
"Ftp": "FTP",
|
"Ftp": "FTP",
|
||||||
"Mtp": "MTP",
|
"Mtp": "MTP",
|
||||||
"Nxlink": "Nxlink",
|
"Nxlink": "NXLink",
|
||||||
"Nxlink Connected": "Nxlink verbunden",
|
"Nxlink Connected": "NXLink | Verbunden",
|
||||||
"Nxlink Upload": "Nxlink Upload",
|
"Nxlink Upload": "NXLink | wird hochgeladen...",
|
||||||
"Nxlink Finished": "Nxlink abgeschlossen",
|
"Nxlink Finished": "NXLink | Hochladen beendet",
|
||||||
"Switch-Handheld!": "Switch-Handheld!",
|
"Switch-Handheld!": "Handheld!",
|
||||||
"Switch-Docked!": "Switch-Dock-Modus!",
|
"Switch-Docked!": "Angedockt!",
|
||||||
"Language": "Sprache",
|
"Language": "Sprache",
|
||||||
"Auto": "Auto",
|
"Auto": "Systemsprache",
|
||||||
"English": "English",
|
"English": "English",
|
||||||
"Japanese": "日本語",
|
"Japanese": "日本語",
|
||||||
"French": "Français",
|
"French": "Français",
|
||||||
@@ -87,55 +87,55 @@
|
|||||||
"Spanish": "Español",
|
"Spanish": "Español",
|
||||||
"Chinese": "中文",
|
"Chinese": "中文",
|
||||||
"Korean": "한국어",
|
"Korean": "한국어",
|
||||||
"Dutch": "Dutch",
|
"Dutch": "Nederlands",
|
||||||
"Portuguese": "Português",
|
"Portuguese": "Português",
|
||||||
"Russian": "Русский",
|
"Russian": "Русский",
|
||||||
"Swedish": "Svenska",
|
"Swedish": "Svenska",
|
||||||
"Vietnamese": "Vietnamese",
|
"Vietnamese": "tiếng Việt",
|
||||||
"Logging": "Logging",
|
"Logging": "Protokollieren",
|
||||||
"Replace hbmenu on exit": "hbmenu beim Beenden ersetzen",
|
"Replace hbmenu on exit": "hbmenu durch sphaira ersetzen",
|
||||||
"Misc": "Sonstiges",
|
"Misc": "Extras",
|
||||||
"Misc Options": "Weitere Optionen",
|
"Misc Options": " Extras | Optionen",
|
||||||
"Web": "Web",
|
"Web": "WEB Browser",
|
||||||
"Install forwarders": "Forwarder installieren",
|
"Install forwarders": "Forwarder installieren",
|
||||||
"Install location": "Installationsort",
|
"Install location": "Einhängepunkt",
|
||||||
"Show install warning": "Installationswarnung anzeigen",
|
"Show install warning": "Warnungen anzeigen",
|
||||||
"Text scroll speed": "Textlaufgeschwindigkeit",
|
"Text scroll speed": "Laufschrift Tempo",
|
||||||
|
|
||||||
"FileBrowser": "Datei-Browser",
|
"FileBrowser": "Datei-Manager",
|
||||||
"%zd files": "%zd Dateien",
|
"%zd files": "%zd Dateien",
|
||||||
"%zd dirs": "%zd Ordner",
|
"%zd dirs": "%zd Ordner",
|
||||||
"File Options": "Datei-Optionen",
|
"File Options": "Datei - Ordner | Optionen",
|
||||||
"Show Hidden": "Versteckte anzeigen",
|
"Show Hidden": "Versteckte zeigen",
|
||||||
"Folders First": "Ordner zuerst",
|
"Folders First": "Ordner zuerst",
|
||||||
"Hidden Last": "Versteckte zuletzt",
|
"Hidden Last": "Versteckte zuletzt",
|
||||||
"Cut": "Ausschneiden",
|
"Cut": "Ausschneiden",
|
||||||
"Copy": "Kopieren",
|
"Copy": "Kopieren",
|
||||||
"Paste": "Einfügen",
|
"Paste": "Einfügen",
|
||||||
"Paste ": "Einfügen ",
|
"Paste ": "Einfügen von: ",
|
||||||
" file(s)?": " Datei(en)?",
|
" file(s)?": " Datei/en?",
|
||||||
"Rename": "Umbenennen",
|
"Rename": "Umbenennen",
|
||||||
"Set New File Name": "Neuen Dateinamen eingeben",
|
"Set New File Name": "Neuen Dateinamen festlegen",
|
||||||
"Advanced": "Erweitert",
|
"Advanced": "Erweitert...",
|
||||||
"Advanced Options": "Erweiterte Optionen",
|
"Advanced Options": " Erweitert | Optionen",
|
||||||
"Create File": "Datei erstellen",
|
"Create File": "Neue Datei",
|
||||||
"Set File Name": "Dateinamen eingeben",
|
"Set File Name": "Dateiname festlegen",
|
||||||
"Create Folder": "Ordner erstellen",
|
"Create Folder": "Neuer Ordner",
|
||||||
"Set Folder Name": "Ordnernamen eingeben",
|
"Set Folder Name": "Ordner umbenennen",
|
||||||
"View as text (unfinished)": "Als Text anzeigen (Beta)",
|
"View as text (unfinished)": "Als Text anzeigen",
|
||||||
"Ignore read only": "Schreibschutz ignorieren",
|
"Ignore read only": "Schreibschutz umgehen?",
|
||||||
"Mount": "Einbinden",
|
"Mount": "Einhängen",
|
||||||
"Empty...": "Leer...",
|
"Empty...": "Keine Daten...",
|
||||||
"Open with DayBreak?": "Mit DayBreak öffnen?",
|
"Open with DayBreak?": "Mit Daybreak öffnen?",
|
||||||
"Launch ": "Starten ",
|
"Launch ": "Starte ",
|
||||||
"Launch option for: ": "Startoption für: ",
|
"Launch option for: ": "Start Option für: ",
|
||||||
"Select launcher for: ": "Launcher auswählen für: ",
|
"Select launcher for: ": "Wähle Launcher für: ",
|
||||||
|
|
||||||
"Homebrew": "Homebrew",
|
"Homebrew": "hbmenu",
|
||||||
"Homebrew Options": "Homebrew-Optionen",
|
"Homebrew Options": " hbmenu | Optionen",
|
||||||
"Hide Sphaira": "Sphaira ausblenden",
|
"Hide Sphaira": "Verstecke sphaira",
|
||||||
"Install Forwarder": "Forwarder installieren",
|
"Install Forwarder": "Forwarder installieren",
|
||||||
"WARNING: Installing forwarders will lead to a ban!": "WARNUNG: Installation von Forwardern führt zum Ban!",
|
"WARNING: Installing forwarders will lead to a ban!": "Installiere Forwarder-NSP´s mit VORSICHT.\nEs erhöht das Risiko eines Konsolen-Banns!",
|
||||||
"Installing Forwarder": "Installiere Forwarder",
|
"Installing Forwarder": "Installiere Forwarder",
|
||||||
"Creating Program": "Erstelle Programm",
|
"Creating Program": "Erstelle Programm",
|
||||||
"Creating Control": "Erstelle Control",
|
"Creating Control": "Erstelle Control",
|
||||||
@@ -144,26 +144,26 @@
|
|||||||
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
|
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
|
||||||
"Pushing application record": "Übertrage Anwendungsdaten",
|
"Pushing application record": "Übertrage Anwendungsdaten",
|
||||||
"Installed!": "Installiert!",
|
"Installed!": "Installiert!",
|
||||||
"Failed to install forwarder": "Forwarder-Installation fehlgeschlagen",
|
"Failed to install forwarder": "Fehler beim installieren des Forwarders",
|
||||||
"Unstarred ": "Favorit entfernt ",
|
"Unstarred ": "Favorit entfernt ",
|
||||||
"Starred ": "Favorit hinzugefügt ",
|
"Starred ": "Favorit ",
|
||||||
|
|
||||||
"AppStore": "AppStore",
|
"AppStore": "hb-AppStore",
|
||||||
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortierung: %s | Reihenfolge: %s",
|
"Filter: %s | Sort: %s | Order: %s": "Rubrik: %s | Sort.nach.: %s | Ordnung: %s",
|
||||||
"AppStore Options": "AppStore-Optionen",
|
"AppStore Options": " hb-AppStore | Optionen",
|
||||||
"All": "Alle",
|
"All": "Alles anzeigen",
|
||||||
"Games": "Spiele",
|
"Games": "Spiele",
|
||||||
"Emulators": "Emulatoren",
|
"Emulators": "Emulatoren",
|
||||||
"Tools": "Tools",
|
"Tools": "Tools",
|
||||||
"Themes": "Themes",
|
"Themes": "Themes",
|
||||||
"Legacy": "Legacy",
|
"Legacy": "Älteres",
|
||||||
"version: %s": "Version: %s",
|
"version: %s": "Version: %s",
|
||||||
"updated: %s": "Aktualisiert: %s",
|
"updated: %s": "Letztes Update am: %s",
|
||||||
"category: %s": "Kategorie: %s",
|
"category: %s": "Rubrik: %s",
|
||||||
"extracted: %.2f MiB": "Entpackt: %.2f MiB",
|
"extracted: %.2f MiB": "Größe: %.2f MiB",
|
||||||
"app_dls: %s": "Downloads: %s",
|
"app_dls: %s": "Anzahl Downloads: %s",
|
||||||
"More by Author": "Mehr vom Entwickler",
|
"More by Author": "Weitere Apps des Entwicklers",
|
||||||
"Leave Feedback": "Feedback geben",
|
"Leave Feedback": "Feedback hinterlassen",
|
||||||
|
|
||||||
"Irs": "IR-Sensor",
|
"Irs": "IR-Sensor",
|
||||||
"Ambient Noise Level: ": "Umgebungsrauschen: ",
|
"Ambient Noise Level: ": "Umgebungsrauschen: ",
|
||||||
@@ -203,55 +203,55 @@
|
|||||||
"External Light Filter": "Externes Lichtfilter",
|
"External Light Filter": "Externes Lichtfilter",
|
||||||
"Load Default": "Standard laden",
|
"Load Default": "Standard laden",
|
||||||
|
|
||||||
"Themezer": "Themezer",
|
"Themezer": "Themezer | NX Themes",
|
||||||
"Themezer Options": "Themezer-Optionen",
|
"Themezer Options": " Themezer | Optionen",
|
||||||
"Nsfw": "NSFW",
|
"Nsfw": "NSFW",
|
||||||
"Page": "Seite",
|
"Page": "Seiten Nr. wählen ",
|
||||||
"Page %zu / %zu": "Seite %zu / %zu",
|
"Page %zu / %zu": " %zu / %zu",
|
||||||
"Enter Page Number": "Seitenzahl eingeben",
|
"Enter Page Number": "Zu Seite Nr.: ___",
|
||||||
"Bad Page": "Ungültige Seite",
|
"Bad Page": "Seite nicht gefunden",
|
||||||
"Download theme?": "Theme herunterladen?",
|
"Download theme?": "Theme herunterladen?",
|
||||||
|
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
"Downloading json": "Lade JSON herunter",
|
"Downloading json": "Lade JSON-File",
|
||||||
"Select asset to download for ": "Wähle Asset zum Download für ",
|
"Select asset to download for ": "Wähle Asset für den Download von ",
|
||||||
|
|
||||||
"Installing ": "Installiere ",
|
"Installing ": "Installiert wird: ",
|
||||||
"Uninstalling ": "Deinstalliere ",
|
"Uninstalling ": "Deinstalliert wird: ",
|
||||||
"Deleting ": "Lösche ",
|
"Deleting ": "Gelöscht wird: ",
|
||||||
"Deleting": "Lösche",
|
"Deleting": "Gelöscht wurde:",
|
||||||
"Pasting ": "Füge ein ",
|
"Pasting ": "Eingefügt wird: ",
|
||||||
"Pasting": "Füge ein",
|
"Pasting": "Eingefügt wurde:",
|
||||||
"Removing ": "Entferne ",
|
"Removing ": "Entfernt wird: ",
|
||||||
"Scanning ": "Scanne ",
|
"Scanning ": "Gescannt wird: ",
|
||||||
"Creating ": "Erstelle ",
|
"Creating ": "Erstellt wird: ",
|
||||||
"Copying ": "Kopiere ",
|
"Copying ": "Kopiert wird: ",
|
||||||
"Trying to load ": "Lade ",
|
"Trying to load ": "Versucht zu laden wird: ",
|
||||||
"Downloading ": "Lade herunter ",
|
"Downloading ": "Heruntergeladen wird: ",
|
||||||
"Downloaded ": "Heruntergeladen ",
|
"Downloaded ": "Heruntergeladen wurde: ",
|
||||||
"Removed ": "Entfernt ",
|
"Removed ": "Entfernt wurde: ",
|
||||||
"Checking MD5": "Prüfe MD5",
|
"Checking MD5": "Checke MD5 Prüfsumme",
|
||||||
"Loading...": "Lade...",
|
"Loading...": "Wird geladen...",
|
||||||
"Loading": "Lade",
|
"Loading": "Wird geladen",
|
||||||
"Empty!": "Leer!",
|
"Empty!": "Keine Daten!",
|
||||||
"Not Ready...": "Nicht bereit...",
|
"Not Ready...": "Nicht bereit...",
|
||||||
"Error loading page!": "Fehler beim Laden!",
|
"Error loading page!": "Ladefehler!",
|
||||||
"Update avaliable: ": "Update verfügbar: ",
|
"Update avaliable: ": "Update verfügbar: ",
|
||||||
"Download update: ": "Update herunterladen: ",
|
"Download update: ": " Herunterladen des Updates: ",
|
||||||
"Updated to ": "Aktualisiert auf ",
|
"Updated to ": "Aktualisiert auf: ",
|
||||||
"Press OK to restart Sphaira": "OK drücken um Sphaira neuzustarten",
|
"Press OK to restart Sphaira": "Drücke OK um sphaira erneut zustarten",
|
||||||
"Restart Sphaira?": "Sphaira neustarten?",
|
"Restart Sphaira?": "sphaira erneut starten?",
|
||||||
"Failed to download update": "Update-Download fehlgeschlagen",
|
"Failed to download update": "Herunterladen des Updates fehlgeschlagen!",
|
||||||
"Restore hbmenu?": "hbmenu wiederherstellen?",
|
"Restore hbmenu?": "hbmenu wiederherstellen?",
|
||||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Konnte /switch/hbmenu.nro nicht finden\nBitte hbmenu über den AppStore neu installieren",
|
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Die Datei /switch/hbmenu.nro fehlt.\nInstalliere hbmenu über den hb-AppStore.",
|
||||||
"Failed to restore hbmenu, please re-download hbmenu": "Wiederherstellung fehlgeschlagen, bitte hbmenu neu herunterladen",
|
"Failed to restore hbmenu, please re-download hbmenu": "Fehler, hbmenu nicht wiederhergrstellt!\nInstalliere hbmenu über den hb-AppStore.",
|
||||||
"Failed to restore hbmenu, using sphaira instead": "Wiederherstellung fehlgeschlagen, verwende stattdessen Sphaira",
|
"Failed to restore hbmenu, using sphaira instead": "Fehler, hbmenu nicht wiederhergrstellt!\nVerwende weiter sphaira",
|
||||||
"Restored hbmenu, closing sphaira": "hbmenu wiederhergestellt, Sphaira wird beendet",
|
"Restored hbmenu, closing sphaira": "hbmenu wurde wiederhergestellt, schließe sphaira",
|
||||||
"Restored hbmenu": "hbmenu wiederhergestellt",
|
"Restored hbmenu": "hbmenu wurde wiederhergestellt",
|
||||||
"Delete Selected files?": "Ausgewählte Dateien löschen?",
|
"Delete Selected files?": "Ausgewähle Dateien löschen?",
|
||||||
"Completely remove ": "Vollständig entfernen ",
|
"Completely remove ": "Komplett gelöscht wird: ",
|
||||||
"Are you sure you want to delete ": "Wirklich löschen ",
|
"Are you sure you want to delete ": "Bist du sicher zu löschen? Bestätige Löschung von: ",
|
||||||
"Are you sure you wish to cancel?": "Wirklich abbrechen?",
|
"Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?",
|
||||||
"Audio disabled due to suspended game": "",
|
"Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch",
|
||||||
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen."
|
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
set(sphaira_VERSION 0.8.2)
|
set(sphaira_VERSION 0.9.1)
|
||||||
|
|
||||||
project(sphaira
|
project(sphaira
|
||||||
VERSION ${sphaira_VERSION}
|
VERSION ${sphaira_VERSION}
|
||||||
@@ -49,6 +49,7 @@ add_executable(sphaira
|
|||||||
source/ui/menus/usb_menu.cpp
|
source/ui/menus/usb_menu.cpp
|
||||||
source/ui/menus/ftp_menu.cpp
|
source/ui/menus/ftp_menu.cpp
|
||||||
source/ui/menus/gc_menu.cpp
|
source/ui/menus/gc_menu.cpp
|
||||||
|
source/ui/menus/game_menu.cpp
|
||||||
|
|
||||||
source/ui/error_box.cpp
|
source/ui/error_box.cpp
|
||||||
source/ui/notification.cpp
|
source/ui/notification.cpp
|
||||||
@@ -61,6 +62,7 @@ add_executable(sphaira
|
|||||||
source/ui/widget.cpp
|
source/ui/widget.cpp
|
||||||
source/ui/list.cpp
|
source/ui/list.cpp
|
||||||
source/ui/bubbles.cpp
|
source/ui/bubbles.cpp
|
||||||
|
source/ui/scrolling_text.cpp
|
||||||
|
|
||||||
source/app.cpp
|
source/app.cpp
|
||||||
source/download.cpp
|
source/download.cpp
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ public:
|
|||||||
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
|
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
|
||||||
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
|
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
|
||||||
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
|
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
|
||||||
|
option::OptionString m_right_side_menu{INI_SECTION, "right_side_menu", "Appstore"};
|
||||||
|
|
||||||
// install options
|
// install options
|
||||||
option::OptionBool m_install{INI_SECTION, "install", false};
|
option::OptionBool m_install{INI_SECTION, "install", false};
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
namespace sphaira::i18n {
|
namespace sphaira::i18n {
|
||||||
|
|
||||||
bool init(long index);
|
bool init(long index);
|
||||||
void exit();
|
void exit();
|
||||||
|
|
||||||
std::string get(const char* str);
|
std::string get(std::string_view str);
|
||||||
|
|
||||||
} // namespace sphaira::i18n
|
} // namespace sphaira::i18n
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include "ui/menus/menu_base.hpp"
|
#include "ui/menus/menu_base.hpp"
|
||||||
#include "ui/scrollable_text.hpp"
|
#include "ui/scrollable_text.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "ui/list.hpp"
|
#include "ui/list.hpp"
|
||||||
#include "nro.hpp"
|
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ struct EntryMenu final : MenuBase {
|
|||||||
EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu);
|
EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu);
|
||||||
~EntryMenu();
|
~EntryMenu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Entry"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
// void OnFocusGained() override;
|
// void OnFocusGained() override;
|
||||||
@@ -135,9 +136,10 @@ enum OrderType {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Menu final : MenuBase {
|
struct Menu final : MenuBase {
|
||||||
Menu(const std::vector<NroEntry>& nro_entries);
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Store"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
@@ -162,13 +164,16 @@ struct Menu final : MenuBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const std::vector<NroEntry>& m_nro_entries;
|
|
||||||
std::vector<Entry> m_entries{};
|
std::vector<Entry> m_entries{};
|
||||||
std::vector<EntryMini> m_entries_index[Filter_MAX]{};
|
std::vector<EntryMini> m_entries_index[Filter_MAX]{};
|
||||||
std::vector<EntryMini> m_entries_index_author{};
|
std::vector<EntryMini> m_entries_index_author{};
|
||||||
std::vector<EntryMini> m_entries_index_search{};
|
std::vector<EntryMini> m_entries_index_search{};
|
||||||
std::span<EntryMini> m_entries_current{};
|
std::span<EntryMini> m_entries_current{};
|
||||||
|
|
||||||
|
ScrollingText m_scroll_name{};
|
||||||
|
ScrollingText m_scroll_author{};
|
||||||
|
ScrollingText m_scroll_version{};
|
||||||
|
|
||||||
Filter m_filter{Filter::Filter_All};
|
Filter m_filter{Filter::Filter_All};
|
||||||
SortType m_sort{SortType::SortType_Updated};
|
SortType m_sort{SortType::SortType_Updated};
|
||||||
OrderType m_order{OrderType::OrderType_Descending};
|
OrderType m_order{OrderType::OrderType_Descending};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu(const fs::FsPath& path);
|
Menu(const fs::FsPath& path);
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "File"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu(const std::vector<NroEntry>& nro_entries);
|
Menu(const std::vector<NroEntry>& nro_entries);
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Files"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "FTP"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
88
sphaira/include/ui/menus/game_menu.hpp
Normal file
88
sphaira/include/ui/menus/game_menu.hpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/menus/menu_base.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
|
#include "ui/list.hpp"
|
||||||
|
#include "fs.hpp"
|
||||||
|
#include "option.hpp"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace sphaira::ui::menu::game {
|
||||||
|
|
||||||
|
enum class NacpLoadStatus {
|
||||||
|
// not yet attempted to be loaded.
|
||||||
|
None,
|
||||||
|
// loaded, ready to parse.
|
||||||
|
Loaded,
|
||||||
|
// failed to load, do not attempt to load again!
|
||||||
|
Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
u64 app_id{};
|
||||||
|
s64 size{};
|
||||||
|
char display_version[0x10]{};
|
||||||
|
NacpLanguageEntry lang{};
|
||||||
|
int image{};
|
||||||
|
|
||||||
|
std::unique_ptr<NsApplicationControlData> control{};
|
||||||
|
u64 control_size{};
|
||||||
|
NacpLoadStatus status{NacpLoadStatus::None};
|
||||||
|
|
||||||
|
auto GetName() const -> const char* {
|
||||||
|
return lang.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetAuthor() const -> const char* {
|
||||||
|
return lang.author;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetDisplayVersion() const -> const char* {
|
||||||
|
return display_version;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SortType {
|
||||||
|
SortType_Updated,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OrderType {
|
||||||
|
OrderType_Descending,
|
||||||
|
OrderType_Ascending,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Menu final : MenuBase {
|
||||||
|
Menu();
|
||||||
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Games"; };
|
||||||
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
|
void OnFocusGained() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetIndex(s64 index);
|
||||||
|
void ScanHomebrew();
|
||||||
|
void Sort();
|
||||||
|
void SortAndFindLastFile();
|
||||||
|
void FreeEntries();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr inline const char* INI_SECTION = "games";
|
||||||
|
|
||||||
|
std::vector<Entry> m_entries{};
|
||||||
|
s64 m_index{}; // where i am in the array
|
||||||
|
std::unique_ptr<List> m_list{};
|
||||||
|
bool m_is_reversed{};
|
||||||
|
bool m_dirty{};
|
||||||
|
|
||||||
|
ScrollingText m_scroll_name{};
|
||||||
|
ScrollingText m_scroll_author{};
|
||||||
|
ScrollingText m_scroll_version{};
|
||||||
|
|
||||||
|
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||||
|
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||||
|
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::ui::menu::game
|
||||||
@@ -43,8 +43,10 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "GC"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
|
void OnFocusGained() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Result GcMount();
|
Result GcMount();
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "GitHub"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/menus/menu_base.hpp"
|
#include "ui/menus/menu_base.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "ui/list.hpp"
|
#include "ui/list.hpp"
|
||||||
#include "nro.hpp"
|
#include "nro.hpp"
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
@@ -26,27 +27,30 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Apps"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|
||||||
void SetIndex(s64 index);
|
|
||||||
void InstallHomebrew();
|
|
||||||
void ScanHomebrew();
|
|
||||||
void Sort();
|
|
||||||
void SortAndFindLastFile();
|
|
||||||
|
|
||||||
auto GetHomebrewList() const -> const std::vector<NroEntry>& {
|
auto GetHomebrewList() const -> const std::vector<NroEntry>& {
|
||||||
return m_entries;
|
return m_entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
|
||||||
|
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetIndex(s64 index);
|
||||||
|
void InstallHomebrew();
|
||||||
|
void ScanHomebrew();
|
||||||
|
void Sort();
|
||||||
|
void SortAndFindLastFile();
|
||||||
|
void FreeEntries();
|
||||||
|
|
||||||
auto IsStarEnabled() -> bool {
|
auto IsStarEnabled() -> bool {
|
||||||
return m_sort.Get() >= SortType_UpdatedStar;
|
return m_sort.Get() >= SortType_UpdatedStar;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon);
|
|
||||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||||
|
|
||||||
@@ -54,6 +58,10 @@ private:
|
|||||||
s64 m_index{}; // where i am in the array
|
s64 m_index{}; // where i am in the array
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
|
|
||||||
|
ScrollingText m_scroll_name{};
|
||||||
|
ScrollingText m_scroll_author{};
|
||||||
|
ScrollingText m_scroll_version{};
|
||||||
|
|
||||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "IRS"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "ui/widget.hpp"
|
#include "ui/widget.hpp"
|
||||||
#include "ui/menus/homebrew.hpp"
|
#include "ui/menus/homebrew.hpp"
|
||||||
#include "ui/menus/filebrowser.hpp"
|
#include "ui/menus/filebrowser.hpp"
|
||||||
#include "ui/menus/appstore.hpp"
|
|
||||||
|
|
||||||
namespace sphaira::ui::menu::main {
|
namespace sphaira::ui::menu::main {
|
||||||
|
|
||||||
@@ -18,6 +17,32 @@ enum class UpdateState {
|
|||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(void)>;
|
||||||
|
|
||||||
|
enum MiscMenuFlag : u8 {
|
||||||
|
// can be set as the rightside menu.
|
||||||
|
MiscMenuFlag_Shortcut = 1 << 0,
|
||||||
|
// needs install option to be enabled.
|
||||||
|
MiscMenuFlag_Install = 1 << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MiscMenuEntry {
|
||||||
|
const char* name;
|
||||||
|
const char* title;
|
||||||
|
MiscMenuFunction func;
|
||||||
|
u8 flag;
|
||||||
|
|
||||||
|
auto IsShortcut() const -> bool {
|
||||||
|
return flag & MiscMenuFlag_Shortcut;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto IsInstall() const -> bool {
|
||||||
|
return flag & MiscMenuFlag_Install;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry>;
|
||||||
|
|
||||||
// this holds 2 menus and allows for switching between them
|
// this holds 2 menus and allows for switching between them
|
||||||
struct MainMenu final : Widget {
|
struct MainMenu final : Widget {
|
||||||
MainMenu();
|
MainMenu();
|
||||||
@@ -39,7 +64,7 @@ private:
|
|||||||
private:
|
private:
|
||||||
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
|
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};
|
||||||
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
|
std::shared_ptr<filebrowser::Menu> m_filebrowser_menu{};
|
||||||
std::shared_ptr<appstore::Menu> m_app_store_menu{};
|
std::shared_ptr<MenuBase> m_right_side_menu{};
|
||||||
std::shared_ptr<MenuBase> m_current_menu{};
|
std::shared_ptr<MenuBase> m_current_menu{};
|
||||||
|
|
||||||
std::string m_update_url{};
|
std::string m_update_url{};
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ struct MenuBase : Widget {
|
|||||||
MenuBase(std::string title);
|
MenuBase(std::string title);
|
||||||
virtual ~MenuBase();
|
virtual ~MenuBase();
|
||||||
|
|
||||||
|
virtual auto GetShortTitle() const -> const char* = 0;
|
||||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||||
virtual void Draw(NVGcontext* vg, Theme* theme);
|
virtual void Draw(NVGcontext* vg, Theme* theme);
|
||||||
|
|
||||||
@@ -21,6 +22,10 @@ struct MenuBase : Widget {
|
|||||||
void SetTitleSubHeading(std::string sub_heading);
|
void SetTitleSubHeading(std::string sub_heading);
|
||||||
void SetSubHeading(std::string sub_heading);
|
void SetSubHeading(std::string sub_heading);
|
||||||
|
|
||||||
|
auto GetTitle() const {
|
||||||
|
return m_title;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateVars();
|
void UpdateVars();
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "ui/menus/menu_base.hpp"
|
#include "ui/menus/menu_base.hpp"
|
||||||
#include "ui/scrollable_text.hpp"
|
#include "ui/scrollable_text.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "ui/list.hpp"
|
#include "ui/list.hpp"
|
||||||
#include "option.hpp"
|
#include "option.hpp"
|
||||||
#include <span>
|
#include <span>
|
||||||
@@ -132,6 +133,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "Themezer"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
@@ -160,6 +162,9 @@ private:
|
|||||||
s64 m_index{}; // where i am in the array
|
s64 m_index{}; // where i am in the array
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
|
|
||||||
|
ScrollingText m_scroll_name{};
|
||||||
|
ScrollingText m_scroll_author{};
|
||||||
|
|
||||||
// options
|
// options
|
||||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ struct Menu final : MenuBase {
|
|||||||
Menu();
|
Menu();
|
||||||
~Menu();
|
~Menu();
|
||||||
|
|
||||||
|
auto GetShortTitle() const -> const char* override { return "USB"; };
|
||||||
void Update(Controller* controller, TouchInfo* touch) override;
|
void Update(Controller* controller, TouchInfo* touch) override;
|
||||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||||
void OnFocusGained() override;
|
void OnFocusGained() override;
|
||||||
|
|||||||
@@ -32,11 +32,9 @@ public:
|
|||||||
using Options = std::vector<Option>;
|
using Options = std::vector<Option>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}); // confirm
|
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}, int image = 0); // confirm
|
||||||
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb); // yesno
|
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb, int image = 0); // yesno
|
||||||
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb); // yesno
|
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb, int image = 0); // yesno
|
||||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, Callback cb); // tri
|
|
||||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb); // tri
|
|
||||||
|
|
||||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||||
@@ -50,6 +48,7 @@ private:
|
|||||||
private:
|
private:
|
||||||
std::string m_message{};
|
std::string m_message{};
|
||||||
Callback m_callback{};
|
Callback m_callback{};
|
||||||
|
int m_image{};
|
||||||
|
|
||||||
Vec4 m_spacer_line{};
|
Vec4 m_spacer_line{};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui/widget.hpp"
|
#include "ui/widget.hpp"
|
||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
#include "ui/list.hpp"
|
#include "ui/list.hpp"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ private:
|
|||||||
s64 m_starting_index{};
|
s64 m_starting_index{};
|
||||||
|
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
|
ScrollingText m_scroll_text{};
|
||||||
|
|
||||||
float m_yoff{};
|
float m_yoff{};
|
||||||
float m_line_top{};
|
float m_line_top{};
|
||||||
|
|||||||
19
sphaira/include/ui/scrolling_text.hpp
Normal file
19
sphaira/include/ui/scrolling_text.hpp
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/widget.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sphaira::ui {
|
||||||
|
|
||||||
|
struct ScrollingText final {
|
||||||
|
public:
|
||||||
|
void Draw(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry);
|
||||||
|
void DrawArgs(NVGcontext*, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) __attribute__ ((format (printf, 10, 11)));
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_str;
|
||||||
|
s64 m_tick;
|
||||||
|
float m_text_xoff;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sphaira::ui
|
||||||
@@ -41,6 +41,7 @@ enum KeyGeneration {
|
|||||||
KeyGeneration_1700 = 0x11,
|
KeyGeneration_1700 = 0x11,
|
||||||
KeyGeneration_1800 = 0x12,
|
KeyGeneration_1800 = 0x12,
|
||||||
KeyGeneration_1900 = 0x13,
|
KeyGeneration_1900 = 0x13,
|
||||||
|
KeyGeneration_2000 = 0x14,
|
||||||
KeyGeneration_Invalid = 0xFF,
|
KeyGeneration_Invalid = 0xFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ union ExtendedHeader {
|
|||||||
NcmDataPatchMetaExtendedHeader data_patch;
|
NcmDataPatchMetaExtendedHeader data_patch;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
||||||
|
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
||||||
|
|
||||||
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
||||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||||
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ struct Base {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual void SignalCancel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Result GetOpenResult() const {
|
Result GetOpenResult() const {
|
||||||
return m_open_result;
|
return m_open_result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ struct Usb final : Base {
|
|||||||
Result_BadCount = MAKERESULT(USBModule, 2),
|
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||||
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||||
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||||
|
Result_Cancelled = MAKERESULT(USBModule, 11),
|
||||||
};
|
};
|
||||||
|
|
||||||
Usb(u64 transfer_timeout);
|
Usb(u64 transfer_timeout);
|
||||||
@@ -28,10 +29,18 @@ struct Usb final : Base {
|
|||||||
Result Finished();
|
Result Finished();
|
||||||
|
|
||||||
Result Init();
|
Result Init();
|
||||||
Result IsUsbConnected(u64 timeout) const;
|
Result IsUsbConnected(u64 timeout);
|
||||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
||||||
void SetFileNameForTranfser(const std::string& name);
|
void SetFileNameForTranfser(const std::string& name);
|
||||||
|
|
||||||
|
auto GetCancelEvent() {
|
||||||
|
return &m_uevent;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalCancel() override {
|
||||||
|
ueventSignal(GetCancelEvent());
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// custom allocator for std::vector that respects alignment.
|
// custom allocator for std::vector that respects alignment.
|
||||||
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
||||||
@@ -69,16 +78,18 @@ private:
|
|||||||
Result SendFileRangeCmd(u64 offset, u64 size);
|
Result SendFileRangeCmd(u64 offset, u64 size);
|
||||||
|
|
||||||
Event *GetCompletionEvent(UsbSessionEndpoint ep) const;
|
Event *GetCompletionEvent(UsbSessionEndpoint ep) const;
|
||||||
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) const;
|
Result WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout);
|
||||||
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) const;
|
Result TransferAsync(UsbSessionEndpoint ep, void *buffer, u32 size, u32 *out_urb_id) const;
|
||||||
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) const;
|
Result GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_requested_size, u32 *out_transferred_size) const;
|
||||||
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) const;
|
Result TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout);
|
||||||
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
|
Result TransferAll(bool read, void *data, u32 size, u64 timeout);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UsbDsInterface* m_interface{};
|
UsbDsInterface* m_interface{};
|
||||||
UsbDsEndpoint* m_endpoints[2]{};
|
UsbDsEndpoint* m_endpoints[2]{};
|
||||||
u64 m_transfer_timeout{};
|
u64 m_transfer_timeout{};
|
||||||
|
UEvent m_uevent{};
|
||||||
|
// std::vector<UEvent*> m_cancel_events{};
|
||||||
// aligned buffer that transfer data is copied to and from.
|
// aligned buffer that transfer data is copied to and from.
|
||||||
// a vector is used to avoid multiple alloc within the transfer loop.
|
// a vector is used to avoid multiple alloc within the transfer loop.
|
||||||
PageAlignedVector m_aligned{};
|
PageAlignedVector m_aligned{};
|
||||||
|
|||||||
@@ -7,12 +7,6 @@
|
|||||||
#include "ui/error_box.hpp"
|
#include "ui/error_box.hpp"
|
||||||
|
|
||||||
#include "ui/menus/main_menu.hpp"
|
#include "ui/menus/main_menu.hpp"
|
||||||
#include "ui/menus/irs_menu.hpp"
|
|
||||||
#include "ui/menus/themezer.hpp"
|
|
||||||
#include "ui/menus/ghdl.hpp"
|
|
||||||
#include "ui/menus/usb_menu.hpp"
|
|
||||||
#include "ui/menus/ftp_menu.hpp"
|
|
||||||
#include "ui/menus/gc_menu.hpp"
|
|
||||||
|
|
||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
@@ -1486,11 +1480,11 @@ void App::DisplayThemeOptions(bool left_side) {
|
|||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Music"_i18n, App::GetThemeMusicEnable(), [](bool& enable){
|
||||||
App::SetThemeMusicEnable(enable);
|
App::SetThemeMusicEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("12 Hour Time"_i18n, App::Get12HourTimeEnable(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("12 Hour Time"_i18n, App::Get12HourTimeEnable(), [](bool& enable){
|
||||||
App::Set12HourTimeEnable(enable);
|
App::Set12HourTimeEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Download Default Music"_i18n, [](){
|
options->Add(std::make_shared<ui::SidebarEntryCallback>("Download Default Music"_i18n, [](){
|
||||||
// check if we already have music
|
// check if we already have music
|
||||||
@@ -1518,33 +1512,19 @@ void App::DisplayMiscOptions(bool left_side) {
|
|||||||
auto options = std::make_shared<ui::Sidebar>("Misc Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
auto options = std::make_shared<ui::Sidebar>("Misc Options"_i18n, left_side ? ui::Sidebar::Side::LEFT : ui::Sidebar::Side::RIGHT);
|
||||||
ON_SCOPE_EXIT(App::Push(options));
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Themezer"_i18n, [](){
|
for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
|
||||||
App::Push(std::make_shared<ui::menu::themezer::Menu>());
|
if (e.name == g_app->m_right_side_menu.Get()) {
|
||||||
}));
|
continue;
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("GitHub"_i18n, [](){
|
|
||||||
App::Push(std::make_shared<ui::menu::gh::Menu>());
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (App::GetApp()->m_install.Get()) {
|
|
||||||
if (App::GetFtpEnable()) {
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Ftp Install"_i18n, [](){
|
|
||||||
App::Push(std::make_shared<ui::menu::ftp::Menu>());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Usb Install"_i18n, [](){
|
if (e.IsInstall() && !App::GetInstallEnable()) {
|
||||||
App::Push(std::make_shared<ui::menu::usb::Menu>());
|
continue;
|
||||||
}));
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("GameCard Install"_i18n, [](){
|
options->Add(std::make_shared<ui::SidebarEntryCallback>(i18n::get(e.title), [e](){
|
||||||
App::Push(std::make_shared<ui::menu::gc::Menu>());
|
App::Push(e.func());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Irs (Infrared Joycon Camera)"_i18n, [](){
|
|
||||||
App::Push(std::make_shared<ui::menu::irs::Menu>());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::DisplayAdvancedOptions(bool left_side) {
|
void App::DisplayAdvancedOptions(bool left_side) {
|
||||||
@@ -1556,17 +1536,52 @@ void App::DisplayAdvancedOptions(bool left_side) {
|
|||||||
text_scroll_speed_items.push_back("Normal"_i18n);
|
text_scroll_speed_items.push_back("Normal"_i18n);
|
||||||
text_scroll_speed_items.push_back("Fast"_i18n);
|
text_scroll_speed_items.push_back("Fast"_i18n);
|
||||||
|
|
||||||
|
std::vector<const char*> menu_names;
|
||||||
|
for (auto& e : ui::menu::main::GetMiscMenuEntries()) {
|
||||||
|
if (!e.IsShortcut()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.IsInstall() && !App::GetInstallEnable()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu_names.emplace_back(e.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::SidebarEntryArray::Items right_side_menu_items;
|
||||||
|
for (auto& str : menu_names) {
|
||||||
|
right_side_menu_items.push_back(i18n::get(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = std::find(menu_names.cbegin(), menu_names.cend(), g_app->m_right_side_menu.Get());
|
||||||
|
if (it == menu_names.cend()) {
|
||||||
|
g_app->m_right_side_menu.Set(menu_names[0]);
|
||||||
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [](bool& enable){
|
||||||
App::SetLogEnable(enable);
|
App::SetLogEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Replace hbmenu on exit"_i18n, App::GetReplaceHbmenuEnable(), [](bool& enable){
|
||||||
App::SetReplaceHbmenuEnable(enable);
|
App::SetReplaceHbmenuEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){
|
options->Add(std::make_shared<ui::SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [](s64& index_out){
|
||||||
App::SetTextScrollSpeed(index_out);
|
App::SetTextScrollSpeed(index_out);
|
||||||
}, (s64)App::GetTextScrollSpeed()));
|
}, App::GetTextScrollSpeed()));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<ui::SidebarEntryArray>("Set right-side menu"_i18n, right_side_menu_items, [menu_names](s64& index_out){
|
||||||
|
const auto e = menu_names[index_out];
|
||||||
|
if (g_app->m_right_side_menu.Get() != e) {
|
||||||
|
g_app->m_right_side_menu.Set(e);
|
||||||
|
App::Push(std::make_shared<ui::OptionBox>(
|
||||||
|
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
|
||||||
|
App::ExitRestart();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}, i18n::get(g_app->m_right_side_menu.Get())));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryCallback>("Install options"_i18n, [left_side](){
|
options->Add(std::make_shared<ui::SidebarEntryCallback>("Install options"_i18n, [left_side](){
|
||||||
App::DisplayInstallOptions(left_side);
|
App::DisplayInstallOptions(left_side);
|
||||||
@@ -1583,11 +1598,11 @@ void App::DisplayInstallOptions(bool left_side) {
|
|||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Enable"_i18n, App::GetApp()->m_install.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Enable"_i18n, App::GetApp()->m_install.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_install.Set(enable);
|
App::GetApp()->m_install.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetApp()->m_install_prompt.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Show install warning"_i18n, App::GetApp()->m_install_prompt.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_install_prompt.Set(enable);
|
App::GetApp()->m_install_prompt.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){
|
options->Add(std::make_shared<ui::SidebarEntryArray>("Install location"_i18n, install_items, [](s64& index_out){
|
||||||
App::SetInstallSdEnable(index_out);
|
App::SetInstallSdEnable(index_out);
|
||||||
@@ -1595,67 +1610,67 @@ void App::DisplayInstallOptions(bool left_side) {
|
|||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Boost CPU clock"_i18n, App::GetApp()->m_boost_mode.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Boost CPU clock"_i18n, App::GetApp()->m_boost_mode.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_boost_mode.Set(enable);
|
App::GetApp()->m_boost_mode.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Allow downgrade"_i18n, App::GetApp()->m_allow_downgrade.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_allow_downgrade.Set(enable);
|
App::GetApp()->m_allow_downgrade.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip if already installed"_i18n, App::GetApp()->m_skip_if_already_installed.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
App::GetApp()->m_skip_if_already_installed.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Ticket only"_i18n, App::GetApp()->m_ticket_only.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_ticket_only.Set(enable);
|
App::GetApp()->m_ticket_only.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip base"_i18n, App::GetApp()->m_skip_base.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_base.Set(enable);
|
App::GetApp()->m_skip_base.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip patch"_i18n, App::GetApp()->m_skip_patch.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_patch.Set(enable);
|
App::GetApp()->m_skip_patch.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip dlc"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip dlc"_i18n, App::GetApp()->m_skip_addon.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_addon.Set(enable);
|
App::GetApp()->m_skip_addon.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip data patch"_i18n, App::GetApp()->m_skip_data_patch.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_data_patch.Set(enable);
|
App::GetApp()->m_skip_data_patch.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip ticket"_i18n, App::GetApp()->m_skip_ticket.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_ticket.Set(enable);
|
App::GetApp()->m_skip_ticket.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("skip NCA hash verify"_i18n, App::GetApp()->m_skip_nca_hash_verify.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
App::GetApp()->m_skip_nca_hash_verify.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA header verify"_i18n, App::GetApp()->m_skip_rsa_header_fixed_key_verify.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
App::GetApp()->m_skip_rsa_header_fixed_key_verify.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Skip RSA NPDM verify"_i18n, App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
App::GetApp()->m_skip_rsa_npdm_fixed_key_verify.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Ignore distribution bit"_i18n, App::GetApp()->m_ignore_distribution_bit.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
App::GetApp()->m_ignore_distribution_bit.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Convert to standard crypto"_i18n, App::GetApp()->m_convert_to_standard_crypto.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
App::GetApp()->m_convert_to_standard_crypto.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower master key"_i18n, App::GetApp()->m_lower_master_key.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_lower_master_key.Set(enable);
|
App::GetApp()->m_lower_master_key.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){
|
options->Add(std::make_shared<ui::SidebarEntryBool>("Lower system version"_i18n, App::GetApp()->m_lower_system_version.Get(), [](bool& enable){
|
||||||
App::GetApp()->m_lower_system_version.Set(enable);
|
App::GetApp()->m_lower_system_version.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
App::~App() {
|
App::~App() {
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ yyjson_doc* json;
|
|||||||
yyjson_val* root;
|
yyjson_val* root;
|
||||||
std::unordered_map<std::string, std::string> g_tr_cache;
|
std::unordered_map<std::string, std::string> g_tr_cache;
|
||||||
|
|
||||||
std::string get_internal(const char* str, size_t len) {
|
std::string get_internal(std::string_view str) {
|
||||||
const std::string kkey = {str, len};
|
const std::string kkey = {str.data(), str.length()};
|
||||||
|
|
||||||
if (auto it = g_tr_cache.find(kkey); it != g_tr_cache.end()) {
|
if (auto it = g_tr_cache.find(kkey); it != g_tr_cache.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
@@ -28,7 +28,7 @@ std::string get_internal(const char* str, size_t len) {
|
|||||||
return kkey;
|
return kkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto key = yyjson_obj_getn(root, str, len);
|
auto key = yyjson_obj_getn(root, str.data(), str.length());
|
||||||
if (!key) {
|
if (!key) {
|
||||||
log_write("\tfailed to find key: [%s]\n", kkey.c_str());
|
log_write("\tfailed to find key: [%s]\n", kkey.c_str());
|
||||||
return kkey;
|
return kkey;
|
||||||
@@ -134,8 +134,8 @@ void exit() {
|
|||||||
g_i18n_data.clear();
|
g_i18n_data.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get(const char* str) {
|
std::string get(std::string_view str) {
|
||||||
return get_internal(str, std::strlen(str));
|
return get_internal(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sphaira::i18n
|
} // namespace sphaira::i18n
|
||||||
@@ -143,7 +143,7 @@ std::string get(const char* str) {
|
|||||||
namespace literals {
|
namespace literals {
|
||||||
|
|
||||||
std::string operator"" _i18n(const char* str, size_t len) {
|
std::string operator"" _i18n(const char* str, size_t len) {
|
||||||
return sphaira::i18n::get_internal(str, len);
|
return sphaira::i18n::get_internal({str, len});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace literals
|
} // namespace literals
|
||||||
|
|||||||
@@ -857,7 +857,8 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
|||||||
u64 hash_data[SHA256_HASH_SIZE / sizeof(u64)];
|
u64 hash_data[SHA256_HASH_SIZE / sizeof(u64)];
|
||||||
const auto hash_path = config.nro_path + config.args;
|
const auto hash_path = config.nro_path + config.args;
|
||||||
sha256CalculateHash(hash_data, hash_path.data(), hash_path.length());
|
sha256CalculateHash(hash_data, hash_path.data(), hash_path.length());
|
||||||
const u64 tid = 0x0100000000000000 | (hash_data[0] & 0x00FFFFFFFFFFF000);
|
const u64 old_tid = 0x0100000000000000 | (hash_data[0] & 0x00FFFFFFFFFFF000);
|
||||||
|
const u64 tid = 0x0500000000000000 | (hash_data[0] & 0x00FFFFFFFFFFF000);
|
||||||
|
|
||||||
std::vector<NcaEntry> nca_entries;
|
std::vector<NcaEntry> nca_entries;
|
||||||
|
|
||||||
@@ -980,6 +981,12 @@ auto install_forwader_internal(ui::ProgressBox* pbox, OwoConfig& config, NcmStor
|
|||||||
R_TRY(nsIsAnyApplicationEntityInstalled(tid, &already_installed));
|
R_TRY(nsIsAnyApplicationEntityInstalled(tid, &already_installed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove old id for forwarders.
|
||||||
|
const auto rc = nsDeleteApplicationCompletely(old_tid);
|
||||||
|
if (R_FAILED(rc) && rc != 0x410) { // not found
|
||||||
|
App::Notify("Failed to remove old forwarder, please manually remove it!");
|
||||||
|
}
|
||||||
|
|
||||||
// remove previous application record
|
// remove previous application record
|
||||||
if (already_installed || hosversionBefore(2,0,0)) {
|
if (already_installed || hosversionBefore(2,0,0)) {
|
||||||
const auto rc = ns::DeleteApplicationRecord(srv_ptr, tid);
|
const auto rc = ns::DeleteApplicationRecord(srv_ptr, tid);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ auto ErrorBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
gfx::drawTextArgs(vg, center_x, 180, 63, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_ERROR), "\uE140");
|
gfx::drawTextArgs(vg, center_x, 180, 63, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_ERROR), "\uE140");
|
||||||
if (m_code.has_value()) {
|
if (m_code.has_value()) {
|
||||||
const auto code = m_code.value();
|
const auto code = m_code.value();
|
||||||
gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Error code: 0x%X Module: %u Description: 0x%X Value: 0x%X", code, R_MODULE(code), R_DESCRIPTION(code), R_VALUE(code));
|
gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Code: 0x%X Module: %u Description: 0x%X Value: 0x%X", code, R_MODULE(code), R_DESCRIPTION(code), R_VALUE(code));
|
||||||
} else {
|
} else {
|
||||||
gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "An error occurred"_i18n.c_str());
|
gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "An error occurred"_i18n.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
|
|||||||
bool crop = false;
|
bool crop = false;
|
||||||
if (iw < w || ih < h) {
|
if (iw < w || ih < h) {
|
||||||
rounded_image = false;
|
rounded_image = false;
|
||||||
gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded ? 15 : 0);
|
gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded ? 5 : 0);
|
||||||
}
|
}
|
||||||
if (iw > w || ih > h) {
|
if (iw > w || ih > h) {
|
||||||
crop = true;
|
crop = true;
|
||||||
@@ -244,7 +244,7 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
|
|||||||
nvgIntersectScissor(vg, x, y, w, h);
|
nvgIntersectScissor(vg, x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx::drawImage(vg, ix, iy, iw, ih, i.image, rounded_image ? 15 : 0);
|
gfx::drawImage(vg, ix, iy, iw, ih, i.image, rounded_image ? 5 : 0);
|
||||||
if (crop) {
|
if (crop) {
|
||||||
nvgRestore(vg);
|
nvgRestore(vg);
|
||||||
}
|
}
|
||||||
@@ -846,13 +846,42 @@ void EntryMenu::SetIndex(s64 index) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}, m_nro_entries{nro_entries} {
|
Menu::Menu() : MenuBase{"AppStore"_i18n} {
|
||||||
fs::FsNativeSd fs;
|
fs::FsNativeSd fs;
|
||||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons");
|
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/icons");
|
||||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
|
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
|
||||||
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/screens");
|
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/screens");
|
||||||
|
|
||||||
this->SetActions(
|
this->SetActions(
|
||||||
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
|
if (m_is_author) {
|
||||||
|
m_is_author = false;
|
||||||
|
if (m_is_search) {
|
||||||
|
SetSearch(m_search_term);
|
||||||
|
} else {
|
||||||
|
SetFilter(m_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetIndex(m_entry_author_jump_back);
|
||||||
|
if (m_entry_author_jump_back >= 9) {
|
||||||
|
m_list->SetYoff((((m_entry_author_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
||||||
|
} else {
|
||||||
|
m_list->SetYoff(0);
|
||||||
|
}
|
||||||
|
} else if (m_is_search) {
|
||||||
|
m_is_search = false;
|
||||||
|
SetFilter(m_filter);
|
||||||
|
SetIndex(m_entry_search_jump_back);
|
||||||
|
if (m_entry_search_jump_back >= 9) {
|
||||||
|
m_list->SetYoff(0);
|
||||||
|
m_list->SetYoff((((m_entry_search_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
||||||
|
} else {
|
||||||
|
m_list->SetYoff(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SetPop();
|
||||||
|
}
|
||||||
|
}}),
|
||||||
std::make_pair(Button::A, Action{"Info"_i18n, [this](){
|
std::make_pair(Button::A, Action{"Info"_i18n, [this](){
|
||||||
if (m_entries_current.empty()) {
|
if (m_entries_current.empty()) {
|
||||||
// log_write("pushing A when empty: size: %zu count: %zu\n", repo_json.size(), m_entries_current.size());
|
// log_write("pushing A when empty: size: %zu count: %zu\n", repo_json.size(), m_entries_current.size());
|
||||||
@@ -1027,7 +1056,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto text_id = ThemeEntryID_TEXT;
|
auto text_id = ThemeEntryID_TEXT;
|
||||||
if (pos == m_index) {
|
const auto selected = pos == m_index;
|
||||||
|
if (selected) {
|
||||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
} else {
|
} else {
|
||||||
@@ -1040,15 +1070,13 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
DrawIcon(vg, e.image, m_default_image, x + 20, y + 20, 115, 115, true, image_scale);
|
DrawIcon(vg, e.image, m_default_image, x + 20, y + 20, 115, 115, true, image_scale);
|
||||||
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
|
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
|
||||||
|
|
||||||
nvgSave(vg);
|
const auto text_off = 148;
|
||||||
nvgIntersectScissor(vg, v.x, v.y, w - 30.f, h); // clip
|
const auto text_x = x + text_off;
|
||||||
{
|
const auto text_clip_w = w - 30.f - text_off;
|
||||||
const float font_size = 18;
|
const float font_size = 18;
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.title.c_str());
|
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.title.c_str());
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 80, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.author.c_str());
|
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.author.c_str());
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
|
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
|
||||||
}
|
|
||||||
nvgRestore(vg);
|
|
||||||
|
|
||||||
float i_size = 22;
|
float i_size = 22;
|
||||||
switch (e.status) {
|
switch (e.status) {
|
||||||
@@ -1265,7 +1293,6 @@ void Menu::Sort() {
|
|||||||
void Menu::SetFilter(Filter filter) {
|
void Menu::SetFilter(Filter filter) {
|
||||||
m_is_search = false;
|
m_is_search = false;
|
||||||
m_is_author = false;
|
m_is_author = false;
|
||||||
RemoveAction(Button::B);
|
|
||||||
|
|
||||||
m_filter = filter;
|
m_filter = filter;
|
||||||
m_entries_current = m_entries_index[m_filter];
|
m_entries_current = m_entries_index[m_filter];
|
||||||
@@ -1304,17 +1331,6 @@ void Menu::SetSearch(const std::string& term) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
|
||||||
SetFilter(m_filter);
|
|
||||||
SetIndex(m_entry_search_jump_back);
|
|
||||||
if (m_entry_search_jump_back >= 9) {
|
|
||||||
m_list->SetYoff(0);
|
|
||||||
m_list->SetYoff((((m_entry_search_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
|
||||||
} else {
|
|
||||||
m_list->SetYoff(0);
|
|
||||||
}
|
|
||||||
}});
|
|
||||||
|
|
||||||
m_is_search = true;
|
m_is_search = true;
|
||||||
m_entries_current = m_entries_index_search;
|
m_entries_current = m_entries_index_search;
|
||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
@@ -1337,21 +1353,6 @@ void Menu::SetAuthor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetAction(Button::B, Action{"Back"_i18n, [this](){
|
|
||||||
if (m_is_search) {
|
|
||||||
SetSearch(m_search_term);
|
|
||||||
} else {
|
|
||||||
SetFilter(m_filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetIndex(m_entry_author_jump_back);
|
|
||||||
if (m_entry_author_jump_back >= 9) {
|
|
||||||
m_list->SetYoff((((m_entry_author_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
|
|
||||||
} else {
|
|
||||||
m_list->SetYoff(0);
|
|
||||||
}
|
|
||||||
}});
|
|
||||||
|
|
||||||
m_is_author = true;
|
m_is_author = true;
|
||||||
m_entries_current = m_entries_index_author;
|
m_entries_current = m_entries_index_author;
|
||||||
SetIndex(0);
|
SetIndex(0);
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
|
|
||||||
const auto set = m_selected_count != m_entries_current.size();
|
const auto set = m_selected_count != m_entries_current.size();
|
||||||
|
|
||||||
for (const auto& i : m_entries_current) {
|
for (u32 i = 0; i < m_entries_current.size(); i++) {
|
||||||
auto& e = GetEntry(i);
|
auto& e = GetEntry(i);
|
||||||
if (e.selected != set) {
|
if (e.selected != set) {
|
||||||
e.selected = set;
|
e.selected = set;
|
||||||
@@ -412,17 +412,17 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
options->Add(std::make_shared<SidebarEntryBool>("Show Hidden"_i18n, m_show_hidden.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryBool>("Show Hidden"_i18n, m_show_hidden.Get(), [this](bool& v_out){
|
||||||
m_show_hidden.Set(v_out);
|
m_show_hidden.Set(v_out);
|
||||||
SortAndFindLastFile();
|
SortAndFindLastFile();
|
||||||
}, "Yes"_i18n, "No"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Folders First"_i18n, m_folders_first.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryBool>("Folders First"_i18n, m_folders_first.Get(), [this](bool& v_out){
|
||||||
m_folders_first.Set(v_out);
|
m_folders_first.Set(v_out);
|
||||||
SortAndFindLastFile();
|
SortAndFindLastFile();
|
||||||
}, "Yes"_i18n, "No"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Hidden Last"_i18n, m_hidden_last.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryBool>("Hidden Last"_i18n, m_hidden_last.Get(), [this](bool& v_out){
|
||||||
m_hidden_last.Set(v_out);
|
m_hidden_last.Set(v_out);
|
||||||
SortAndFindLastFile();
|
SortAndFindLastFile();
|
||||||
}, "Yes"_i18n, "No"_i18n));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (m_entries_current.size()) {
|
if (m_entries_current.size()) {
|
||||||
@@ -552,7 +552,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
|
|
||||||
if (m_entries_current.size()) {
|
if (m_entries_current.size()) {
|
||||||
if (check_all_ext(ZIP_EXTENSIONS)) {
|
if (check_all_ext(ZIP_EXTENSIONS)) {
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Extract"_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Extract zip"_i18n, [this](){
|
||||||
auto options = std::make_shared<Sidebar>("Extract Options"_i18n, Sidebar::Side::RIGHT);
|
auto options = std::make_shared<Sidebar>("Extract Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
ON_SCOPE_EXIT(App::Push(options));
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
@@ -564,6 +564,19 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Extract to root"_i18n, [this](){
|
||||||
|
App::Push(std::make_shared<OptionBox>("Are you sure you want to extract to root?"_i18n,
|
||||||
|
"No"_i18n, "Yes"_i18n, 0, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
if (!m_selected_count) {
|
||||||
|
UnzipFile("/", GetEntry());
|
||||||
|
} else {
|
||||||
|
UnzipFiles("/", GetSelectedEntries());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Extract to..."_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Extract to..."_i18n, [this](){
|
||||||
std::string out;
|
std::string out;
|
||||||
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) {
|
if (R_SUCCEEDED(swkbd::ShowText(out, "Enter the path to the folder to extract into", fs::AppendPath(m_path, ""))) && !out.empty()) {
|
||||||
@@ -578,7 +591,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!check_all_ext(ZIP_EXTENSIONS) || m_selected_count) {
|
if (!check_all_ext(ZIP_EXTENSIONS) || m_selected_count) {
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Compress"_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Compress to zip"_i18n, [this](){
|
||||||
auto options = std::make_shared<Sidebar>("Compress Options"_i18n, Sidebar::Side::RIGHT);
|
auto options = std::make_shared<Sidebar>("Compress Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
ON_SCOPE_EXIT(App::Push(options));
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
@@ -660,7 +673,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
|
|||||||
options->Add(std::make_shared<SidebarEntryBool>("Ignore read only"_i18n, m_ignore_read_only.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryBool>("Ignore read only"_i18n, m_ignore_read_only.Get(), [this](bool& v_out){
|
||||||
m_ignore_read_only.Set(v_out);
|
m_ignore_read_only.Set(v_out);
|
||||||
m_fs->SetIgnoreReadOnly(v_out);
|
m_fs->SetIgnoreReadOnly(v_out);
|
||||||
}, "Yes"_i18n, "No"_i18n));
|
}));
|
||||||
|
|
||||||
SidebarEntryArray::Items mount_items;
|
SidebarEntryArray::Items mount_items;
|
||||||
mount_items.push_back("Sd"_i18n);
|
mount_items.push_back("Sd"_i18n);
|
||||||
@@ -734,7 +747,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto text_id = ThemeEntryID_TEXT;
|
auto text_id = ThemeEntryID_TEXT;
|
||||||
if (m_index == i) {
|
const auto selected = m_index == i;
|
||||||
|
if (selected) {
|
||||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
452
sphaira/source/ui/menus/game_menu.cpp
Normal file
452
sphaira/source/ui/menus/game_menu.cpp
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
#include "app.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "fs.hpp"
|
||||||
|
#include "ui/menus/game_menu.hpp"
|
||||||
|
#include "ui/sidebar.hpp"
|
||||||
|
#include "ui/error_box.hpp"
|
||||||
|
#include "ui/option_box.hpp"
|
||||||
|
#include "ui/progress_box.hpp"
|
||||||
|
#include "ui/popup_list.hpp"
|
||||||
|
#include "ui/nvg_util.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include "i18n.hpp"
|
||||||
|
#include "yati/nx/ncm.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace sphaira::ui::menu::game {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// thank you Shchmue ^^
|
||||||
|
struct ApplicationOccupiedSizeEntry {
|
||||||
|
u8 storageId;
|
||||||
|
u8 padding[0x7];
|
||||||
|
u64 sizeApplication;
|
||||||
|
u64 sizePatch;
|
||||||
|
u64 sizeAddOnContent;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApplicationOccupiedSize {
|
||||||
|
ApplicationOccupiedSizeEntry entry[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(ApplicationOccupiedSize) == sizeof(NsApplicationOccupiedSize));
|
||||||
|
|
||||||
|
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
||||||
|
|
||||||
|
Result Notify(Result rc, const std::string& error_message) {
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
||||||
|
i18n::get(error_message)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
App::Notify("Success");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetMetaEntries(const Entry& e, MetaEntries& out) {
|
||||||
|
s32 count;
|
||||||
|
R_TRY(nsCountApplicationContentMeta(e.app_id, &count));
|
||||||
|
|
||||||
|
out.resize(count);
|
||||||
|
R_TRY(nsListApplicationContentMetaStatus(e.app_id, 0, out.data(), out.size(), &count));
|
||||||
|
|
||||||
|
out.resize(count);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// also sets the status to error.
|
||||||
|
void FakeNacpEntry(Entry& e) {
|
||||||
|
e.status = NacpLoadStatus::Error;
|
||||||
|
// fake the nacp entry
|
||||||
|
std::strcpy(e.lang.name, "Corrupted");
|
||||||
|
std::strcpy(e.lang.author, "Corrupted");
|
||||||
|
std::strcpy(e.display_version, "0.0.0");
|
||||||
|
e.control.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadControlImage(Entry& e) {
|
||||||
|
if (!e.image && e.control) {
|
||||||
|
const auto jpeg_size = e.control_size - sizeof(NacpStruct);
|
||||||
|
e.image = nvgCreateImageMem(App::GetVg(), 0, e.control->icon, jpeg_size);
|
||||||
|
e.control.reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadControlEntry(Entry& e, bool force_image_load = false) {
|
||||||
|
if (e.status == NacpLoadStatus::None) {
|
||||||
|
e.control = std::make_unique<NsApplicationControlData>();
|
||||||
|
if (R_FAILED(nsGetApplicationControlData(NsApplicationControlSource_Storage, e.app_id, e.control.get(), sizeof(NsApplicationControlData), &e.control_size))) {
|
||||||
|
FakeNacpEntry(e);
|
||||||
|
} else {
|
||||||
|
NacpLanguageEntry* lang{};
|
||||||
|
if (R_FAILED(nsGetApplicationDesiredLanguage(&e.control->nacp, &lang)) || !lang) {
|
||||||
|
FakeNacpEntry(e);
|
||||||
|
} else {
|
||||||
|
e.lang = *lang;
|
||||||
|
std::memcpy(e.display_version, e.control->nacp.display_version, sizeof(e.display_version));
|
||||||
|
e.status = NacpLoadStatus::Loaded;
|
||||||
|
|
||||||
|
if (force_image_load) {
|
||||||
|
LoadControlImage(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeEntry(NVGcontext* vg, Entry& e) {
|
||||||
|
nvgDeleteImage(vg, e.image);
|
||||||
|
e.image = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LaunchEntry(const Entry& e) {
|
||||||
|
const auto rc = appletRequestLaunchApplication(e.app_id, nullptr);
|
||||||
|
Notify(rc, "Failed to launch application");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Menu::Menu() : MenuBase{"Games"_i18n} {
|
||||||
|
this->SetActions(
|
||||||
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
|
SetPop();
|
||||||
|
}}),
|
||||||
|
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
|
||||||
|
if (m_entries.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LaunchEntry(m_entries[m_index]);
|
||||||
|
}}),
|
||||||
|
std::make_pair(Button::X, Action{"Options"_i18n, [this](){
|
||||||
|
auto options = std::make_shared<Sidebar>("Game Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
|
if (m_entries.size()) {
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Sort By"_i18n, [this](){
|
||||||
|
auto options = std::make_shared<Sidebar>("Sort Options"_i18n, Sidebar::Side::RIGHT);
|
||||||
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
|
|
||||||
|
SidebarEntryArray::Items sort_items;
|
||||||
|
sort_items.push_back("Updated"_i18n);
|
||||||
|
|
||||||
|
SidebarEntryArray::Items order_items;
|
||||||
|
order_items.push_back("Descending"_i18n);
|
||||||
|
order_items.push_back("Ascending"_i18n);
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||||
|
m_sort.Set(index_out);
|
||||||
|
SortAndFindLastFile();
|
||||||
|
}, m_sort.Get()));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
|
||||||
|
m_order.Set(index_out);
|
||||||
|
SortAndFindLastFile();
|
||||||
|
}, m_order.Get()));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryBool>("Hide forwarders"_i18n, m_hide_forwarders.Get(), [this](bool& v_out){
|
||||||
|
m_hide_forwarders.Set(v_out);
|
||||||
|
m_dirty = true;
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Info"_i18n, [this](){
|
||||||
|
|
||||||
|
}));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Launch random game"_i18n, [this](){
|
||||||
|
const auto random_index = randomGet64() % std::size(m_entries);
|
||||||
|
auto& e = m_entries[random_index];
|
||||||
|
LoadControlEntry(e, true);
|
||||||
|
|
||||||
|
App::Push(std::make_shared<OptionBox>(
|
||||||
|
"Launch "_i18n + e.GetName(),
|
||||||
|
"Back"_i18n, "Launch"_i18n, 1, [this, &e](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
LaunchEntry(e);
|
||||||
|
}
|
||||||
|
}, e.image
|
||||||
|
));
|
||||||
|
}));
|
||||||
|
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("List meta records"_i18n, [this](){
|
||||||
|
MetaEntries meta_entries;
|
||||||
|
const auto rc = GetMetaEntries(m_entries[m_index], meta_entries);
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
App::Push(std::make_shared<ui::ErrorBox>(rc,
|
||||||
|
i18n::get("Failed to list application meta entries")
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (meta_entries.empty()) {
|
||||||
|
App::Notify("No meta entries found...\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupList::Items items;
|
||||||
|
for (auto& e : meta_entries) {
|
||||||
|
char buf[256];
|
||||||
|
std::snprintf(buf, sizeof(buf), "Type: %s Storage: %s [%016lX][v%u]", ncm::GetMetaTypeStr(e.meta_type), ncm::GetStorageIdStr(e.storageID), e.application_id, e.version);
|
||||||
|
items.emplace_back(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
App::Push(std::make_shared<PopupList>(
|
||||||
|
"Entries", items, [this, meta_entries](auto op_index){
|
||||||
|
#if 0
|
||||||
|
if (op_index) {
|
||||||
|
const auto& e = meta_entries[*op_index];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}));
|
||||||
|
|
||||||
|
// completely deletes the application record and all data.
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Delete"_i18n, [this](){
|
||||||
|
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?";
|
||||||
|
App::Push(std::make_shared<OptionBox>(
|
||||||
|
buf,
|
||||||
|
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
const auto rc = nsDeleteApplicationCompletely(m_entries[m_index].app_id);
|
||||||
|
if (R_SUCCEEDED(Notify(rc, "Failed to delete application"))) {
|
||||||
|
FreeEntry(App::GetVg(), m_entries[m_index]);
|
||||||
|
m_entries.erase(m_entries.begin() + m_index);
|
||||||
|
SetIndex(m_index ? m_index - 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, m_entries[m_index].image
|
||||||
|
));
|
||||||
|
}, true));
|
||||||
|
|
||||||
|
// removes installed data but keeps the record, basically archiving.
|
||||||
|
options->Add(std::make_shared<SidebarEntryCallback>("Delete entity"_i18n, [this](){
|
||||||
|
const auto buf = "Are you sure you want to delete "_i18n + m_entries[m_index].GetName() + "?";
|
||||||
|
App::Push(std::make_shared<OptionBox>(
|
||||||
|
buf,
|
||||||
|
"Back"_i18n, "Delete"_i18n, 0, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
const auto rc = nsDeleteApplicationEntity(m_entries[m_index].app_id);
|
||||||
|
Notify(rc, "Failed to delete application");
|
||||||
|
}
|
||||||
|
}, m_entries[m_index].image
|
||||||
|
));
|
||||||
|
}, true));
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
);
|
||||||
|
|
||||||
|
const Vec4 v{75, 110, 370, 155};
|
||||||
|
const Vec2 pad{10, 10};
|
||||||
|
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
|
||||||
|
nsInitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu::~Menu() {
|
||||||
|
FreeEntries();
|
||||||
|
nsExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||||
|
MenuBase::Update(controller, touch);
|
||||||
|
m_list->OnUpdate(controller, touch, m_index, m_entries.size(), [this](bool touch, auto i) {
|
||||||
|
if (touch && m_index == i) {
|
||||||
|
FireAction(Button::A);
|
||||||
|
} else {
|
||||||
|
App::PlaySoundEffect(SoundEffect_Focus);
|
||||||
|
SetIndex(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||||
|
MenuBase::Draw(vg, theme);
|
||||||
|
|
||||||
|
// max images per frame, in order to not hit io / gpu too hard.
|
||||||
|
const int image_load_max = 2;
|
||||||
|
int image_load_count = 0;
|
||||||
|
|
||||||
|
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
|
||||||
|
const auto& [x, y, w, h] = v;
|
||||||
|
auto& e = m_entries[pos];
|
||||||
|
|
||||||
|
if (e.status == NacpLoadStatus::None) {
|
||||||
|
LoadControlEntry(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lazy load image
|
||||||
|
if (image_load_count < image_load_max) {
|
||||||
|
if (LoadControlImage(e)) {
|
||||||
|
image_load_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text_id = ThemeEntryID_TEXT;
|
||||||
|
const auto selected = pos == m_index;
|
||||||
|
if (selected) {
|
||||||
|
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||||
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
|
} else {
|
||||||
|
DrawElement(v, ThemeEntryID_GRID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float image_size = 115;
|
||||||
|
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
|
||||||
|
|
||||||
|
const auto text_off = 148;
|
||||||
|
const auto text_x = x + text_off;
|
||||||
|
const auto text_clip_w = w - 30.f - text_off;
|
||||||
|
const float font_size = 18;
|
||||||
|
m_scroll_name.Draw(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetName());
|
||||||
|
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
|
||||||
|
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::OnFocusGained() {
|
||||||
|
MenuBase::OnFocusGained();
|
||||||
|
if (m_dirty || m_entries.empty()) {
|
||||||
|
ScanHomebrew();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::SetIndex(s64 index) {
|
||||||
|
m_index = index;
|
||||||
|
if (!m_index) {
|
||||||
|
m_list->SetYoff(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char title_id[33];
|
||||||
|
std::snprintf(title_id, sizeof(title_id), "%016lX", m_entries[m_index].app_id);
|
||||||
|
SetTitleSubHeading(title_id);
|
||||||
|
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::ScanHomebrew() {
|
||||||
|
constexpr auto ENTRY_CHUNK_COUNT = 1000;
|
||||||
|
const auto hide_forwarders = m_hide_forwarders.Get();
|
||||||
|
TimeStamp ts;
|
||||||
|
|
||||||
|
FreeEntries();
|
||||||
|
m_entries.reserve(ENTRY_CHUNK_COUNT);
|
||||||
|
|
||||||
|
std::vector<NsApplicationRecord> record_list(ENTRY_CHUNK_COUNT);
|
||||||
|
s32 offset{};
|
||||||
|
while (true) {
|
||||||
|
s32 record_count{};
|
||||||
|
if (R_FAILED(nsListApplicationRecord(record_list.data(), record_list.size(), offset, &record_count))) {
|
||||||
|
log_write("failed to list application records at offset: %d\n", offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// finished parsing all entries.
|
||||||
|
if (!record_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (s32 i = 0; i < record_count; i++) {
|
||||||
|
const auto& e = record_list[i];
|
||||||
|
#if 0
|
||||||
|
u8 unk_x09 = e.unk_x09;
|
||||||
|
u64 unk_x0a;// = e.unk_x0a;
|
||||||
|
u8 unk_x10 = e.unk_x10;
|
||||||
|
u64 unk_x11;// = e.unk_x11;
|
||||||
|
memcpy(&unk_x0a, e.unk_x0a, sizeof(e.unk_x0a));
|
||||||
|
memcpy(&unk_x11, e.unk_x11, sizeof(e.unk_x11));
|
||||||
|
log_write("ID: %016lx got type: %u unk_x09: %u unk_x0a: %zu unk_x10: %u unk_x11: %zu\n", e.application_id, e.type,
|
||||||
|
unk_x09,
|
||||||
|
unk_x0a,
|
||||||
|
unk_x10,
|
||||||
|
unk_x11
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
if (hide_forwarders && (e.application_id & 0x0500000000000000) == 0x0500000000000000) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 size{};
|
||||||
|
// code for sorting by size, it's too slow however...
|
||||||
|
#if 0
|
||||||
|
ApplicationOccupiedSize occupied_size;
|
||||||
|
if (R_SUCCEEDED(nsCalculateApplicationOccupiedSize(e.application_id, (NsApplicationOccupiedSize*)&occupied_size))) {
|
||||||
|
for (auto& s : occupied_size.entry) {
|
||||||
|
size += s.sizeApplication;
|
||||||
|
size += s.sizePatch;
|
||||||
|
size += s.sizeAddOnContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_entries.emplace_back(e.application_id, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += record_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_is_reversed = false;
|
||||||
|
m_dirty = false;
|
||||||
|
log_write("games found: %zu time_taken: %.2f seconds %zu ms %zu ns\n", m_entries.size(), ts.GetSecondsD(), ts.GetMs(), ts.GetNs());
|
||||||
|
this->Sort();
|
||||||
|
SetIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::Sort() {
|
||||||
|
// const auto sort = m_sort.Get();
|
||||||
|
const auto order = m_order.Get();
|
||||||
|
|
||||||
|
if (order == OrderType_Ascending) {
|
||||||
|
if (!m_is_reversed) {
|
||||||
|
std::reverse(m_entries.begin(), m_entries.end());
|
||||||
|
m_is_reversed = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (m_is_reversed) {
|
||||||
|
std::reverse(m_entries.begin(), m_entries.end());
|
||||||
|
m_is_reversed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::SortAndFindLastFile() {
|
||||||
|
const auto app_id = m_entries[m_index].app_id;
|
||||||
|
Sort();
|
||||||
|
SetIndex(0);
|
||||||
|
|
||||||
|
s64 index = -1;
|
||||||
|
for (u64 i = 0; i < m_entries.size(); i++) {
|
||||||
|
if (app_id == m_entries[i].app_id) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
// guesstimate where the position is
|
||||||
|
if (index >= 9) {
|
||||||
|
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY());
|
||||||
|
} else {
|
||||||
|
m_list->SetYoff(0);
|
||||||
|
}
|
||||||
|
SetIndex(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Menu::FreeEntries() {
|
||||||
|
auto vg = App::GetVg();
|
||||||
|
|
||||||
|
for (auto&p : m_entries) {
|
||||||
|
FreeEntry(vg, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::ui::menu::game
|
||||||
@@ -184,8 +184,6 @@ Menu::Menu() : MenuBase{"GameCard"_i18n} {
|
|||||||
fsOpenDeviceOperator(std::addressof(m_dev_op));
|
fsOpenDeviceOperator(std::addressof(m_dev_op));
|
||||||
fsOpenGameCardDetectionEventNotifier(std::addressof(m_event_notifier));
|
fsOpenGameCardDetectionEventNotifier(std::addressof(m_event_notifier));
|
||||||
fsEventNotifierGetEventHandle(std::addressof(m_event_notifier), std::addressof(m_event), true);
|
fsEventNotifierGetEventHandle(std::addressof(m_event_notifier), std::addressof(m_event), true);
|
||||||
GcOnEvent();
|
|
||||||
UpdateStorageSize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
@@ -269,6 +267,13 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::OnFocusGained() {
|
||||||
|
MenuBase::OnFocusGained();
|
||||||
|
|
||||||
|
GcOnEvent();
|
||||||
|
UpdateStorageSize();
|
||||||
|
}
|
||||||
|
|
||||||
Result Menu::GcMount() {
|
Result Menu::GcMount() {
|
||||||
GcUnmount();
|
GcUnmount();
|
||||||
|
|
||||||
@@ -391,20 +396,18 @@ Result Menu::GcMount() {
|
|||||||
} else {
|
} else {
|
||||||
App::Notify("Gc install failed!"_i18n);
|
App::Notify("Gc install failed!"_i18n);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateStorageSize();
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
|
|
||||||
if (m_entries.size() > 1) {
|
if (m_entries.size() > 1) {
|
||||||
SetAction(Button::L, Action{"Prev"_i18n, [this](){
|
SetAction(Button::L2, Action{"Prev"_i18n, [this](){
|
||||||
if (m_entry_index != 0) {
|
if (m_entry_index != 0) {
|
||||||
OnChangeIndex(m_entry_index - 1);
|
OnChangeIndex(m_entry_index - 1);
|
||||||
}
|
}
|
||||||
}});
|
}});
|
||||||
SetAction(Button::R, Action{"Next"_i18n, [this](){
|
SetAction(Button::R2, Action{"Next"_i18n, [this](){
|
||||||
if (m_entry_index < m_entries.size()) {
|
if (m_entry_index < m_entries.size()) {
|
||||||
OnChangeIndex(m_entry_index + 1);
|
OnChangeIndex(m_entry_index + 1);
|
||||||
}
|
}
|
||||||
@@ -424,8 +427,8 @@ void Menu::GcUnmount() {
|
|||||||
m_lang_entry = {};
|
m_lang_entry = {};
|
||||||
FreeImage();
|
FreeImage();
|
||||||
|
|
||||||
RemoveAction(Button::L);
|
RemoveAction(Button::L2);
|
||||||
RemoveAction(Button::R);
|
RemoveAction(Button::R2);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Menu::GcPoll(bool* inserted) {
|
Result Menu::GcPoll(bool* inserted) {
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FreeEntry(NVGcontext* vg, NroEntry& e) {
|
||||||
|
nvgDeleteImage(vg, e.image);
|
||||||
|
e.image = 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
||||||
@@ -64,7 +69,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
|||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){
|
options->Add(std::make_shared<SidebarEntryBool>("Hide Sphaira"_i18n, m_hide_sphaira.Get(), [this](bool& enable){
|
||||||
m_hide_sphaira.Set(enable);
|
m_hide_sphaira.Set(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
@@ -80,11 +85,12 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
|||||||
"Back"_i18n, "Delete"_i18n, 1, [this](auto op_index){
|
"Back"_i18n, "Delete"_i18n, 1, [this](auto op_index){
|
||||||
if (op_index && *op_index) {
|
if (op_index && *op_index) {
|
||||||
if (R_SUCCEEDED(fs::FsNativeSd().DeleteFile(m_entries[m_index].path))) {
|
if (R_SUCCEEDED(fs::FsNativeSd().DeleteFile(m_entries[m_index].path))) {
|
||||||
|
FreeEntry(App::GetVg(), m_entries[m_index]);
|
||||||
m_entries.erase(m_entries.begin() + m_index);
|
m_entries.erase(m_entries.begin() + m_index);
|
||||||
SetIndex(m_index ? m_index - 1 : 0);
|
SetIndex(m_index ? m_index - 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, m_entries[m_index].image
|
||||||
));
|
));
|
||||||
}, true));
|
}, true));
|
||||||
|
|
||||||
@@ -97,7 +103,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
|||||||
if (op_index && *op_index) {
|
if (op_index && *op_index) {
|
||||||
InstallHomebrew();
|
InstallHomebrew();
|
||||||
}
|
}
|
||||||
}
|
}, m_entries[m_index].image
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
InstallHomebrew();
|
InstallHomebrew();
|
||||||
@@ -114,11 +120,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
auto vg = App::GetVg();
|
FreeEntries();
|
||||||
|
|
||||||
for (auto&p : m_entries) {
|
|
||||||
nvgDeleteImage(vg, p.image);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
void Menu::Update(Controller* controller, TouchInfo* touch) {
|
||||||
@@ -160,7 +162,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto text_id = ThemeEntryID_TEXT;
|
auto text_id = ThemeEntryID_TEXT;
|
||||||
if (pos == m_index) {
|
const auto selected = pos == m_index;
|
||||||
|
if (selected) {
|
||||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
} else {
|
} else {
|
||||||
@@ -168,25 +171,23 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const float image_size = 115;
|
const float image_size = 115;
|
||||||
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 15);
|
gfx::drawImage(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage(), 5);
|
||||||
|
|
||||||
nvgSave(vg);
|
const auto text_off = 148;
|
||||||
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
|
const auto text_x = x + text_off;
|
||||||
{
|
const auto text_clip_w = w - 30.f - text_off;
|
||||||
bool has_star = false;
|
bool has_star = false;
|
||||||
if (IsStarEnabled()) {
|
if (IsStarEnabled()) {
|
||||||
if (!e.has_star.has_value()) {
|
if (!e.has_star.has_value()) {
|
||||||
e.has_star = fs::FsNativeSd().FileExists(GenerateStarPath(e.path));
|
e.has_star = fs::FsNativeSd().FileExists(GenerateStarPath(e.path));
|
||||||
}
|
|
||||||
has_star = e.has_star.value();
|
|
||||||
}
|
}
|
||||||
|
has_star = e.has_star.value();
|
||||||
const float font_size = 18;
|
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s%s", has_star ? "\u2605 " : "", e.GetName());
|
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 80, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
|
|
||||||
gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
|
|
||||||
}
|
}
|
||||||
nvgRestore(vg);
|
|
||||||
|
const float font_size = 18;
|
||||||
|
m_scroll_name.DrawArgs(vg, selected, text_x, y + 45, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s%s", has_star ? "\u2605 " : "", e.GetName());
|
||||||
|
m_scroll_author.Draw(vg, selected, text_x, y + 80, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
|
||||||
|
m_scroll_version.Draw(vg, selected, text_x, y + 115, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +239,7 @@ void Menu::InstallHomebrew() {
|
|||||||
|
|
||||||
void Menu::ScanHomebrew() {
|
void Menu::ScanHomebrew() {
|
||||||
TimeStamp ts;
|
TimeStamp ts;
|
||||||
|
FreeEntries();
|
||||||
nro_scan("/switch", m_entries, m_hide_sphaira.Get());
|
nro_scan("/switch", m_entries, m_hide_sphaira.Get());
|
||||||
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSecondsD());
|
log_write("nros found: %zu time_taken: %.2f\n", m_entries.size(), ts.GetSecondsD());
|
||||||
|
|
||||||
@@ -394,6 +396,16 @@ void Menu::SortAndFindLastFile() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Menu::FreeEntries() {
|
||||||
|
auto vg = App::GetVg();
|
||||||
|
|
||||||
|
for (auto&p : m_entries) {
|
||||||
|
FreeEntry(vg, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
|
Result Menu::InstallHomebrew(const fs::FsPath& path, const NacpStruct& nacp, const std::vector<u8>& icon) {
|
||||||
OwoConfig config{};
|
OwoConfig config{};
|
||||||
config.nro_path = path.toString();
|
config.nro_path = path.toString();
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ Menu::Menu() : MenuBase{"Irs"_i18n} {
|
|||||||
options->Add(std::make_shared<SidebarEntryBool>("External Light Filter"_i18n, m_config.is_external_light_filter_enabled, [this](bool& enable){
|
options->Add(std::make_shared<SidebarEntryBool>("External Light Filter"_i18n, m_config.is_external_light_filter_enabled, [this](bool& enable){
|
||||||
m_config.is_external_light_filter_enabled = enable;
|
m_config.is_external_light_filter_enabled = enable;
|
||||||
UpdateConfig(&m_config);
|
UpdateConfig(&m_config);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Load Default"_i18n, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Load Default"_i18n, [this](){
|
||||||
@@ -204,17 +204,6 @@ Menu::Menu() : MenuBase{"Irs"_i18n} {
|
|||||||
PollCameraStatus(true);
|
PollCameraStatus(true);
|
||||||
// load default config
|
// load default config
|
||||||
LoadDefaultConfig();
|
LoadDefaultConfig();
|
||||||
// poll to get first available handle.
|
|
||||||
PollCameraStatus(false);
|
|
||||||
|
|
||||||
// find the first available entry and connect to that.
|
|
||||||
for (s64 i = 0; i < std::size(m_entries); i++) {
|
|
||||||
if (m_entries[i].status == IrsIrCameraStatus_Available) {
|
|
||||||
m_index = i;
|
|
||||||
UpdateConfig(&m_config);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
@@ -307,6 +296,20 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
|
|
||||||
void Menu::OnFocusGained() {
|
void Menu::OnFocusGained() {
|
||||||
MenuBase::OnFocusGained();
|
MenuBase::OnFocusGained();
|
||||||
|
|
||||||
|
if (m_entries[m_index].status != IrsIrCameraStatus_Available) {
|
||||||
|
// poll to get first available handle.
|
||||||
|
PollCameraStatus(false);
|
||||||
|
|
||||||
|
// find the first available entry and connect to that.
|
||||||
|
for (s64 i = 0; i < std::size(m_entries); i++) {
|
||||||
|
if (m_entries[i].status == IrsIrCameraStatus_Available) {
|
||||||
|
m_index = i;
|
||||||
|
UpdateConfig(&m_config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::PollCameraStatus(bool statup) {
|
void Menu::PollCameraStatus(bool statup) {
|
||||||
|
|||||||
@@ -6,12 +6,20 @@
|
|||||||
#include "ui/progress_box.hpp"
|
#include "ui/progress_box.hpp"
|
||||||
#include "ui/error_box.hpp"
|
#include "ui/error_box.hpp"
|
||||||
|
|
||||||
|
#include "ui/menus/irs_menu.hpp"
|
||||||
|
#include "ui/menus/themezer.hpp"
|
||||||
|
#include "ui/menus/ghdl.hpp"
|
||||||
|
#include "ui/menus/usb_menu.hpp"
|
||||||
|
#include "ui/menus/ftp_menu.hpp"
|
||||||
|
#include "ui/menus/gc_menu.hpp"
|
||||||
|
#include "ui/menus/game_menu.hpp"
|
||||||
|
#include "ui/menus/appstore.hpp"
|
||||||
|
|
||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
#include "log.hpp"
|
#include "log.hpp"
|
||||||
#include "download.hpp"
|
#include "download.hpp"
|
||||||
#include "defines.hpp"
|
#include "defines.hpp"
|
||||||
#include "i18n.hpp"
|
#include "i18n.hpp"
|
||||||
#include "ui/menus/usb_menu.hpp"
|
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <minizip/unzip.h>
|
#include <minizip/unzip.h>
|
||||||
@@ -23,6 +31,22 @@ namespace {
|
|||||||
constexpr const char* GITHUB_URL{"https://api.github.com/repos/ITotalJustice/sphaira/releases/latest"};
|
constexpr const char* GITHUB_URL{"https://api.github.com/repos/ITotalJustice/sphaira/releases/latest"};
|
||||||
constexpr fs::FsPath CACHE_PATH{"/switch/sphaira/cache/sphaira_latest.json"};
|
constexpr fs::FsPath CACHE_PATH{"/switch/sphaira/cache/sphaira_latest.json"};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto MiscMenuFuncGenerator() {
|
||||||
|
return std::make_shared<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const MiscMenuEntry MISC_MENU_ENTRIES[] = {
|
||||||
|
{ .name = "Appstore", .title = "Appstore", .func = MiscMenuFuncGenerator<ui::menu::appstore::Menu>, .flag = MiscMenuFlag_Shortcut },
|
||||||
|
{ .name = "Games", .title = "Games", .func = MiscMenuFuncGenerator<ui::menu::game::Menu>, .flag = MiscMenuFlag_Shortcut },
|
||||||
|
{ .name = "Themezer", .title = "Themezer", .func = MiscMenuFuncGenerator<ui::menu::themezer::Menu>, .flag = MiscMenuFlag_Shortcut },
|
||||||
|
{ .name = "GitHub", .title = "GitHub", .func = MiscMenuFuncGenerator<ui::menu::gh::Menu>, .flag = MiscMenuFlag_Shortcut },
|
||||||
|
{ .name = "FTP", .title = "FTP Install", .func = MiscMenuFuncGenerator<ui::menu::ftp::Menu>, .flag = MiscMenuFlag_Install },
|
||||||
|
{ .name = "USB", .title = "USB Install", .func = MiscMenuFuncGenerator<ui::menu::usb::Menu>, .flag = MiscMenuFlag_Install },
|
||||||
|
{ .name = "GameCard", .title = "GameCard Install", .func = MiscMenuFuncGenerator<ui::menu::gc::Menu>, .flag = MiscMenuFlag_Shortcut|MiscMenuFlag_Install },
|
||||||
|
{ .name = "IRS", .title = "IRS (Infrared Joycon Camera)", .func = MiscMenuFuncGenerator<ui::menu::irs::Menu>, .flag = MiscMenuFlag_Shortcut },
|
||||||
|
};
|
||||||
|
|
||||||
auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> bool {
|
auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string version) -> bool {
|
||||||
static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"};
|
static fs::FsPath zip_out{"/switch/sphaira/cache/update.zip"};
|
||||||
constexpr auto chunk_size = 1024 * 512; // 512KiB
|
constexpr auto chunk_size = 1024 * 512; // 512KiB
|
||||||
@@ -140,8 +164,24 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto CreateRightSideMenu() -> std::shared_ptr<MenuBase> {
|
||||||
|
const auto name = App::GetApp()->m_right_side_menu.Get();
|
||||||
|
|
||||||
|
for (auto& e : GetMiscMenuEntries()) {
|
||||||
|
if (e.name == name) {
|
||||||
|
return e.func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<ui::menu::appstore::Menu>();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry> {
|
||||||
|
return MISC_MENU_ENTRIES;
|
||||||
|
}
|
||||||
|
|
||||||
MainMenu::MainMenu() {
|
MainMenu::MainMenu() {
|
||||||
curl::Api().ToFileAsync(
|
curl::Api().ToFileAsync(
|
||||||
curl::Url{GITHUB_URL},
|
curl::Url{GITHUB_URL},
|
||||||
@@ -209,9 +249,7 @@ MainMenu::MainMenu() {
|
|||||||
|
|
||||||
this->SetActions(
|
this->SetActions(
|
||||||
std::make_pair(Button::START, Action{App::Exit}),
|
std::make_pair(Button::START, Action{App::Exit}),
|
||||||
std::make_pair(Button::SELECT, Action{"Misc"_i18n, [this](){
|
std::make_pair(Button::SELECT, Action{App::DisplayMiscOptions}),
|
||||||
App::DisplayMiscOptions();
|
|
||||||
}}),
|
|
||||||
std::make_pair(Button::Y, Action{"Menu"_i18n, [this](){
|
std::make_pair(Button::Y, Action{"Menu"_i18n, [this](){
|
||||||
auto options = std::make_shared<Sidebar>("Menu Options"_i18n, "v" APP_VERSION_HASH, Sidebar::Side::LEFT);
|
auto options = std::make_shared<Sidebar>("Menu Options"_i18n, "v" APP_VERSION_HASH, Sidebar::Side::LEFT);
|
||||||
ON_SCOPE_EXIT(App::Push(options));
|
ON_SCOPE_EXIT(App::Push(options));
|
||||||
@@ -242,15 +280,15 @@ MainMenu::MainMenu() {
|
|||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Ftp"_i18n, App::GetFtpEnable(), [](bool& enable){
|
options->Add(std::make_shared<SidebarEntryBool>("Ftp"_i18n, App::GetFtpEnable(), [](bool& enable){
|
||||||
App::SetFtpEnable(enable);
|
App::SetFtpEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Mtp"_i18n, App::GetMtpEnable(), [](bool& enable){
|
options->Add(std::make_shared<SidebarEntryBool>("Mtp"_i18n, App::GetMtpEnable(), [](bool& enable){
|
||||||
App::SetMtpEnable(enable);
|
App::SetMtpEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [](bool& enable){
|
options->Add(std::make_shared<SidebarEntryBool>("Nxlink"_i18n, App::GetNxlinkEnable(), [](bool& enable){
|
||||||
App::SetNxlinkEnable(enable);
|
App::SetNxlinkEnable(enable);
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
if (m_update_state == UpdateState::Update) {
|
if (m_update_state == UpdateState::Update) {
|
||||||
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
|
options->Add(std::make_shared<SidebarEntryCallback>("Download update: "_i18n + m_update_version, [this](){
|
||||||
@@ -289,7 +327,7 @@ MainMenu::MainMenu() {
|
|||||||
|
|
||||||
m_homebrew_menu = std::make_shared<homebrew::Menu>();
|
m_homebrew_menu = std::make_shared<homebrew::Menu>();
|
||||||
m_filebrowser_menu = std::make_shared<filebrowser::Menu>(m_homebrew_menu->GetHomebrewList());
|
m_filebrowser_menu = std::make_shared<filebrowser::Menu>(m_homebrew_menu->GetHomebrewList());
|
||||||
m_app_store_menu = std::make_shared<appstore::Menu>(m_homebrew_menu->GetHomebrewList());
|
m_right_side_menu = CreateRightSideMenu();
|
||||||
m_current_menu = m_homebrew_menu;
|
m_current_menu = m_homebrew_menu;
|
||||||
|
|
||||||
AddOnLRPress();
|
AddOnLRPress();
|
||||||
@@ -340,16 +378,16 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
|
|||||||
|
|
||||||
void MainMenu::AddOnLRPress() {
|
void MainMenu::AddOnLRPress() {
|
||||||
if (m_current_menu != m_filebrowser_menu) {
|
if (m_current_menu != m_filebrowser_menu) {
|
||||||
const auto label = m_current_menu == m_homebrew_menu ? "Files" : "Apps";
|
const auto label = m_current_menu == m_homebrew_menu ? m_filebrowser_menu->GetShortTitle() : m_homebrew_menu->GetShortTitle();
|
||||||
SetAction(Button::L, Action{i18n::get(label), [this]{
|
SetAction(Button::L, Action{i18n::get(label), [this]{
|
||||||
OnLRPress(m_filebrowser_menu, Button::L);
|
OnLRPress(m_filebrowser_menu, Button::L);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_current_menu != m_app_store_menu) {
|
if (m_current_menu != m_right_side_menu) {
|
||||||
const auto label = m_current_menu == m_homebrew_menu ? "Store" : "Apps";
|
const auto label = m_current_menu == m_homebrew_menu ? m_right_side_menu->GetShortTitle() : m_homebrew_menu->GetShortTitle();
|
||||||
SetAction(Button::R, Action{i18n::get(label), [this]{
|
SetAction(Button::R, Action{i18n::get(label), [this]{
|
||||||
OnLRPress(m_app_store_menu, Button::R);
|
OnLRPress(m_right_side_menu, Button::R);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
|||||||
options->Add(std::make_shared<SidebarEntryBool>("Nsfw"_i18n, m_nsfw.Get(), [this](bool& v_out){
|
options->Add(std::make_shared<SidebarEntryBool>("Nsfw"_i18n, m_nsfw.Get(), [this](bool& v_out){
|
||||||
m_nsfw.Set(v_out);
|
m_nsfw.Set(v_out);
|
||||||
InvalidateAllPages();
|
InvalidateAllPages();
|
||||||
}, "Enabled"_i18n, "Disabled"_i18n));
|
}));
|
||||||
|
|
||||||
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
|
||||||
if (m_sort.Get() != index_out) {
|
if (m_sort.Get() != index_out) {
|
||||||
@@ -453,7 +453,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}}),
|
}}),
|
||||||
std::make_pair(Button::R, Action{"Next Page"_i18n, [this](){
|
std::make_pair(Button::R2, Action{"Next"_i18n, [this](){
|
||||||
m_page_index++;
|
m_page_index++;
|
||||||
if (m_page_index >= m_page_index_max) {
|
if (m_page_index >= m_page_index_max) {
|
||||||
m_page_index = m_page_index_max - 1;
|
m_page_index = m_page_index_max - 1;
|
||||||
@@ -461,7 +461,7 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
|
|||||||
PackListDownload();
|
PackListDownload();
|
||||||
}
|
}
|
||||||
}}),
|
}}),
|
||||||
std::make_pair(Button::L, Action{"Prev Page"_i18n, [this](){
|
std::make_pair(Button::L2, Action{"Prev"_i18n, [this](){
|
||||||
if (m_page_index) {
|
if (m_page_index) {
|
||||||
m_page_index--;
|
m_page_index--;
|
||||||
PackListDownload();
|
PackListDownload();
|
||||||
@@ -537,7 +537,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
auto& e = page.m_packList[pos];
|
auto& e = page.m_packList[pos];
|
||||||
|
|
||||||
auto text_id = ThemeEntryID_TEXT;
|
auto text_id = ThemeEntryID_TEXT;
|
||||||
if (pos == m_index) {
|
const auto selected = pos == m_index;
|
||||||
|
if (selected) {
|
||||||
text_id = ThemeEntryID_TEXT_SELECTED;
|
text_id = ThemeEntryID_TEXT_SELECTED;
|
||||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
} else {
|
} else {
|
||||||
@@ -604,16 +605,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx::drawImage(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage(), 15);
|
gfx::drawImage(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage(), 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
nvgSave(vg);
|
const auto text_x = x + xoff;
|
||||||
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
|
const auto text_clip_w = w - 30.f - xoff;
|
||||||
{
|
const float font_size = 18;
|
||||||
gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s", e.details.name.c_str());
|
m_scroll_name.Draw(vg, selected, text_x, y + 180 + 20, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.details.name.c_str());
|
||||||
gfx::drawTextArgs(vg, x + xoff, y + 180 + 55, 18, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s", e.creator.display_name.c_str());
|
m_scroll_author.Draw(vg, selected, text_x, y + 180 + 55, text_clip_w, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.creator.display_name.c_str());
|
||||||
}
|
|
||||||
nvgRestore(vg);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
namespace sphaira::ui::menu::usb {
|
namespace sphaira::ui::menu::usb {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr u64 CONNECTION_TIMEOUT = 1e+9 * 1; // 1 second
|
constexpr u64 CONNECTION_TIMEOUT = UINT64_MAX;
|
||||||
constexpr u64 TRANSFER_TIMEOUT = 1e+9 * 5; // 5 seconds
|
constexpr u64 TRANSFER_TIMEOUT = UINT64_MAX;
|
||||||
|
|
||||||
void thread_func(void* user) {
|
void thread_func(void* user) {
|
||||||
auto app = static_cast<Menu*>(user);
|
auto app = static_cast<Menu*>(user);
|
||||||
@@ -22,6 +22,9 @@ void thread_func(void* user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
|
const auto rc = app->m_usb_source->IsUsbConnected(CONNECTION_TIMEOUT);
|
||||||
|
if (rc == app->m_usb_source->Result_Cancelled) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// set connected status
|
// set connected status
|
||||||
mutexLock(&app->m_mutex);
|
mutexLock(&app->m_mutex);
|
||||||
@@ -76,6 +79,7 @@ Menu::Menu() : MenuBase{"USB"_i18n} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mutexInit(&m_mutex);
|
mutexInit(&m_mutex);
|
||||||
|
|
||||||
if (m_state != State::Failed) {
|
if (m_state != State::Failed) {
|
||||||
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, 0x2C, 1);
|
threadCreate(&m_thread, thread_func, this, nullptr, 1024*32, 0x2C, 1);
|
||||||
threadStart(&m_thread);
|
threadStart(&m_thread);
|
||||||
@@ -85,6 +89,7 @@ Menu::Menu() : MenuBase{"USB"_i18n} {
|
|||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
// signal for thread to exit and wait.
|
// signal for thread to exit and wait.
|
||||||
m_stop_source.request_stop();
|
m_stop_source.request_stop();
|
||||||
|
m_usb_source->SignalCancel();
|
||||||
threadWaitForExit(&m_thread);
|
threadWaitForExit(&m_thread);
|
||||||
threadClose(&m_thread);
|
threadClose(&m_thread);
|
||||||
|
|
||||||
@@ -117,6 +122,8 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
|
|||||||
|
|
||||||
const auto rc = yati::InstallFromSource(pbox, m_usb_source, file_name);
|
const auto rc = yati::InstallFromSource(pbox, m_usb_source, file_name);
|
||||||
if (R_FAILED(rc)) {
|
if (R_FAILED(rc)) {
|
||||||
|
m_usb_source->SignalCancel();
|
||||||
|
log_write("exiting usb install\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "ui/nvg_util.hpp"
|
#include "ui/nvg_util.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
@@ -10,6 +11,9 @@
|
|||||||
namespace sphaira::ui::gfx {
|
namespace sphaira::ui::gfx {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto ALIGN_HOR = NVG_ALIGN_LEFT|NVG_ALIGN_CENTER|NVG_ALIGN_RIGHT;
|
||||||
|
constexpr auto ALIGN_VER = NVG_ALIGN_TOP|NVG_ALIGN_MIDDLE|NVG_ALIGN_BOTTOM|NVG_ALIGN_BASELINE;
|
||||||
|
|
||||||
constexpr std::array buttons = {
|
constexpr std::array buttons = {
|
||||||
std::pair{Button::A, "\uE0E0"},
|
std::pair{Button::A, "\uE0E0"},
|
||||||
std::pair{Button::B, "\uE0E1"},
|
std::pair{Button::B, "\uE0E1"},
|
||||||
@@ -17,10 +21,8 @@ constexpr std::array buttons = {
|
|||||||
std::pair{Button::Y, "\uE0E3"},
|
std::pair{Button::Y, "\uE0E3"},
|
||||||
std::pair{Button::L, "\uE0E4"},
|
std::pair{Button::L, "\uE0E4"},
|
||||||
std::pair{Button::R, "\uE0E5"},
|
std::pair{Button::R, "\uE0E5"},
|
||||||
std::pair{Button::L, "\uE0E6"},
|
std::pair{Button::L2, "\uE0E6"},
|
||||||
std::pair{Button::R, "\uE0E7"},
|
std::pair{Button::R2, "\uE0E7"},
|
||||||
std::pair{Button::L2, "\uE0E8"},
|
|
||||||
std::pair{Button::R2, "\uE0E9"},
|
|
||||||
std::pair{Button::UP, "\uE0EB"},
|
std::pair{Button::UP, "\uE0EB"},
|
||||||
std::pair{Button::DOWN, "\uE0EC"},
|
std::pair{Button::DOWN, "\uE0EC"},
|
||||||
std::pair{Button::LEFT, "\uE0ED"},
|
std::pair{Button::LEFT, "\uE0ED"},
|
||||||
@@ -33,8 +35,29 @@ constexpr std::array buttons = {
|
|||||||
std::pair{Button::R3, "\uE105"},
|
std::pair{Button::R3, "\uE105"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// software based clipping, saves a few cpu cycles.
|
||||||
|
bool ClipRect(float x, float y) {
|
||||||
|
return x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipText(float x, float y, int align) {
|
||||||
|
if ((!(align & ALIGN_HOR) || (align & NVG_ALIGN_LEFT)) && x >= SCREEN_WIDTH) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!(align & ALIGN_VER) || (align & NVG_ALIGN_TOP)) && y >= SCREEN_HEIGHT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// NEW ---------------------
|
// NEW ---------------------
|
||||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) {
|
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rounded) {
|
||||||
|
if (ClipRect(v.x, v.y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
||||||
nvgFillColor(vg, c);
|
nvgFillColor(vg, c);
|
||||||
@@ -42,6 +65,10 @@ void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, float rou
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) {
|
void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, float rounded) {
|
||||||
|
if (ClipRect(v.x, v.y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
|
||||||
nvgFillPaint(vg, p);
|
nvgFillPaint(vg, p);
|
||||||
@@ -120,6 +147,10 @@ void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGcolor& c) {
|
void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGcolor& c) {
|
||||||
|
if (ClipRect(v.x, v.y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto corner_radius = 0.5;
|
const auto corner_radius = 0.5;
|
||||||
drawRectOutlineInternal(vg, theme, size, v);
|
drawRectOutlineInternal(vg, theme, size, v);
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
@@ -129,6 +160,10 @@ void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
|
void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
|
||||||
|
if (ClipText(v.x, v.y, align)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
nvgFontSize(vg, size);
|
nvgFontSize(vg, size);
|
||||||
nvgTextAlign(vg, align);
|
nvgTextAlign(vg, align);
|
||||||
@@ -166,6 +201,10 @@ void drawImage(NVGcontext* vg, float x, float y, float w, float h, int texture,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align, const char* end) {
|
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align, const char* end) {
|
||||||
|
if (ClipText(x, y, align)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
nvgBeginPath(vg);
|
nvgBeginPath(vg);
|
||||||
nvgFontSize(vg, size);
|
nvgFontSize(vg, size);
|
||||||
nvgTextAlign(vg, align);
|
nvgTextAlign(vg, align);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ auto OptionBoxEntry::Selected(bool enable) -> void {
|
|||||||
m_selected = enable;
|
m_selected = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionBox::OptionBox(const std::string& message, const Option& a, Callback cb)
|
OptionBox::OptionBox(const std::string& message, const Option& a, Callback cb, int image)
|
||||||
: m_message{message}
|
: m_message{message}
|
||||||
, m_callback{cb} {
|
, m_callback{cb} {
|
||||||
|
|
||||||
@@ -40,14 +40,15 @@ OptionBox::OptionBox(const std::string& message, const Option& a, Callback cb)
|
|||||||
Setup(0);
|
Setup(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb)
|
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb, int image)
|
||||||
: OptionBox{message, a, b, 0, cb} {
|
: OptionBox{message, a, b, 0, cb, image} {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb)
|
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb, int image)
|
||||||
: m_message{message}
|
: m_message{message}
|
||||||
, m_callback{cb} {
|
, m_callback{cb}
|
||||||
|
, m_image{image} {
|
||||||
|
|
||||||
m_pos.w = 770.f;
|
m_pos.w = 770.f;
|
||||||
m_pos.h = 295.f;
|
m_pos.h = 295.f;
|
||||||
@@ -65,17 +66,6 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
|
|||||||
Setup(index);
|
Setup(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, Callback cb)
|
|
||||||
: OptionBox{message, a, b, c, 0, cb} {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb)
|
|
||||||
: m_message{message}
|
|
||||||
, m_callback{cb} {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
auto OptionBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
auto OptionBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
||||||
Widget::Update(controller, touch);
|
Widget::Update(controller, touch);
|
||||||
|
|
||||||
@@ -92,13 +82,25 @@ auto OptionBox::Update(Controller* controller, TouchInfo* touch) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto OptionBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
auto OptionBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
||||||
const float padding = 15;
|
|
||||||
gfx::dimBackground(vg);
|
gfx::dimBackground(vg);
|
||||||
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
|
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP), 5);
|
||||||
|
|
||||||
nvgSave(vg);
|
nvgSave(vg);
|
||||||
nvgTextLineHeight(vg, 1.5);
|
nvgTextLineHeight(vg, 1.5);
|
||||||
gfx::drawTextBox(vg, m_pos.x + padding, m_pos.y + 110.f, 26.f, m_pos.w - padding*2, theme->GetColour(ThemeEntryID_TEXT), m_message.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
|
if (m_image) {
|
||||||
|
Vec4 image{m_pos};
|
||||||
|
image.x += 40;
|
||||||
|
image.y += 40;
|
||||||
|
image.w = 150;
|
||||||
|
image.h = 150;
|
||||||
|
|
||||||
|
const float padding = 40;
|
||||||
|
gfx::drawImage(vg, image, m_image, 5);
|
||||||
|
gfx::drawTextBox(vg, image.x + image.w + padding, m_pos.y + 110.f, 22.f, m_pos.w - (image.x - m_pos.x) - image.w - padding*2, theme->GetColour(ThemeEntryID_TEXT), m_message.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_BASELINE);
|
||||||
|
} else {
|
||||||
|
const float padding = 30;
|
||||||
|
gfx::drawTextBox(vg, m_pos.x + padding, m_pos.y + 110.f, 24.f, m_pos.w - padding*2, theme->GetColour(ThemeEntryID_TEXT), m_message.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
|
||||||
|
}
|
||||||
nvgRestore(vg);
|
nvgRestore(vg);
|
||||||
|
|
||||||
gfx::drawRect(vg, m_spacer_line, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
|
gfx::drawRect(vg, m_spacer_line, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
|
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
|
||||||
const auto& [x, y, w, h] = v;
|
const auto& [x, y, w, h] = v;
|
||||||
auto colour = ThemeEntryID_TEXT;
|
auto colour = ThemeEntryID_TEXT;
|
||||||
if (m_index == i) {
|
const auto selected = m_index == i;
|
||||||
|
if (selected) {
|
||||||
gfx::drawRectOutline(vg, theme, 4.f, v);
|
gfx::drawRectOutline(vg, theme, 4.f, v);
|
||||||
} else {
|
} else {
|
||||||
if (i != m_items.size() - 1) {
|
if (i != m_items.size() - 1) {
|
||||||
@@ -126,7 +127,9 @@ auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
gfx::drawText(vg, x + w - m_text_xoffset, y + (h / 2.f), 20.f, "\uE14B", NULL, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(colour));
|
gfx::drawText(vg, x + w - m_text_xoffset, y + (h / 2.f), 20.f, "\uE14B", NULL, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(colour));
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx::drawText(vg, x + m_text_xoffset, y + (h / 2.f), 20.f, m_items[i].c_str(), NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour));
|
const auto text_x = x + m_text_xoffset;
|
||||||
|
const auto text_clip_w = w - 60.f - m_text_xoffset;
|
||||||
|
m_scroll_text.Draw(vg, selected, text_x, y + (h / 2.f), text_clip_w, 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(colour), m_items[i]);
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget::Draw(vg, theme);
|
Widget::Draw(vg, theme);
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gfx::dimBackground(vg);
|
gfx::dimBackground(vg);
|
||||||
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
|
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP), 5);
|
||||||
|
|
||||||
// The pop up shape.
|
// The pop up shape.
|
||||||
// const Vec4 box = { 255, 145, 770, 430 };
|
// const Vec4 box = { 255, 145, 770, 430 };
|
||||||
|
|||||||
81
sphaira/source/ui/scrolling_text.cpp
Normal file
81
sphaira/source/ui/scrolling_text.cpp
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
#include "ui/scrolling_text.hpp"
|
||||||
|
#include "ui/nvg_util.hpp"
|
||||||
|
#include "app.hpp"
|
||||||
|
#include <cstdarg>
|
||||||
|
|
||||||
|
namespace sphaira::ui {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto GetTextScrollSpeed() -> float {
|
||||||
|
switch (App::GetTextScrollSpeed()) {
|
||||||
|
case 0: return 0.5;
|
||||||
|
default: case 1: return 1.0;
|
||||||
|
case 2: return 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawClipped(NVGcontext* vg, const Vec4& clip, float x, float y, float size, int align, const NVGcolor& colour, const std::string& str) {
|
||||||
|
nvgSave(vg);
|
||||||
|
nvgIntersectScissor(vg, clip.x, clip.y, clip.w, clip.h); // clip
|
||||||
|
gfx::drawText(vg, x, y, size, colour, str.c_str(), align);
|
||||||
|
nvgRestore(vg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ScrollingText::Draw(NVGcontext* vg, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const std::string& text_entry) {
|
||||||
|
const Vec4 clip{x, 0, w, 720};
|
||||||
|
|
||||||
|
if (!focus) {
|
||||||
|
DrawClipped(vg, clip, x, y, size, align, colour, text_entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_str != text_entry) {
|
||||||
|
m_str = text_entry;
|
||||||
|
m_tick = 0;
|
||||||
|
m_text_xoff = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float bounds[4];
|
||||||
|
auto value_str = text_entry;
|
||||||
|
nvgFontSize(vg, size);
|
||||||
|
nvgTextAlign(vg, align);
|
||||||
|
nvgTextBounds(vg, 0, 0, value_str.c_str(), nullptr, bounds);
|
||||||
|
|
||||||
|
if (focus) {
|
||||||
|
const auto scroll_amount = GetTextScrollSpeed();
|
||||||
|
if (bounds[2] > w) {
|
||||||
|
value_str += " ";
|
||||||
|
nvgTextBounds(vg, 0, 0, value_str.c_str(), nullptr, bounds);
|
||||||
|
|
||||||
|
if (!m_text_xoff) {
|
||||||
|
m_tick++;
|
||||||
|
if (m_tick >= 60) {
|
||||||
|
m_tick = 0;
|
||||||
|
m_text_xoff += scroll_amount;
|
||||||
|
}
|
||||||
|
} else if (bounds[2] > m_text_xoff) {
|
||||||
|
m_text_xoff += std::min(scroll_amount, bounds[2] - m_text_xoff);
|
||||||
|
} else {
|
||||||
|
m_text_xoff = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_str += text_entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= m_text_xoff;
|
||||||
|
DrawClipped(vg, clip, x, y, size, align, colour, value_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScrollingText::DrawArgs(NVGcontext* vg, bool focus, float x, float y, float w, float size, int align, const NVGcolor& colour, const char* s, ...) {
|
||||||
|
std::va_list v{};
|
||||||
|
va_start(v, s);
|
||||||
|
char buffer[0x100];
|
||||||
|
std::vsnprintf(buffer, sizeof(buffer), s, v);
|
||||||
|
va_end(v);
|
||||||
|
Draw(vg, focus, x, y, w, size, align, colour, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::ui
|
||||||
@@ -43,6 +43,13 @@ SidebarEntryBool::SidebarEntryBool(std::string title, bool option, Callback cb,
|
|||||||
, m_true_str{std::move(true_str)}
|
, m_true_str{std::move(true_str)}
|
||||||
, m_false_str{std::move(false_str)} {
|
, m_false_str{std::move(false_str)} {
|
||||||
|
|
||||||
|
if (m_true_str == "On") {
|
||||||
|
m_true_str = i18n::get(m_true_str);
|
||||||
|
}
|
||||||
|
if (m_false_str == "Off") {
|
||||||
|
m_false_str = i18n::get(m_false_str);
|
||||||
|
}
|
||||||
|
|
||||||
SetAction(Button::A, Action{"OK"_i18n, [this](){
|
SetAction(Button::A, Action{"OK"_i18n, [this](){
|
||||||
m_option ^= 1;
|
m_option ^= 1;
|
||||||
m_callback(m_option);
|
m_callback(m_option);
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ auto Widget::GetUiButtons() const -> uiButtons {
|
|||||||
uiButtons draw_actions;
|
uiButtons draw_actions;
|
||||||
draw_actions.reserve(m_actions.size());
|
draw_actions.reserve(m_actions.size());
|
||||||
|
|
||||||
|
const std::pair<Button, Button> swap_buttons[] = {
|
||||||
|
{Button::L, Button::R},
|
||||||
|
{Button::L2, Button::R2},
|
||||||
|
};
|
||||||
|
|
||||||
// build array
|
// build array
|
||||||
for (const auto& [button, action] : m_actions) {
|
for (const auto& [button, action] : m_actions) {
|
||||||
if (action.IsHidden() || action.m_hint.empty()) {
|
if (action.IsHidden() || action.m_hint.empty()) {
|
||||||
@@ -94,13 +99,19 @@ auto Widget::GetUiButtons() const -> uiButtons {
|
|||||||
|
|
||||||
uiButton ui_button{button, action};
|
uiButton ui_button{button, action};
|
||||||
|
|
||||||
// swap
|
bool should_swap = false;
|
||||||
if (button == Button::R && draw_actions.size() && draw_actions.back().m_button == Button::L) {
|
for (auto [left, right] : swap_buttons) {
|
||||||
const auto s = draw_actions.back();
|
if (button == right && draw_actions.size() && draw_actions.back().m_button == left) {
|
||||||
draw_actions.back().m_button = button;
|
const auto s = draw_actions.back();
|
||||||
draw_actions.back().m_action = action;
|
draw_actions.back().m_button = button;
|
||||||
draw_actions.emplace_back(s);
|
draw_actions.back().m_action = action;
|
||||||
} else {
|
draw_actions.emplace_back(s);
|
||||||
|
should_swap = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!should_swap) {
|
||||||
draw_actions.emplace_back(ui_button);
|
draw_actions.emplace_back(ui_button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ auto GetKeyGenStr(u8 key_gen) -> const char* {
|
|||||||
case KeyGeneration_1700: return "17.0.0";
|
case KeyGeneration_1700: return "17.0.0";
|
||||||
case KeyGeneration_1800: return "18.0.0";
|
case KeyGeneration_1800: return "18.0.0";
|
||||||
case KeyGeneration_1900: return "19.0.0";
|
case KeyGeneration_1900: return "19.0.0";
|
||||||
|
case KeyGeneration_2000: return "20.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
|
|||||||
@@ -7,6 +7,38 @@ namespace {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
auto GetMetaTypeStr(u8 meta_type) -> const char* {
|
||||||
|
switch (meta_type) {
|
||||||
|
case NcmContentMetaType_Unknown: return "Unknown";
|
||||||
|
case NcmContentMetaType_SystemProgram: return "SystemProgram";
|
||||||
|
case NcmContentMetaType_SystemData: return "SystemData";
|
||||||
|
case NcmContentMetaType_SystemUpdate: return "SystemUpdate";
|
||||||
|
case NcmContentMetaType_BootImagePackage: return "BootImagePackage";
|
||||||
|
case NcmContentMetaType_BootImagePackageSafe: return "BootImagePackageSafe";
|
||||||
|
case NcmContentMetaType_Application: return "Application";
|
||||||
|
case NcmContentMetaType_Patch: return "Patch";
|
||||||
|
case NcmContentMetaType_AddOnContent: return "AddOnContent";
|
||||||
|
case NcmContentMetaType_Delta: return "Delta";
|
||||||
|
case NcmContentMetaType_DataPatch: return "DataPatch";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto GetStorageIdStr(u8 storage_id) -> const char* {
|
||||||
|
switch (storage_id) {
|
||||||
|
case NcmStorageId_None: return "None";
|
||||||
|
case NcmStorageId_Host: return "Host";
|
||||||
|
case NcmStorageId_GameCard: return "GameCard";
|
||||||
|
case NcmStorageId_BuiltInSystem: return "BuiltInSystem";
|
||||||
|
case NcmStorageId_BuiltInUser: return "BuiltInUser";
|
||||||
|
case NcmStorageId_SdCard: return "SdCard";
|
||||||
|
case NcmStorageId_Any: return "Any";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
auto GetAppId(u8 meta_type, u64 id) -> u64 {
|
auto GetAppId(u8 meta_type, u64 id) -> u64 {
|
||||||
if (meta_type == NcmContentMetaType_Patch) {
|
if (meta_type == NcmContentMetaType_Patch) {
|
||||||
return id ^ 0x800;
|
return id ^ 0x800;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
|||||||
Usb::Usb(u64 transfer_timeout) {
|
Usb::Usb(u64 transfer_timeout) {
|
||||||
m_open_result = usbDsInitialize();
|
m_open_result = usbDsInitialize();
|
||||||
m_transfer_timeout = transfer_timeout;
|
m_transfer_timeout = transfer_timeout;
|
||||||
|
ueventCreate(GetCancelEvent(), true);
|
||||||
// this avoids allocations during transfers.
|
// this avoids allocations during transfers.
|
||||||
m_aligned.reserve(1024 * 1024 * 16);
|
m_aligned.reserve(1024 * 1024 * 16);
|
||||||
}
|
}
|
||||||
@@ -223,8 +224,49 @@ Result Usb::Init() {
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::IsUsbConnected(u64 timeout) const {
|
// the blow code is taken from libnx, with the addition of a uevent to cancel.
|
||||||
return usbDsWaitReady(timeout);
|
Result Usb::IsUsbConnected(u64 timeout) {
|
||||||
|
Result rc;
|
||||||
|
UsbState state = UsbState_Detached;
|
||||||
|
|
||||||
|
rc = usbDsGetState(&state);
|
||||||
|
if (R_FAILED(rc)) return rc;
|
||||||
|
if (state == UsbState_Configured) return 0;
|
||||||
|
|
||||||
|
bool has_timeout = timeout != UINT64_MAX;
|
||||||
|
u64 deadline = 0;
|
||||||
|
|
||||||
|
const std::array waiters{
|
||||||
|
waiterForEvent(usbDsGetStateChangeEvent()),
|
||||||
|
waiterForUEvent(GetCancelEvent()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (has_timeout)
|
||||||
|
deadline = armGetSystemTick() + armNsToTicks(timeout);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (has_timeout) {
|
||||||
|
s64 remaining = deadline - armGetSystemTick();
|
||||||
|
timeout = remaining > 0 ? armTicksToNs(remaining) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
s32 idx;
|
||||||
|
rc = waitObjects(&idx, waiters.data(), waiters.size(), timeout);
|
||||||
|
eventClear(usbDsGetStateChangeEvent());
|
||||||
|
|
||||||
|
// check if we got one of the cancel events.
|
||||||
|
if (R_SUCCEEDED(rc) && idx != 0) {
|
||||||
|
rc = Result_Cancelled; // cancelled.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = usbDsGetState(&state);
|
||||||
|
} while (R_SUCCEEDED(rc) && state != UsbState_Configured && timeout > 0);
|
||||||
|
|
||||||
|
if (R_SUCCEEDED(rc) && state != UsbState_Configured && timeout == 0)
|
||||||
|
return KERNELRESULT(TimedOut);
|
||||||
|
|
||||||
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::WaitForConnection(u64 timeout, std::vector<std::string>& out_names) {
|
Result Usb::WaitForConnection(u64 timeout, std::vector<std::string>& out_names) {
|
||||||
@@ -261,9 +303,22 @@ Event *Usb::GetCompletionEvent(UsbSessionEndpoint ep) const {
|
|||||||
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
return std::addressof(m_endpoints[ep]->CompletionEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) const {
|
Result Usb::WaitTransferCompletion(UsbSessionEndpoint ep, u64 timeout) {
|
||||||
auto event = GetCompletionEvent(ep);
|
auto event = GetCompletionEvent(ep);
|
||||||
const auto rc = eventWait(event, timeout);
|
|
||||||
|
const std::array waiters{
|
||||||
|
waiterForEvent(event),
|
||||||
|
waiterForUEvent(GetCancelEvent()),
|
||||||
|
};
|
||||||
|
|
||||||
|
s32 idx;
|
||||||
|
auto rc = waitObjects(&idx, waiters.data(), waiters.size(), timeout);
|
||||||
|
|
||||||
|
// check if we got one of the cancel events.
|
||||||
|
if (R_SUCCEEDED(rc) && idx != 0) {
|
||||||
|
log_write("got usb cancel event\n");
|
||||||
|
rc = Result_Cancelled; // cancelled.
|
||||||
|
}
|
||||||
|
|
||||||
if (R_FAILED(rc)) {
|
if (R_FAILED(rc)) {
|
||||||
R_TRY(usbDsEndpoint_Cancel(m_endpoints[ep]));
|
R_TRY(usbDsEndpoint_Cancel(m_endpoints[ep]));
|
||||||
@@ -287,7 +342,7 @@ Result Usb::GetTransferResult(UsbSessionEndpoint ep, u32 urb_id, u32 *out_reques
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result Usb::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) const {
|
Result Usb::TransferPacketImpl(bool read, void *page, u32 size, u32 *out_size_transferred, u64 timeout) {
|
||||||
u32 urb_id;
|
u32 urb_id;
|
||||||
|
|
||||||
/* If we're not configured yet, wait to become configured first. */
|
/* If we're not configured yet, wait to become configured first. */
|
||||||
|
|||||||
Reference in New Issue
Block a user