44 Commits
touch ... 0.6.2

Author SHA1 Message Date
ITotalJustice
e002aa9ec2 only disable audio in applet mode if an app is suspended, bump version. 2025-01-17 03:52:32 +00:00
ITotalJustice
0aaf460dbf bump version for release (i forgot to do this before making a new release...) 2025-01-16 21:48:40 +00:00
ITotalJustice
76c8b806d0 bump ftpsrv version to 1.2.2 which fixes mdtm. 2025-01-16 21:40:24 +00:00
glitched_nx
61783bc530 update de.json with missing translations and corrections (#95) 2025-01-16 21:38:47 +00:00
ITotalJustice
a3a2a04991 fix hbmenu restore prompt not triggering if /hbmenu.nro does not exist
fixes #99
2025-01-16 21:28:13 +00:00
ITotalJustice
b6304fca75 fix deko3d mem leak when using docked mode
fixes #97
2025-01-16 21:24:21 +00:00
ITotalJustice
5612ae5691 disable audio in applet mode due to audren fatal.
fixes #92
2025-01-16 21:03:26 +00:00
ITotalJustice
657c160599 enable warning flags, fix all warning, default init all vars, bump stb libs used in nanovg
fixes #98
2025-01-16 21:01:17 +00:00
HenryBaby
f66494aeb5 Fixed the "decending" typo. (#91)
Co-authored-by: ITotalJustice <47043333+ITotalJustice@users.noreply.github.com>
2025-01-16 05:17:59 +00:00
shadow2560
650e7812e5 Update french language. (#94)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-01-16 05:13:31 +00:00
Ny'hrarr
cca54340a2 Update pt.json (#93) 2025-01-16 05:13:08 +00:00
cucholix
8161b52e7b Update es.json (#90)
Updated es.json localization, missing strings translated
2025-01-16 05:12:41 +00:00
Yorunokyujitsu
9390bd3865 add new strings and update ko.json, ja.json (#88) 2025-01-14 16:06:28 +00:00
ITotalJustice
483be133a5 mention discord server in readme
fixes #70
2025-01-14 16:01:39 +00:00
ITotalJustice
e2022eac4c progress box should use stop source for requesting exit 2025-01-14 15:54:34 +00:00
ITotalJustice
977331c3b2 remove download non-thread_queue code, fix thread queue exit bug
due to the previous commit, requesting the stop token to exit during a download
would cause the thread queue itself to exit.
2025-01-14 15:45:52 +00:00
ITotalJustice
64a40ae672 use stop token to manage object lifetime across async callbacks, such as download async 2025-01-14 15:35:09 +00:00
ITotalJustice
4e5e1a801b bump version for new release 2025-01-12 23:26:29 +00:00
ITotalJustice
01e06a79a5 use strstr to find sphaira within update zip, force restart upon update success. 2025-01-12 23:22:20 +00:00
ITotalJustice
c762dafc67 add text scrolling to sidebar array
see #87
2025-01-12 23:16:12 +00:00
shadow2560
fd1d461ea8 Fix update when homebrew nro is not /switch/sphaira/sphaira.nro. (#64)
Signed-off-by: shadow2560 <24191064+shadow2560@users.noreply.github.com>
2025-01-12 23:11:56 +00:00
ITotalJustice
2e14e4b09b Merge pull request #82 from ITotalJustice/theme_v2
Theme v2
2025-01-12 22:51:27 +00:00
ITotalJustice
fb7b37736b Merge branch 'master' into theme_v2 2025-01-12 22:48:38 +00:00
ITotalJustice
12e5069168 Merge remote-tracking branch 'refs/remotes/origin/theme_v2' into theme_v2 2025-01-12 22:48:02 +00:00
Ny'hrarr
b81bc51b1c Update pt.json (#86) 2025-01-12 22:22:57 +00:00
ITotalJustice
e3f846c9ec Change a few colors of the theme. (#87)
Co-authored-by: Yorunokyujitsu <Yorunokyujitsu@gmail.com>
2025-01-12 22:21:54 +00:00
ITotalJustice
7d5876d881 add bubbles
not used yet
2025-01-12 19:16:55 +00:00
BIGBIGSUI
990948b912 Update zh.json (#85)
This updates the zh.json for the project. I carefully reviewed the text to ensure it aligns with the intent of the original content. I hope this contribution will be helpful and appreciated.
2025-01-12 19:10:37 +00:00
ITotalJustice
91a08d36b4 tweek popup_list height to match qlaunch, change white/abyss scrollbar colour, fix menu using text instead of line colours 2025-01-09 19:34:16 +00:00
ITotalJustice
abc7a0799d Merge branch 'master' into theme_v2 2025-01-09 16:19:26 +00:00
LNLenost
ab973a3f99 Upated Italian Translations (#80)
* Update it.json
2025-01-09 16:17:37 +00:00
Funz-001
d0179b8719 Create vi.json (#79)
add vietnamese
2025-01-09 16:16:45 +00:00
ITotalJustice
78ecdc014b add Vietnamese language support
needed for #79 and #81
2025-01-09 16:15:57 +00:00
ITotalJustice
0751fa9a2e optimise theme inherit, load default_music.bfstm if available. 2025-01-09 15:55:46 +00:00
ITotalJustice
f05230e870 initial work on theme v2
see #78
2025-01-09 15:03:51 +00:00
ITotalJustice
9915307be0 fix themezer crash due to accessing list before creating it 2025-01-07 04:10:43 +00:00
ITotalJustice
62183f4524 remove old index_offset code, fix popup_list initial index being offscreen 2025-01-07 02:45:42 +00:00
ITotalJustice
ca1b31329d prompt user to restart sphaira upon language change 2025-01-07 02:38:27 +00:00
ITotalJustice
3a3b8008a1 add theme inheritance, fix broken theme when no longer installed
fixes #74
2025-01-07 01:20:54 +00:00
ITotalJustice
e9f0d2349c add back pulsing select box
this was commented out during testing, i forgot to undo said change before pushing the change
2025-01-06 22:53:22 +00:00
Miguel Alexandre Uhlein
26f195b54f update pt.json (#75) 2025-01-06 22:50:10 +00:00
ITotalJustice
aa48c1696d bump libpulsar version, improves read speed.
this was needed to support large background music files.
2025-01-06 22:49:09 +00:00
Yorunokyujitsu
e526f376fe Add new string to all lang.json (#72)
* Add new string to all lang.json

Add new string to all lang.json
Update Korean, Japanese translation.

* Updates Swedish and French translations.

Swedish - https://github.com/ITotalJustice/sphaira/pull/71
French - https://github.com/ITotalJustice/sphaira/pull/72#issuecomment-2568976996

* Update languages.json
2025-01-06 22:39:16 +00:00
ITotalJustice
78bda75985 add touch support (#77)
* initial work on touch support

* add touch support to all objects

* add touch scrolling, fix scrollbar, fix appstore search

- when fireing an action, the action array may change. so the loop should break early as soon as an action is handled.
  this fixes the appstore search when pressing B.
- scrollbar no longer goes oob. fixes #76

currently, scrolling has no acceleration.
2025-01-06 22:37:38 +00:00
71 changed files with 2276 additions and 1763 deletions

View File

@@ -4,6 +4,8 @@ A homebrew menu for the switch.
[See the gbatemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/). [See the gbatemp thread for more details / discussion](https://gbatemp.net/threads/sphaira-hbmenu-replacement.664523/).
[We have now have a Discord server!](https://discord.gg/8vZBsrprEc). Please use the issues tab to report bugs, as it is much easier for me to track.
## showcase ## showcase
| | | | | |

View File

@@ -19,6 +19,7 @@
"Details": "Details", "Details": "Details",
"Update": "Update", "Update": "Update",
"Remove": "Entfernen", "Remove": "Entfernen",
"Restore": "Wiederherstellen",
"Download": "Download", "Download": "Download",
"Next Page": "Nächste Seite", "Next Page": "Nächste Seite",
"Prev Page": "Vorherige Seite", "Prev Page": "Vorherige Seite",
@@ -26,6 +27,12 @@
"Star": "Favorit", "Star": "Favorit",
"System memory": "System-Speicher", "System memory": "System-Speicher",
"microSD card": "microSD-Karte", "microSD card": "microSD-Karte",
"Sd": "SD",
"Image System memory": "System-Speicher Bild",
"Image microSD card": "microSD-Karten Bild",
"Slow": "Langsam",
"Normal": "Normal",
"Fast": "Schnell",
"Yes": "Ja", "Yes": "Ja",
"No": "Nein", "No": "Nein",
"Enabled": "Aktiviert", "Enabled": "Aktiviert",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alphabetisch (Favoriten)", "Alphabetical (Star)": "Alphabetisch (Favoriten)",
"Likes": "Likes", "Likes": "Likes",
"ID": "ID", "ID": "ID",
"Decending": "Absteigend", "Descending": "Absteigend",
"Descending (down)": "Absteigend", "Descending (down)": "Absteigend",
"Desc": "Abst.", "Desc": "Abst.",
"Ascending": "Aufsteigend", "Ascending": "Aufsteigend",
@@ -54,7 +61,6 @@
"Asc": "Aufst.", "Asc": "Aufst.",
"Menu Options": "Menü-Optionen", "Menu Options": "Menü-Optionen",
"Header": "Header",
"Theme": "Theme", "Theme": "Theme",
"Theme Options": "Theme-Optionen", "Theme Options": "Theme-Optionen",
"Select Theme": "Theme auswählen", "Select Theme": "Theme auswählen",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Logging", "Logging": "Logging",
"Replace hbmenu on exit": "hbmenu beim Beenden ersetzen", "Replace hbmenu on exit": "hbmenu beim Beenden ersetzen",
"Misc": "Sonstiges", "Misc": "Sonstiges",
@@ -92,6 +99,7 @@
"Install forwarders": "Forwarder installieren", "Install forwarders": "Forwarder installieren",
"Install location": "Installationsort", "Install location": "Installationsort",
"Show install warning": "Installationswarnung anzeigen", "Show install warning": "Installationswarnung anzeigen",
"Text scroll speed": "Textlaufgeschwindigkeit",
"FileBrowser": "Datei-Browser", "FileBrowser": "Datei-Browser",
"%zd files": "%zd Dateien", "%zd files": "%zd Dateien",
@@ -114,6 +122,8 @@
"Create Folder": "Ordner erstellen", "Create Folder": "Ordner erstellen",
"Set Folder Name": "Ordnernamen eingeben", "Set Folder Name": "Ordnernamen eingeben",
"View as text (unfinished)": "Als Text anzeigen (Beta)", "View as text (unfinished)": "Als Text anzeigen (Beta)",
"Ignore read only": "Schreibschutz ignorieren",
"Mount": "Einbinden",
"Empty...": "Leer...", "Empty...": "Leer...",
"Open with DayBreak?": "Mit DayBreak öffnen?", "Open with DayBreak?": "Mit DayBreak öffnen?",
"Launch ": "Starten ", "Launch ": "Starten ",
@@ -201,9 +211,9 @@
"Bad Page": "Ungültige Seite", "Bad Page": "Ungültige Seite",
"Download theme?": "Theme herunterladen?", "Download theme?": "Theme herunterladen?",
"GitHub": "", "GitHub": "GitHub",
"Downloading json": "", "Downloading json": "Lade JSON herunter",
"Select asset to download for ": "", "Select asset to download for ": "Wähle Asset zum Download für ",
"Installing ": "Installiere ", "Installing ": "Installiere ",
"Uninstalling ": "Deinstalliere ", "Uninstalling ": "Deinstalliere ",
@@ -217,8 +227,8 @@
"Copying ": "Kopiere ", "Copying ": "Kopiere ",
"Trying to load ": "Lade ", "Trying to load ": "Lade ",
"Downloading ": "Lade herunter ", "Downloading ": "Lade herunter ",
"Downloaded ": "", "Downloaded ": "Heruntergeladen ",
"Removed ": "", "Removed ": "Entfernt ",
"Checking MD5": "Prüfe MD5", "Checking MD5": "Prüfe MD5",
"Loading...": "Lade...", "Loading...": "Lade...",
"Loading": "Lade", "Loading": "Lade",
@@ -228,11 +238,18 @@
"Update avaliable: ": "Update verfügbar: ", "Update avaliable: ": "Update verfügbar: ",
"Download update: ": "Update herunterladen: ", "Download update: ": "Update herunterladen: ",
"Updated to ": "Aktualisiert auf ", "Updated to ": "Aktualisiert auf ",
"Press OK to restart Sphaira": "OK drücken um Sphaira neuzustarten",
"Restart Sphaira?": "Sphaira neustarten?", "Restart Sphaira?": "Sphaira neustarten?",
"Failed to download update": "Update-Download fehlgeschlagen", "Failed to download update": "Update-Download fehlgeschlagen",
"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 restore hbmenu, please re-download hbmenu": "Wiederherstellung fehlgeschlagen, bitte hbmenu neu herunterladen",
"Failed to restore hbmenu, using sphaira instead": "Wiederherstellung fehlgeschlagen, verwende stattdessen Sphaira",
"Restored hbmenu, closing sphaira": "hbmenu wiederhergestellt, Sphaira wird beendet",
"Restored hbmenu": "hbmenu wiederhergestellt",
"Delete Selected files?": "Ausgewählte Dateien löschen?", "Delete Selected files?": "Ausgewählte Dateien löschen?",
"Completely remove ": "Vollständig entfernen ", "Completely remove ": "Vollständig entfernen ",
"Are you sure you want to delete ": "Wirklich löschen ", "Are you sure you want to delete ": "Wirklich löschen ",
"Are you sure you wish to cancel?": "Wirklich abbrechen?", "Are you sure you wish to cancel?": "Wirklich abbrechen?",
"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."
} }

View File

@@ -19,6 +19,7 @@
"Details": "Details", "Details": "Details",
"Update": "Update", "Update": "Update",
"Remove": "Remove", "Remove": "Remove",
"Restore": "Restore",
"Download": "Download", "Download": "Download",
"Next Page": "Next Page", "Next Page": "Next Page",
"Prev Page": "Prev Page", "Prev Page": "Prev Page",
@@ -26,6 +27,12 @@
"Star": "Star", "Star": "Star",
"System memory": "System memory", "System memory": "System memory",
"microSD card": "microSD card", "microSD card": "microSD card",
"Sd": "Sd",
"Image System memory": "Image System memory",
"Image microSD card": "Image microSD card",
"Slow": "Slow",
"Normal": "Normal",
"Fast": "Fast",
"Yes": "Yes", "Yes": "Yes",
"No": "No", "No": "No",
"Enabled": "Enabled", "Enabled": "Enabled",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alphabetical (Star)", "Alphabetical (Star)": "Alphabetical (Star)",
"Likes": "Likes", "Likes": "Likes",
"ID": "ID", "ID": "ID",
"Decending": "Decending", "Descending": "Descending",
"Descending (down)": "Descending (down)", "Descending (down)": "Descending (down)",
"Desc": "Desc", "Desc": "Desc",
"Ascending": "Ascending", "Ascending": "Ascending",
@@ -54,7 +61,6 @@
"Asc": "Asc", "Asc": "Asc",
"Menu Options": "Menu Options", "Menu Options": "Menu Options",
"Header": "Header",
"Theme": "Theme", "Theme": "Theme",
"Theme Options": "Theme Options", "Theme Options": "Theme Options",
"Select Theme": "Select Theme", "Select Theme": "Select Theme",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Logging", "Logging": "Logging",
"Replace hbmenu on exit": "Replace hbmenu on exit", "Replace hbmenu on exit": "Replace hbmenu on exit",
"Misc": "Misc", "Misc": "Misc",
@@ -92,6 +99,7 @@
"Install forwarders": "Install forwarders", "Install forwarders": "Install forwarders",
"Install location": "Install location", "Install location": "Install location",
"Show install warning": "Show install warning", "Show install warning": "Show install warning",
"Text scroll speed": "Text scroll speed",
"FileBrowser": "FileBrowser", "FileBrowser": "FileBrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -114,6 +122,8 @@
"Create Folder": "Create Folder", "Create Folder": "Create Folder",
"Set Folder Name": "Set Folder Name", "Set Folder Name": "Set Folder Name",
"View as text (unfinished)": "View as text (unfinished)", "View as text (unfinished)": "View as text (unfinished)",
"Ignore read only": "Ignore read only",
"Mount": "Mount",
"Empty...": "Empty...", "Empty...": "Empty...",
"Open with DayBreak?": "Open with DayBreak?", "Open with DayBreak?": "Open with DayBreak?",
"Launch ": "Launch ", "Launch ": "Launch ",
@@ -228,8 +238,15 @@
"Update avaliable: ": "Update avaliable: ", "Update avaliable: ": "Update avaliable: ",
"Download update: ": "Download update: ", "Download update: ": "Download update: ",
"Updated to ": "Updated to ", "Updated to ": "Updated to ",
"Press OK to restart Sphaira": "Press OK to restart Sphaira",
"Restart Sphaira?": "Restart Sphaira?", "Restart Sphaira?": "Restart Sphaira?",
"Failed to download update": "Failed to download update", "Failed to download update": "Failed to download update",
"Restore hbmenu?": "Restore hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Failed to restore hbmenu, please re-download hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Failed to restore hbmenu, using sphaira instead",
"Restored hbmenu, closing sphaira": "Restored hbmenu, closing sphaira",
"Restored hbmenu": "Restored hbmenu",
"Delete Selected files?": "Delete Selected files?", "Delete Selected files?": "Delete Selected files?",
"Completely remove ": "Completely remove ", "Completely remove ": "Completely remove ",
"Are you sure you want to delete ": "Are you sure you want to delete ", "Are you sure you want to delete ": "Are you sure you want to delete ",

View File

@@ -19,6 +19,7 @@
"Details": "Detalles", "Details": "Detalles",
"Update": "Actualizar", "Update": "Actualizar",
"Remove": "Borrar", "Remove": "Borrar",
"Restore": "Restaurar",
"Download": "Descargar", "Download": "Descargar",
"Next Page": "Página siguiente", "Next Page": "Página siguiente",
"Prev Page": "Página anterior", "Prev Page": "Página anterior",
@@ -26,6 +27,12 @@
"Star": "Favorito", "Star": "Favorito",
"System memory": "Memoria de sistema", "System memory": "Memoria de sistema",
"microSD card": "microSD", "microSD card": "microSD",
"Sd": "SD",
"Image System memory": "Imagen memoria interna",
"Image microSD card": "Imagen tarjeta microSD",
"Slow": "Lento",
"Normal": "Normal",
"Fast": "Rápido",
"Yes": "Sí", "Yes": "Sí",
"No": "No", "No": "No",
"Enabled": "Activado", "Enabled": "Activado",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alfabético (favorito)", "Alphabetical (Star)": "Alfabético (favorito)",
"Likes": "Me Gusta", "Likes": "Me Gusta",
"ID": "ID", "ID": "ID",
"Decending": "Descendente", "Descending": "Descendente",
"Descending (down)": "Descendente (abajo)", "Descending (down)": "Descendente (abajo)",
"Desc": "Descendente", "Desc": "Descendente",
"Ascending": "Ascendente", "Ascending": "Ascendente",
@@ -54,7 +61,6 @@
"Asc": "Ascendente", "Asc": "Ascendente",
"Menu Options": "Opciones de menú", "Menu Options": "Opciones de menú",
"Header": "Encabezado",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opciones de tema", "Theme Options": "Opciones de tema",
"Select Theme": "Seleccionar tema", "Select Theme": "Seleccionar tema",
@@ -84,18 +90,20 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Logging": "Registros", "Vietnamese": "Vietnamese",
"Replace hbmenu on exit": "Reemplazar hbmenu al salir", "Logging": "Registro",
"Replace hbmenu on exit": "Reemplazar hbmenu",
"Misc": "Varios", "Misc": "Varios",
"Misc Options": "Opciones varias", "Misc Options": "Opciones varias",
"Web": "Web", "Web": "Web",
"Install forwarders": "Instalar forwarders", "Install forwarders": "Instalar forwarders",
"Install location": "Ruta de instalación ", "Install location": "Dispositivo de instalación",
"Show install warning": "Mostrar precaución de instalación", "Show install warning": "Precaución de instalación",
"Text scroll speed": "Velocidad de scroll",
"FileBrowser": "Explorador de archivos", "FileBrowser": "Explorador de archivos",
"%zd files": "%zd files", "%zd files": "%zd archivos",
"%zd dirs": "%zd dirs", "%zd dirs": "%zd carpetas",
"File Options": "Opciones de archivo", "File Options": "Opciones de archivo",
"Show Hidden": "Mostrar archivos ocultos", "Show Hidden": "Mostrar archivos ocultos",
"Folders First": "Carpetas primero", "Folders First": "Carpetas primero",
@@ -114,13 +122,15 @@
"Create Folder": "Crear carpeta", "Create Folder": "Crear carpeta",
"Set Folder Name": "Establecer nombre de carpeta", "Set Folder Name": "Establecer nombre de carpeta",
"View as text (unfinished)": "Ver como texto (sin terminar)", "View as text (unfinished)": "Ver como texto (sin terminar)",
"Ignore read only": "Ignorar sólo lectura",
"Mount": "Montar",
"Empty...": "Vacío...", "Empty...": "Vacío...",
"Open with DayBreak?": "¿Abrir con DayBreak?", "Open with DayBreak?": "¿Abrir con DayBreak?",
"Launch ": "Abrir ", "Launch ": "Abrir ",
"Launch option for: ": "Opción de abrir con: ", "Launch option for: ": "Opción de abrir con: ",
"Select launcher for: ": "Seleccionar abrir con: ", "Select launcher for: ": "Seleccionar abrir con: ",
"Homebrew": "Honebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Opciones de Homebrew", "Homebrew Options": "Opciones de Homebrew",
"Hide Sphaira": "Ocultar Sphaira", "Hide Sphaira": "Ocultar Sphaira",
"Install Forwarder": "Instalar Forwarder", "Install Forwarder": "Instalar Forwarder",
@@ -157,16 +167,16 @@
"Irs": "IRS", "Irs": "IRS",
"Ambient Noise Level: ": "Nivel de Ruido Ambiente", "Ambient Noise Level: ": "Nivel de Ruido Ambiente",
"Controller": "Control", "Controller": "Control",
"Pad ": "Almohadilla ", "Pad ": "GamePad ",
" (Available)": " (Disponible)", " (Available)": " (Disponible)",
" (Unsupported)": "(No Compatible)", " (Unsupported)": "(No Compatible)",
" (Unconnected)": " (Desconectado)", " (Unconnected)": " (Desconectado)",
"HandHeld": "Portátil", "HandHeld": "Portátil",
"Rotation": "Rotación", "Rotation": "Rotación",
"0 (Sideways)": "0 (De lado)", "0 (Sideways)": "0° (De lado)",
"90 (Flat)": "90 (Plano)", "90 (Flat)": "90° (Plano)",
"180 (-Sideways)": "180 (-De lado)", "180 (-Sideways)": "180° (De lado)",
"270 (Upside down)": "270 (Al revés)", "270 (Upside down)": "270° (Al revés)",
"Colour": "Color", "Colour": "Color",
"Grey": "Gris", "Grey": "Gris",
"Ironbow": "Paleta térmica", "Ironbow": "Paleta térmica",
@@ -201,9 +211,9 @@
"Bad Page": "Página Errónea", "Bad Page": "Página Errónea",
"Download theme?": "¿Descargar Tema?", "Download theme?": "¿Descargar Tema?",
"GitHub": "", "GitHub": "GitHub",
"Downloading json": "", "Downloading json": "Descargando json",
"Select asset to download for ": "", "Select asset to download for ": "Seleccionar recurso a descargar para ",
"Installing ": "Instalando ", "Installing ": "Instalando ",
"Uninstalling ": "Desinstalando ", "Uninstalling ": "Desinstalando ",
@@ -215,10 +225,10 @@
"Scanning ": "Escaneando ", "Scanning ": "Escaneando ",
"Creating ": "Creando ", "Creating ": "Creando ",
"Copying ": "Copiando ", "Copying ": "Copiando ",
"Trying to load ": "Intentando cargar", "Trying to load ": "Intentando cargar ",
"Downloading ": "Descargando ", "Downloading ": "Descargando ",
"Downloaded ": "", "Downloaded ": "Descargado ",
"Removed ": "", "Removed ": "Removido ",
"Checking MD5": "Chequeando MD5", "Checking MD5": "Chequeando MD5",
"Loading...": "Cargando...", "Loading...": "Cargando...",
"Loading": "Cargando", "Loading": "Cargando",
@@ -228,11 +238,18 @@
"Update avaliable: ": "Actualización disponible: ", "Update avaliable: ": "Actualización disponible: ",
"Download update: ": "Descargar actualización: ", "Download update: ": "Descargar actualización: ",
"Updated to ": "Actualizado a ", "Updated to ": "Actualizado a ",
"Restart Sphaira?": "¿Reiniciar Sphaira?", "Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
"Restart Sphaira?": "¿Reiniciar sphaira?",
"Failed to download update": "Fallo al descargar actualización", "Failed to download update": "Fallo al descargar actualización",
"Restore hbmenu?": "¿Restaurar hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUsar la Tienda para reinstalar hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu, por favor volver a descargar hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Fallo al restaurar hbmenu, se usará sphaira",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, cerrando sphaira",
"Restored hbmenu": "hbmenu restaurado",
"Delete Selected files?": "¿Eliminar archivos seleccionados?", "Delete Selected files?": "¿Eliminar archivos seleccionados?",
"Completely remove ": "Eliminar completamente", "Completely remove ": "Eliminar completamente",
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ", "Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?", "Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'." "If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'."
} }

View File

@@ -19,6 +19,7 @@
"Details": "Détails", "Details": "Détails",
"Update": "Mise à jour", "Update": "Mise à jour",
"Remove": "Supprimer", "Remove": "Supprimer",
"Restore": "Restaurer",
"Download": "Télécharger", "Download": "Télécharger",
"Next Page": "Page Suiv.", "Next Page": "Page Suiv.",
"Prev Page": "Page Préc.", "Prev Page": "Page Préc.",
@@ -26,6 +27,12 @@
"Star": "Ajouter aux favories", "Star": "Ajouter aux favories",
"System memory": "Mémoire système", "System memory": "Mémoire système",
"microSD card": "Carte microSD", "microSD card": "Carte microSD",
"Sd": "Sd",
"Image System memory": "Image de la mémoire System",
"Image microSD card": "Image de la Carte microSD",
"Slow": "Lent",
"Normal": "Normal",
"Fast": "Rapide",
"Yes": "Oui", "Yes": "Oui",
"No": "Non", "No": "Non",
"Enabled": "Activé(e)", "Enabled": "Activé(e)",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "Alphabétique (Favories)", "Alphabetical (Star)": "Alphabétique (Favories)",
"Likes": "Likes", "Likes": "Likes",
"ID": "ID", "ID": "ID",
"Decending": "Décroissant", "Descending": "Décroissant",
"Descending (down)": "Décroissant", "Descending (down)": "Décroissant",
"Desc": "Décroissant", "Desc": "Décroissant",
"Ascending": "Croissant", "Ascending": "Croissant",
@@ -54,7 +61,6 @@
"Asc": "Croissant", "Asc": "Croissant",
"Menu Options": "Options des Menus", "Menu Options": "Options des Menus",
"Header": "En-tête",
"Theme": "Thème", "Theme": "Thème",
"Theme Options": "Options de Thème", "Theme Options": "Options de Thème",
"Select Theme": "Choisir un Thème", "Select Theme": "Choisir un Thème",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Journalisation", "Logging": "Journalisation",
"Replace hbmenu on exit": "Remplacer hbmenu quand quitté", "Replace hbmenu on exit": "Remplacer hbmenu quand quitté",
"Misc": "Divers", "Misc": "Divers",
@@ -92,6 +99,7 @@
"Install forwarders": "Installer les Forwarders", "Install forwarders": "Installer les Forwarders",
"Install location": "Emplacement d'installation", "Install location": "Emplacement d'installation",
"Show install warning": "Afficher l'avertissement d'installation", "Show install warning": "Afficher l'avertissement d'installation",
"Text scroll speed": "Vitesse de défilement du texte",
"FileBrowser": "Explorateur de Fichiers", "FileBrowser": "Explorateur de Fichiers",
"%zd files": "%zd fichiers", "%zd files": "%zd fichiers",
@@ -114,6 +122,8 @@
"Create Folder": "Créer un Dossier", "Create Folder": "Créer un Dossier",
"Set Folder Name": "Nommer Le Dossier", "Set Folder Name": "Nommer Le Dossier",
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)", "View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
"Ignore read only": "Ignorer lecture seule",
"Mount": "Monter",
"Empty...": "Vide...", "Empty...": "Vide...",
"Open with DayBreak?": "Ouvrir avec DayBreak?", "Open with DayBreak?": "Ouvrir avec DayBreak?",
"Launch ": "Lancer ", "Launch ": "Lancer ",
@@ -228,8 +238,15 @@
"Update avaliable: ": "Mise à jour disponible: ", "Update avaliable: ": "Mise à jour disponible: ",
"Download update: ": "Télécharger la mise à jour: ", "Download update: ": "Télécharger la mise à jour: ",
"Updated to ": "Mis à jour vers ", "Updated to ": "Mis à jour vers ",
"Press OK to restart Sphaira": "Appuyez sur OK pour redémarrer Sphaira",
"Restart Sphaira?": "Redémarrer Sphaira?", "Restart Sphaira?": "Redémarrer Sphaira?",
"Failed to download update": "Echec du téléchargement de la mise à jour", "Failed to download update": "Echec du téléchargement de la mise à jour",
"Restore hbmenu?": "Restaurer hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmenu.nro n'a pas été trouvé\nUtiliser l'Appstore pour réinstaller le hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Echec de la restauration de hbmenu, veuillez le réinstaller",
"Failed to restore hbmenu, using sphaira instead": "Echec de la restauration de hbmenu, sphaira sera utilisé à la place",
"Restored hbmenu, closing sphaira": "Hbmenu restauré, fermeture de sphaira",
"Restored hbmenu": "Hbmenu restauré",
"Delete Selected files?": "Supprimer les fichiers sélectionnés?", "Delete Selected files?": "Supprimer les fichiers sélectionnés?",
"Completely remove ": "Supprimer totalement ", "Completely remove ": "Supprimer totalement ",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ", "Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",

View File

@@ -1,31 +1,38 @@
{ {
"[Applet Mode]": "[Modalità applet]", "[Applet Mode]": "[Modalità applet]",
"No Internet": "Niente Internet", "No Internet": "Niente Internet",
"Files": "", "Files": "File",
"Apps": "", "Apps": "App",
"Store": "", "Store": "Store",
"Menu": "Menu", "Menu": "Menu",
"Options": "Opzioni", "Options": "Opzioni",
"OK": "", "OK": "OK",
"Back": "Indietro", "Back": "Indietro",
"Select": "", "Select": "Seleziona",
"Open": "Apri", "Open": "Apri",
"Launch": "Lancia", "Launch": "Lancia",
"Info": "Informazioni", "Info": "Informazioni",
"Install": "Installa", "Install": "Installa",
"Delete": "Elimina", "Delete": "Elimina",
"Restart": "", "Restart": "Riavvia",
"Changelog": "", "Changelog": "Patch notes",
"Details": "", "Details": "Dettagli",
"Update": "", "Update": "Aggiorna",
"Remove": "", "Remove": "Rimuovi",
"Restore": "Ripristina",
"Download": "Download", "Download": "Download",
"Next Page": "Pagina successiva", "Next Page": "Pagina successiva",
"Prev Page": "Pagina precedente", "Prev Page": "Pagina precedente",
"Unstar": "", "Unstar": "Rimuovi dai preferiti",
"Star": "", "Star": "Aggiungi ai preferiti",
"System memory": "", "System memory": "Memoria di sistema",
"microSD card": "", "microSD card": "Scheda microSD",
"Sd": "SD",
"Image System memory": "Immagine memoria di sistema",
"Image microSD card": "Immagine scheda microSD",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Sì", "Yes": "Sì",
"No": "No", "No": "No",
"Enabled": "Abilitato", "Enabled": "Abilitato",
@@ -40,13 +47,13 @@
"Updated": "Aggiornato", "Updated": "Aggiornato",
"Updated (Star)": "", "Updated (Star)": "",
"Downloads": "Download", "Downloads": "Download",
"Size": "Misurare", "Size": "Dimensione",
"Size (Star)": "", "Size (Star)": "Dimensione (Preferiti)",
"Alphabetical": "Alfabetico", "Alphabetical": "Alfabetico",
"Alphabetical (Star)": "", "Alphabetical (Star)": "Alfabetico (Preferiti)",
"Likes": "", "Likes": "Mi Piace",
"ID": "", "ID": "ID",
"Decending": "Decrescente", "Descending": "Decrescente",
"Descending (down)": "Decrescente", "Descending (down)": "Decrescente",
"Desc": "Decrescente", "Desc": "Decrescente",
"Ascending": "Crescente", "Ascending": "Crescente",
@@ -54,7 +61,6 @@
"Asc": "Crescente", "Asc": "Crescente",
"Menu Options": "Opzioni menu", "Menu Options": "Opzioni menu",
"Header": "Intestazione",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opzioni tema", "Theme Options": "Opzioni tema",
"Select Theme": "Seleziona tema", "Select Theme": "Seleziona tema",
@@ -65,13 +71,13 @@
"Ftp": "FTP", "Ftp": "FTP",
"Mtp": "MTP", "Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "", "Nxlink Connected": "Nxlink connesso",
"Nxlink Upload": "", "Nxlink Upload": "Nxlink upload",
"Nxlink Finished": "", "Nxlink Finished": "Nxlink finito",
"Switch-Handheld!": "", "Switch-Handheld!": "Switch Portatile",
"Switch-Docked!": "", "Switch-Docked!": "Switch Dock",
"Language": "Lingua", "Language": "Lingua",
"Auto": "", "Auto": "Auto",
"English": "English", "English": "English",
"Japanese": "日本語", "Japanese": "日本語",
"French": "Français", "French": "Français",
@@ -84,14 +90,16 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Logging", "Logging": "Logging",
"Replace hbmenu on exit": "Sostituisci hbmenu all'uscita", "Replace hbmenu on exit": "Sostituisci hbmenu all'uscita",
"Misc": "Varie", "Misc": "Varie",
"Misc Options": "Opzioni varie", "Misc Options": "Opzioni varie",
"Web": "Rete", "Web": "Rete",
"Install forwarders": "", "Install forwarders": "Installa forwarder",
"Install location": "", "Install location": "Installa posizione",
"Show install warning": "", "Show install warning": "Mostra avvertimento installazione",
"Text scroll speed": "",
"FileBrowser": "FileBrowser", "FileBrowser": "FileBrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -102,23 +110,25 @@
"Hidden Last": "Ultimo nascosto", "Hidden Last": "Ultimo nascosto",
"Cut": "Taglia", "Cut": "Taglia",
"Copy": "Copia", "Copy": "Copia",
"Paste": "", "Paste": "Incolla",
"Paste ": "", "Paste ": "Incolla ",
" file(s)?": "", " file(s)?": "(i)file?",
"Rename": "Rinomina", "Rename": "Rinomina",
"Set New File Name": "", "Set New File Name": "Imposta nuovo nome",
"Advanced": "Avanzato", "Advanced": "Avanzato",
"Advanced Options": "Opzioni avanzate", "Advanced Options": "Opzioni avanzate",
"Create File": "Crea file", "Create File": "Crea file",
"Set File Name": "", "Set File Name": "Imposta nome",
"Create Folder": "Crea cartella", "Create Folder": "Crea cartella",
"Set Folder Name": "", "Set Folder Name": "Imposta nome",
"View as text (unfinished)": "Visualizza come testo (non finito)", "View as text (unfinished)": "Visualizza come testo (non finito)",
"Empty...": "", "Ignore read only": "Ignora read only",
"Open with DayBreak?": "", "Mount": "Monta",
"Launch ": "", "Empty...": "Vuoto...",
"Launch option for: ": "", "Open with DayBreak?": "Vuoi aprire con Daybreak?",
"Select launcher for: ": "", "Launch ": "Lancia",
"Launch option for: ": "Lancia opzione per",
"Select launcher for: ": "Scegli launcher per",
"Homebrew": "Homebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Opzioni Homebrew", "Homebrew Options": "Opzioni Homebrew",
@@ -228,8 +238,15 @@
"Update avaliable: ": "", "Update avaliable: ": "",
"Download update: ": "", "Download update: ": "",
"Updated to ": "", "Updated to ": "",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Failed to download update": "", "Failed to download update": "",
"Restore hbmenu?": "",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
"Failed to restore hbmenu, please re-download hbmenu": "",
"Failed to restore hbmenu, using sphaira instead": "",
"Restored hbmenu, closing sphaira": "",
"Restored hbmenu": "",
"Delete Selected files?": "", "Delete Selected files?": "",
"Completely remove ": "", "Completely remove ": "",
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ", "Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",

View File

@@ -19,6 +19,7 @@
"Details": "詳細", "Details": "詳細",
"Update": "アップデート", "Update": "アップデート",
"Remove": "除去", "Remove": "除去",
"Restore": "復元",
"Download": "ダウンロード", "Download": "ダウンロード",
"Next Page": "次のページ", "Next Page": "次のページ",
"Prev Page": "前のページ", "Prev Page": "前のページ",
@@ -26,6 +27,12 @@
"Star": "お気に入り", "Star": "お気に入り",
"System memory": "システムメモリ", "System memory": "システムメモリ",
"microSD card": "SDメモリーカード", "microSD card": "SDメモリーカード",
"Sd": "SDメモリーカード",
"Image System memory": "システムメモリイメージ",
"Image microSD card": "SDイメージ",
"Slow": "遅い",
"Normal": "普通",
"Fast": "速い",
"Yes": "はい", "Yes": "はい",
"No": "いいえ", "No": "いいえ",
"Enabled": "", "Enabled": "",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "アルファベット順(お気に入り)", "Alphabetical (Star)": "アルファベット順(お気に入り)",
"Likes": "いいね順", "Likes": "いいね順",
"ID": "デベロッパー順", "ID": "デベロッパー順",
"Decending": "降順", "Descending": "降順",
"Descending (down)": "降順", "Descending (down)": "降順",
"Desc": "降順", "Desc": "降順",
"Ascending": "上昇", "Ascending": "上昇",
@@ -54,7 +61,6 @@
"Asc": "上昇", "Asc": "上昇",
"Menu Options": "メニュー設定", "Menu Options": "メニュー設定",
"Header": "ヘッダー",
"Theme": "テーマ", "Theme": "テーマ",
"Theme Options": "テーマ設定", "Theme Options": "テーマ設定",
"Select Theme": "テーマを選ぶ", "Select Theme": "テーマを選ぶ",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "ログの取得", "Logging": "ログの取得",
"Replace hbmenu on exit": "終了時に hbmenu を置き換える", "Replace hbmenu on exit": "終了時に hbmenu を置き換える",
"Misc": "その他", "Misc": "その他",
@@ -92,6 +99,7 @@
"Install forwarders": "Forwarderのインストール機能", "Install forwarders": "Forwarderのインストール機能",
"Install location": "インストール経路", "Install location": "インストール経路",
"Show install warning": "警告文を示す", "Show install warning": "警告文を示す",
"Text scroll speed": "流れる文字の速さ",
"FileBrowser": "ファイルブラウザ", "FileBrowser": "ファイルブラウザ",
"%zd files": "%zd個のファイル", "%zd files": "%zd個のファイル",
@@ -114,6 +122,8 @@
"Create Folder": "フォルダーの作成", "Create Folder": "フォルダーの作成",
"Set Folder Name": "名前を入力", "Set Folder Name": "名前を入力",
"View as text (unfinished)": "テキストとして表示 (未完成)", "View as text (unfinished)": "テキストとして表示 (未完成)",
"Ignore read only": "読み取り専用を無視する",
"Mount": "マウント",
"Empty...": "このフォルダーは空です", "Empty...": "このフォルダーは空です",
"Open with DayBreak?": "DayBreakで開きますか?", "Open with DayBreak?": "DayBreakで開きますか?",
"Launch ": "起動しますか", "Launch ": "起動しますか",
@@ -228,8 +238,15 @@
"Update avaliable: ": "アップデート可能: ", "Update avaliable: ": "アップデート可能: ",
"Download update: ": "アップデートをダウンロード: ", "Download update: ": "アップデートをダウンロード: ",
"Updated to ": "アップデート: ", "Updated to ": "アップデート: ",
"Press OK to restart Sphaira": "確認ボタンを押してSphairaを再起動",
"Restart Sphaira?": "Sphairaを再起動しますか?", "Restart Sphaira?": "Sphairaを再起動しますか?",
"Failed to download update": "アップデートのダウンロード失敗", "Failed to download update": "アップデートのダウンロード失敗",
"Restore hbmenu?": "hbmenuに戻しますか?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nroが見つかりません\nAppstoreから再インストールしてください",
"Failed to restore hbmenu, please re-download hbmenu": "hbmenuを復元できませんでした、再インストールしてください",
"Failed to restore hbmenu, using sphaira instead": "hbmenuを復元できませんでした、sphairaを引き続き使います",
"Restored hbmenu, closing sphaira": "hbmenuに復元されました、sphairaを終了します",
"Restored hbmenu": "hbmenuに復元されました",
"Delete Selected files?": "本当に削除しますか?", "Delete Selected files?": "本当に削除しますか?",
"Completely remove ": "除去しますか ", "Completely remove ": "除去しますか ",
"Are you sure you want to delete ": "消去してもよろしいですか ", "Are you sure you want to delete ": "消去してもよろしいですか ",

View File

@@ -1,238 +1,255 @@
{ {
"[Applet Mode]": "[애플릿 모드]", "[Applet Mode]": "[ 애플릿 모드 ]",
"No Internet": "인터넷 연결 없음", "No Internet": "인터넷 연결 없음",
"Files": "파일 탐색기", "Files": "파일 탐색기",
"Apps": "홈브류", "Apps": "홈브류",
"Store": "앱스토어", "Store": "앱스토어",
"Menu": "메뉴", "Menu": "메뉴",
"Options": "설정", "Options": "설정",
"OK": "확인", "OK": "확인",
"Back": "뒤로", "Back": "뒤로",
"Select": "선택", "Select": "선택",
"Open": "열기", "Open": "열기",
"Launch": "실행", "Launch": "실행",
"Info": "정보", "Info": "정보",
"Install": "설치", "Install": "설치",
"Delete": "삭제", "Delete": "삭제",
"Restart": "재시작", "Restart": "재시작",
"Changelog": "변경 내역", "Changelog": "변경 내역",
"Details": "상세", "Details": "상세",
"Update": "업데이트", "Update": "업데이트",
"Remove": "제거", "Remove": "제거",
"Download": "다운로드", "Restore": "복원",
"Next Page": "다음 페이지", "Download": "다운로드",
"Prev Page": "이전 페이지", "Next Page": "다음 페이지",
"Unstar": "즐겨찾기 해제", "Prev Page": "이전 페이지",
"Star": "즐겨찾기", "Unstar": "즐겨찾기 해제",
"System memory": "낸드 저장소", "Star": "즐겨찾기",
"microSD card": "SD 카드", "System memory": "낸드 저장소",
"Yes": "", "microSD card": "SD 카드",
"No": "아니요", "Sd": "SD 카드",
"Enabled": "", "Image System memory": "낸드 이미지",
"Disabled": "", "Image microSD card": "SD 이미지",
"Slow": "느림",
"Sort By": "정렬", "Normal": "보통",
"Sort Options": "정렬 옵션", "Fast": "빠름",
"Filter": "필터", "Yes": "",
"Sort": "분류", "No": "아니요",
"Order": "정렬", "Enabled": "",
"Search": "검색", "Disabled": "",
"Updated": "업데이트순",
"Updated (Star)": "업데이트순 (즐겨찾기)", "Sort By": "정렬",
"Downloads": "다운로드순", "Sort Options": "정렬 옵션",
"Size": "크기순", "Filter": "필터",
"Size (Star)": "크기순 (즐겨찾기)", "Sort": "분류",
"Alphabetical": "알파벳순", "Order": "정렬",
"Alphabetical (Star)": "알파벳순 (즐겨찾기)", "Search": "검색",
"Likes": "좋아요순", "Updated": "업데이트순",
"ID": "ID순", "Updated (Star)": "업데이트순 (즐겨찾기)",
"Decending": "내림차순", "Downloads": "다운로드순",
"Descending (down)": "내림차순", "Size": "크기순",
"Desc": "내림차순", "Size (Star)": "크기순 (즐겨찾기)",
"Ascending": "오름차순", "Alphabetical": "알파벳순",
"Ascending (Up)": "오름차순", "Alphabetical (Star)": "알파벳순 (즐겨찾기)",
"Asc": "오름차순", "Likes": "좋아요순",
"ID": "ID순",
"Menu Options": "메뉴", "Descending": "내림차순",
"Header": "헤더", "Descending (down)": "내림차순",
"Theme": "테마", "Desc": "내림차순",
"Theme Options": "테마 옵션", "Ascending": "오름차순",
"Select Theme": "테마 선택", "Ascending (Up)": "오름차순",
"Shuffle": "셔플", "Asc": "오름차순",
"Music": "BGM",
"Network": "네트워크", "Menu Options": "메뉴",
"Network Options": "네트워크 옵션", "Theme": "테마",
"Ftp": "FTP (무선)", "Theme Options": "테마 옵션",
"Mtp": "MTP (유선)", "Select Theme": "테마 선택",
"Nxlink": "Nxlink", "Shuffle": "셔플",
"Nxlink Connected": "Nxlink 연결됨", "Music": "BGM",
"Nxlink Upload": "Nxlink 업로드", "Network": "네트워크",
"Nxlink Finished": "Nxlink 종료됨", "Network Options": "네트워크 옵션",
"Switch-Handheld!": "휴대모드로 전환됨!", "Ftp": "FTP (무선)",
"Switch-Docked!": "독 모드로 전환됨!", "Mtp": "MTP (유선)",
"Language": "언어", "Nxlink": "Nxlink",
"Auto": "자동", "Nxlink Connected": "Nxlink 연결됨",
"English": "English", "Nxlink Upload": "Nxlink 업로드",
"Japanese": "日本語", "Nxlink Finished": "Nxlink 종료됨",
"French": "Français", "Switch-Handheld!": "휴대모드로 전환됨!",
"German": "Deutsch", "Switch-Docked!": "독 모드로 전환됨!",
"Italian": "Italiano", "Language": "언어",
"Spanish": "Español", "Auto": "자동",
"Chinese": "中文", "English": "English",
"Korean": "한국어", "Japanese": "日本語",
"Dutch": "Dutch", "French": "Français",
"Portuguese": "Português", "German": "Deutsch",
"Russian": "Русский", "Italian": "Italiano",
"Swedish": "Svenska", "Spanish": "Español",
"Logging": "로깅", "Chinese": "中文",
"Replace hbmenu on exit": "hbmenu  sphaira 교체", "Korean": "한국어",
"Misc": "기타", "Dutch": "Dutch",
"Misc Options": "기타", "Portuguese": "Português",
"Web": "웹 브라우저", "Russian": "Русский",
"Install forwarders": "바로가기 설치", "Swedish": "Svenska",
"Install location": "설치 위치", "Vietnamese": "Vietnamese",
"Show install warning": "설치 경고 표시", "Logging": "로깅",
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
"FileBrowser": "파일 탐색기", "Misc": "기",
"%zd files": "%zd 개 파일", "Misc Options": "기타 옵션",
"%zd dirs": "%zd 개 폴더", "Web": "웹 브라우저",
"File Options": "파일 옵션", "Install forwarders": "바로가기 설치",
"Show Hidden": "숨겨진 항목 표시", "Install location": "설치 위치",
"Folders First": "폴더 우선 정렬", "Show install warning": "경고 메시지",
"Hidden Last": "숨겨진 항목 후순 정렬", "Text scroll speed": "흐르는 텍스트 속도",
"Cut": "잘라내기",
"Copy": "복사", "FileBrowser": "파일 탐색기",
"Paste": "붙여넣기", "%zd files": "%zd 개 파일",
"Paste ": " ", "%zd dirs": "%zd 개 폴더",
" file(s)?": "개 항목을 붙여넣을까요?", "File Options": "파일 옵션",
"Rename": "이름 바꾸기", "Show Hidden": "숨겨진 항목 표시",
"Set New File Name": "새 파일명 입력", "Folders First": "폴더 우선 정렬",
"Advanced": "고급", "Hidden Last": "숨겨진 항목 후순 정렬",
"Advanced Options": "고급 옵션", "Cut": "잘라내기",
"Create File": "새 파일", "Copy": "복사",
"Set File Name": "파일명 입력", "Paste": "붙여넣기",
"Create Folder": "새 폴더", "Paste ": " ",
"Set Folder Name": "폴더명 입력", " file(s)?": "개 항목을 붙여넣을까요?",
"View as text (unfinished)": "텍스트로 보기 (미완성)", "Rename": "이름 바꾸기",
"Empty...": "비어있음...", "Set New File Name": "새 파일명 입력",
"Open with DayBreak?": "DayBreak로 열까요?", "Advanced": "고급",
"Launch ": "실행할까요 ", "Advanced Options": "고급 옵션",
"Launch option for: ": "실행 옵션: ", "Create File": "새 파일",
"Select launcher for: ": "실행 런처: ", "Set File Name": "파일명 입력",
"Create Folder": "새 폴더",
"Homebrew": "홈브류", "Set Folder Name": "폴더명 입력",
"Homebrew Options": "홈브류 옵션", "View as text (unfinished)": "텍스트로 보기 (미완성)",
"Hide Sphaira": "Sphaira 숨기기", "Ignore read only": "읽기 전용 설정 무시",
"Install Forwarder": "바로가기 설치", "Mount": "마운트",
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에서 바로가기 설치시 밴 위험이 있습니다!", "Empty...": "비어있음...",
"Installing Forwarder": "바로가기 설치", "Open with DayBreak?": "DayBreak로 열까요?",
"Creating Program": "프로그램 생성", "Launch ": "실행할까요 ",
"Creating Control": "컨트롤 생성", "Launch option for: ": "실행 옵션: ",
"Creating Meta": "메타 생성", "Select launcher for: ": "실행 런처: ",
"Writing Nca": "Nca 쓰기",
"Updating ncm databse": "Ncm 데이터베이스 업데이트", "Homebrew": "홈브류",
"Pushing application record": "응용 프로그램 기록 푸싱", "Homebrew Options": "홈브류 옵션",
"Installed!": "설치 완료!", "Hide Sphaira": "Sphaira 숨기기",
"Failed to install forwarder": "바로가기 설치 실패", "Install Forwarder": "바로가기 설치",
"Unstarred ": "즐겨찾기 해제: ", "WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 바로가기 설치 시, 밴 위험이 있습니다!",
"Starred ": "즐겨찾기 적용: ", "Installing Forwarder": "바로가기 설치",
"Creating Program": "프로그램 생성",
"AppStore": "앱스토어", "Creating Control": "컨트롤 생성",
"Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s", "Creating Meta": "메타 생성",
"AppStore Options": "앱스토어 옵션", "Writing Nca": "Nca 쓰기",
"All": "모두", "Updating ncm databse": "Ncm 데이터베이스 업데이트",
"Games": "게임", "Pushing application record": "응용 프로그램 기록 푸싱",
"Emulators": "에뮬레이터", "Installed!": "설치 완료!",
"Tools": "도구", "Failed to install forwarder": "바로가기 설치 실패",
"Themes": "테마", "Unstarred ": "즐겨찾기 해제: ",
"Legacy": "레거시", "Starred ": "즐겨찾기 적용: ",
"version: %s": "버전: %s",
"updated: %s": "업데이트: %s", "AppStore": "앱스토어",
"category: %s": "카테고리: %s", "Filter: %s | Sort: %s | Order: %s": "필터: %s | 분류: %s | 정렬: %s",
"extracted: %.2f MiB": "용량: %.2f MiB", "AppStore Options": "앱스토어 옵션",
"app_dls: %s": "다운로드 횟수: %s", "All": "모두",
"More by Author": "개발자의 다른 앱 더 보기", "Games": "게임",
"Leave Feedback": "피드백 남기기", "Emulators": "에뮬레이터",
"Tools": "도구",
"Irs": "조이콘 적외선 카메라", "Themes": "테마",
"Ambient Noise Level: ": "주변 노이즈 레벨: ", "Legacy": "레거시",
"Controller": "컨트롤러", "version: %s": "버전: %s",
"Pad ": "조이콘 ", "updated: %s": "업데이트: %s",
" (Available)": " (사용 가능)", "category: %s": "카테고리: %s",
" (Unsupported)": " (지원 안됨)", "extracted: %.2f MiB": "용량: %.2f MiB",
" (Unconnected)": " (연결 없음)", "app_dls: %s": "다운로드 횟수: %s",
"HandHeld": "본체 연결", "More by Author": "개발자의 다른 앱 더 보기",
"Rotation": "화면 회전", "Leave Feedback": "피드백 남기기",
"0 (Sideways)": "반시계방향 90° 회전",
"90 (Flat)": "정방향", "Irs": "조이콘 적외선 카메라",
"180 (-Sideways)": "시계방향 90° 회전", "Ambient Noise Level: ": "주변 노이즈 레벨: ",
"270 (Upside down)": "상하반전", "Controller": "컨트롤러",
"Colour": "색상", "Pad ": "조이콘 ",
"Grey": "회색", " (Available)": " (사용 가능)",
"Ironbow": "아이언보우", " (Unsupported)": " (지원 안됨)",
"Green": "초록색", " (Unconnected)": " (연결 없음)",
"Red": "빨간색", "HandHeld": "본체 연결",
"Blue": "파란색", "Rotation": "화면 회전",
"Light Target": "반사 표적", "0 (Sideways)": "반시계방향 90° 회전",
"All leds": "모든 LED 켜기", "90 (Flat)": "정방향",
"Bright group": "Bright LED 켜기", "180 (-Sideways)": "시계방향 90° 회전",
"Dim group": "Dim LED 켜기", "270 (Upside down)": "상하반전",
"None": "LED 끄기", "Colour": "색상",
"Gain": "대비", "Grey": "회색",
"Negative Image": "화상 이미지", "Ironbow": "아이언보우",
"Normal image": "일반", "Green": "초록색",
"Negative image": "반전", "Red": "빨간색",
"Format": "해상도", "Blue": "파란색",
"320x240": "320×240", "Light Target": "반사 표적",
"160x120": "160×120", "All leds": "모든 LED 켜기",
"80x60": "80×60", "Bright group": "Bright LED 켜기",
"40x30": "40×30", "Dim group": "Dim LED 켜기",
"20x15": "20×15", "None": "LED 끄기",
"Trimming Format": "트리밍 해상도", "Gain": "대비",
"External Light Filter": "외부 조명 필터", "Negative Image": "화상 이미지",
"Load Default": "기본값으로 설정", "Normal image": "일반",
"Negative image": "반전",
"Themezer": "Themezer", "Format": "해상도",
"Themezer Options": "Themezer 옵션", "320x240": "320×240",
"Nsfw": "선정성 테마", "160x120": "160×120",
"Page": "페이지", "80x60": "80×60",
"Page %zu / %zu": "페이지 %zu / %zu", "40x30": "40×30",
"Enter Page Number": "페이지 번호 입력", "20x15": "20×15",
"Bad Page": "잘못된 페이지", "Trimming Format": "트리밍 해상도",
"Download theme?": "테마를 다운로드할까요?", "External Light Filter": "외부 조명 필터",
"Load Default": "기본값으로 설정",
"GitHub": "GitHub",
"Downloading json": "JSON에서 다운로드", "Themezer": "Themezer",
"Select asset to download for ": "다운로드 아이템 선택 ", "Themezer Options": "Themezer 옵션",
"Nsfw": "선정성 테마",
"Installing ": "설치 ", "Page": "페이지",
"Uninstalling ": "설치 제거 ", "Page %zu / %zu": "페이지 %zu / %zu",
"Deleting ": "삭제 ", "Enter Page Number": "페이지 번호 입력",
"Deleting": "삭제", "Bad Page": "잘못된 페이지",
"Pasting ": "붙여넣기 ", "Download theme?": "테마를 다운로드할까요?",
"Pasting": "붙여넣기",
"Removing ": "제거 ", "GitHub": "GitHub",
"Scanning ": "스캔 ", "Downloading json": "JSON에서 다운로드",
"Creating ": "생성 ", "Select asset to download for ": "다운로드 아이템: ",
"Copying ": "복사 ",
"Trying to load ": "썸네일 받아오는 중... ", "Installing ": "설치 ",
"Downloading ": "다운로드 ", "Uninstalling ": "설치 제거 ",
"Downloaded ": "다운로드 완료: ", "Deleting ": "삭제 ",
"Removed ": "제거 됨: ", "Deleting": "제",
"Checking MD5": "MD5 확인", "Pasting ": "붙여넣기 ",
"Loading...": "로딩 중...", "Pasting": "붙여넣기",
"Loading": "로딩 중...", "Removing ": "제거 ",
"Empty!": "찾을 수 없습니다!", "Scanning ": "스캔 ",
"Not Ready...": "준비되지 않음...", "Creating ": "생성 ",
"Error loading page!": "페이지 로딩 오류!", "Copying ": "복사 ",
"Update avaliable: ": "업데이트 가능: ", "Trying to load ": "썸네일 받아오는 중... ",
"Download update: ": "업데이트 다운로드: ", "Downloading ": "다운로드 ",
"Updated to ": "업데이트: ", "Downloaded ": "다운로드 완료: ",
"Restart Sphaira?": "Sphaira를 재시작할까요?", "Removed ": "제거 됨: ",
"Failed to download update": "업데이트 다운로드 실패함", "Checking MD5": "MD5 확인",
"Delete Selected files?": "선택한 파일을 삭제할까요?", "Loading...": "로딩 중...",
"Completely remove ": "정말 삭제할까요 ", "Loading": "로딩 중...",
"Are you sure you want to delete ": "정말 삭제할까요 ", "Empty!": "찾을 수 없습니다!",
"Are you sure you wish to cancel?": "정말 취소할까요?", "Not Ready...": "준비되지 않음...",
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 등록하세요." "Error loading page!": "페이지 로딩 오류!",
"Update avaliable: ": "업데이트 가능: ",
"Download update: ": "업데이트 다운로드: ",
"Updated to ": "업데이트: ",
"Press OK to restart Sphaira": "확인 버튼 입력하여 Sphaira 재시작",
"Restart Sphaira?": "Sphaira를 재시작할까요?",
"Failed to download update": "업데이트 다운로드 실패함",
"Restore hbmenu?": "hbmenu로 교체할까요?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "/switch/hbmemu.nro를 찾을 수 없습니다\n앱스토어에서 다시 설치하세요",
"Failed to restore hbmenu, please re-download hbmenu": "hbmenu 교체 실패함, hbmenu를 다시 다운로드하세요",
"Failed to restore hbmenu, using sphaira instead": "hbmenu 교체 실패함, sphaira를 계속 사용합니다",
"Restored hbmenu, closing sphaira": "hbmenu로 교체됨, sphaira를 종료합니다",
"Restored hbmenu": "hbmenu로 교체됨",
"Delete Selected files?": "선택한 파일을 삭제할까요?",
"Completely remove ": "정말 삭제할까요 ",
"Are you sure you want to delete ": "정말 삭제할까요 ",
"Are you sure you wish to cancel?": "정말 취소할까요?",
"If this message appears repeatedly, please open an issue.": "해당 메시지가 반복해서 나타나는 경우, 이슈를 등록하세요."
} }

View File

@@ -19,6 +19,7 @@
"Details": "", "Details": "",
"Update": "", "Update": "",
"Remove": "", "Remove": "",
"Restore": "",
"Download": "Downloaden", "Download": "Downloaden",
"Next Page": "Volgende pagina", "Next Page": "Volgende pagina",
"Prev Page": "Vorige pagina", "Prev Page": "Vorige pagina",
@@ -26,6 +27,12 @@
"Star": "", "Star": "",
"System memory": "", "System memory": "",
"microSD card": "", "microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Ja", "Yes": "Ja",
"No": "Nee", "No": "Nee",
"Enabled": "Ingeschakeld", "Enabled": "Ingeschakeld",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "", "Alphabetical (Star)": "",
"Likes": "", "Likes": "",
"ID": "", "ID": "",
"Decending": "Aflopend", "Descending": "Aflopend",
"Descending (down)": "Aflopend", "Descending (down)": "Aflopend",
"Desc": "Aflopend", "Desc": "Aflopend",
"Ascending": "Oplopend", "Ascending": "Oplopend",
@@ -54,7 +61,6 @@
"Asc": "Oplopend", "Asc": "Oplopend",
"Menu Options": "Menu-opties", "Menu Options": "Menu-opties",
"Header": "Koptekst",
"Theme": "Thema", "Theme": "Thema",
"Theme Options": "Thema Opties", "Theme Options": "Thema Opties",
"Select Theme": "Selecteer Thema", "Select Theme": "Selecteer Thema",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Loggen", "Logging": "Loggen",
"Replace hbmenu on exit": "Vervang hbmenu bij afsluiten", "Replace hbmenu on exit": "Vervang hbmenu bij afsluiten",
"Misc": "Diversen", "Misc": "Diversen",
@@ -92,6 +99,7 @@
"Install forwarders": "", "Install forwarders": "",
"Install location": "", "Install location": "",
"Show install warning": "", "Show install warning": "",
"Text scroll speed": "",
"FileBrowser": "Bestandsbrowser", "FileBrowser": "Bestandsbrowser",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -114,6 +122,8 @@
"Create Folder": "Map maken", "Create Folder": "Map maken",
"Set Folder Name": "", "Set Folder Name": "",
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)", "View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
"Ignore read only": "",
"Mount": "",
"Empty...": "", "Empty...": "",
"Open with DayBreak?": "", "Open with DayBreak?": "",
"Launch ": "", "Launch ": "",
@@ -228,8 +238,15 @@
"Update avaliable: ": "", "Update avaliable: ": "",
"Download update: ": "", "Download update: ": "",
"Updated to ": "", "Updated to ": "",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Failed to download update": "", "Failed to download update": "",
"Restore hbmenu?": "",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
"Failed to restore hbmenu, please re-download hbmenu": "",
"Failed to restore hbmenu, using sphaira instead": "",
"Restored hbmenu, closing sphaira": "",
"Restored hbmenu": "",
"Delete Selected files?": "", "Delete Selected files?": "",
"Completely remove ": "", "Completely remove ": "",
"Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ", "Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ",

View File

@@ -15,50 +15,56 @@
"Install": "Instalar", "Install": "Instalar",
"Delete": "Excluir", "Delete": "Excluir",
"Restart": "Reiniciar", "Restart": "Reiniciar",
"Changelog": "Changelog", "Changelog": "Alterações",
"Details": "Detalhes", "Details": "Detalhes",
"Update": "Atualizar", "Update": "Atualizar",
"Remove": "Remover", "Remove": "Remover",
"Download": "Download", "Restore": "Restaurar",
"Download": "Baixar",
"Next Page": "Próxima página", "Next Page": "Próxima página",
"Prev Page": "Página anterior", "Prev Page": "Página anterior",
"Unstar": "Desfavoritar", "Unstar": "Desfavoritar",
"Star": "Favoritar", "Star": "Favoritar",
"System memory": "Memória do console", "System memory": "Memória do console",
"microSD card": "Cartão microSD", "microSD card": "Cartão microSD",
"Sd": "SD",
"Image System memory": "Imagem (memória do console)",
"Image microSD card": "Imagem (cartão microSD)",
"Slow": "Lenta",
"Normal": "Normal",
"Fast": "Rápida",
"Yes": "Sim", "Yes": "Sim",
"No": "Não", "No": "Não",
"Enabled": "Habilitado", "Enabled": "Sim",
"Disabled": "Desabilitado", "Disabled": "o",
"Sort By": "Ordenar por", "Sort By": "Ordernar/Organizar",
"Sort Options": "Opções de classificação", "Sort Options": "Ordernar/Organizar",
"Filter": "Filtro", "Filter": "Filtro",
"Sort": "Organizar", "Sort": "Organizar por",
"Order": "Ordem", "Order": "Ordem",
"Search": "Procurar", "Search": "Buscar",
"Updated": "Atualizado", "Updated": "Atualizado",
"Updated (Star)": "Atualizado (Favoritos)", "Updated (Star)": "Atualizado (favoritos)",
"Downloads": "Downloads", "Downloads": "Nº de downloads",
"Size": "Tamanho", "Size": "Tamanho",
"Size (Star)": "Tamanho (Favoritos)", "Size (Star)": "Tamanho (favoritos)",
"Alphabetical": "Alfabético", "Alphabetical": "Ordem alfabética",
"Alphabetical (Star)": "Alfabético (Favoritos)", "Alphabetical (Star)": "Ordem alfabética (favoritos)",
"Likes": "Curtidas", "Likes": "Nº de curtidas",
"ID": "ID", "ID": "ID",
"Decending": "Decrescente", "Descending": "Decrescente",
"Descending (down)": "Decrescente (Baixo)", "Descending (down)": "Decrescente (baixo)",
"Desc": "Decr.", "Desc": "Decr.",
"Ascending": "Ascendente", "Ascending": "Ascendente",
"Ascending (Up)": "Ascendente (Cima)", "Ascending (Up)": "Ascendente (cima)",
"Asc": "Asc.", "Asc": "Asc.",
"Menu Options": "Opções do menu", "Menu Options": "Opções do menu",
"Header": "Cabeçalho",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Opções de tema", "Theme Options": "Opções de tema",
"Select Theme": "Selecionar tema", "Select Theme": "Tema atual",
"Shuffle": "Embaralhar", "Shuffle": "Embaralhar temas",
"Music": "Música", "Music": "Música",
"Network": "Rede", "Network": "Rede",
"Network Options": "Opções de rede", "Network Options": "Opções de rede",
@@ -84,47 +90,51 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Logging": "Logging", "Vietnamese": "Vietnamese",
"Logging": "Registro de depuração",
"Replace hbmenu on exit": "Substituir hbmenu ao sair", "Replace hbmenu on exit": "Substituir hbmenu ao sair",
"Misc": "Diversos", "Misc": "Diversos",
"Misc Options": "Opções diversas", "Misc Options": "Opções diversas",
"Web": "Navegador web", "Web": "Navegador de internet",
"Install forwarders": "Instalar forwarder", "Install forwarders": "Instalar forwarders",
"Install location": "Local de instalação", "Install location": "Local de instalação",
"Show install warning": "Mostrar aviso de instalação", "Show install warning": "Mostrar aviso de instalação",
"Text scroll speed": "Rolagem do texto",
"FileBrowser": "Navegador de arquivos", "FileBrowser": "Arquivos",
"%zd files": "%zd arquivo(s)", "%zd files": "%zd arquivo(s)",
"%zd dirs": "%zd diretório(s)", "%zd dirs": "%zd diretório(s)",
"File Options": "Opções de arquivo", "File Options": "Opções de arquivo",
"Show Hidden": "Mostrar ocultos", "Show Hidden": "Mostrar ocultos",
"Folders First": "Pastas primeiro", "Folders First": "Pastas primeiro",
"Hidden Last": "Ocultos por último", "Hidden Last": "Ocultos por último",
"Cut": "Cortar", "Cut": "Recortar",
"Copy": "Copiar", "Copy": "Copiar",
"Paste": "Colar", "Paste": "Colar",
"Paste ": "Colar", "Paste ": "Colar ",
" file(s)?": " arquivo(s)?", " file(s)?": " arquivo(s)?",
"Rename": "Renomear", "Rename": "Renomear",
"Set New File Name": "Definir novo nome do arquivo", "Set New File Name": "Defina o nome do novo arquivo",
"Advanced": "Avançado", "Advanced": "Avançado",
"Advanced Options": "Opções avançadas", "Advanced Options": "Opções avançadas",
"Create File": "Criar arquivo", "Create File": "Criar arquivo",
"Set File Name": "Definir nome do arquivo", "Set File Name": "Defina o nome do arquivo",
"Create Folder": "Criar pasta", "Create Folder": "Criar pasta",
"Set Folder Name": "Definir novo nome da pasta", "Set Folder Name": "Defina o nome da pasta",
"View as text (unfinished)": "Ver como texto (inacabado)", "View as text (unfinished)": "Ver como texto (inacabado)",
"Ignore read only": "Ignorar somente leitura",
"Mount": "Montar",
"Empty...": "Vazio...", "Empty...": "Vazio...",
"Open with DayBreak?": "Abrir com DayBreak?", "Open with DayBreak?": "Abrir com DayBreak?",
"Launch ": "Iniciar", "Launch ": "Iniciar ",
"Launch option for: ": "Opções de inicialização para: ", "Launch option for: ": "Opções de inicialização para: ",
"Select launcher for: ": "Selecionar launcher para: ", "Select launcher for: ": "Selecionar launcher para: ",
"Homebrew": "Homebrew", "Homebrew": "Aplicativos",
"Homebrew Options": "Opções do Homebrew", "Homebrew Options": "Opções do aplicativo",
"Hide Sphaira": "Esconder Sphaira", "Hide Sphaira": "Esconder sphaira",
"Install Forwarder": "Instalar forwarder", "Install Forwarder": "Instalar forwarder",
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Isso pode resultar em um banimento!", "WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar forwarders pode\nresultar em um banimento!",
"Installing Forwarder": "Instalando forwarder", "Installing Forwarder": "Instalando forwarder",
"Creating Program": "Criando Program", "Creating Program": "Criando Program",
"Creating Control": "Criando Control", "Creating Control": "Criando Control",
@@ -137,9 +147,9 @@
"Unstarred ": "Desfavoritado ", "Unstarred ": "Desfavoritado ",
"Starred ": "Favoritado ", "Starred ": "Favoritado ",
"AppStore": "AppStore", "AppStore": "Loja",
"Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Organizar: %s | Ordem: %s", "Filter: %s | Sort: %s | Order: %s": "Filtro: %s | Por: %s | Ordem: %s",
"AppStore Options": "Opções da AppStore", "AppStore Options": "Opções da loja",
"All": "Todos", "All": "Todos",
"Games": "Jogos", "Games": "Jogos",
"Emulators": "Emuladores", "Emulators": "Emuladores",
@@ -151,29 +161,29 @@
"category: %s": "categoria: %s", "category: %s": "categoria: %s",
"extracted: %.2f MiB": "tam. extraído: %.2f MiB", "extracted: %.2f MiB": "tam. extraído: %.2f MiB",
"app_dls: %s": "downloads: %s", "app_dls: %s": "downloads: %s",
"More by Author": "Mais do autor", "More by Author": "Mais deste autor",
"Leave Feedback": "Deixar um feedback", "Leave Feedback": "Deixar um feedback",
"Irs": "Irs", "Irs": "IRS",
"Ambient Noise Level: ": "Nível de ruído ambiente", "Ambient Noise Level: ": "Nível de ruído ambiente: ",
"Controller": "Controle", "Controller": "Controle",
"Pad ": "Pad ", "Pad ": "Pad ",
" (Available)": " (Disponível)", " (Available)": " (disponível)",
" (Unsupported)": "(Não suportado)", " (Unsupported)": "(não suportado)",
" (Unconnected)": " (Desconectado)", " (Unconnected)": " (desconectado)",
"HandHeld": "Portátil", "HandHeld": "Portátil",
"Rotation": "Rotação", "Rotation": "Rotação",
"0 (Sideways)": "0 (Lateralmente)", "0 (Sideways)": "0 (lateralmente)",
"90 (Flat)": "90 (plano)", "90 (Flat)": "90 (plano)",
"180 (-Sideways)": "180 (-Lateralmente)", "180 (-Sideways)": "180 (-lateralmente)",
"270 (Upside down)": "270 (De cabeça para baixo)", "270 (Upside down)": "270 (de cabeça para baixo)",
"Colour": "Cor", "Colour": "Cor",
"Grey": "Cinza", "Grey": "Cinza",
"Ironbow": "Arco de ferro", "Ironbow": "Arco de ferro",
"Green": "Verde", "Green": "Verde",
"Red": "Vermelho", "Red": "Vermelho",
"Blue": "Azul", "Blue": "Azul",
"Light Target": "Alvo leve", "Light Target": "Alvo de luz",
"All leds": "Todos os LEDs", "All leds": "Todos os LEDs",
"Bright group": "Grupo claro", "Bright group": "Grupo claro",
"Dim group": "Grupo escuro", "Dim group": "Grupo escuro",
@@ -194,21 +204,21 @@
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Opções do Themezer", "Themezer Options": "Opções do Themezer",
"Nsfw": "NSFW", "Nsfw": "Temas 18+ (NSFW)",
"Page": "Página", "Page": "Ir para página",
"Page %zu / %zu": "Page %zu / %zu", "Page %zu / %zu": "Página %zu / %zu",
"Enter Page Number": "Digite o número da página", "Enter Page Number": "Número da página",
"Bad Page": "Página inválida", "Bad Page": "Página inválida",
"Download theme?": "Baixar tema?", "Download theme?": "Baixar tema?",
"GitHub": "", "GitHub": "GitHub",
"Downloading json": "", "Downloading json": "Baixando JSON",
"Select asset to download for ": "", "Select asset to download for ": "Selecione o recurso para baixar em ",
"Installing ": "Instalando ", "Installing ": "Instalando ",
"Uninstalling ": "Desinstalando ", "Uninstalling ": "Desinstalando ",
"Deleting ": "Deletando ", "Deleting ": "Excluindo ",
"Deleting": "Deletando ", "Deleting": "Excluindo",
"Pasting ": "Colando ", "Pasting ": "Colando ",
"Pasting": "Colando ", "Pasting": "Colando ",
"Removing ": "Removendo ", "Removing ": "Removendo ",
@@ -217,22 +227,29 @@
"Copying ": "Copiando ", "Copying ": "Copiando ",
"Trying to load ": "Tentando carregar ", "Trying to load ": "Tentando carregar ",
"Downloading ": "Baixando ", "Downloading ": "Baixando ",
"Downloaded ": "", "Downloaded ": "Baixado ",
"Removed ": "", "Removed ": "Removido ",
"Checking MD5": "Checando MD5", "Checking MD5": "Checando MD5",
"Loading...": "Carregando...", "Loading...": "Carregando...",
"Loading": "Carregando", "Loading": "Carregando",
"Empty!": "Vazio!", "Empty!": "Vazio",
"Not Ready...": "Não está pronto...", "Not Ready...": "Não está pronto...",
"Error loading page!": "Erro ao carregar página!", "Error loading page!": "Erro ao carregar página!",
"Update avaliable: ": "Atualização disponível: ", "Update avaliable: ": "Atualização disponível: ",
"Download update: ": "Baixar autalização: ", "Download update: ": "Baixar autalização: ",
"Updated to ": "Atualizado para ", "Updated to ": "Atualizado para ",
"Restart Sphaira?": "Reiniciar Sphaira?", "Press OK to restart Sphaira": "Selecione OK para reiniciar o sphaira",
"Restart Sphaira?": "Reiniciar sphaira?",
"Failed to download update": "Falha ao baixar a atualização", "Failed to download update": "Falha ao baixar a atualização",
"Delete Selected files?": "Deletar arquivos selecionados?", "Restore hbmenu?": "Restaurar hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Falha ao buscar /switch/hbmenu.nro\nUse a AppStore para reinstalar o hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Falha ao restaurar o hbmenu, baixe o hbmenu novamente",
"Failed to restore hbmenu, using sphaira instead": "Falha ao restaurar hbmenu, usando sphaira",
"Restored hbmenu, closing sphaira": "hbmenu restaurado, fechando sphaira",
"Restored hbmenu": "hbmenu restaurado",
"Delete Selected files?": "Excluir os arquivos selecionados?",
"Completely remove ": "Remover completamente ", "Completely remove ": "Remover completamente ",
"Are you sure you want to delete ": "Você tem certeza que quer deletar ", "Are you sure you want to delete ": "Você tem certeza que quer excluir ",
"Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?", "Are you sure you wish to cancel?": "Você tem certeza que quer cancelar?",
"If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue." "If this message appears repeatedly, please open an issue.": "Se esta mensagem aparecer repetidamente, abra um issue."
} }

View File

@@ -19,6 +19,7 @@
"Details": "", "Details": "",
"Update": "", "Update": "",
"Remove": "", "Remove": "",
"Restore": "",
"Download": "Скачать", "Download": "Скачать",
"Next Page": "Следующая страница", "Next Page": "Следующая страница",
"Prev Page": "Предыдущая страница", "Prev Page": "Предыдущая страница",
@@ -26,6 +27,12 @@
"Star": "", "Star": "",
"System memory": "", "System memory": "",
"microSD card": "", "microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Да", "Yes": "Да",
"No": "Нет", "No": "Нет",
"Enabled": "Включено", "Enabled": "Включено",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "", "Alphabetical (Star)": "",
"Likes": "", "Likes": "",
"ID": "", "ID": "",
"Decending": "По убыванию", "Descending": "По убыванию",
"Descending (down)": "По убыванию", "Descending (down)": "По убыванию",
"Desc": "По убыванию", "Desc": "По убыванию",
"Ascending": "По возрастанию", "Ascending": "По возрастанию",
@@ -54,7 +61,6 @@
"Asc": "По возрастанию", "Asc": "По возрастанию",
"Menu Options": "Параметры меню", "Menu Options": "Параметры меню",
"Header": "Заголовок",
"Theme": "Тема", "Theme": "Тема",
"Theme Options": "Параметры темы", "Theme Options": "Параметры темы",
"Select Theme": "Выберите тему", "Select Theme": "Выберите тему",
@@ -84,6 +90,7 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Журналирование", "Logging": "Журналирование",
"Replace hbmenu on exit": "Заменить hbmenu при выходе", "Replace hbmenu on exit": "Заменить hbmenu при выходе",
"Misc": "Прочее", "Misc": "Прочее",
@@ -92,6 +99,7 @@
"Install forwarders": "", "Install forwarders": "",
"Install location": "", "Install location": "",
"Show install warning": "", "Show install warning": "",
"Text scroll speed": "",
"FileBrowser": "Файловый менеджер", "FileBrowser": "Файловый менеджер",
"%zd files": "%zd files", "%zd files": "%zd files",
@@ -114,6 +122,8 @@
"Create Folder": "Создать папку", "Create Folder": "Создать папку",
"Set Folder Name": "", "Set Folder Name": "",
"View as text (unfinished)": "Посмотреть как текст (незакончено)", "View as text (unfinished)": "Посмотреть как текст (незакончено)",
"Ignore read only": "",
"Mount": "",
"Empty...": "", "Empty...": "",
"Open with DayBreak?": "", "Open with DayBreak?": "",
"Launch ": "", "Launch ": "",
@@ -228,8 +238,15 @@
"Update avaliable: ": "", "Update avaliable: ": "",
"Download update: ": "", "Download update: ": "",
"Updated to ": "", "Updated to ": "",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "", "Restart Sphaira?": "",
"Failed to download update": "", "Failed to download update": "",
"Restore hbmenu?": "",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "",
"Failed to restore hbmenu, please re-download hbmenu": "",
"Failed to restore hbmenu, using sphaira instead": "",
"Restored hbmenu, closing sphaira": "",
"Restored hbmenu": "",
"Delete Selected files?": "", "Delete Selected files?": "",
"Completely remove ": "", "Completely remove ": "",
"Are you sure you want to delete ": "Вы уверены, что хотите удалить ", "Are you sure you want to delete ": "Вы уверены, что хотите удалить ",

View File

@@ -1,6 +1,6 @@
{ {
"[Applet Mode]": "[Applet-läge]", "[Applet Mode]": "[Applet-läge]",
"No Internet": "Ingen internetanslutning", "No Internet": "Ingen Internetanslutning",
"Files": "Filer", "Files": "Filer",
"Apps": "Appar", "Apps": "Appar",
"Store": "Butik", "Store": "Butik",
@@ -14,47 +14,53 @@
"Info": "Info", "Info": "Info",
"Install": "Installera", "Install": "Installera",
"Delete": "Radera", "Delete": "Radera",
"Restart": "", "Restart": "Starta om",
"Changelog": "Ändringslogg", "Changelog": "Ändringslogg",
"Details": "Detaljer", "Details": "Detaljer",
"Update": "Uppdatera", "Update": "Uppdatera",
"Remove": "Ta bort", "Remove": "Ta bort",
"Restore": "Återställ",
"Download": "Ladda ner", "Download": "Ladda ner",
"Next Page": "Nästa sida", "Next Page": "Nästa sida",
"Prev Page": "Föregående sida", "Prev Page": "Föregående sida",
"Unstar": "", "Unstar": "Avmarkera stjärna",
"Star": "", "Star": "Markera stjärna",
"System memory": "", "System memory": "Systemminne",
"microSD card": "", "microSD card": "microSD-kort",
"Sd": "Sd",
"Image System memory": "Avbild Systemminne",
"Image microSD card": "Avbild microSD-kort",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Ja", "Yes": "Ja",
"No": "Nej", "No": "Nej",
"Enabled": "Aktiverad", "Enabled": "Aktiverad",
"Disabled": "Avaktiverad", "Disabled": "Inaktiverad",
"Sort By": "Sortera efter", "Sort By": "Sortera efter",
"Sort Options": "Sorteringsalternativ", "Sort Options": "Sorteringsalternativ",
"Filter": "Filter", "Filter": "Filtrera",
"Sort": "Sortera", "Sort": "Sortera",
"Order": "Ordning", "Order": "Ordning",
"Search": "Sök", "Search": "Sök",
"Updated": "Uppdaterad", "Updated": "Uppdaterad",
"Updated (Star)": "", "Updated (Star)": "Uppdaterad (Stjärna)",
"Downloads": "Nedladdningar", "Downloads": "Nedladdningar",
"Size": "Storlek", "Size": "Storlek",
"Size (Star)": "", "Size (Star)": "Storlek (Stjärna)",
"Alphabetical": "Alfabetisk", "Alphabetical": "Alfabetisk",
"Alphabetical (Star)": "", "Alphabetical (Star)": "Alfabetisk (Stjärna)",
"Likes": "Gillar", "Likes": "Gillar",
"ID": "ID", "ID": "ID",
"Decending": "Fallande", "Descending": "Fallande",
"Descending (down)": "Fallande (nedåt)", "Descending (down)": "Fallande (nedåt)",
"Desc": "Fallande", "Desc": "Fall",
"Ascending": "Stigande", "Ascending": "Stigande",
"Ascending (Up)": "Stigande (uppåt)", "Ascending (Up)": "Stigande (uppåt)",
"Asc": "Stigande", "Asc": "Stig",
"Menu Options": "Menyalternativ", "Menu Options": "Menyalternativ",
"Header": "Rubrik",
"Theme": "Tema", "Theme": "Tema",
"Theme Options": "Temaalternativ", "Theme Options": "Temaalternativ",
"Select Theme": "Välj tema", "Select Theme": "Välj tema",
@@ -66,10 +72,10 @@
"Mtp": "MTP", "Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink ansluten", "Nxlink Connected": "Nxlink ansluten",
"Nxlink Upload": "Nxlink uppladdning", "Nxlink Upload": "Nxlink överför",
"Nxlink Finished": "Nxlink klar", "Nxlink Finished": "Nxlink klar",
"Switch-Handheld!": "", "Switch-Handheld!": "Switch Handhållen!",
"Switch-Docked!": "", "Switch-Docked!": "Switch Dockad!",
"Language": "Språk", "Language": "Språk",
"Auto": "Auto", "Auto": "Auto",
"English": "Engelska", "English": "Engelska",
@@ -84,15 +90,17 @@
"Portuguese": "Portugisiska", "Portuguese": "Portugisiska",
"Russian": "Ryska", "Russian": "Ryska",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "Loggning", "Logging": "Loggning",
"Replace hbmenu on exit": "Ersätt hbmenu vid avslut", "Replace hbmenu on exit": "Ersätt hbmenu vid avslut",
"Misc": "Övrigt", "Misc": "Övrigt",
"Misc Options": "Övriga alternativ", "Misc Options": "Övriga alternativ",
"Web": "Webb", "Web": "Webb",
"Install forwarders": "", "Install forwarders": "Installera genvägar",
"Install location": "", "Install location": "Installationsplats",
"Show install warning": "", "Show install warning": "Visa installationsvarning",
"Text scroll speed": "",
"FileBrowser": "Filbläddrare", "FileBrowser": "Filbläddrare",
"%zd files": "%zd filer", "%zd files": "%zd filer",
"%zd dirs": "%zd kataloger", "%zd dirs": "%zd kataloger",
@@ -114,29 +122,31 @@
"Create Folder": "Skapa mapp", "Create Folder": "Skapa mapp",
"Set Folder Name": "Ange mappnamn", "Set Folder Name": "Ange mappnamn",
"View as text (unfinished)": "Visa som text (ofärdig)", "View as text (unfinished)": "Visa som text (ofärdig)",
"Ignore read only": "Ignorera skrivskydd",
"Mount": "Montera",
"Empty...": "Tom...", "Empty...": "Tom...",
"Open with DayBreak?": "Öppna med DayBreak?", "Open with DayBreak?": "Öppna med DayBreak?",
"Launch ": "", "Launch ": "Starta ",
"Launch option for: ": "Startalternativ för: ", "Launch option for: ": "Startalternativ för: ",
"Select launcher for: ": "", "Select launcher for: ": "Välj startprogram för: ",
"Homebrew": "Homebrew", "Homebrew": "Homebrew",
"Homebrew Options": "Homebrew-alternativ", "Homebrew Options": "Homebrew-alternativ",
"Hide Sphaira": "Dölj Sphaira", "Hide Sphaira": "Dölj Sphaira",
"Install Forwarder": "Installera forwarder", "Install Forwarder": "Installera genväg",
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera forwarders leder till en avstängning!", "WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera genvägar kan leda till avstängning!",
"Installing Forwarder": "Installerar forwarder", "Installing Forwarder": "Installerar genväg",
"Creating Program": "Skapar program", "Creating Program": "Skapar program",
"Creating Control": "Skapar kontroll", "Creating Control": "Skapar kontroll",
"Creating Meta": "Skapar meta", "Creating Meta": "Skapar metadata",
"Writing Nca": "Skriver Nca", "Writing Nca": "Skriver Nca",
"Updating ncm databse": "Uppdaterar ncm-databas", "Updating ncm databse": "Uppdaterar ncm-databas",
"Pushing application record": "Lägger till applikationspost", "Pushing application record": "Skickar programpost",
"Installed!": "Installerad!", "Installed!": "Installerad!",
"Failed to install forwarder": "Misslyckades med att installera forwarder", "Failed to install forwarder": "Misslyckades att installera genväg",
"Unstarred ": "", "Unstarred ": "Avmarkerad ",
"Starred ": "", "Starred ": "Markerad ",
"AppStore": "AppStore", "AppStore": "AppStore",
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s", "Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s",
"AppStore Options": "AppStore-alternativ", "AppStore Options": "AppStore-alternativ",
@@ -151,21 +161,21 @@
"category: %s": "kategori: %s", "category: %s": "kategori: %s",
"extracted: %.2f MiB": "extraherad: %.2f MiB", "extracted: %.2f MiB": "extraherad: %.2f MiB",
"app_dls: %s": "app_nedladdningar: %s", "app_dls: %s": "app_nedladdningar: %s",
"More by Author": "Mer från författaren", "More by Author": "Mer av författaren",
"Leave Feedback": "Lämna feedback", "Leave Feedback": "Lämna feedback",
"Irs": "Irs", "Irs": "Irs",
"Ambient Noise Level: ": "Omgivningsljudnivå: ", "Ambient Noise Level: ": "Omgivningsljudnivå: ",
"Controller": "Kontroll", "Controller": "Kontroller",
"Pad ": "Handkontroll ", "Pad ": "Handkontroll ",
" (Available)": " (Tillgänglig)", " (Available)": " (Tillgänglig)",
" (Unsupported)": "", " (Unsupported)": " (Ej stödd)",
" (Unconnected)": " (Ej ansluten)", " (Unconnected)": " (Ej ansluten)",
"HandHeld": "Handhållen", "HandHeld": "Handhållen",
"Rotation": "Rotation", "Rotation": "Rotation",
"0 (Sideways)": "0 (Sido)", "0 (Sideways)": "0 (Sidan)",
"90 (Flat)": "90 (Platt)", "90 (Flat)": "90 (Platt)",
"180 (-Sideways)": "180 (-Sido)", "180 (-Sideways)": "180 (-Sidan)",
"270 (Upside down)": "270 (Upp och ner)", "270 (Upside down)": "270 (Upp och ner)",
"Colour": "Färg", "Colour": "Färg",
"Grey": "Grå", "Grey": "Grå",
@@ -175,8 +185,8 @@
"Blue": "Blå", "Blue": "Blå",
"Light Target": "Ljusmål", "Light Target": "Ljusmål",
"All leds": "Alla lysdioder", "All leds": "Alla lysdioder",
"Bright group": "Ljusstark grupp", "Bright group": "Ljus grupp",
"Dim group": "Dämpad grupp", "Dim group": "Dimma grupp",
"None": "Ingen", "None": "Ingen",
"Gain": "Förstärkning", "Gain": "Förstärkning",
"Negative Image": "Negativ bild", "Negative Image": "Negativ bild",
@@ -188,10 +198,10 @@
"80x60": "80×60", "80x60": "80×60",
"40x30": "40×30", "40x30": "40×30",
"20x15": "20×15", "20x15": "20×15",
"Trimming Format": "Trimformat", "Trimming Format": "Trimningsformat",
"External Light Filter": "Extern ljusfilter", "External Light Filter": "Externt ljusfilter",
"Load Default": "Ladda standardinställningar", "Load Default": "Ladda standard",
"Themezer": "Themezer", "Themezer": "Themezer",
"Themezer Options": "Themezer-alternativ", "Themezer Options": "Themezer-alternativ",
"Nsfw": "Nsfw", "Nsfw": "Nsfw",
@@ -201,9 +211,9 @@
"Bad Page": "Ogiltig sida", "Bad Page": "Ogiltig sida",
"Download theme?": "Ladda ner tema?", "Download theme?": "Ladda ner tema?",
"GitHub": "", "GitHub": "GitHub",
"Downloading json": "", "Downloading json": "Laddar ner JSON",
"Select asset to download for ": "", "Select asset to download for ": "Välj tillgång att ladda ner för ",
"Installing ": "Installerar ", "Installing ": "Installerar ",
"Uninstalling ": "Avinstallerar ", "Uninstalling ": "Avinstallerar ",
@@ -215,24 +225,31 @@
"Scanning ": "Skannar ", "Scanning ": "Skannar ",
"Creating ": "Skapar ", "Creating ": "Skapar ",
"Copying ": "Kopierar ", "Copying ": "Kopierar ",
"Trying to load ": "", "Trying to load ": "Försöker ladda ",
"Downloading ": "Laddar ner ", "Downloading ": "Laddar ner ",
"Downloaded ": "", "Downloaded ": "Nedladdad ",
"Removed ": "", "Removed ": "Borttagen ",
"Checking MD5": "Kontrollerar MD5", "Checking MD5": "Kontrollerar MD5",
"Loading...": "Laddar...", "Loading...": "Laddar...",
"Loading": "Laddar", "Loading": "Laddar",
"Empty!": "Tomt!", "Empty!": "Tomt!",
"Not Ready...": "Ej redo...", "Not Ready...": "Inte redo...",
"Error loading page!": "Fel vid laddning av sida!", "Error loading page!": "Fel vid laddning av sida!",
"Update avaliable: ": "Uppdatering tillgänglig: ", "Update avaliable: ": "Uppdatering tillgänglig: ",
"Download update: ": "Ladda ner uppdatering: ", "Download update: ": "Ladda ner uppdatering: ",
"Updated to ": "", "Updated to ": "Uppdaterad till ",
"Restart Sphaira?": "", "Press OK to restart Sphaira": "",
"Failed to download update": "Misslyckades med att ladda ner uppdatering", "Restart Sphaira?": "Starta om Sphaira?",
"Failed to download update": "Misslyckades att ladda ner uppdatering",
"Restore hbmenu?": "Återställ hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Kunde inte hitta /switch/hbmenu.nro\nInstallera om hbmenu från Appstore ",
"Failed to restore hbmenu, please re-download hbmenu": "Misslyckades med att återställa hbmenu, vänligen ladda ner hbmenu igen.",
"Failed to restore hbmenu, using sphaira instead": "Misslyckades med att återställa hbmenu, använder istället sphaira.",
"Restored hbmenu, closing sphaira": "Återställde hbmenu, stänger sphaira.",
"Restored hbmenu": "Återställde hbmenu.",
"Delete Selected files?": "Radera valda filer?", "Delete Selected files?": "Radera valda filer?",
"Completely remove ": "Ta bort helt ", "Completely remove ": "Ta bort helt ",
"Are you sure you want to delete ": "Är du säker på att du vill radera ", "Are you sure you want to delete ": "Är du säker på att du vill radera ",
"Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?", "Are you sure you wish to cancel?": "Är du säker på att du vill avbryta?",
"If this message appears repeatedly, please open an issue.": "" "If this message appears repeatedly, please open an issue.": "Om detta meddelande visas upprepade gånger, vänligen öppna en felanmälan.",
} }

255
assets/romfs/i18n/vi.json Normal file
View File

@@ -0,0 +1,255 @@
{
"[Applet Mode]": "[Applet Mode]",
"No Internet": "Không có Internet",
"Files": "Tập tin",
"Apps": "Ứng dụng",
"Store": "Cửa hàng",
"Menu": "Menu",
"Options": "Tuỳ chọn",
"OK": "OK",
"Back": "Trở về",
"Select": "Chọn",
"Open": "Mở",
"Launch": "Chạy",
"Info": "Thông tin",
"Install": "Cài đặt",
"Delete": "Xoá",
"Restart": "Khởi động lại",
"Changelog": "Thay đổi",
"Details": "Chi tiết",
"Update": "Cập nhật",
"Remove": "Gỡ",
"Restore": "Khôi phục",
"Download": "Tải về",
"Next Page": "Trang kế",
"Prev Page": "Trang trước",
"Unstar": "Xoá yêu thích",
"Star": "Yêu thích",
"System memory": "Bộ nhớ máy",
"microSD card": "Thẻ nhớ",
"Sd": "Sd",
"Image System memory": "Bộ nhớ hệ thống hình ảnh",
"Image microSD card": "Thẻ nhớ hệ thống hình ảnh",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "Có",
"No": "Không",
"Enabled": "Bật",
"Disabled": "Tắt",
"Sort By": "Sắp xếp bởi",
"Sort Options": "Tuỳ chọn sắp xếp",
"Filter": "Lọc",
"Sort": "Sắp xếp",
"Order": "Thứ tự",
"Search": "Tìm kiếm",
"Updated": "Updated",
"Updated (Star)": "Đã cập nhật (Yêu thích)",
"Downloads": "Danh sách tải về",
"Size": "Kích thước",
"Size (Star)": "Kích thước (Yêu thích)",
"Alphabetical": "A-Z",
"Alphabetical (Star)": "A-Z (Yêu thích)",
"Likes": "Thích",
"ID": "ID",
"Descending": "Giảm dần",
"Descending (down)": "Giảm dần (xuống)",
"Desc": "Giảm",
"Ascending": "Tăng dần",
"Ascending (Up)": "Tăng dần (lên)",
"Asc": "Tăng",
"Menu Options": "Menu tuỳ chọn",
"Theme": "Theme",
"Theme Options": "Theme tuỳ chọn",
"Select Theme": "Chọn Theme",
"Shuffle": "Trộn",
"Music": "Âm nhạc",
"Network": "Mạng",
"Network Options": "Tuỳ chọn mạng",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink Kết Nối",
"Nxlink Upload": "Nxlink Đăng Tải",
"Nxlink Finished": "Nxlink Hoàn Thành",
"Switch-Handheld!": "Switch-Handheld!",
"Switch-Docked!": "Switch-Docked!",
"Language": "Ngôn ngữ",
"Auto": "Tự động",
"English": "English",
"Japanese": "日本語",
"French": "Français",
"German": "Deutsch",
"Italian": "Italiano",
"Spanish": "Español",
"Chinese": "中文",
"Korean": "한국어",
"Dutch": "Dutch",
"Portuguese": "Português",
"Russian": "Русский",
"Swedish": "Svenska",
"Vietnamese": "Việt Nam",
"Logging": "Logging",
"Replace hbmenu on exit": "Thay thế hbmenu khi thoát",
"Misc": "Tiện ích",
"Misc Options": "Tiện ích mở rộng",
"Web": "Web",
"Install forwarders": "Cài ra màn hình",
"Install location": "Vị trí cài đặt",
"Show install warning": "Hiển thị cảnh báo cài đặt",
"Text scroll speed": "",
"FileBrowser": "Duyệt tập tin",
"%zd files": "%zd tập tin",
"%zd dirs": "%zd thư mục",
"File Options": "Tuỳ chọn tập tin",
"Show Hidden": "Hiển thị tập tin ẩn",
"Folders First": "Thư mục đầu tiên",
"Hidden Last": "Ẩn cuối",
"Cut": "Cắt",
"Copy": "Sao chép",
"Paste": "Dán",
"Paste ": "Paste ",
" file(s)?": " tập tin(nhiều)?",
"Rename": "Đổi tên",
"Set New File Name": "Đặt tên mới cho tập tin",
"Advanced": "Mở rộng",
"Advanced Options": "Tuỳ chọn mở rộng",
"Create File": "Tạo tập tin",
"Set File Name": "Đặt tên cho tập tin",
"Create Folder": "Tạo thư mục",
"Set Folder Name": "Đặt tên thư mục",
"View as text (unfinished)": "Xem dạng văn bản (chưa xong)",
"Ignore read only": "Bỏ qua chỉ đọc",
"Mount": "Gắn",
"Empty...": "Rỗng...",
"Open with DayBreak?": "Mở với DayBreak?",
"Launch ": "Chạy ",
"Launch option for: ": "Chạy với tuỳ chọn cho: ",
"Select launcher for: ": "Chọn trình chạy cho: ",
"Homebrew": "Homebrew",
"Homebrew Options": "Tuỳ chọn Homebrew",
"Hide Sphaira": "Ẩn Sphaira",
"Install Forwarder": "Cài ra ngoài màn hình",
"WARNING: Installing forwarders will lead to a ban!": "CẢNH BÁO: Bạn có chắn muốn cài ra ngoài màn hình!",
"Installing Forwarder": "Đang cài đặt ra ngoài màn hình",
"Creating Program": "Tạo chương trình",
"Creating Control": "Tạo điều khiển",
"Creating Meta": "Tạo Meta",
"Writing Nca": "Ghi Nca",
"Updating ncm databse": "Cập nhật ncm databse",
"Pushing application record": "Đẩy ứng dụng",
"Installed!": "Đã cài xong!",
"Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại",
"Unstarred ": "Bỏ yêu thích ",
"Starred ": "Đã yêu thích ",
"AppStore": "AppStore",
"Filter: %s | Sort: %s | Order: %s": "Lọc: %s | Sắp xếp: %s | Thứ tự: %s",
"AppStore Options": "Tuỳ chọn AppStore",
"All": "Tất cả",
"Games": "Games",
"Emulators": "Emulators",
"Tools": "Tools",
"Themes": "Themes",
"Legacy": "Legacy",
"version: %s": "version: %s",
"updated: %s": "updated: %s",
"category: %s": "category: %s",
"extracted: %.2f MiB": "extracted: %.2f MiB",
"app_dls: %s": "app_dls: %s",
"More by Author": "Xem thêm tác giả",
"Leave Feedback": "Để lại phản hồi",
"Irs": "Irs",
"Ambient Noise Level: ": "Mức ồn xung quanh: ",
"Controller": "Điều khiển",
"Pad ": "Pad ",
" (Available)": " (Có sẵn)",
" (Unsupported)": " (Không hỗ trợ)",
" (Unconnected)": " (Không kết nối)",
"HandHeld": "Cầm tay",
"Rotation": "Xoay",
"0 (Sideways)": "0 (Đi ngang)",
"90 (Flat)": "90 (Phẳng)",
"180 (-Sideways)": "180 (-Đi ngang)",
"270 (Upside down)": "270 (Lộn ngược)",
"Colour": "Màu sắc",
"Grey": "Xám",
"Ironbow": "Ironbow",
"Green": "Xanh",
"Red": "Đỏ",
"Blue": "Xanh dương",
"Light Target": "Điểm sáng",
"All leds": "Tất cả đèn led",
"Bright group": "Nhóm sáng",
"Dim group": "Nhóm tối",
"None": "Không có",
"Gain": "Tăng",
"Negative Image": "Ảnh âm bản",
"Normal image": "Ảnh bình thường",
"Negative image": "Ảnh âm bản",
"Format": "Định dạng",
"320x240": "320×240",
"160x120": "160×120",
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"Trimming Format": "Định dạng cắt tỉa",
"External Light Filter": "Bộ lộc ánh sáng bên ngoài",
"Load Default": "Tải mặc định",
"Themezer": "Themezer",
"Themezer Options": "Tuỳ chọn Themezer",
"Nsfw": "18+",
"Page": "Trang",
"Page %zu / %zu": "Trang %zu / %zu",
"Enter Page Number": "Nhập số trang",
"Bad Page": "Trang không tồn tại",
"Download theme?": "Tải theme?",
"GitHub": "GitHub",
"Downloading json": "Đang tải json",
"Select asset to download for ": "Chọn nội dung để tải xuống cho ",
"Installing ": "Đang cài đặt ",
"Uninstalling ": "Đang gỡ cài đặt ",
"Deleting ": "Đang xoá ",
"Deleting": "Đang xoá",
"Pasting ": "Đang dán ",
"Pasting": "Đang dán",
"Removing ": "Đang gỡ ",
"Scanning ": "Đang quét ",
"Creating ": "Đang tạo ",
"Copying ": "Đang sao chép ",
"Trying to load ": "Đang cố gắn mở ",
"Downloading ": "Đang tải xuống ",
"Downloaded ": "Đã tải xong ",
"Removed ": "Đã gỡ ",
"Checking MD5": "Kiểm tra MD5",
"Loading...": "Đang tải...",
"Loading": "Đang tải",
"Empty!": "Trống!",
"Not Ready...": "Chưa sẵn sàng...",
"Error loading page!": "Lỗi tải trang!",
"Update avaliable: ": "Cập nhậc có sẵn: ",
"Download update: ": "Tải cập nhật: ",
"Updated to ": "Đã cập nhật ",
"Press OK to restart Sphaira": "",
"Restart Sphaira?": "Khởi động lại Sphaira?",
"Failed to download update": "Cập nhật thất bại",
"Restore hbmenu?": "Khôi phục hbmenu?",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Không tìm thấy /switch/hbmenu.nro\nSử dụng AppStore để cài lại hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "Không thể khôi phục hbmenu, Vui lòng tải lại hbmenu",
"Failed to restore hbmenu, using sphaira instead": "Không thể khôi phục hbmenu, thay vào đó sử dụng Sphira",
"Restored hbmenu, closing sphaira": "Khôi mục hbmenu thành công, đóng sphaira",
"Restored hbmenu": "Đã khôi phục hbmenu",
"Delete Selected files?": "Xoá những tập tin được chọn?",
"Completely remove ": "Đã gỡ thành công ",
"Are you sure you want to delete ": "Bạn có muốn xoá ",
"Are you sure you wish to cancel?": "Bạn có chắn muốn huỷ không?",
"If this message appears repeatedly, please open an issue.": "Nếu thấy tin nhắn này, hãy báo lỗi."
}

View File

@@ -14,11 +14,12 @@
"Info": "信息", "Info": "信息",
"Install": "安装", "Install": "安装",
"Delete": "删除", "Delete": "删除",
"Restart": "", "Restart": "重启",
"Changelog": "更新日志", "Changelog": "更新日志",
"Details": "详情", "Details": "详情",
"Update": "更新", "Update": "更新",
"Remove": "删除", "Remove": "删除",
"Restore": "恢复",
"Download": "下载", "Download": "下载",
"Next Page": "下一页", "Next Page": "下一页",
"Prev Page": "上一页", "Prev Page": "上一页",
@@ -26,6 +27,12 @@
"Star": "星标", "Star": "星标",
"System memory": "主机内存", "System memory": "主机内存",
"microSD card": "SD卡", "microSD card": "SD卡",
"Sd": "SD卡",
"Image System memory": "主机内存图像",
"Image microSD card": "SD卡图像",
"Slow": "",
"Normal": "",
"Fast": "",
"Yes": "是", "Yes": "是",
"No": "否", "No": "否",
"Enabled": "启用", "Enabled": "启用",
@@ -46,7 +53,7 @@
"Alphabetical (Star)": "按字母顺序(星标优先)", "Alphabetical (Star)": "按字母顺序(星标优先)",
"Likes": "点赞量", "Likes": "点赞量",
"ID": "ID", "ID": "ID",
"Decending": "降序", "Descending": "降序",
"Descending (down)": "降序", "Descending (down)": "降序",
"Desc": "降序", "Desc": "降序",
"Ascending": "升序", "Ascending": "升序",
@@ -54,7 +61,6 @@
"Asc": "升序", "Asc": "升序",
"Menu Options": "菜单选项", "Menu Options": "菜单选项",
"Header": "标题",
"Theme": "主题", "Theme": "主题",
"Theme Options": "主题选项", "Theme Options": "主题选项",
"Select Theme": "选择主题", "Select Theme": "选择主题",
@@ -64,12 +70,12 @@
"Network Options": "网络选项", "Network Options": "网络选项",
"Ftp": "FTP", "Ftp": "FTP",
"Mtp": "MTP", "Mtp": "MTP",
"Nxlink": "Nxlink", "Nxlink": "Nxlink插件提交",
"Nxlink Connected": "Nxlink 已连接", "Nxlink Connected": "Nxlink 已连接",
"Nxlink Upload": "Nxlink 上传中", "Nxlink Upload": "Nxlink 上传中",
"Nxlink Finished": "Nxlink 已结束", "Nxlink Finished": "Nxlink 已结束",
"Switch-Handheld!": "", "Switch-Handheld!": "切换至掌机模式!",
"Switch-Docked!": "", "Switch-Docked!": "切换至底座模式!",
"Language": "语言", "Language": "语言",
"Auto": "自动", "Auto": "自动",
"English": "English", "English": "English",
@@ -84,14 +90,16 @@
"Portuguese": "Português", "Portuguese": "Português",
"Russian": "Русский", "Russian": "Русский",
"Swedish": "Svenska", "Swedish": "Svenska",
"Vietnamese": "Vietnamese",
"Logging": "日志", "Logging": "日志",
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu", "Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
"Misc": "杂项", "Misc": "拓展",
"Misc Options": "杂项设置", "Misc Options": "拓展设置",
"Web": "网页浏览器", "Web": "网页浏览器",
"Install forwarders": "允许安装前端应用", "Install forwarders": "允许安装前端应用",
"Install location": "安装位置", "Install location": "安装位置",
"Show install warning": "显示安装警告", "Show install warning": "显示安装警告",
"Text scroll speed": "",
"FileBrowser": "文件浏览", "FileBrowser": "文件浏览",
"%zd files": "%zd 个文件", "%zd files": "%zd 个文件",
@@ -114,12 +122,14 @@
"Create Folder": "新建文件夹", "Create Folder": "新建文件夹",
"Set Folder Name": "输入文件夹名", "Set Folder Name": "输入文件夹名",
"View as text (unfinished)": "以文本形式查看(未完善)", "View as text (unfinished)": "以文本形式查看(未完善)",
"Ignore read only": "忽略只读",
"Mount": "挂载",
"Empty...": "空...", "Empty...": "空...",
"Open with DayBreak?": "使用DayBreak打开", "Open with DayBreak?": "使用DayBreak打开",
"Launch ": "", "Launch ": "启动 ",
"Launch option for: ": "启动选项:", "Launch option for: ": "启动选项:",
"Select launcher for: ": "", "Select launcher for: ": "选择启动器用于:",
"Homebrew": "应用列表", "Homebrew": "应用列表",
"Homebrew Options": "应用选项", "Homebrew Options": "应用选项",
"Hide Sphaira": "在应用列表中隐藏Sphaira", "Hide Sphaira": "在应用列表中隐藏Sphaira",
@@ -134,8 +144,8 @@
"Pushing application record": "正在推送应用记录", "Pushing application record": "正在推送应用记录",
"Installed!": "安装完成!", "Installed!": "安装完成!",
"Failed to install forwarder": "前端应用安装失败", "Failed to install forwarder": "前端应用安装失败",
"Unstarred ": "", "Unstarred ": "取消星标 ",
"Starred ": "", "Starred ": "已星标 ",
"AppStore": "应用商店", "AppStore": "应用商店",
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s", "Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
@@ -178,7 +188,7 @@
"Bright group": "亮色组", "Bright group": "亮色组",
"Dim group": "暗色组", "Dim group": "暗色组",
"None": "无", "None": "无",
"Gain": "增益", "Gain": "曝光",
"Negative Image": "负片图像", "Negative Image": "负片图像",
"Normal image": "正常图像", "Normal image": "正常图像",
"Negative image": "负片图像", "Negative image": "负片图像",
@@ -201,9 +211,9 @@
"Bad Page": "错误的页面", "Bad Page": "错误的页面",
"Download theme?": "下载该主题?", "Download theme?": "下载该主题?",
"GitHub": "", "GitHub": "GitHub",
"Downloading json": "", "Downloading json": "正在下载 json",
"Select asset to download for ": "", "Select asset to download for ": "选择要下载的资源用于 ",
"Installing ": "正在安装 ", "Installing ": "正在安装 ",
"Uninstalling ": "正在卸载 ", "Uninstalling ": "正在卸载 ",
@@ -215,24 +225,31 @@
"Scanning ": "正在扫描 ", "Scanning ": "正在扫描 ",
"Creating ": "正在创建 ", "Creating ": "正在创建 ",
"Copying ": "正在复制 ", "Copying ": "正在复制 ",
"Trying to load ": "", "Trying to load ": "尝试加载 ",
"Downloading ": "正在下载 ", "Downloading ": "正在下载 ",
"Downloaded ": "", "Downloaded ": "已下载 ",
"Removed ": "", "Removed ": "已移除 ",
"Checking MD5": "正在校验 MD5", "Checking MD5": "正在校验 MD5",
"Loading...": "加载中...", "Loading...": "加载中...",
"Loading": "加载中", "Loading": "加载中",
"Empty!": "空!", "Empty!": "空空如野",
"Not Ready...": "尚未准备好...", "Not Ready...": "尚未准备好...",
"Error loading page!": "页面加载失败!", "Error loading page!": "页面加载失败!",
"Update avaliable: ": "有可用更新!", "Update avaliable: ": "有可用更新!",
"Download update: ": "下载更新:", "Download update: ": "下载更新:",
"Updated to ": "", "Updated to ": "更新至 ",
"Restart Sphaira?": "", "Press OK to restart Sphaira": "",
"Restart Sphaira?": "重启 Sphaira",
"Failed to download update": "更新下载失败", "Failed to download update": "更新下载失败",
"Restore hbmenu?": "恢复 hbmenu",
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "未能找到 /switch/hbmenu.nro\n请使用应用商店重新安装 hbmenu",
"Failed to restore hbmenu, please re-download hbmenu": "恢复 hbmenu 失败,请重新下载 hbmenu",
"Failed to restore hbmenu, using sphaira instead": "恢复 hbmenu 失败,改用 Sphaira",
"Restored hbmenu, closing sphaira": "已恢复 hbmenu正在关闭 Sphaira",
"Restored hbmenu": "已恢复 hbmenu",
"Delete Selected files?": "删除选中的文件?", "Delete Selected files?": "删除选中的文件?",
"Completely remove ": "彻底删除 ", "Completely remove ": "彻底删除 ",
"Are you sure you want to delete ": "您确定要删除吗 ", "Are you sure you want to delete ": "您确定要删除吗 ",
"Are you sure you wish to cancel?": "您确定要取消吗?", "Are you sure you wish to cancel?": "您确定要取消吗?",
"If this message appears repeatedly, please open an issue.": "" "If this message appears repeatedly, please open an issue.": "如果此消息反复出现,请提交一个 issue。"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -1,22 +1,23 @@
[meta] [meta]
name=Abyss name=Abyss
author=TotalJustice author=TotalJustice
version=1.0.0 version=1.1.0
; unused currently inherit=romfs:/themes/base_black_theme.ini
preview=romfs:/theme/preview.jpg
[theme] [theme]
background=0x0f111aff background = 0x0f111a
grid=0x0f115c30 grid = 0x0f115c30
selected=0x0f115cff popup = 0x0f115c
selected_overlay=0x529cffff
text=0xffbc41ff
text_selected=0x529cffff
icon_audio=romfs:/theme/icon_audio.png line = 0xffbc41
icon_video=romfs:/theme/icon_video.png line_seperator = 0xffbc41
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png text = 0xffbc41
icon_folder=romfs:/theme/icon_folder.png text_info = 0xd79f36
icon_zip=romfs:/theme/icon_zip.png text_selected = 0x529cff
icon_nro=romfs:/theme/icon_nro.png selected_background = 0x0f115c
scrollbar = 0x529cff
scrollbar_background = ; hide the background
progressbar = 0x3250f0

View File

@@ -0,0 +1,33 @@
[theme]
background = 0x2d2d2d
grid = 0x46464630
popup = 0x2d2d2d
error = 0xfa5a3a
line = 0xfbfbfb
line_separator = 0x707070
text = 0xfbfbfb
text_info = 0xd1d1d1
text_selected = 0x00ffc8
selected_background = 0x212227
sidebar = 0x000000dc
scrollbar = 0x00ffc8
scrollbar_background = ; hide the background
; scrollbar_background = 0x464646
progressbar = 0x00ffc8
progressbar_background = 0x464646
highlight_1 = 0x1989c6
highlight_2 = 0x89f0f2
icon_audio = romfs:/theme/icon_audio.png
icon_video = romfs:/theme/icon_video.png
icon_image = romfs:/theme/icon_image.png
icon_file = romfs:/theme/icon_file.png
icon_folder = romfs:/theme/icon_folder.png
icon_zip = romfs:/theme/icon_zip.png
icon_nro = romfs:/theme/icon_nro.png

View File

@@ -0,0 +1,34 @@
[theme]
background = 0xebebeb
grid = 0xf0f0f0
popup = 0xebebeb
error = 0xfa5a3a
line = 0x373737
line_separator = 0x6d787a
text = 0x373737
text_info = 0x808080
text_selected = 0x3250f0
selected_background = 0xfdfdfd
sidebar = 0xe2e2e2f5
scrollbar = 0xB0B0B0
scrollbar_background = ; hide the background
; scrollbar_background = 0xababab
progressbar = 0x3250f0
progressbar_background = 0x808080
highlight_1 = 0x1989c6
highlight_2 = 0x89f0f2
icon_colour = 0x6d787a
icon_audio = romfs:/theme/icon_audio.png
icon_video = romfs:/theme/icon_video.png
icon_image = romfs:/theme/icon_image.png
icon_file = romfs:/theme/icon_file.png
icon_folder = romfs:/theme/icon_folder.png
icon_zip = romfs:/theme/icon_zip.png
icon_nro = romfs:/theme/icon_nro.png

View File

@@ -1,23 +1,5 @@
[meta] [meta]
name=Black name=Black
author=TotalJustice author=TotalJustice
version=1.0.0 version=1.1.0
preview=romfs:/theme/preview.jpg inherit=romfs:/themes/base_black_theme.ini
[theme]
background=0x2d2d2dff
cursor=romfs:/theme/cursor.png
cursor_drag=romfs:/theme/cursor_drag.png
grid=0x46464630
selected=0x464646ff
selected_overlay=0x00ffc8ff
text=0xfbfbfbff
text_selected=0x00ffc8ff
icon_audio=romfs:/theme/icon_audio.png
icon_video=romfs:/theme/icon_video.png
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png
icon_folder=romfs:/theme/icon_folder.png
icon_zip=romfs:/theme/icon_zip.png
icon_nro=romfs:/theme/icon_nro.png

View File

@@ -1,23 +1,13 @@
[meta] [meta]
name=OLED Black name=OLED Black
author=iTotalJustice/Sanras author=TotalJustice/Sanras
version=1.0.0 version=1.1.0
preview=romfs:/theme/preview.jpg inherit=romfs:/themes/base_black_theme.ini
[theme] [theme]
background=0x000000ff background = 0x000000
cursor=romfs:/theme/cursor.png grid = 0x46464640
cursor_drag=romfs:/theme/cursor_drag.png popup = 0x323232
grid=0x46464640 text = 0xfbfbfb
selected=0x323232ff text_selected = 0x00ffc8
selected_overlay=0x00ffc8ff selected_background = 0x323232
text=0xfbfbfbff
text_selected=0x00ffc8ff
icon_audio=romfs:/theme/icon_audio.png
icon_video=romfs:/theme/icon_video.png
icon_image=romfs:/theme/icon_image.png
icon_file=romfs:/theme/icon_file.png
icon_folder=romfs:/theme/icon_folder.png
icon_zip=romfs:/theme/icon_zip.png
icon_nro=romfs:/theme/icon_nro.png

View File

@@ -0,0 +1,5 @@
[meta]
name=White
author=TotalJustice/Yorunokyujitsu
version=1.0.0
inherit=romfs:/themes/base_white_theme.ini

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(sphaira_VERSION 0.5.0) set(sphaira_VERSION 0.6.2)
project(sphaira project(sphaira
VERSION ${sphaira_VERSION} VERSION ${sphaira_VERSION}
@@ -57,6 +57,7 @@ add_executable(sphaira
source/ui/sidebar.cpp source/ui/sidebar.cpp
source/ui/widget.cpp source/ui/widget.cpp
source/ui/list.cpp source/ui/list.cpp
source/ui/bubbles.cpp
source/app.cpp source/app.cpp
source/download.cpp source/download.cpp
@@ -80,12 +81,54 @@ target_compile_definitions(sphaira PRIVATE
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}" -DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
) )
target_compile_options(sphaira PRIVATE
-Wall
-Wextra
# unsure if it's a good idea to enable these by default as
# it may cause breakage upon compiler updates.
# -Werror
# -Wfatal-errors
# disabled as nx uses s64 for size and offset, however stl uses size_t instead, thus
# there being a lot of warnings.
-Wno-sign-compare
# disabled as many overriden methods don't use the params.
-Wno-unused-parameter
# pedantic warning, missing fields are set to 0.
-Wno-missing-field-initializers
# disabled as it warns for strcat 2 paths together, but it will never
# overflow due to fs enforcing a max path len anyway.
-Wno-format-truncation
# the below are taken from my gba emulator, they've served me well ;)
-Wformat-overflow=2
-Wundef
-Wmissing-include-dirs
-fstrict-aliasing
-Wstrict-overflow=2
-Walloca
-Wduplicated-cond
-Wwrite-strings
-Wdate-time
-Wlogical-op
-Wpacked
-Wcast-qual
-Wcast-align
-Wimplicit-fallthrough=5
-Wsuggest-final-types
-Wuninitialized
-fimplicit-constexpr
-Wmissing-requires
)
include(FetchContent) include(FetchContent)
set(FETCHCONTENT_QUIET FALSE) set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 1.2.1 GIT_TAG 1.2.2
SOURCE_SUBDIR NONE
) )
FetchContent_Declare(libhaze FetchContent_Declare(libhaze
@@ -95,12 +138,12 @@ FetchContent_Declare(libhaze
FetchContent_Declare(libpulsar FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG d729be3 GIT_TAG de656e4
) )
FetchContent_Declare(nanovg FetchContent_Declare(nanovg
GIT_REPOSITORY https://github.com/ITotalJustice/nanovg-deko3d.git GIT_REPOSITORY https://github.com/ITotalJustice/nanovg-deko3d.git
GIT_TAG 1902b38 GIT_TAG 845c9fc
) )
FetchContent_Declare(stb FetchContent_Declare(stb
@@ -115,7 +158,7 @@ FetchContent_Declare(yyjson
FetchContent_Declare(minIni FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 63ec295 GIT_TAG 11cac8b
) )
set(MININI_LIB_NAME minIni) set(MININI_LIB_NAME minIni)
@@ -135,8 +178,6 @@ set(NANOVG_NO_GIF ON)
set(NANOVG_NO_HDR ON) set(NANOVG_NO_HDR ON)
set(NANOVG_NO_PIC ON) set(NANOVG_NO_PIC ON)
set(NANOVG_NO_PNM ON) set(NANOVG_NO_PNM ON)
set(NANOVG_STBI_STATIC OFF)
set(NANOVG_STBTT_STATIC ON)
set(YYJSON_DISABLE_READER OFF) set(YYJSON_DISABLE_READER OFF)
set(YYJSON_DISABLE_WRITER OFF) set(YYJSON_DISABLE_WRITER OFF)
@@ -147,7 +188,7 @@ set(YYJSON_DISABLE_UTF8_VALIDATION ON)
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF) set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
FetchContent_MakeAvailable( FetchContent_MakeAvailable(
# ftpsrv ftpsrv
libhaze libhaze
libpulsar libpulsar
nanovg nanovg
@@ -156,11 +197,6 @@ FetchContent_MakeAvailable(
yyjson yyjson
) )
FetchContent_GetProperties(ftpsrv)
if (NOT ftpsrv_POPULATED)
FetchContent_Populate(ftpsrv)
endif()
set(FTPSRV_LIB_BUILD TRUE) set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_SOCK_UNISTD TRUE) set(FTPSRV_LIB_SOCK_UNISTD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h) set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
@@ -177,6 +213,7 @@ set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:TRUE> USE_VFS_SAVE=$<BOOL:TRUE>
USE_VFS_STORAGE=$<BOOL:TRUE> USE_VFS_STORAGE=$<BOOL:TRUE>
USE_VFS_GC=$<BOOL:${USE_VFS_GC}> USE_VFS_GC=$<BOOL:${USE_VFS_GC}>
USE_VFS_USBHSFS=$<BOOL:FALSE>
VFS_NX_BUFFER_IO=$<BOOL:TRUE> VFS_NX_BUFFER_IO=$<BOOL:TRUE>
) )
@@ -226,39 +263,6 @@ set_target_properties(libhaze PROPERTIES
COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Os>" COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Os>"
) )
# todo: upstream cmake
add_library(libpulsar
${libpulsar_SOURCE_DIR}/src/archive/archive_file.c
${libpulsar_SOURCE_DIR}/src/archive/archive.c
${libpulsar_SOURCE_DIR}/src/bfgrp/bfgrp_location.c
${libpulsar_SOURCE_DIR}/src/bfgrp/bfgrp.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_file.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_group.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_sound.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_string.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar_wave_archive.c
${libpulsar_SOURCE_DIR}/src/bfsar/bfsar.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm_channel.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm_info.c
${libpulsar_SOURCE_DIR}/src/bfstm/bfstm.c
${libpulsar_SOURCE_DIR}/src/bfwar/bfwar_file.c
${libpulsar_SOURCE_DIR}/src/bfwar/bfwar.c
${libpulsar_SOURCE_DIR}/src/bfwav/bfwav_info.c
${libpulsar_SOURCE_DIR}/src/bfwav/bfwav.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd_sound_data.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd_wave_id.c
${libpulsar_SOURCE_DIR}/src/bfwsd/bfwsd.c
${libpulsar_SOURCE_DIR}/src/player/player_load_formats.c
${libpulsar_SOURCE_DIR}/src/player/player_load_lookup.c
${libpulsar_SOURCE_DIR}/src/player/player_load.c
${libpulsar_SOURCE_DIR}/src/player/player.c
)
target_include_directories(libpulsar PUBLIC ${libpulsar_SOURCE_DIR}/include)
set_target_properties(libpulsar PROPERTIES
C_STANDARD 11
C_EXTENSIONS ON
)
add_library(stb INTERFACE) add_library(stb INTERFACE)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR}) target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})

View File

@@ -25,6 +25,7 @@ enum SoundEffect {
SoundEffect_Startup, SoundEffect_Startup,
SoundEffect_Install, SoundEffect_Install,
SoundEffect_Error, SoundEffect_Error,
SoundEffect_MAX,
}; };
enum class LaunchType { enum class LaunchType {
@@ -79,6 +80,7 @@ public:
static auto GetThemeShuffleEnable() -> bool; static auto GetThemeShuffleEnable() -> bool;
static auto GetThemeMusicEnable() -> bool; static auto GetThemeMusicEnable() -> bool;
static auto GetLanguage() -> long; static auto GetLanguage() -> long;
static auto GetTextScrollSpeed() -> long;
static void SetMtpEnable(bool enable); static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable); static void SetFtpEnable(bool enable);
@@ -91,6 +93,7 @@ public:
static void SetThemeShuffleEnable(bool enable); static void SetThemeShuffleEnable(bool enable);
static void SetThemeMusicEnable(bool enable); static void SetThemeMusicEnable(bool enable);
static void SetLanguage(long index); static void SetLanguage(long index);
static void SetTextScrollSpeed(long index);
static auto Install(OwoConfig& config) -> Result; static auto Install(OwoConfig& config) -> Result;
static auto Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result; static auto Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result;
@@ -104,9 +107,9 @@ public:
// void DrawElement(float x, float y, float w, float h, ui::ThemeEntryID id); // void DrawElement(float x, float y, float w, float h, ui::ThemeEntryID id);
auto LoadElementImage(std::string_view value) -> ElementEntry; auto LoadElementImage(std::string_view value) -> ElementEntry;
auto LoadElementColour(std::string_view value) -> ElementEntry; auto LoadElementColour(std::string_view value) -> ElementEntry;
auto LoadElement(std::string_view data) -> ElementEntry; auto LoadElement(std::string_view data, ElementType type) -> ElementEntry;
void LoadTheme(const fs::FsPath& path); void LoadTheme(const ThemeMeta& meta);
void CloseTheme(); void CloseTheme();
void ScanThemes(const std::string& path); void ScanThemes(const std::string& path);
void ScanThemeEntries(); void ScanThemeEntries();
@@ -116,6 +119,21 @@ public:
return type == AppletType_Application || type == AppletType_SystemApplication; return type == AppletType_Application || type == AppletType_SystemApplication;
} }
static auto IsApplet() -> bool {
return !IsApplication();
}
// returns true if launched in applet mode with a title suspended in the background.
static auto IsAppletWithSuspendedApp() -> bool {
R_UNLESS(IsApplet(), false);
R_TRY_RESULT(pmdmntInitialize(), false);
ON_SCOPE_EXIT(pmdmntExit());
u64 pid;
return R_SUCCEEDED(pmdmntGetApplicationProcessId(&pid));
}
// private: // private:
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini"; static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini"; static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
@@ -160,9 +178,10 @@ public:
option::OptionBool m_theme_shuffle{INI_SECTION, "theme_shuffle", false}; option::OptionBool m_theme_shuffle{INI_SECTION, "theme_shuffle", false};
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true}; option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
// todo: move this into it's own menu
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
PLSR_BFSAR m_qlaunch_bfsar{}; PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
PLSR_PlayerSoundId m_sound_ids[24]{};
private: // from nanovg decko3d example by adubbz private: // from nanovg decko3d example by adubbz
static constexpr unsigned NumFramebuffers = 2; static constexpr unsigned NumFramebuffers = 2;

View File

@@ -6,6 +6,7 @@
#include <functional> #include <functional>
#include <unordered_map> #include <unordered_map>
#include <algorithm> #include <algorithm>
#include <stop_token>
#include <switch.h> #include <switch.h>
namespace sphaira::curl { namespace sphaira::curl {
@@ -29,6 +30,7 @@ struct ApiResult;
using Path = fs::FsPath; using Path = fs::FsPath;
using OnComplete = std::function<void(ApiResult& result)>; using OnComplete = std::function<void(ApiResult& result)>;
using OnProgress = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>; using OnProgress = std::function<bool(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow)>;
using StopToken = std::stop_token;
struct Url { struct Url {
Url() = default; Url() = default;
@@ -71,6 +73,7 @@ struct ApiResult {
struct DownloadEventData { struct DownloadEventData {
OnComplete callback; OnComplete callback;
ApiResult result; ApiResult result;
StopToken stoken;
}; };
auto Init() -> bool; auto Init() -> bool;
@@ -114,6 +117,7 @@ struct Api {
auto ToMemory(Ts&&... ts) { auto ToMemory(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified"); static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory"); static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...); Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemory(*this); return curl::ToMemory(*this);
} }
@@ -122,6 +126,7 @@ struct Api {
auto ToFile(Ts&&... ts) { auto ToFile(Ts&&... ts) {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified"); static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified"); static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(!std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must not be specified");
Api::set_option(std::forward<Ts>(ts)...); Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFile(*this); return curl::ToFile(*this);
} }
@@ -131,6 +136,7 @@ struct Api {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified"); static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified"); static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory"); static_assert(!std::disjunction_v<std::is_same<Path, Ts>...>, "Path must not valid for memory");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...); Api::set_option(std::forward<Ts>(ts)...);
return curl::ToMemoryAsync(*this); return curl::ToMemoryAsync(*this);
} }
@@ -140,18 +146,38 @@ struct Api {
static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified"); static_assert(std::disjunction_v<std::is_same<Url, Ts>...>, "Url must be specified");
static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified"); static_assert(std::disjunction_v<std::is_same<Path, Ts>...>, "Path must be specified");
static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified"); static_assert(std::disjunction_v<std::is_same<OnComplete, Ts>...>, "OnComplete must be specified");
static_assert(std::disjunction_v<std::is_same<StopToken, Ts>...>, "StopToken must be specified");
Api::set_option(std::forward<Ts>(ts)...); Api::set_option(std::forward<Ts>(ts)...);
return curl::ToFileAsync(*this); return curl::ToFileAsync(*this);
} }
Url m_url; auto& GetUrl() const {
Fields m_fields{}; return m_url.m_str;
Header m_header{}; }
Flags m_flags{}; auto& GetFields() const {
Path m_path{}; return m_fields.m_str;
OnComplete m_on_complete = nullptr; }
OnProgress m_on_progress = nullptr; auto& GetHeader() const {
Priority m_prio = Priority::High; return m_header;
}
auto& GetFlags() const {
return m_flags.m_flags;
}
auto& GetPath() const {
return m_path;
}
auto& GetOnComplete() const {
return m_on_complete;
}
auto& GetOnProgress() const {
return m_on_progress;
}
auto& GetPriority() const {
return m_prio;
}
auto& GetToken() const {
return m_stoken;
}
private: private:
void SetOption(Url&& v) { void SetOption(Url&& v) {
@@ -178,6 +204,9 @@ private:
void SetOption(Priority&& v) { void SetOption(Priority&& v) {
m_prio = v; m_prio = v;
} }
void SetOption(StopToken&& v) {
m_stoken = v;
}
template <typename T> template <typename T>
void set_option(T&& t) { void set_option(T&& t) {
@@ -189,6 +218,18 @@ private:
set_option(std::forward<T>(t)); set_option(std::forward<T>(t));
set_option(std::forward<Ts>(ts)...); set_option(std::forward<Ts>(ts)...);
} }
private:
Url m_url;
Fields m_fields{};
Header m_header{};
Flags m_flags{};
Path m_path{};
OnComplete m_on_complete{nullptr};
OnProgress m_on_progress{nullptr};
Priority m_prio{Priority::High};
std::stop_source m_stop_source{};
StopToken m_stoken{m_stop_source.get_token()};
}; };
} // namespace sphaira::curl } // namespace sphaira::curl

View File

@@ -0,0 +1,10 @@
#include "ui/types.hpp"
#include "ui/object.hpp"
namespace sphaira::ui::bubble {
void Init();
void Draw(NVGcontext* vg, Theme* theme);
void Exit();
} // namespace sphaira::ui::bubble

View File

@@ -13,10 +13,10 @@ public:
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
private: private:
Result m_code; Result m_code{};
std::string m_message; std::string m_message{};
std::string m_module_str; std::string m_module_str{};
std::string m_description_str; std::string m_description_str{};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -43,8 +43,8 @@ private:
const s64 m_row; const s64 m_row;
const s64 m_page; const s64 m_page;
Vec4 m_v; Vec4 m_v{};
Vec2 m_pad; Vec2 m_pad{};
Vec4 m_scrollbar{}; Vec4 m_scrollbar{};

View File

@@ -42,26 +42,26 @@ enum class EntryStatus {
}; };
struct Entry { struct Entry {
std::string category; // todo: lable std::string category{}; // todo: lable
std::string binary; // optional, only valid for .nro std::string binary{}; // optional, only valid for .nro
std::string updated; // date of update std::string updated{}; // date of update
std::string name; std::string name{};
std::string license; // optional std::string license{}; // optional
std::string title; // same as name but with spaces std::string title{}; // same as name but with spaces
std::string url; // url of repo (optional?) std::string url{}; // url of repo (optional?)
std::string description; std::string description{};
std::string author; std::string author{};
std::string changelog; // optional std::string changelog{}; // optional
u64 screens; // number of screenshots u64 screens{}; // number of screenshots
u64 extracted; // extracted size in KiB u64 extracted{}; // extracted size in KiB
std::string version; std::string version{};
u64 filesize; // compressed size in KiB u64 filesize{}; // compressed size in KiB
std::string details; std::string details{};
u64 app_dls; u64 app_dls{};
std::string md5; // md5 of the zip std::string md5{}; // md5 of the zip
LazyImage image; LazyImage image{};
u32 updated_num; u32 updated_num{};
EntryStatus status{EntryStatus::Get}; EntryStatus status{EntryStatus::Get};
}; };
@@ -99,13 +99,13 @@ private:
Menu& m_menu; Menu& m_menu;
s64 m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
std::vector<Option> m_options; std::vector<Option> m_options{};
LazyImage m_banner; LazyImage m_banner{};
std::unique_ptr<List> m_list; std::unique_ptr<List> m_list{};
std::shared_ptr<ScrollableText> m_details; std::shared_ptr<ScrollableText> m_details{};
std::shared_ptr<ScrollableText> m_changelog; std::shared_ptr<ScrollableText> m_changelog{};
std::shared_ptr<ScrollableText> m_detail_changelog; std::shared_ptr<ScrollableText> m_detail_changelog{};
bool m_show_changlog{}; bool m_show_changlog{};
}; };
@@ -130,38 +130,10 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
struct FeedbackEntry {
u32 id;
u64 time;
std::string package; // name of package
std::string content; // the feedback message that was sent
std::string reply; // the reply, "" if no reply yet :)
};
struct FeedbackMenu final : MenuBase {
FeedbackMenu(const std::vector<Entry>& package_entries, LazyImage& default_image);
~FeedbackMenu();
void Update(Controller* controller, TouchInfo* touch) override;
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
private:
const std::vector<Entry>& m_package_entries;
LazyImage& m_default_image;
std::vector<FeedbackEntry> m_entries;
s64 m_index{}; // where i am in the array
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
};
struct Menu final : MenuBase { struct Menu final : MenuBase {
Menu(const std::vector<NroEntry>& nro_entries); Menu(const std::vector<NroEntry>& nro_entries);
~Menu(); ~Menu();
@@ -191,27 +163,27 @@ struct Menu final : MenuBase {
private: private:
const std::vector<NroEntry>& m_nro_entries; 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{};
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_Decending}; OrderType m_order{OrderType::OrderType_Descending};
s64 m_index{}; // where i am in the array s64 m_index{}; // where i am in the array
LazyImage m_default_image; LazyImage m_default_image{};
LazyImage m_update; LazyImage m_update{};
LazyImage m_get; LazyImage m_get{};
LazyImage m_local; LazyImage m_local{};
LazyImage m_installed; LazyImage m_installed{};
ImageDownloadState m_repo_download_state{ImageDownloadState::None}; ImageDownloadState m_repo_download_state{ImageDownloadState::None};
std::unique_ptr<List> m_list; std::unique_ptr<List> m_list{};
std::string m_search_term; std::string m_search_term{};
std::string m_author_term; std::string m_author_term{};
s64 m_entry_search_jump_back{}; s64 m_entry_search_jump_back{};
s64 m_entry_author_jump_back{}; s64 m_entry_author_jump_back{};
bool m_is_search{}; bool m_is_search{};

View File

@@ -16,12 +16,12 @@ struct Menu final : MenuBase {
private: private:
const fs::FsPath m_path; const fs::FsPath m_path;
fs::FsNativeSd m_fs; fs::FsNativeSd m_fs{};
FsFile m_file; FsFile m_file{};
s64 m_file_size{}; s64 m_file_size{};
s64 m_file_offset{}; s64 m_file_offset{};
std::unique_ptr<ScrollableText> m_scroll_text; std::unique_ptr<ScrollableText> m_scroll_text{};
s64 m_start{}; s64 m_start{};
s64 m_index{}; // where i am in the array s64 m_index{}; // where i am in the array

View File

@@ -29,7 +29,7 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
@@ -86,23 +86,23 @@ struct FileEntry : FsDirectoryEntry {
struct FileAssocEntry { struct FileAssocEntry {
fs::FsPath path{}; // ini name fs::FsPath path{}; // ini name
std::string name; // ini name std::string name{}; // ini name
std::vector<std::string> ext; // list of ext std::vector<std::string> ext{}; // list of ext
std::vector<std::string> database; // list of systems std::vector<std::string> database{}; // list of systems
}; };
struct LastFile { struct LastFile {
fs::FsPath name; fs::FsPath name{};
s64 index; s64 index{};
float offset; float offset{};
s64 entries_count; s64 entries_count{};
}; };
struct FsDirCollection { struct FsDirCollection {
fs::FsPath path; fs::FsPath path{};
fs::FsPath parent_name; fs::FsPath parent_name{};
std::vector<FsDirectoryEntry> files; std::vector<FsDirectoryEntry> files{};
std::vector<FsDirectoryEntry> dirs; std::vector<FsDirectoryEntry> dirs{};
}; };
using FsDirCollections = std::vector<FsDirCollection>; using FsDirCollections = std::vector<FsDirCollection>;
@@ -231,38 +231,38 @@ private:
static constexpr inline const char* INI_SECTION = "filebrowser"; static constexpr inline const char* INI_SECTION = "filebrowser";
const std::vector<NroEntry>& m_nro_entries; const std::vector<NroEntry>& m_nro_entries;
std::unique_ptr<fs::FsNative> m_fs; std::unique_ptr<fs::FsNative> m_fs{};
FsType m_fs_type; FsType m_fs_type{};
fs::FsPath m_path; fs::FsPath m_path{};
std::vector<FileEntry> m_entries; std::vector<FileEntry> m_entries{};
std::vector<u32> m_entries_index; // files not including hidden std::vector<u32> m_entries_index{}; // files not including hidden
std::vector<u32> m_entries_index_hidden; // includes hidden files std::vector<u32> m_entries_index_hidden{}; // includes hidden files
std::vector<u32> m_entries_index_search; // files found via search std::vector<u32> m_entries_index_search{}; // files found via search
std::span<u32> m_entries_current; std::span<u32> m_entries_current{};
std::unique_ptr<List> m_list; std::unique_ptr<List> m_list{};
std::optional<fs::FsPath> m_daybreak_path; std::optional<fs::FsPath> m_daybreak_path{};
// search options // search options
// show files [X] // show files [X]
// show folders [X] // show folders [X]
// recursive (slow) [ ] // recursive (slow) [ ]
std::vector<FileAssocEntry> m_assoc_entries; std::vector<FileAssocEntry> m_assoc_entries{};
std::vector<FileEntry> m_selected_files; std::vector<FileEntry> m_selected_files{};
// this keeps track of the highlighted file before opening a folder // this keeps track of the highlighted file before opening a folder
// if the user presses B to go back to the previous dir // if the user presses B to go back to the previous dir
// this vector is popped, then, that entry is checked if it still exists // this vector is popped, then, that entry is checked if it still exists
// if it does, the index becomes that file. // if it does, the index becomes that file.
std::vector<LastFile> m_previous_highlighted_file; std::vector<LastFile> m_previous_highlighted_file{};
fs::FsPath m_selected_path; fs::FsPath m_selected_path{};
s64 m_index{}; s64 m_index{};
s64 m_selected_count{}; s64 m_selected_count{};
SelectedType m_selected_type{SelectedType::None}; SelectedType m_selected_type{SelectedType::None};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical}; option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending}; option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false}; option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
option::OptionBool m_folders_first{INI_SECTION, "folders_first", true}; option::OptionBool m_folders_first{INI_SECTION, "folders_first", true};
option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false}; option::OptionBool m_hidden_last{INI_SECTION, "hidden_last", false};

View File

@@ -10,35 +10,35 @@
namespace sphaira::ui::menu::gh { namespace sphaira::ui::menu::gh {
struct AssetEntry { struct AssetEntry {
std::string name; std::string name{};
std::string path; std::string path{};
std::string pre_install_message; std::string pre_install_message{};
std::string post_install_message; std::string post_install_message{};
}; };
struct Entry { struct Entry {
fs::FsPath json_path; fs::FsPath json_path{};
std::string url; std::string url{};
std::string owner; std::string owner{};
std::string repo; std::string repo{};
std::string tag; std::string tag{};
std::string pre_install_message; std::string pre_install_message{};
std::string post_install_message; std::string post_install_message{};
std::vector<AssetEntry> assets; std::vector<AssetEntry> assets{};
}; };
struct GhApiAsset { struct GhApiAsset {
std::string name; std::string name{};
std::string content_type; std::string content_type{};
u64 size; u64 size{};
u64 download_count; u64 download_count{};
std::string browser_download_url; std::string browser_download_url{};
}; };
struct GhApiEntry { struct GhApiEntry {
std::string tag_name; std::string tag_name{};
std::string name; std::string name{};
std::vector<GhApiAsset> assets; std::vector<GhApiAsset> assets{};
}; };
struct Menu final : MenuBase { struct Menu final : MenuBase {
@@ -66,10 +66,9 @@ private:
void UpdateSubheading(); void UpdateSubheading();
private: private:
std::vector<Entry> m_entries; std::vector<Entry> m_entries{};
s64 m_index{}; s64 m_index{};
s64 m_index_offset{}; std::unique_ptr<List> m_list{};
std::unique_ptr<List> m_list;
}; };
} // namespace sphaira::ui::menu::gh } // namespace sphaira::ui::menu::gh

View File

@@ -18,7 +18,7 @@ enum SortType {
}; };
enum OrderType { enum OrderType {
OrderType_Decending, OrderType_Descending,
OrderType_Ascending, OrderType_Ascending,
}; };
@@ -50,12 +50,12 @@ struct Menu final : MenuBase {
private: private:
static constexpr inline const char* INI_SECTION = "homebrew"; static constexpr inline const char* INI_SECTION = "homebrew";
std::vector<NroEntry> m_entries; std::vector<NroEntry> m_entries{};
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{};
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_Decending}; 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};
}; };

View File

@@ -25,9 +25,9 @@ private:
void UpdateVars(); void UpdateVars();
private: private:
std::string m_title; std::string m_title{};
std::string m_title_sub_heading; std::string m_title_sub_heading{};
std::string m_sub_heading; std::string m_sub_heading{};
struct tm m_tm{}; struct tm m_tm{};
TimeStamp m_poll_timestamp{}; TimeStamp m_poll_timestamp{};

View File

@@ -42,79 +42,50 @@ enum class PageLoadState {
Error, Error,
}; };
// all commented out entries are those that we don't query for.
// this saves time not only processing the json, but also the download
// of said json.
// by reducing the fields to only what we need, the size is 4-5x smaller.
struct Creator { struct Creator {
std::string id; std::string id{};
std::string display_name; std::string display_name{};
}; };
struct Details { struct Details {
std::string name; std::string name{};
// std::string description;
}; };
struct Preview { struct Preview {
// std::string original; std::string thumb{};
std::string thumb; LazyImage lazy_image{};
LazyImage lazy_image;
}; };
struct DownloadPack { struct DownloadPack {
std::string filename; std::string filename{};
std::string url; std::string url{};
std::string mimetype; std::string mimetype{};
}; };
using DownloadTheme = DownloadPack; using DownloadTheme = DownloadPack;
struct ThemeEntry { struct ThemeEntry {
std::string id; std::string id{};
// Creator creator; Preview preview{};
// Details details;
// std::string last_updated;
// u64 dl_count;
// u64 like_count;
// std::vector<std::string> categories;
// std::string target;
Preview preview;
}; };
// struct Pack {
// std::string id;
// Creator creator;
// Details details;
// std::string last_updated;
// std::vector<std::string> categories;
// u64 dl_count;
// u64 like_count;
// std::vector<ThemeEntry> themes;
// };
struct PackListEntry { struct PackListEntry {
std::string id; std::string id{};
Creator creator; Creator creator{};
Details details; Details details{};
// std::string last_updated; std::vector<ThemeEntry> themes{};
// std::vector<std::string> categories;
// u64 dl_count;
// u64 like_count;
std::vector<ThemeEntry> themes;
}; };
struct Pagination { struct Pagination {
u64 page; u64 page{};
u64 limit; u64 limit{};
u64 page_count; u64 page_count{};
u64 item_count; u64 item_count{};
}; };
struct PackList { struct PackList {
std::vector<PackListEntry> packList; std::vector<PackListEntry> packList{};
Pagination pagination; Pagination pagination{};
}; };
struct Config { struct Config {
@@ -123,10 +94,10 @@ struct Config {
u32 sort_index{}; u32 sort_index{};
u32 order_index{}; u32 order_index{};
// search query, if empty, its not used // search query, if empty, its not used
std::string query; std::string query{};
// this is actually an array of creator ids, but we don't support that feature // this is actually an array of creator ids, but we don't support that feature
// if empty, its not used // if empty, its not used
std::string creator; std::string creator{};
// defaults // defaults
u32 page{1}; u32 page{1};
u32 limit{18}; u32 limit{18};
@@ -152,7 +123,7 @@ struct Config {
struct Menu; // fwd struct Menu; // fwd
struct PageEntry { struct PageEntry {
std::vector<PackListEntry> m_packList; std::vector<PackListEntry> m_packList{};
Pagination m_pagination{}; Pagination m_pagination{};
PageLoadState m_ready{PageLoadState::None}; PageLoadState m_ready{PageLoadState::None};
}; };
@@ -172,9 +143,6 @@ struct Menu final : MenuBase {
} }
} }
// void SetSearch(const std::string& term);
// void SetAuthor();
void InvalidateAllPages(); void InvalidateAllPages();
void PackListDownload(); void PackListDownload();
void OnPackListDownload(); void OnPackListDownload();
@@ -183,14 +151,14 @@ private:
static constexpr inline const char* INI_SECTION = "themezer"; static constexpr inline const char* INI_SECTION = "themezer";
static constexpr inline u32 MAX_ON_PAGE = 16; // same as website static constexpr inline u32 MAX_ON_PAGE = 16; // same as website
std::vector<PageEntry> m_pages; std::vector<PageEntry> m_pages{};
s64 m_page_index{}; s64 m_page_index{};
s64 m_page_index_max{1}; s64 m_page_index_max{1};
std::string m_search{}; std::string m_search{};
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{};
// options // options
option::OptionLong m_sort{INI_SECTION, "sort", 0}; option::OptionLong m_sort{INI_SECTION, "sort", 0};

View File

@@ -22,9 +22,9 @@ private:
void Draw(NVGcontext* vg, Theme* theme) override; void Draw(NVGcontext* vg, Theme* theme) override;
private: private:
std::string m_text; std::string m_text{};
std::size_t m_count{180}; // count down to zero std::size_t m_count{180}; // count down to zero
Side m_side; Side m_side{};
bool m_bounds_measured{}; bool m_bounds_measured{};
}; };
@@ -47,8 +47,8 @@ private:
void Draw(NVGcontext* vg, Theme* theme, Entries& entries); void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
private: private:
Entries m_entries_left; Entries m_entries_left{};
Entries m_entries_right; Entries m_entries_right{};
Mutex m_mutex{}; Mutex m_mutex{};
}; };

View File

@@ -5,87 +5,37 @@
namespace sphaira::ui::gfx { namespace sphaira::ui::gfx {
enum class Colour {
BLACK,
LIGHT_BLACK,
SILVER,
DARK_GREY,
GREY,
WHITE,
CYAN,
TEAL,
BLUE,
LIGHT_BLUE,
YELLOW,
RED,
};
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture); void drawImage(NVGcontext*, float x, float y, float w, float h, int texture);
void drawImage(NVGcontext*, Vec4 v, int texture); void drawImage(NVGcontext*, const Vec4& v, int texture);
void drawImageRounded(NVGcontext*, float x, float y, float w, float h, int texture); void drawImageRounded(NVGcontext*, float x, float y, float w, float h, int texture);
void drawImageRounded(NVGcontext*, Vec4 v, int texture); void drawImageRounded(NVGcontext*, const Vec4& v, int texture);
auto getColour(Colour c) -> NVGcolor;
void dimBackground(NVGcontext*); void dimBackground(NVGcontext*);
void drawRect(NVGcontext*, float x, float y, float w, float h, Colour c, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, Colour c, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, bool rounded = false); void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor& c, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGcolor&& c, bool rounded = false); void drawRect(NVGcontext*, const Vec4& v, const NVGcolor& c, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor& c, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor&& c, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, bool rounded = false); void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, float x, float y, float w, float h, const NVGpaint&& p, bool rounded = false); void drawRect(NVGcontext*, const Vec4& v, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint&& p, bool rounded = false);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, Colour c); void drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, Colour c); void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor&& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor&& c);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint&& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p);
void drawRectOutline(NVGcontext*, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint&& p);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, Colour c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor&& c);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p);
void drawTriangle(NVGcontext*, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint&& p);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, Colour c);
void drawText(NVGcontext*, float x, float y, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, Colour c);
void drawText(NVGcontext*, Vec2 vec, float size, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c);
void drawText(NVGcontext*, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor&& c);
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawText(NVGcontext*, float x, float y, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, float x, float y, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawText(NVGcontext*, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor& c); void drawText(NVGcontext*, const Vec2& v, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor&& c);
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawText(NVGcontext*, Vec2 vec, float size, const NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, Colour c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) __attribute__ ((format (printf, 7, 8))); void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) __attribute__ ((format (printf, 7, 8)));
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr); void drawTextBox(NVGcontext*, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, NVGcolor&& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, Colour c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6))); void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
// void textBounds(NVGcontext*, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
// void textBounds(NVGcontext*, float *bounds, const char* str);
auto getButton(Button button) -> const char*; auto getButton(Button button) -> const char*;
void drawScrollbar(NVGcontext* vg, Theme* theme, u32 index_off, u32 count, u32 max_per_page); void drawScrollbar(NVGcontext*, const Theme*, u32 index_off, u32 count, u32 max_per_page);
void drawScrollbar(NVGcontext* vg, Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page); void drawScrollbar(NVGcontext*, const Theme*, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page);
void drawScrollbar2(NVGcontext* vg, Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page); void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page);
void drawScrollbar2(NVGcontext* vg, Theme* theme, s64 index_off, s64 count, s64 row, s64 page); void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row, s64 page);
void updateHighlightAnimation(); void updateHighlightAnimation();
void getHighlightAnimation(float* gradientX, float* gradientY, float* color); void getHighlightAnimation(float* gradientX, float* gradientY, float* color);

View File

@@ -1,13 +1,16 @@
#pragma once #pragma once
#include "types.hpp" #include "types.hpp"
#include <stop_token>
namespace sphaira::ui { namespace sphaira::ui {
class Object { class Object {
public: public:
Object() = default; Object() = default;
virtual ~Object() = default; virtual ~Object() {
m_stop_source.request_stop();
}
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0; virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0;
@@ -71,8 +74,14 @@ public:
m_hidden = value; m_hidden = value;
} }
auto GetToken() const {
return m_stop_source.get_token();
}
protected: protected:
Vec4 m_pos{}; Vec4 m_pos{};
// used for lifetime management across threads.
std::stop_source m_stop_source{};
bool m_hidden{false}; bool m_hidden{false};
}; };

View File

@@ -18,7 +18,7 @@ public:
private: private:
private: private:
std::string m_text; std::string m_text{};
Vec2 m_text_pos{}; Vec2 m_text_pos{};
bool m_selected{false}; bool m_selected{false};
}; };
@@ -48,13 +48,13 @@ private:
void SetIndex(s64 index); void SetIndex(s64 index);
private: private:
std::string m_message; std::string m_message{};
Callback m_callback; Callback m_callback{};
Vec4 m_spacer_line{}; Vec4 m_spacer_line{};
s64 m_index{}; s64 m_index{};
std::vector<OptionBoxEntry> m_entries; std::vector<OptionBoxEntry> m_entries{};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -32,13 +32,12 @@ private:
static constexpr float m_text_xoffset{15.f}; static constexpr float m_text_xoffset{15.f};
static constexpr float m_line_width{1220.f}; static constexpr float m_line_width{1220.f};
std::string m_title; std::string m_title{};
Items m_items; Items m_items{};
Callback m_callback; Callback m_callback{};
s64 m_index; // index in list array s64 m_index{}; // index in list array
s64 m_index_offset{}; // drawing from array start
std::unique_ptr<List> m_list; std::unique_ptr<List> m_list{};
float m_yoff{}; float m_yoff{};
float m_line_top{}; float m_line_top{};

View File

@@ -42,9 +42,9 @@ struct ProgressBox final : Widget {
public: public:
struct ThreadData { struct ThreadData {
ProgressBox* pbox; ProgressBox* pbox{};
ProgressBoxCallback callback; ProgressBoxCallback callback{};
bool result; bool result{};
}; };
private: private:
@@ -57,7 +57,6 @@ private:
std::string m_transfer{}; std::string m_transfer{};
s64 m_size{}; s64 m_size{};
s64 m_offset{}; s64 m_offset{};
bool m_exit_requested{};
}; };
// this is a helper function that does many things. // this is a helper function that does many things.

View File

@@ -14,15 +14,15 @@ struct ScrollableText final : Widget {
// float m_y_off = m_y_off_base; // float m_y_off = m_y_off_base;
// static constexpr float m_clip_y = 250.0F; // static constexpr float m_clip_y = 250.0F;
static constexpr inline float m_step = 30;
const float m_font_size;
const float m_y_off_base; const float m_y_off_base;
float m_y_off;
const float m_clip_y; const float m_clip_y;
const float m_end_w; const float m_end_w;
static constexpr float m_step = 30;
int m_index = 0; float m_y_off{};
const float m_font_size; int m_index{};
float m_bounds[4]; float m_bounds[4]{};
}; };
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -13,7 +13,6 @@ public:
protected: protected:
std::string m_title; std::string m_title;
Vec2 m_offset{};
}; };
class SidebarEntryBool final : public SidebarEntryBase { class SidebarEntryBool final : public SidebarEntryBase {
@@ -58,12 +57,16 @@ public:
SidebarEntryArray(std::string title, Items items, std::string& index); SidebarEntryArray(std::string title, Items items, std::string& index);
auto Draw(NVGcontext* vg, Theme* theme) -> void override; auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private: private:
Items m_items; Items m_items;
ListCallback m_list_callback; ListCallback m_list_callback;
Callback m_callback; Callback m_callback;
s64 m_index; s64 m_index;
s64 m_tick{};
float m_text_yoff{};
}; };
template <typename T> template <typename T>
@@ -117,7 +120,6 @@ private:
Side m_side; Side m_side;
Items m_items; Items m_items;
s64 m_index{}; s64 m_index{};
s64 m_index_offset{};
std::unique_ptr<List> m_list; std::unique_ptr<List> m_list;

View File

@@ -162,21 +162,55 @@ struct ElementEntry {
}; };
enum ThemeEntryID { enum ThemeEntryID {
// colour of the background, can be an image.
ThemeEntryID_BACKGROUND, ThemeEntryID_BACKGROUND,
// colour of the grid background (homebrew, appstore), can be an image.
ThemeEntryID_GRID, ThemeEntryID_GRID,
ThemeEntryID_SELECTED, // background colour of a popup.
ThemeEntryID_SELECTED_OVERLAY, ThemeEntryID_POPUP,
ThemeEntryID_TEXT, // colour of the error text / button.
ThemeEntryID_TEXT_SELECTED, ThemeEntryID_ERROR,
// colour of all text.
ThemeEntryID_TEXT,
// colour of text info and subheaders.
ThemeEntryID_TEXT_INFO,
// colour of selected item text.
ThemeEntryID_TEXT_SELECTED,
// background colour of a selected item, can be an image (not recommended).
ThemeEntryID_SELECTED_BACKGROUND,
// colour of line separators in a list.
ThemeEntryID_LINE,
ThemeEntryID_LINE_SEPARATOR,
// colour of the sidebar backrgound.
ThemeEntryID_SIDEBAR,
// colour of the scrollbar (full portion).
ThemeEntryID_SCROLLBAR,
// colour of the scrollbar background (empty portion).
ThemeEntryID_SCROLLBAR_BACKGROUND,
// colour of the progressbar (full portion).
ThemeEntryID_PROGRESSBAR,
// colour of the progressbar background (empty portion).
ThemeEntryID_PROGRESSBAR_BACKGROUND,
// the colours of the pulsing effect, from 1 -> 2.
ThemeEntryID_HIGHLIGHT_1,
ThemeEntryID_HIGHLIGHT_2,
// changes the colours of the internal icons used below.
ThemeEntryID_ICON_COLOUR,
// images used in the filebrowser.
ThemeEntryID_ICON_AUDIO, ThemeEntryID_ICON_AUDIO,
ThemeEntryID_ICON_VIDEO, ThemeEntryID_ICON_VIDEO,
ThemeEntryID_ICON_IMAGE, ThemeEntryID_ICON_IMAGE,
ThemeEntryID_ICON_FILE, ThemeEntryID_ICON_FILE,
ThemeEntryID_ICON_FOLDER, ThemeEntryID_ICON_FOLDER,
ThemeEntryID_ICON_ZIP, ThemeEntryID_ICON_ZIP,
ThemeEntryID_ICON_GAME,
ThemeEntryID_ICON_NRO, ThemeEntryID_ICON_NRO,
ThemeEntryID_MAX, ThemeEntryID_MAX,
@@ -186,16 +220,18 @@ struct ThemeMeta {
std::string name; std::string name;
std::string author; std::string author;
std::string version; std::string version;
std::string ini_path; fs::FsPath inherit;
fs::FsPath ini_path;
}; };
struct Theme { struct Theme {
std::string name; ThemeMeta meta;
std::string author;
std::string version;
fs::FsPath path;
PLSR_BFSTM music; PLSR_BFSTM music;
ElementEntry elements[ThemeEntryID_MAX]; ElementEntry elements[ThemeEntryID_MAX];
auto GetColour(ThemeEntryID id) const {
return elements[id].colour;
}
}; };
// enum class TouchGesture { // enum class TouchGesture {
@@ -287,37 +323,36 @@ inline ActionType operator|(ActionType a, ActionType b) {
} }
struct Action final { struct Action final {
using CallbackEmpty = std::function<void()>;
using CallbackWithBool = std::function<void(bool)>;
using Callback = std::variant< using Callback = std::variant<
std::function<void()>, CallbackEmpty,
std::function<void(bool)> CallbackWithBool
>; >;
Action(Callback cb) : m_type{ActionType::DOWN}, m_hint{""}, m_callback{cb}, m_hidden{true} {} Action(Callback cb) : Action{ActionType::DOWN, "", cb} {}
Action(std::string hint, Callback cb) : m_type{ActionType::DOWN}, m_hint{hint}, m_callback{cb} {} Action(std::string hint, Callback cb) : Action{ActionType::DOWN, hint, cb} {}
Action(u8 type, Callback cb) : m_type{type}, m_hint{""}, m_callback{cb}, m_hidden{true} {} Action(u8 type, Callback cb) : Action{type, "", cb} {}
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_hint{hint}, m_callback{cb} {} Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
auto IsHidden() const noexcept { return m_hidden; } auto IsHidden() const noexcept { return m_hint.empty(); }
auto Invoke(bool down) const { auto Invoke(bool down) const {
// todo: make this a visit std::visit([down](auto&& arg){
switch (m_callback.index()) { using T = std::decay_t<decltype(arg)>;
case 0: if constexpr(std::is_same_v<T, CallbackEmpty>) {
std::get<0>(m_callback)(); arg();
break; } else if constexpr(std::is_same_v<T, CallbackWithBool>) {
case 1: arg(down);
std::get<1>(m_callback)(down); } else {
break; static_assert(false, "non-exhaustive visitor!");
} }
// std::visit([down, this](auto& cb){ }, m_callback);
// cb(down);
// }), m_callback;
} }
u8 m_type; u8 m_type{};
std::string m_hint; // todo: make optional Callback m_callback{};
Callback m_callback; std::string m_hint{};
bool m_hidden{false}; // replace this optional text
}; };
struct Controller { struct Controller {

View File

@@ -78,7 +78,7 @@ struct Widget : public Object {
auto GetUiButtons() const -> uiButtons; auto GetUiButtons() const -> uiButtons;
Actions m_actions; Actions m_actions{};
Vec2 m_button_pos{1220, 675}; Vec2 m_button_pos{1220, 675};
bool m_focus{false}; bool m_focus{false};
bool m_pop{false}; bool m_pop{false};

View File

@@ -1,6 +1,7 @@
#include "ui/menus/main_menu.hpp" #include "ui/menus/main_menu.hpp"
#include "ui/error_box.hpp" #include "ui/error_box.hpp"
#include "ui/option_box.hpp" #include "ui/option_box.hpp"
#include "ui/bubbles.hpp"
#include "app.hpp" #include "app.hpp"
#include "log.hpp" #include "log.hpp"
@@ -33,6 +34,50 @@ extern "C" {
namespace sphaira { namespace sphaira {
namespace { namespace {
struct ThemeData {
fs::FsPath music_path{"/config/sphaira/themes/default_music.bfstm"};
std::string elements[ThemeEntryID_MAX]{};
};
struct ThemeIdPair {
const char* label;
ThemeEntryID id;
ElementType type{ElementType::None};
};
struct FrameBufferSize {
Vec2 size;
Vec2 scale;
};
constexpr ThemeIdPair THEME_ENTRIES[] = {
{ "background", ThemeEntryID_BACKGROUND },
{ "grid", ThemeEntryID_GRID },
{ "text", ThemeEntryID_TEXT, ElementType::Colour },
{ "text_info", ThemeEntryID_TEXT_INFO, ElementType::Colour },
{ "text_selected", ThemeEntryID_TEXT_SELECTED, ElementType::Colour },
{ "selected_background", ThemeEntryID_SELECTED_BACKGROUND, ElementType::Colour },
{ "error", ThemeEntryID_ERROR, ElementType::Colour },
{ "popup", ThemeEntryID_POPUP, ElementType::Colour },
{ "line", ThemeEntryID_LINE, ElementType::Colour },
{ "line_separator", ThemeEntryID_LINE_SEPARATOR, ElementType::Colour },
{ "sidebar", ThemeEntryID_SIDEBAR, ElementType::Colour },
{ "scrollbar", ThemeEntryID_SCROLLBAR, ElementType::Colour },
{ "scrollbar_background", ThemeEntryID_SCROLLBAR_BACKGROUND, ElementType::Colour },
{ "progressbar", ThemeEntryID_PROGRESSBAR, ElementType::Colour },
{ "progressbar_background", ThemeEntryID_PROGRESSBAR_BACKGROUND, ElementType::Colour },
{ "highlight_1", ThemeEntryID_HIGHLIGHT_1, ElementType::Colour },
{ "highlight_2", ThemeEntryID_HIGHLIGHT_2, ElementType::Colour },
{ "icon_colour", ThemeEntryID_ICON_COLOUR, ElementType::Colour },
{ "icon_audio", ThemeEntryID_ICON_AUDIO, ElementType::Texture },
{ "icon_video", ThemeEntryID_ICON_VIDEO, ElementType::Texture },
{ "icon_image", ThemeEntryID_ICON_IMAGE, ElementType::Texture },
{ "icon_file", ThemeEntryID_ICON_FILE, ElementType::Texture },
{ "icon_folder", ThemeEntryID_ICON_FOLDER, ElementType::Texture },
{ "icon_zip", ThemeEntryID_ICON_ZIP, ElementType::Texture },
{ "icon_nro", ThemeEntryID_ICON_NRO, ElementType::Texture },
};
constinit App* g_app{}; constinit App* g_app{};
void deko3d_error_cb(void* userData, const char* context, DkResult result, const char* message) { void deko3d_error_cb(void* userData, const char* context, DkResult result, const char* message) {
@@ -183,6 +228,26 @@ void appplet_hook_calback(AppletHookType type, void *param) {
} }
} }
auto GetFrameBufferSize() -> FrameBufferSize {
FrameBufferSize fb{};
switch (appletGetOperationMode()) {
case AppletOperationMode_Handheld:
fb.size.x = 1280;
fb.size.y = 720;
break;
case AppletOperationMode_Console:
fb.size.x = 1920;
fb.size.y = 1080;
break;
}
fb.scale.x = fb.size.x / SCREEN_WIDTH;
fb.scale.y = fb.size.y / SCREEN_HEIGHT;
return fb;
}
// this will try to decompress the icon and then re-convert it to jpg // this will try to decompress the icon and then re-convert it to jpg
// in order to strip exif data. // in order to strip exif data.
// this doesn't take long at all, but it's very overkill. // this doesn't take long at all, but it's very overkill.
@@ -203,6 +268,95 @@ auto GetNroIcon(const std::vector<u8>& nro_icon) -> std::vector<u8> {
return nro_icon; return nro_icon;
} }
auto LoadThemeMeta(const fs::FsPath& path, ThemeMeta& meta) -> bool {
meta = {};
char buf[FS_MAX_PATH]{};
int len{};
len = ini_gets("meta", "name", "", buf, sizeof(buf) - 1, path);
if (len <= 1) {
return false;
}
meta.name = buf;
len = ini_gets("meta", "author", "", buf, sizeof(buf) - 1, path);
if (len <= 1) {
return false;
}
meta.author = buf;
len = ini_gets("meta", "version", "", buf, sizeof(buf) - 1, path);
if (len <= 1) {
return false;
}
meta.version = buf;
len = ini_gets("meta", "inherit", "", buf, sizeof(buf) - 1, path);
if (len > 1) {
meta.inherit = buf;
}
log_write("loaded meta from: %s\n", path.s);
meta.ini_path = path;
return true;
}
void LoadThemeInternal(ThemeMeta meta, ThemeData& theme_data, int inherit_level = 0) {
constexpr auto inherit_level_max = 5;
// all themes will inherit from black theme by default.
if (meta.inherit.empty() && !inherit_level) {
meta.inherit = "romfs:/themes/base_black_theme.ini";
}
// check if the theme inherits from another, if so, load it.
// block inheriting from itself.
if (inherit_level < inherit_level_max && !meta.inherit.empty() && strcasecmp(meta.inherit, "none") && meta.inherit != meta.ini_path) {
log_write("inherit is not empty: %s\n", meta.inherit.s);
if (R_SUCCEEDED(romfsInit())) {
ThemeMeta inherit_meta;
const auto has_meta = LoadThemeMeta(meta.inherit, inherit_meta);
romfsExit();
// base themes do not have a meta
if (!has_meta) {
inherit_meta.ini_path = meta.inherit;
}
LoadThemeInternal(inherit_meta, theme_data, inherit_level + 1);
}
}
static constexpr auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
auto theme_data = static_cast<ThemeData*>(UserData);
if (!std::strcmp(Section, "theme")) {
if (!std::strcmp(Key, "music")) {
theme_data->music_path = Value;
} else {
for (auto& e : THEME_ENTRIES) {
if (!std::strcmp(Key, e.label)) {
theme_data->elements[e.id] = Value;
break;
}
}
}
}
return 1;
};
if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit());
if (!ini_browse(cb, &theme_data, meta.ini_path)) {
log_write("failed to open ini: %s\n", meta.ini_path.s);
} else {
log_write("opened ini: %s\n", meta.ini_path.s);
}
}
}
void haze_callback(const HazeCallbackData *data) { void haze_callback(const HazeCallbackData *data) {
App::NotifyFlashLed(); App::NotifyFlashLed();
evman::push(*data, false); evman::push(*data, false);
@@ -292,31 +446,21 @@ void App::Loop() {
} }
} else if constexpr(std::is_same_v<T, curl::DownloadEventData>) { } else if constexpr(std::is_same_v<T, curl::DownloadEventData>) {
log_write("[DownloadEventData] got event\n"); log_write("[DownloadEventData] got event\n");
arg.callback(arg.result); if (arg.callback && !arg.stoken.stop_requested()) {
arg.callback(arg.result);
}
} else { } else {
static_assert(false, "non-exhaustive visitor!"); static_assert(false, "non-exhaustive visitor!");
} }
}, event.value()); }, event.value());
} }
u32 w{},h{}; const auto fb = GetFrameBufferSize();
switch (appletGetOperationMode()) { if (fb.size.x != s_width || fb.size.y != s_height) {
case AppletOperationMode_Handheld: s_width = fb.size.x;
w = 1280; s_height = fb.size.y;
h = 720; m_scale = fb.scale;
break; this->destroyFramebufferResources();
case AppletOperationMode_Console:
w = 1920;
h = 1080;
break;
}
if (w != s_width || h != s_height) {
s_width = w;
s_height = h;
m_scale.x = (float)s_width / SCREEN_WIDTH;
m_scale.y = (float)s_height / SCREEN_HEIGHT;
this->createFramebufferResources(); this->createFramebufferResources();
renderer->UpdateViewSize(s_width, s_height); renderer->UpdateViewSize(s_width, s_height);
} }
@@ -392,7 +536,7 @@ auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
} }
void App::SetTheme(s64 theme_index) { void App::SetTheme(s64 theme_index) {
g_app->LoadTheme(g_app->m_theme_meta_entries[theme_index].ini_path.c_str()); g_app->LoadTheme(g_app->m_theme_meta_entries[theme_index]);
g_app->m_theme_index = theme_index; g_app->m_theme_index = theme_index;
} }
@@ -456,6 +600,10 @@ auto App::GetLanguage() -> long {
return g_app->m_language.Get(); return g_app->m_language.Get();
} }
auto App::GetTextScrollSpeed() -> long {
return g_app->m_text_scroll_speed.Get();
}
void App::SetNxlinkEnable(bool enable) { void App::SetNxlinkEnable(bool enable) {
if (App::GetNxlinkEnable() != enable) { if (App::GetNxlinkEnable() != enable) {
g_app->m_nxlink_enabled.Set(enable); g_app->m_nxlink_enabled.Set(enable);
@@ -483,13 +631,11 @@ void App::SetReplaceHbmenuEnable(bool enable) {
g_app->m_replace_hbmenu.Set(enable); g_app->m_replace_hbmenu.Set(enable);
if (!enable) { if (!enable) {
// check we have already replaced hbmenu with sphaira // check we have already replaced hbmenu with sphaira
NacpStruct hbmenu_nacp; NacpStruct hbmenu_nacp{};
if (R_FAILED(nro_get_nacp("/hbmenu.nro", hbmenu_nacp))) { if (R_SUCCEEDED(nro_get_nacp("/hbmenu.nro", hbmenu_nacp))) {
return; if (std::strcmp(hbmenu_nacp.lang[0].name, "sphaira")) {
} return;
}
if (std::strcmp(hbmenu_nacp.lang[0].name, "sphaira")) {
return;
} }
// ask user if they want to restore hbmenu // ask user if they want to restore hbmenu
@@ -531,7 +677,7 @@ void App::SetReplaceHbmenuEnable(bool enable) {
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) { if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
if (std::strcmp(sphaira_nacp.display_version, hbmenu_nacp.display_version) < 0) { if (std::strcmp(sphaira_nacp.display_version, hbmenu_nacp.display_version) < 0) {
if (R_FAILED(rc = fs.copy_entire_file(sphaira_path, "/hbmenu.nro"))) { if (R_FAILED(rc = fs.copy_entire_file(sphaira_path, "/hbmenu.nro"))) {
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc)); log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
} else { } else {
log_write("success with updating hbmenu!\n"); log_write("success with updating hbmenu!\n");
} }
@@ -626,9 +772,22 @@ void App::SetLanguage(long index) {
if (App::GetLanguage() != index) { if (App::GetLanguage() != index) {
g_app->m_language.Set(index); g_app->m_language.Set(index);
on_i18n_change(); on_i18n_change();
App::Push(std::make_shared<ui::OptionBox>(
"Restart Sphaira?"_i18n,
"Back"_i18n, "Restart"_i18n, 1, [](auto op_index){
if (op_index && *op_index) {
App::ExitRestart();
}
}
));
} }
} }
void App::SetTextScrollSpeed(long index) {
g_app->m_text_scroll_speed.Set(index);
}
auto App::Install(OwoConfig& config) -> Result { auto App::Install(OwoConfig& config) -> Result {
R_TRY(romfsInit()); R_TRY(romfsInit());
ON_SCOPE_EXIT(romfsExit()); ON_SCOPE_EXIT(romfsExit());
@@ -705,6 +864,51 @@ void App::Poll() {
hidGetTouchScreenStates(&state, 1); hidGetTouchScreenStates(&state, 1);
m_touch_info.is_clicked = false; m_touch_info.is_clicked = false;
// todo: replace old touch code with gestures from below
#if 0
static HidGestureState prev_gestures[17]{};
HidGestureState gestures[17]{};
const auto gesture_count = hidGetGestureStates(gestures, std::size(gestures));
for (int i = (int)gesture_count - 1; i >= 0; i--) {
bool found = false;
for (int j = 0; j < gesture_count; j++) {
if (gestures[i].type == prev_gestures[j].type && gestures[i].sampling_number == prev_gestures[j].sampling_number) {
found = true;
break;
}
}
if (found) {
continue;
}
auto gesture = gestures[i];
if (gesture_count && gesture.type == HidGestureType_Touch) {
log_write("[TOUCH] got gesture attr: %u direction: %u sampling_number: %zu context_number: %zu\n", gesture.attributes, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Swipe) {
log_write("[SWIPE] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Tap) {
log_write("[TAP] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Press) {
log_write("[PRESS] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Cancel) {
log_write("[CANCEL] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Complete) {
log_write("[COMPLETE] got gesture direction: %u sampling_number: %zu context_number: %zu\n", gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Pan) {
log_write("[PAN] got gesture direction: %u sampling_number: %zu context_number: %zu x: %d y: %d dx: %d dy: %d vx: %.2f vy: %.2f count: %d\n", gesture.direction, gesture.sampling_number, gesture.context_number, gesture.x, gesture.y, gesture.delta_x, gesture.delta_y, gesture.velocity_x, gesture.velocity_y, gesture.point_count);
}
}
memcpy(prev_gestures, gestures, sizeof(gestures));
#endif
if (state.count == 1 && !m_touch_info.is_touching) { if (state.count == 1 && !m_touch_info.is_touching) {
m_touch_info.initial = m_touch_info.cur = state.touches[0]; m_touch_info.initial = m_touch_info.cur = state.touches[0];
m_touch_info.is_touching = true; m_touch_info.is_touching = true;
@@ -797,6 +1001,7 @@ void App::Draw() {
} }
m_notif_manager.Draw(vg, &m_theme); m_notif_manager.Draw(vg, &m_theme);
ui::bubble::Draw(vg, &m_theme);
nvgResetTransform(vg); nvgResetTransform(vg);
nvgEndFrame(this->vg); nvgEndFrame(this->vg);
@@ -818,7 +1023,13 @@ void DrawElement(const Vec4& v, ThemeEntryID id) {
case ElementType::None: { case ElementType::None: {
} break; } break;
case ElementType::Texture: { case ElementType::Texture: {
const auto paint = nvgImagePattern(g_app->vg, v.x, v.y, v.w, v.h, 0, e.texture, 1.f); auto paint = nvgImagePattern(g_app->vg, v.x, v.y, v.w, v.h, 0, e.texture, 1.f);
// override the icon colours if set
if (id > ThemeEntryID_ICON_COLOUR && id < ThemeEntryID_MAX) {
if (g_app->m_theme.elements[ThemeEntryID_ICON_COLOUR].type != ElementType::None) {
paint.innerColor = g_app->m_theme.GetColour(ThemeEntryID_ICON_COLOUR);
}
}
ui::gfx::drawRect(g_app->vg, v, paint); ui::gfx::drawRect(g_app->vg, v, paint);
} break; } break;
case ElementType::Colour: { case ElementType::Colour: {
@@ -843,120 +1054,87 @@ auto App::LoadElementColour(std::string_view value) -> ElementEntry {
if (value.starts_with("0x")) { if (value.starts_with("0x")) {
value = value.substr(2); value = value.substr(2);
} else if (value.starts_with('#')) { } else {
value = value.substr(1); return {};
} }
const u32 c = std::strtol(value.data(), nullptr, 16); char* end;
if (c) { u32 c = std::strtoul(value.data(), &end, 16);
entry.colour = nvgRGBA((c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF); if (!c && value.data() == end) {
entry.type = ElementType::Colour; return {};
} }
// force alpha bit if not already set.
if (value.length() <= 6) {
c <<= 8;
c |= 0xFF;
}
entry.colour = nvgRGBA((c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);
entry.type = ElementType::Colour;
return entry; return entry;
} }
auto App::LoadElement(std::string_view value) -> ElementEntry { auto App::LoadElement(std::string_view value, ElementType type) -> ElementEntry {
if (value.size() <= 1) { if (value.size() <= 1) {
return {}; return {};
} }
if (auto e = LoadElementImage(value); e.type != ElementType::None) { if (type == ElementType::None || type == ElementType::Colour) {
return e; // most assets are colours, so prioritise this first
if (auto e = LoadElementColour(value); e.type != ElementType::None) {
return e;
}
} }
if (auto e = LoadElementColour(value); e.type != ElementType::None) { if (type == ElementType::None || type == ElementType::Texture) {
return e; if (auto e = LoadElementImage(value); e.type != ElementType::None) {
return e;
}
} }
return {}; return {};
} }
void App::CloseTheme() { void App::CloseTheme() {
m_theme.name.clear();
m_theme.author.clear();
m_theme.version.clear();
m_theme.path.clear();
if (m_sound_ids[SoundEffect_Music]) { if (m_sound_ids[SoundEffect_Music]) {
plsrPlayerFree(m_sound_ids[SoundEffect_Music]); plsrPlayerFree(m_sound_ids[SoundEffect_Music]);
m_sound_ids[SoundEffect_Music] = nullptr; m_sound_ids[SoundEffect_Music] = nullptr;
plsrBFSTMClose(&m_theme.music); plsrBFSTMClose(&m_theme.music);
} }
for (auto& e : m_theme.elements) { for (auto& e : m_theme.elements) {
if (e.type == ElementType::Texture) { if (e.type == ElementType::Texture) {
nvgDeleteImage(vg, e.texture); nvgDeleteImage(vg, e.texture);
} }
e.type = ElementType::None;
} }
m_theme = {};
} }
void App::LoadTheme(const fs::FsPath& path) { void App::LoadTheme(const ThemeMeta& meta) {
// reset theme // reset theme
CloseTheme(); CloseTheme();
m_theme.path = path;
const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int { ThemeData theme_data{};
auto app = static_cast<App*>(UserData); LoadThemeInternal(meta, theme_data);
auto& theme = app->m_theme; m_theme.meta = meta;
std::string_view section{Section};
std::string_view key{Key};
std::string_view value{Value};
if (section == "meta") {
if (key == "name") {
theme.name = key;
} else if (key == "author") {
theme.author = key;
} else if (key == "version") {
theme.version = key;
}
} else if (section == "theme") {
if (key == "background") {
theme.elements[ThemeEntryID_BACKGROUND] = app->LoadElement(value);
} else if (key == "music") {
if (R_SUCCEEDED(plsrBFSTMOpen(Value, &theme.music))) {
if (R_SUCCEEDED(plsrPlayerLoadStream(&theme.music, &app->m_sound_ids[SoundEffect_Music]))) {
app->PlaySoundEffect(SoundEffect_Music);
}
}
} else if (key == "grid") {
theme.elements[ThemeEntryID_GRID] = app->LoadElement(value);
} else if (key == "selected") {
theme.elements[ThemeEntryID_SELECTED] = app->LoadElement(value);
} else if (key == "selected_overlay") {
theme.elements[ThemeEntryID_SELECTED_OVERLAY] = app->LoadElement(value);
} else if (key == "text") {
theme.elements[ThemeEntryID_TEXT] = app->LoadElementColour(value);
} else if (key == "text_selected") {
theme.elements[ThemeEntryID_TEXT_SELECTED] = app->LoadElementColour(value);
} else if (key == "icon_audio") {
theme.elements[ThemeEntryID_ICON_AUDIO] = app->LoadElement(value);
} else if (key == "icon_video") {
theme.elements[ThemeEntryID_ICON_VIDEO] = app->LoadElement(value);
} else if (key == "icon_image") {
theme.elements[ThemeEntryID_ICON_IMAGE] = app->LoadElement(value);
} else if (key == "icon_file") {
theme.elements[ThemeEntryID_ICON_FILE] = app->LoadElement(value);
} else if (key == "icon_folder") {
theme.elements[ThemeEntryID_ICON_FOLDER] = app->LoadElement(value);
} else if (key == "icon_zip") {
theme.elements[ThemeEntryID_ICON_ZIP] = app->LoadElement(value);
} else if (key == "icon_game") {
theme.elements[ThemeEntryID_ICON_GAME] = app->LoadElement(value);
} else if (key == "icon_nro") {
theme.elements[ThemeEntryID_ICON_NRO] = app->LoadElement(value);
}
}
return 1;
};
if (R_SUCCEEDED(romfsInit())) { if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit()); ON_SCOPE_EXIT(romfsExit());
if (!ini_browse(cb, this, path)) {
log_write("failed to open ini: %s\n", path); // load all assets / colours.
} else { for (auto& e : THEME_ENTRIES) {
log_write("opened ini: %s\n", path); m_theme.elements[e.id] = LoadElement(theme_data.elements[e.id], e.type);
}
// load music
if (!theme_data.music_path.empty()) {
if (R_SUCCEEDED(plsrBFSTMOpen(theme_data.music_path, &m_theme.music))) {
if (R_SUCCEEDED(plsrPlayerLoadStream(&m_theme.music, &m_sound_ids[SoundEffect_Music]))) {
PlaySoundEffect(SoundEffect_Music);
}
}
} }
} }
} }
@@ -986,40 +1164,10 @@ void App::ScanThemes(const std::string& path) {
const auto full_path = path + name; const auto full_path = path + name;
if (!ini_haskey("meta", "name", full_path.c_str())) {
continue;
}
if (!ini_haskey("meta", "author", full_path.c_str())) {
continue;
}
if (!ini_haskey("meta", "version", full_path.c_str())) {
continue;
}
ThemeMeta meta{}; ThemeMeta meta{};
if (LoadThemeMeta(full_path, meta)) {
char buf[FS_MAX_PATH]{}; m_theme_meta_entries.emplace_back(meta);
int len{};
len = ini_gets("meta", "name", "", buf, sizeof(buf) - 1, full_path.c_str());
if (len <= 1) {
continue;
} }
meta.name = buf;
len = ini_gets("meta", "author", "", buf, sizeof(buf) - 1, full_path.c_str());
if (len <= 1) {
continue;
}
meta.author = buf;
len = ini_gets("meta", "version", "", buf, sizeof(buf) - 1, full_path.c_str());
if (len <= 1) {
continue;
}
meta.version = buf;
meta.ini_path = full_path;
m_theme_meta_entries.emplace_back(meta);
} }
} }
@@ -1073,6 +1221,12 @@ App::App(const char* argv0) {
curl::Init(); curl::Init();
// get current size of the framebuffer
const auto fb = GetFrameBufferSize();
s_width = fb.size.x;
s_height = fb.size.y;
m_scale = fb.scale;
// Create the deko3d device // Create the deko3d device
this->device = dk::DeviceMaker{} this->device = dk::DeviceMaker{}
.setCbDebug(deko3d_error_cb) .setCbDebug(deko3d_error_cb)
@@ -1096,7 +1250,7 @@ App::App(const char* argv0) {
// Create the framebuffer resources // Create the framebuffer resources
this->createFramebufferResources(); this->createFramebufferResources();
this->renderer.emplace(SCREEN_WIDTH, SCREEN_HEIGHT, this->device, this->queue, *this->pool_images, *this->pool_code, *this->pool_data); this->renderer.emplace(s_width, s_height, this->device, this->queue, *this->pool_images, *this->pool_code, *this->pool_data);
this->vg = nvgCreateDk(&*this->renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES); this->vg = nvgCreateDk(&*this->renderer, NVG_ANTIALIAS | NVG_STENCIL_STROKES);
i18n::init(GetLanguage()); i18n::init(GetLanguage());
@@ -1128,21 +1282,31 @@ App::App(const char* argv0) {
} }
} }
// disable audio in applet mode with a suspended application due to audren fatal.
// see: https://github.com/ITotalJustice/sphaira/issues/92
if (IsAppletWithSuspendedApp()) {
App::Notify("Audio disabled due to suspended game"_i18n);
} else {
plsrPlayerInit();
}
if (R_SUCCEEDED(romfsMountDataStorageFromProgram(0x0100000000001000, "qlaunch"))) { if (R_SUCCEEDED(romfsMountDataStorageFromProgram(0x0100000000001000, "qlaunch"))) {
ON_SCOPE_EXIT(romfsUnmount("qlaunch")); ON_SCOPE_EXIT(romfsUnmount("qlaunch"));
plsrPlayerInit(); PLSR_BFSAR qlaunch_bfsar;
plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &m_qlaunch_bfsar); if (R_SUCCEEDED(plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &qlaunch_bfsar))) {
ON_SCOPE_EXIT(plsrBFSARClose(&qlaunch_bfsar));
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconLimit", &m_sound_ids[SoundEffect_Limit]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconLimit", &m_sound_ids[SoundEffect_Limit]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeStartupMenu_game", &m_sound_ids[SoundEffect_Startup]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeStartupMenu_game", &m_sound_ids[SoundEffect_Startup]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconAdd", &m_sound_ids[SoundEffect_Install]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeGameIconAdd", &m_sound_ids[SoundEffect_Install]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeInsertError", &m_sound_ids[SoundEffect_Error]); plsrPlayerLoadSoundByName(&qlaunch_bfsar, "SeInsertError", &m_sound_ids[SoundEffect_Error]);
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Limit], 2.0f); plsrPlayerSetVolume(m_sound_ids[SoundEffect_Limit], 2.0f);
plsrPlayerSetVolume(m_sound_ids[SoundEffect_Focus], 0.5f); plsrPlayerSetVolume(m_sound_ids[SoundEffect_Focus], 0.5f);
PlaySoundEffect(SoundEffect_Startup); PlaySoundEffect(SoundEffect_Startup);
}
} else { } else {
log_write("failed to mount romfs 0x0100000000001000\n"); log_write("failed to mount romfs 0x0100000000001000\n");
} }
@@ -1150,16 +1314,29 @@ App::App(const char* argv0) {
ScanThemeEntries(); ScanThemeEntries();
fs::FsPath theme_path{}; fs::FsPath theme_path{};
constexpr fs::FsPath default_theme_path{"romfs:/themes/abyss_theme.ini"};
if (App::GetThemeShuffleEnable() && m_theme_meta_entries.size()) { if (App::GetThemeShuffleEnable() && m_theme_meta_entries.size()) {
theme_path = m_theme_meta_entries[randomGet64() % m_theme_meta_entries.size()].ini_path; theme_path = m_theme_meta_entries[randomGet64() % m_theme_meta_entries.size()].ini_path;
} else { } else {
ini_gets("config", "theme", "romfs:/themes/abyss_theme.ini", theme_path, sizeof(theme_path), CONFIG_PATH); ini_gets("config", "theme", default_theme_path, theme_path, sizeof(theme_path), CONFIG_PATH);
} }
LoadTheme(theme_path);
// try and load previous theme, default to previous version otherwise.
ThemeMeta theme_meta;
if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit());
if (!LoadThemeMeta(theme_path, theme_meta)) {
log_write("failed to load meta using default\n");
theme_path = default_theme_path;
LoadThemeMeta(theme_path, theme_meta);
}
}
log_write("loading theme from: %s\n", theme_meta.ini_path.s);
LoadTheme(theme_meta);
// find theme index using the path of the theme.ini // find theme index using the path of the theme.ini
for (u64 i = 0; i < m_theme_meta_entries.size(); i++) { for (u64 i = 0; i < m_theme_meta_entries.size(); i++) {
if (m_theme.path == m_theme_meta_entries[i].ini_path) { if (m_theme.meta.ini_path == m_theme_meta_entries[i].ini_path) {
m_theme_index = i; m_theme_index = i;
break; break;
} }
@@ -1168,6 +1345,7 @@ App::App(const char* argv0) {
appletHook(&m_appletHookCookie, appplet_hook_calback, this); appletHook(&m_appletHookCookie, appplet_hook_calback, this);
hidInitializeTouchScreen(); hidInitializeTouchScreen();
hidInitializeGesture();
padConfigureInput(8, HidNpadStyleSet_NpadStandard); padConfigureInput(8, HidNpadStyleSet_NpadStandard);
// padInitializeDefault(&m_pad); // padInitializeDefault(&m_pad);
padInitializeAny(&m_pad); padInitializeAny(&m_pad);
@@ -1186,7 +1364,7 @@ App::App(const char* argv0) {
log_write("launching from sphaira created forwarder\n"); log_write("launching from sphaira created forwarder\n");
m_is_launched_via_sphaira_forwader = true; m_is_launched_via_sphaira_forwader = true;
} else { } else {
log_write("launching from unknown forwader: %.*s size: %zu\n", loader_info_size, envGetLoaderInfo(), loader_info_size); log_write("launching from unknown forwader: %.*s size: %zu\n", (int)loader_info_size, envGetLoaderInfo(), loader_info_size);
} }
} else { } else {
log_write("not launching from forwarder\n"); log_write("not launching from forwarder\n");
@@ -1214,6 +1392,9 @@ App::App(const char* argv0) {
} }
} }
// soon (tm)
// ui::bubble::Init();
App::Push(std::make_shared<ui::menu::main::MainMenu>()); App::Push(std::make_shared<ui::menu::main::MainMenu>());
log_write("finished app constructor\n"); log_write("finished app constructor\n");
} }
@@ -1237,6 +1418,8 @@ App::~App() {
i18n::exit(); i18n::exit();
curl::Exit(); curl::Exit();
ui::bubble::Exit();
// this has to be called before any cleanup to ensure the lifetime of // this has to be called before any cleanup to ensure the lifetime of
// nvg is still active as some widgets may need to free images. // nvg is still active as some widgets may need to free images.
m_widgets.clear(); m_widgets.clear();
@@ -1244,7 +1427,7 @@ App::~App() {
appletUnhook(&m_appletHookCookie); appletUnhook(&m_appletHookCookie);
ini_puts("config", "theme", m_theme.path, CONFIG_PATH); ini_puts("config", "theme", m_theme.meta.ini_path, CONFIG_PATH);
CloseTheme(); CloseTheme();
@@ -1255,11 +1438,8 @@ App::~App() {
} }
} }
// Close the archive
plsrBFSARClose(&m_qlaunch_bfsar);
// De-initialize our player // De-initialize our player
plsrPlayerExit(); plsrPlayerExit();
this->destroyFramebufferResources(); this->destroyFramebufferResources();
nvgDeleteDk(this->vg); nvgDeleteDk(this->vg);
@@ -1281,7 +1461,7 @@ App::~App() {
} }
if (R_FAILED(rc = fs.copy_entire_file("/hbmenu.nro", GetExePath()))) { if (R_FAILED(rc = fs.copy_entire_file("/hbmenu.nro", GetExePath()))) {
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", GetExePath(), rc, R_MODULE(rc), R_DESCRIPTION(rc)); log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", GetExePath().s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
} else { } else {
log_write("success with copying over root file!\n"); log_write("success with copying over root file!\n");
} }
@@ -1306,7 +1486,7 @@ App::~App() {
if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) { if (R_SUCCEEDED(rc) && !std::strcmp(sphaira_nacp.lang[0].name, "sphaira")) {
if (std::strcmp(hbmenu_nacp.display_version, sphaira_nacp.display_version) < 0) { if (std::strcmp(hbmenu_nacp.display_version, sphaira_nacp.display_version) < 0) {
if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path))) { if (R_FAILED(rc = fs.copy_entire_file(GetExePath(), sphaira_path))) {
log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path, rc, R_MODULE(rc), R_DESCRIPTION(rc)); log_write("failed to copy entire file: %s 0x%X module: %u desc: %u\n", sphaira_path.s, rc, R_MODULE(rc), R_DESCRIPTION(rc));
} else { } else {
log_write("success with updating hbmenu!\n"); log_write("success with updating hbmenu!\n");
} }

View File

@@ -25,7 +25,6 @@ namespace {
log_write("curl_share_setopt(%s, %s) msg: %s\n", #opt, #v, curl_share_strerror(r)); \ log_write("curl_share_setopt(%s, %s) msg: %s\n", #opt, #v, curl_share_strerror(r)); \
} \ } \
#define USE_THREAD_QUEUE 1
constexpr auto API_AGENT = "ITotalJustice"; constexpr auto API_AGENT = "ITotalJustice";
constexpr u64 CHUNK_SIZE = 1024*1024; constexpr u64 CHUNK_SIZE = 1024*1024;
constexpr auto MAX_THREADS = 4; constexpr auto MAX_THREADS = 4;
@@ -302,18 +301,19 @@ struct ThreadQueue {
} }
auto Add(const Api& api) -> bool { auto Add(const Api& api) -> bool {
if (api.GetUrl().empty() || api.GetPath().empty() || !api.GetOnComplete()) {
return false;
}
mutexLock(&m_mutex); mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
ThreadQueueEntry entry{}; switch (api.GetPriority()) {
entry.api = api;
switch (api.m_prio) {
case Priority::Normal: case Priority::Normal:
m_entries.emplace_back(entry); m_entries.emplace_back(api);
break; break;
case Priority::High: case Priority::High:
m_entries.emplace_front(entry); m_entries.emplace_front(api);
break; break;
} }
@@ -350,13 +350,13 @@ auto ProgressCallbackFunc1(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
} }
auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> size_t { auto ProgressCallbackFunc2(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> size_t {
if (!g_running) { auto api = static_cast<Api*>(clientp);
if (!g_running || api->GetToken().stop_requested()) {
return 1; return 1;
} }
// log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow); // log_write("pcall called %u %u %u %u\n", dltotal, dlnow, ultotal, ulnow);
auto callback = *static_cast<OnProgress*>(clientp); if (!api->GetOnProgress()(dltotal, dlnow, ultotal, ulnow)) {
if (!callback(dltotal, dlnow, ultotal, ulnow)) {
return 1; return 1;
} }
@@ -443,12 +443,17 @@ auto header_callback(char* b, size_t size, size_t nitems, void* userdata) -> siz
} }
auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult { auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
// check if stop has been requested before starting download
if (e.GetToken().stop_requested()) {
return {};
}
fs::FsPath tmp_buf; fs::FsPath tmp_buf;
const bool has_file = !e.m_path.empty() && e.m_path != ""; const bool has_file = !e.GetPath().empty() && e.GetPath() != "";
const bool has_post = !e.m_fields.m_str.empty() && e.m_fields.m_str != ""; const bool has_post = !e.GetFields().empty() && e.GetFields() != "";
DataStruct chunk; DataStruct chunk;
Header header_in = e.m_header; Header header_in = e.GetHeader();
Header header_out; Header header_out;
fs::FsNativeSd fs; fs::FsNativeSd fs;
@@ -457,17 +462,17 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
fs.CreateDirectoryRecursivelyWithPath(tmp_buf); fs.CreateDirectoryRecursivelyWithPath(tmp_buf);
if (auto rc = fs.CreateFile(tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) { if (auto rc = fs.CreateFile(tmp_buf, 0, 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s\n", tmp_buf); log_write("failed to create file: %s\n", tmp_buf.s);
return {}; return {};
} }
if (R_FAILED(fs.OpenFile(tmp_buf, FsOpenMode_Write|FsOpenMode_Append, &chunk.f))) { if (R_FAILED(fs.OpenFile(tmp_buf, FsOpenMode_Write|FsOpenMode_Append, &chunk.f))) {
log_write("failed to open file: %s\n", tmp_buf); log_write("failed to open file: %s\n", tmp_buf.s);
return {}; return {};
} }
if (e.m_flags.m_flags & Flag_Cache) { if (e.GetFlags() & Flag_Cache) {
g_cache.get(e.m_path, header_in); g_cache.get(e.GetPath(), header_in);
} }
} }
@@ -475,7 +480,7 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
chunk.data.reserve(CHUNK_SIZE); chunk.data.reserve(CHUNK_SIZE);
curl_easy_reset(curl); curl_easy_reset(curl);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.m_url.m_str.c_str()); CURL_EASY_SETOPT_LOG(curl, CURLOPT_URL, e.GetUrl().c_str());
CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice"); CURL_EASY_SETOPT_LOG(curl, CURLOPT_USERAGENT, "TotalJustice");
CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L); CURL_EASY_SETOPT_LOG(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L); CURL_EASY_SETOPT_LOG(curl, CURLOPT_SSL_VERIFYPEER, 0L);
@@ -487,8 +492,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out); CURL_EASY_SETOPT_LOG(curl, CURLOPT_HEADERDATA, &header_out);
if (has_post) { if (has_post) {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.m_fields.m_str.c_str()); CURL_EASY_SETOPT_LOG(curl, CURLOPT_POSTFIELDS, e.GetFields().c_str());
log_write("setting post field: %s\n", e.m_fields.m_str.c_str()); log_write("setting post field: %s\n", e.GetFields().c_str());
} }
struct curl_slist* list = NULL; struct curl_slist* list = NULL;
@@ -517,8 +522,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
} }
// progress calls. // progress calls.
if (e.m_on_progress) { if (e.GetOnProgress()) {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e.m_on_progress); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFODATA, &e);
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc2);
} else { } else {
CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1); CURL_EASY_SETOPT_LOG(curl, CURLOPT_XFERINFOFUNCTION, ProgressCallbackFunc1);
@@ -546,16 +551,16 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
if (res == CURLE_OK) { if (res == CURLE_OK) {
if (http_code == 304) { if (http_code == 304) {
log_write("cached download: %s\n", e.m_url.m_str.c_str()); log_write("cached download: %s\n", e.GetUrl().c_str());
} else { } else {
log_write("un-cached download: %s code: %u\n", e.m_url.m_str.c_str(), http_code); log_write("un-cached download: %s code: %lu\n", e.GetUrl().c_str(), http_code);
if (e.m_flags.m_flags & Flag_Cache) { if (e.GetFlags() & Flag_Cache) {
g_cache.set(e.m_path, header_out); g_cache.set(e.GetPath(), header_out);
} }
fs.DeleteFile(e.m_path); fs.DeleteFile(e.GetPath());
fs.CreateDirectoryRecursivelyWithPath(e.m_path); fs.CreateDirectoryRecursivelyWithPath(e.GetPath());
if (R_FAILED(fs.RenameFile(tmp_buf, e.m_path))) { if (R_FAILED(fs.RenameFile(tmp_buf, e.GetPath()))) {
success = false; success = false;
} }
} }
@@ -568,8 +573,8 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
} }
} }
log_write("Downloaded %s %s\n", e.m_url.m_str.c_str(), curl_easy_strerror(res)); log_write("Downloaded %s %s\n", e.GetUrl().c_str(), curl_easy_strerror(res));
return {success, http_code, header_out, chunk.data, e.m_path}; return {success, http_code, header_out, chunk.data, e.GetPath()};
} }
auto DownloadInternal(const Api& e) -> ApiResult { auto DownloadInternal(const Api& e) -> ApiResult {
@@ -596,23 +601,18 @@ void ThreadEntry::ThreadFunc(void* p) {
auto rc = waitSingle(waiterForUEvent(&data->m_uevent), UINT64_MAX); auto rc = waitSingle(waiterForUEvent(&data->m_uevent), UINT64_MAX);
// log_write("woke up\n"); // log_write("woke up\n");
if (!g_running) { if (!g_running) {
return; break;
} }
if (R_FAILED(rc)) { if (R_FAILED(rc)) {
continue; continue;
} }
#if 1
const auto result = DownloadInternal(data->m_curl, data->m_api); const auto result = DownloadInternal(data->m_curl, data->m_api);
if (g_running) { if (g_running && data->m_api.GetOnComplete() && !data->m_api.GetToken().stop_requested()) {
const DownloadEventData event_data{data->m_api.m_on_complete, result}; const DownloadEventData event_data{data->m_api.GetOnComplete(), result, data->m_api.GetToken()};
evman::push(std::move(event_data), false); evman::push(std::move(event_data), false);
} else {
break;
} }
#endif
// mutexLock(&data->m_mutex);
// ON_SCOPE_EXIT(mutexUnlock(&data->m_mutex));
data->m_in_progress = false; data->m_in_progress = false;
// notify the queue that there's a space free // notify the queue that there's a space free
@@ -736,53 +736,25 @@ void Exit() {
} }
auto ToMemory(const Api& e) -> ApiResult { auto ToMemory(const Api& e) -> ApiResult {
if (!e.m_path.empty()) { if (!e.GetPath().empty()) {
return {}; return {};
} }
return DownloadInternal(e); return DownloadInternal(e);
} }
auto ToFile(const Api& e) -> ApiResult { auto ToFile(const Api& e) -> ApiResult {
if (e.m_path.empty()) { if (e.GetPath().empty()) {
return {}; return {};
} }
return DownloadInternal(e); return DownloadInternal(e);
} }
auto ToMemoryAsync(const Api& api) -> bool { auto ToMemoryAsync(const Api& api) -> bool {
#if USE_THREAD_QUEUE
return g_thread_queue.Add(api); return g_thread_queue.Add(api);
#else
// mutexLock(&g_thread_queue.m_mutex);
// ON_SCOPE_EXIT(mutexUnlock(&g_thread_queue.m_mutex));
for (auto& entry : g_threads) {
if (!entry.InProgress()) {
return entry.Setup(callback, url);
}
}
log_write("failed to start download, no avaliable threads\n");
return false;
#endif
} }
auto ToFileAsync(const Api& e) -> bool { auto ToFileAsync(const Api& e) -> bool {
#if USE_THREAD_QUEUE
return g_thread_queue.Add(e); return g_thread_queue.Add(e);
#else
// mutexLock(&g_thread_queue.m_mutex);
// ON_SCOPE_EXIT(mutexUnlock(&g_thread_queue.m_mutex));
for (auto& entry : g_threads) {
if (!entry.InProgress()) {
return entry.Setup(callback, url, out);
}
}
log_write("failed to start download, no avaliable threads\n");
return false;
#endif
} }
} // namespace sphaira::curl } // namespace sphaira::curl

View File

@@ -135,7 +135,7 @@ Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& _path, bool ig
} }
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s\n", path); log_write("failed to create folder: %s\n", path.s);
return rc; return rc;
} }
@@ -167,7 +167,7 @@ Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& _path,
} }
if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder recursively: %s\n", path); log_write("failed to create folder recursively: %s\n", path.s);
return rc; return rc;
} }

View File

@@ -77,6 +77,7 @@ bool init(long index) {
case 10: setLanguage = SetLanguage_PT; break; // "Portuguese" case 10: setLanguage = SetLanguage_PT; break; // "Portuguese"
case 11: setLanguage = SetLanguage_RU; break; // "Russian" case 11: setLanguage = SetLanguage_RU; break; // "Russian"
case 12: lang_name = "se"; break; // "Swedish" case 12: lang_name = "se"; break; // "Swedish"
case 13: lang_name = "vi"; break; // "Vietnamese"
} }
switch (setLanguage) { switch (setLanguage) {
@@ -91,6 +92,7 @@ bool init(long index) {
case SetLanguage_PT: lang_name = "pt"; break; case SetLanguage_PT: lang_name = "pt"; break;
case SetLanguage_RU: lang_name = "ru"; break; case SetLanguage_RU: lang_name = "ru"; break;
case SetLanguage_ZHTW: lang_name = "zh"; break; case SetLanguage_ZHTW: lang_name = "zh"; break;
default: break;
} }
const fs::FsPath sdmc_path = "/config/sphaira/i18n/" + lang_name + ".json"; const fs::FsPath sdmc_path = "/config/sphaira/i18n/" + lang_name + ".json";

View File

@@ -4,14 +4,19 @@
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Warray-bounds=" #pragma GCC diagnostic ignored "-Warray-bounds="
#include "nanovg/stb_image.h" #pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC #define STB_IMAGE_WRITE_STATIC
#define STBI_WRITE_NO_STDIO #define STBI_WRITE_NO_STDIO
#include "stb_image_write.h" #include <stb_image_write.h>
#define STB_IMAGE_RESIZE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC #define STB_IMAGE_RESIZE_STATIC
#include "stb_image_resize2.h" #include <stb_image_resize2.h>
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#pragma GCC diagnostic pop #pragma GCC diagnostic pop

View File

@@ -124,7 +124,7 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
if (e.type == FsDirEntryType_Dir) { if (e.type == FsDirEntryType_Dir) {
// assert(!root && "dir should only be scanned on non-root!"); // assert(!root && "dir should only be scanned on non-root!");
fs::FsPath fullpath; fs::FsPath fullpath;
std::snprintf(fullpath, sizeof(fullpath), "%s/%s/%s.nro", path, e.name, e.name); std::snprintf(fullpath, sizeof(fullpath), "%s/%s/%s.nro", path.s, e.name, e.name);
// fast path for detecting an nro in a folder // fast path for detecting an nro in a folder
NroEntry entry; NroEntry entry;
@@ -133,12 +133,12 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
nros.emplace_back(entry); nros.emplace_back(entry);
} else { } else {
// slow path... // slow path...
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path, e.name); std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
nro_scan_internal(fullpath, nros, hide_sphaira, nested, scan_all_dir, false); nro_scan_internal(fullpath, nros, hide_sphaira, nested, scan_all_dir, false);
} }
} else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) { } else if (e.type == FsDirEntryType_File && std::string_view{e.name}.ends_with(".nro")) {
fs::FsPath fullpath; fs::FsPath fullpath;
std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path, e.name); std::snprintf(fullpath, sizeof(fullpath), "%s/%s", path.s, e.name);
NroEntry entry; NroEntry entry;
if (R_SUCCEEDED(nro_parse_internal(fs, fullpath, entry))) { if (R_SUCCEEDED(nro_parse_internal(fs, fullpath, entry))) {
@@ -148,7 +148,7 @@ auto nro_scan_internal(const fs::FsPath& path, std::vector<NroEntry>& nros, bool
R_SUCCEED(); R_SUCCEED();
} }
} else { } else {
log_write("error when trying to parse %s\n", fullpath); log_write("error when trying to parse %s\n", fullpath.s);
} }
} }
} }

View File

@@ -301,7 +301,7 @@ void loop(void* args) {
continue; continue;
} }
log_write("got name: %s\n", name); log_write("got name: %s\n", name.s);
u32 filesize{}; u32 filesize{};
if (!recvall(connfd, &filesize, sizeof(filesize))) { if (!recvall(connfd, &filesize, sizeof(filesize))) {

View File

@@ -0,0 +1,115 @@
#include "ui/types.hpp"
#include "ui/object.hpp"
#include "ui/nvg_util.hpp"
#include "app.hpp"
namespace sphaira::ui::bubble {
namespace {
constexpr auto MAX_BUBBLES = 20;
struct Bubble {
int start_x;
int texture;
int x,y,w,h;
int y_inc;
float sway_inc;
float sway;
bool sway_right_flag;
};
Bubble bubbles[MAX_BUBBLES]{};
int g_textures[3];
bool g_is_init = false;
void setup_bubble(Bubble *bubble) {
// setup normal vars.
bubble->texture = (randomGet64() % std::size(g_textures));
bubble->start_x = randomGet64() % (int)SCREEN_WIDTH;
bubble->x = bubble->start_x;
bubble->y = (int)SCREEN_HEIGHT - ( randomGet64() % 60 );
const int size = (randomGet64() % 50) + 40;
bubble->w = size;
bubble->h = size;
bubble->y_inc = (randomGet64() % 5) + 1;
bubble->sway_inc = ((randomGet64() % 6) + 3) / 10;
bubble->sway = 0;
}
void setup_bubbles(void) {
for (auto& bubble : bubbles) {
setup_bubble(&bubble);
}
}
void update_bubbles(void) {
for (auto& bubble : bubbles) {
if (bubble.y + bubble.h < 0) {
setup_bubble(&bubble);
} else {
bubble.y -= bubble.y_inc;
if (bubble.sway_right_flag) {
bubble.x = bubble.start_x + (bubble.sway -= bubble.sway_inc);
if (bubble.sway <= 0) {
bubble.sway_right_flag = false;
}
} else {
bubble.x = bubble.start_x + (bubble.sway += bubble.sway_inc);
if (bubble.sway > 30) {
bubble.sway_right_flag = true;
}
}
}
}
}
} // namespace
void Init() {
if (g_is_init) {
return;
}
if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit());
auto vg = App::GetVg();
g_textures[0] = nvgCreateImage(vg, "romfs:/theme/bubble1.png", 0);
g_textures[1] = nvgCreateImage(vg, "romfs:/theme/bubble2.png", 0);
g_textures[2] = nvgCreateImage(vg, "romfs:/theme/bubble3.png", 0);
setup_bubbles();
g_is_init = true;
}
}
void Draw(NVGcontext* vg, Theme* theme) {
if (!g_is_init) {
return;
}
update_bubbles();
for (auto& bubble : bubbles) {
gfx::drawImage(vg, bubble.x, bubble.y, bubble.w, bubble.h, g_textures[bubble.texture]);
}
}
void Exit() {
if (!g_is_init) {
return;
}
auto vg = App::GetVg();
for (auto& texture : g_textures) {
if (texture) {
nvgDeleteImage(vg, texture);
texture = 0;
}
}
g_is_init = false;
}
} // namespace sphaira::ui::bubble

View File

@@ -1136,18 +1136,18 @@ auto ErrorBox::Update(Controller* controller, TouchInfo* touch) -> void {
auto ErrorBox::Draw(NVGcontext* vg, Theme* theme) -> void { auto ErrorBox::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::dimBackground(vg); gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
const Vec4 box = { 455, 470, 365, 65 }; const Vec4 box = { 455, 470, 365, 65 };
const auto center_x = m_pos.x + m_pos.w/2; const auto center_x = m_pos.x + m_pos.w/2;
gfx::drawTextArgs(vg, center_x, 180, 63, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::RED, "\uE140"); gfx::drawTextArgs(vg, center_x, 180, 63, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_ERROR), "\uE140");
gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "Error code: 0x%X Module: %s Description: %s", m_code, m_module_str.c_str(), m_description_str.c_str()); gfx::drawTextArgs(vg, center_x, 270, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "Error code: 0x%X Module: %s Description: %s", m_code, m_module_str.c_str(), m_description_str.c_str());
gfx::drawTextArgs(vg, center_x, 325, 23, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "%s", m_message.c_str()); gfx::drawTextArgs(vg, center_x, 325, 23, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%s", m_message.c_str());
gfx::drawTextArgs(vg, center_x, 380, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::SILVER, "If this message appears repeatedly, please open an issue."_i18n.c_str()); gfx::drawTextArgs(vg, center_x, 380, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT_INFO), "If this message appears repeatedly, please open an issue."_i18n.c_str());
gfx::drawTextArgs(vg, center_x, 415, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::SILVER, "https://github.com/ITotalJustice/sphaira/issues"); gfx::drawTextArgs(vg, center_x, 415, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT_INFO), "https://github.com/ITotalJustice/sphaira/issues");
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, box, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, box);
gfx::drawTextArgs(vg, center_x, box.y + box.h/2, 23, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::WHITE, "OK"_i18n.c_str()); gfx::drawTextArgs(vg, center_x, box.y + box.h/2, 23, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_SELECTED), "OK"_i18n.c_str());
} }
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -18,7 +18,7 @@
#include <string> #include <string>
#include <cstring> #include <cstring>
#include <yyjson.h> #include <yyjson.h>
#include <nanovg/stb_image.h> #include <stb_image.h>
#include <minizip/unzip.h> #include <minizip/unzip.h>
#include <mbedtls/md5.h> #include <mbedtls/md5.h>
#include <ranges> #include <ranges>
@@ -65,60 +65,27 @@ auto BuildIconUrl(const Entry& e) -> std::string {
return out; return out;
} }
#if 0
auto BuildInfoUrl(const Entry& e) -> std::string {
char out[0x100];
std::snprintf(out, sizeof(out), "%s/packages/%s/info.json", URL_BASE, e.name.c_str());
return out;
}
#endif
auto BuildBannerUrl(const Entry& e) -> std::string { auto BuildBannerUrl(const Entry& e) -> std::string {
char out[0x100]; char out[0x100];
std::snprintf(out, sizeof(out), "%s/packages/%s/screen.png", URL_BASE, e.name.c_str()); std::snprintf(out, sizeof(out), "%s/packages/%s/screen.png", URL_BASE, e.name.c_str());
return out; return out;
} }
#if 0
auto BuildScreensUrl(const Entry& e, u8 num) -> std::string {
char out[0x100];
std::snprintf(out, sizeof(out), "%s/packages/%s/screen%u.png", URL_BASE, e.name.c_str(), num+1);
return out;
}
#endif
auto BuildMainifestUrl(const Entry& e) -> std::string {
char out[0x100];
std::snprintf(out, sizeof(out), "%s/packages/%s/manifest.install", URL_BASE, e.name.c_str());
return out;
}
auto BuildZipUrl(const Entry& e) -> std::string { auto BuildZipUrl(const Entry& e) -> std::string {
char out[0x100]; char out[0x100];
std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str()); std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str());
return out; return out;
} }
auto BuildFeedbackUrl(std::span<u32> ids) -> std::string {
std::string out{"https://wiiubru.com/feedback/messages?ids="};
for (u32 i = 0; i < ids.size(); i++) {
if (i != 0) {
out.push_back(',');
}
out += std::to_string(ids[i]);
}
return out;
}
auto BuildIconCachePath(const Entry& e) -> fs::FsPath { auto BuildIconCachePath(const Entry& e) -> fs::FsPath {
fs::FsPath out; fs::FsPath out;
std::snprintf(out, sizeof(out), "%s/icons/%s.png", CACHE_PATH, e.name.c_str()); std::snprintf(out, sizeof(out), "%s/icons/%s.png", CACHE_PATH.s, e.name.c_str());
return out; return out;
} }
auto BuildBannerCachePath(const Entry& e) -> fs::FsPath { auto BuildBannerCachePath(const Entry& e) -> fs::FsPath {
fs::FsPath out; fs::FsPath out;
std::snprintf(out, sizeof(out), "%s/banners/%s.png", CACHE_PATH, e.name.c_str()); std::snprintf(out, sizeof(out), "%s/banners/%s.png", CACHE_PATH.s, e.name.c_str());
return out; return out;
} }
@@ -271,11 +238,19 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
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); gfx::drawRect(vg, x, y, w, h, nvgRGB(i.first_pixel[0], i.first_pixel[1], i.first_pixel[2]), rounded);
} }
if (iw > w || ih > h) {
crop = true;
nvgSave(vg);
nvgIntersectScissor(vg, x, y, w, h);
}
if (rounded_image) { if (rounded_image) {
gfx::drawImageRounded(vg, ix, iy, iw, ih, i.image); gfx::drawImageRounded(vg, ix, iy, iw, ih, i.image);
} else { } else {
gfx::drawImage(vg, ix, iy, iw, ih, i.image); gfx::drawImage(vg, ix, iy, iw, ih, i.image);
} }
if (crop) {
nvgRestore(vg);
}
} }
void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, Vec4 vec, bool rounded = true, float scale = 1.0) { void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, Vec4 vec, bool rounded = true, float scale = 1.0) {
@@ -294,7 +269,6 @@ auto AppDlToStr(u32 value) -> std::string {
void ReadFromInfoJson(Entry& e) { void ReadFromInfoJson(Entry& e) {
const auto info_path = BuildInfoCachePath(e); const auto info_path = BuildInfoCachePath(e);
const auto manifest_path = BuildManifestCachePath(e);
yyjson_read_err err; yyjson_read_err err;
auto doc = yyjson_read_file(info_path, YYJSON_READ_NOFLAG, nullptr, &err); auto doc = yyjson_read_file(info_path, YYJSON_READ_NOFLAG, nullptr, &err);
@@ -332,9 +306,9 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
const auto safe_buf = fs::AppendPath("/", e.path); const auto safe_buf = fs::AppendPath("/", e.path);
// this will handle read only files, ie, hbmenu.nro // this will handle read only files, ie, hbmenu.nro
if (R_FAILED(fs.DeleteFile(safe_buf))) { if (R_FAILED(fs.DeleteFile(safe_buf))) {
log_write("failed to delete file: %s\n", safe_buf); log_write("failed to delete file: %s\n", safe_buf.s);
} else { } else {
log_write("deleted file: %s\n", safe_buf); log_write("deleted file: %s\n", safe_buf.s);
// todo: delete empty directories! // todo: delete empty directories!
// fs::delete_directory(safe_buf); // fs::delete_directory(safe_buf);
} }
@@ -345,9 +319,9 @@ auto UninstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
const auto dir = BuildPackageCachePath(entry); const auto dir = BuildPackageCachePath(entry);
pbox->NewTransfer("Removing "_i18n + dir); pbox->NewTransfer("Removing "_i18n + dir);
if (R_FAILED(fs.DeleteDirectoryRecursively(dir))) { if (R_FAILED(fs.DeleteDirectoryRecursively(dir))) {
log_write("failed to delete folder: %s\n", dir); log_write("failed to delete folder: %s\n", dir.s);
} else { } else {
log_write("deleted: %s\n", dir); log_write("deleted: %s\n", dir.s);
} }
return true; return true;
} }
@@ -450,7 +424,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); auto zfile = unzOpen64(zip_out);
if (!zfile) { if (!zfile) {
log_write("failed to open zip: %s\n", zip_out); log_write("failed to open zip: %s\n", zip_out.s);
return false; return false;
} }
ON_SCOPE_EXIT(unzClose(zfile)); ON_SCOPE_EXIT(unzClose(zfile));
@@ -493,7 +467,7 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
pbox->NewTransfer(inzip); pbox->NewTransfer(inzip);
if (UNZ_END_OF_LIST_OF_FILE == unzLocateFile(zfile, inzip, 0)) { if (UNZ_END_OF_LIST_OF_FILE == unzLocateFile(zfile, inzip, 0)) {
log_write("failed to find %s\n", inzip); log_write("failed to find %s\n", inzip.s);
return false; return false;
} }
@@ -518,19 +492,19 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
Result rc; Result rc;
if (R_FAILED(rc = fs.CreateFile(output, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateFile(output, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", output, rc); log_write("failed to create file: %s 0x%04X\n", output.s, rc);
return false; return false;
} }
FsFile f; FsFile f;
if (R_FAILED(rc = fs.OpenFile(output, FsOpenMode_Write, &f))) { if (R_FAILED(rc = fs.OpenFile(output, FsOpenMode_Write, &f))) {
log_write("failed to open file: %s 0x%04X\n", output, rc); log_write("failed to open file: %s 0x%04X\n", output.s, rc);
return false; return false;
} }
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(fsFileClose(&f));
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) { if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
log_write("failed to set file size: %s 0x%04X\n", output, rc); log_write("failed to set file size: %s 0x%04X\n", output.s, rc);
return false; return false;
} }
@@ -543,12 +517,12 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size()); const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) { if (bytes_read <= 0) {
log_write("failed to read zip file: %s\n", inzip); log_write("failed to read zip file: %s\n", inzip.s);
return false; return false;
} }
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) { if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
log_write("failed to write file: %s 0x%04X\n", output, rc); log_write("failed to write file: %s 0x%04X\n", output.s, rc);
return false; return false;
} }
@@ -607,9 +581,9 @@ auto InstallApp(ProgressBox* pbox, const Entry& entry) -> bool {
const auto safe_buf = fs::AppendPath("/", old_entry.path); const auto safe_buf = fs::AppendPath("/", old_entry.path);
// std::strcat(safe_buf, old_entry.path); // std::strcat(safe_buf, old_entry.path);
if (R_FAILED(fs.DeleteFile(safe_buf))) { if (R_FAILED(fs.DeleteFile(safe_buf))) {
log_write("failed to delete: %s\n", safe_buf); log_write("failed to delete: %s\n", safe_buf.s);
} else { } else {
log_write("deleted file: %s\n", safe_buf); log_write("deleted file: %s\n", safe_buf.s);
} }
} }
} }
@@ -655,6 +629,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
curl::Url{URL_POST_FEEDBACK}, curl::Url{URL_POST_FEEDBACK},
curl::Path{file}, curl::Path{file},
curl::Fields{post}, curl::Fields{post},
curl::StopToken{this->GetToken()},
curl::OnComplete{[](auto& result){ curl::OnComplete{[](auto& result){
if (result.success) { if (result.success) {
log_write("got feedback!\n"); log_write("got feedback!\n");
@@ -689,6 +664,7 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
curl::Url{url}, curl::Url{url},
curl::Path{path}, curl::Path{path},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this, path](auto& result){ curl::OnComplete{[this, path](auto& result){
if (result.success) { if (result.success) {
if (result.code == 304) { if (result.code == 304) {
@@ -725,11 +701,13 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
constexpr Vec4 line_vec(30, 86, 1220, 646); constexpr Vec4 line_vec(30, 86, 1220, 646);
constexpr Vec4 banner_vec(70, line_vec.y + 20, 848.f, 208.f); constexpr Vec4 banner_vec(70, line_vec.y + 20, 848.f, 208.f);
constexpr Vec4 icon_vec(968, line_vec.y + 30, 256, 150); constexpr Vec4 icon_vec(968, line_vec.y + 30, 256, 150);
constexpr Vec4 grid_vec(icon_vec.x - 50, line_vec.y + 1, line_vec.w, line_vec.h - line_vec.y - 1);
// nvgSave(vg); // nvgSave(vg);
// nvgScissor(vg, line_vec.x, line_vec.y, line_vec.w - line_vec.x, line_vec.h - line_vec.y); // clip // nvgScissor(vg, line_vec.x, line_vec.y, line_vec.w - line_vec.x, line_vec.h - line_vec.y); // clip
// ON_SCOPE_EXIT(nvgRestore(vg)); // ON_SCOPE_EXIT(nvgRestore(vg));
gfx::drawRect(vg, grid_vec, theme->GetColour(ThemeEntryID_GRID));
DrawIcon(vg, m_banner, m_entry.image.image ? m_entry.image : m_default_icon, banner_vec, false); DrawIcon(vg, m_banner, m_entry.image.image ? m_entry.image : m_default_icon, banner_vec, false);
DrawIcon(vg, m_entry.image, m_default_icon, icon_vec); DrawIcon(vg, m_entry.image, m_default_icon, icon_vec);
@@ -738,24 +716,20 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
const float text_inc_y = 32; const float text_inc_y = 32;
const float font_size = 20; const float font_size = 20;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "version: %s"_i18n.c_str(), m_entry.version.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "version: %s"_i18n.c_str(), m_entry.version.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "updated: %s"_i18n.c_str(), m_entry.updated.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "updated: %s"_i18n.c_str(), m_entry.updated.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "category: %s"_i18n.c_str(), m_entry.category.c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "category: %s"_i18n.c_str(), m_entry.category.c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "extracted: %.2f MiB"_i18n.c_str(), (double)m_entry.extracted / 1024.0); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "extracted: %.2f MiB"_i18n.c_str(), (double)m_entry.extracted / 1024.0);
text_start_y += text_inc_y; text_start_y += text_inc_y;
gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "app_dls: %s"_i18n.c_str(), AppDlToStr(m_entry.app_dls).c_str()); gfx::drawTextArgs(vg, text_start_x, text_start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "app_dls: %s"_i18n.c_str(), AppDlToStr(m_entry.app_dls).c_str());
text_start_y += text_inc_y; text_start_y += text_inc_y;
// for (const auto& option : m_options) {
const auto& text_col = theme->elements[ThemeEntryID_TEXT].colour;
// todo: rewrite this mess and use list // todo: rewrite this mess and use list
constexpr float mm = 0;//20; constexpr float mm = 0;//20;
constexpr Vec4 block{968.f + mm, 110.f, 256.f - mm*2, 60.f}; constexpr Vec4 block{968.f + mm, 110.f, 256.f - mm*2, 60.f};
constexpr float text_xoffset{15.f};
const float x = block.x; const float x = block.x;
float y = 1.f + text_start_y + (text_inc_y * 3) ; float y = 1.f + text_start_y + (text_inc_y * 3) ;
const float h = block.h; const float h = block.h;
@@ -766,15 +740,10 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (m_index == i) { if (m_index == i) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, Vec4{x, y, w, h});
} else {
// if (i == m_index_offset) {
// gfx::drawRect(vg, x, y, w, 1.f, text_col);
// }
// gfx::drawRect(vg, x, y + h, w, 1.f, text_col);
} }
gfx::drawTextArgs(vg, x + w / 2, y + h / 2, 22, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, theme->elements[ThemeEntryID_TEXT].colour, option.display_text.c_str()); gfx::drawTextArgs(vg, x + w / 2, y + h / 2, 22, NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER, theme->GetColour(text_id), option.display_text.c_str());
y -= block.h + 18; y -= block.h + 18;
} }
@@ -955,7 +924,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
sort_items.push_back("Alphabetical"_i18n); sort_items.push_back("Alphabetical"_i18n);
SidebarEntryArray::Items order_items; SidebarEntryArray::Items order_items;
order_items.push_back("Decending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){
@@ -985,6 +954,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
curl::Url{URL_JSON}, curl::Url{URL_JSON},
curl::Path{REPO_PATH}, curl::Path{REPO_PATH},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this](auto& result){ curl::OnComplete{[this](auto& result){
if (result.success) { if (result.success) {
m_repo_download_state = ImageDownloadState::Done; m_repo_download_state = ImageDownloadState::Done;
@@ -1027,12 +997,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
if (m_entries.empty()) { if (m_entries.empty()) {
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Loading..."_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Loading..."_i18n.c_str());
return; return;
} }
if (m_entries_current.empty()) { if (m_entries_current.empty()) {
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Empty!"_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Empty!"_i18n.c_str());
return; return;
} }
@@ -1066,6 +1036,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
curl::Url{url}, curl::Url{url},
curl::Path{path}, curl::Path{path},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this, &image](auto& result) { curl::OnComplete{[this, &image](auto& result) {
if (result.success) { if (result.success) {
image.state = ImageDownloadState::Done; image.state = ImageDownloadState::Done;
@@ -1101,7 +1072,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (pos == m_index) { if (pos == m_index) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
} else { } else {
DrawElement(x, y, w, h, ThemeEntryID_GRID); DrawElement(x, y, w, h, ThemeEntryID_GRID);
} }
@@ -1116,9 +1087,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
nvgIntersectScissor(vg, v.x, v.y, w - 30.f, h); // clip nvgIntersectScissor(vg, v.x, v.y, w - 30.f, h); // clip
{ {
const float font_size = 18; const float font_size = 18;
gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, e.title.c_str()); gfx::drawTextArgs(vg, x + 148, y + 45, 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->elements[text_id].colour, e.author.c_str()); gfx::drawTextArgs(vg, x + 148, y + 80, 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->elements[text_id].colour, e.version.c_str()); gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
} }
nvgRestore(vg); nvgRestore(vg);
@@ -1289,7 +1260,7 @@ void Menu::Sort() {
case SortType_Updated: { case SortType_Updated: {
if (lhs.updated_num == rhs.updated_num) { if (lhs.updated_num == rhs.updated_num) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Decending) { } else if (m_order == OrderType_Descending) {
return lhs.updated_num > rhs.updated_num; return lhs.updated_num > rhs.updated_num;
} else { } else {
return lhs.updated_num < rhs.updated_num; return lhs.updated_num < rhs.updated_num;
@@ -1298,7 +1269,7 @@ void Menu::Sort() {
case SortType_Downloads: { case SortType_Downloads: {
if (lhs.app_dls == rhs.app_dls) { if (lhs.app_dls == rhs.app_dls) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Decending) { } else if (m_order == OrderType_Descending) {
return lhs.app_dls > rhs.app_dls; return lhs.app_dls > rhs.app_dls;
} else { } else {
return lhs.app_dls < rhs.app_dls; return lhs.app_dls < rhs.app_dls;
@@ -1307,14 +1278,14 @@ void Menu::Sort() {
case SortType_Size: { case SortType_Size: {
if (lhs.extracted == rhs.extracted) { if (lhs.extracted == rhs.extracted) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else if (m_order == OrderType_Decending) { } else if (m_order == OrderType_Descending) {
return lhs.extracted > rhs.extracted; return lhs.extracted > rhs.extracted;
} else { } else {
return lhs.extracted < rhs.extracted; return lhs.extracted < rhs.extracted;
} }
} break; } break;
case SortType_Alphabetical: { case SortType_Alphabetical: {
if (m_order == OrderType_Decending) { if (m_order == OrderType_Descending) {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) < 0;
} else { } else {
return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0; return strcasecmp(lhs.name.c_str(), rhs.name.c_str()) > 0;

View File

@@ -128,7 +128,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> int {
if (( if ((
p.folder.length() == db_name.length() && !strncasecmp(p.folder.data(), db_name.data(), p.folder.length())) || p.folder.length() == db_name.length() && !strncasecmp(p.folder.data(), db_name.data(), p.folder.length())) ||
(p.database.length() == db_name.length() && !strncasecmp(p.database.data(), db_name.data(), p.database.length()))) { (p.database.length() == db_name.length() && !strncasecmp(p.database.data(), db_name.data(), p.database.length()))) {
log_write("found it :) %.*s\n", p.database.length(), p.database.data()); log_write("found it :) %.*s\n", (int)p.database.length(), p.database.data());
return i; return i;
} }
} }
@@ -144,7 +144,7 @@ auto GetRomDatabaseFromPath(std::string_view path) -> int {
if (( if ((
p.folder.length() == db_name2.length() && !strcasecmp(p.folder.data(), db_name2.data())) || p.folder.length() == db_name2.length() && !strcasecmp(p.folder.data(), db_name2.data())) ||
(p.database.length() == db_name2.length() && !strcasecmp(p.database.data(), db_name2.data()))) { (p.database.length() == db_name2.length() && !strcasecmp(p.database.data(), db_name2.data()))) {
log_write("found it :) %.*s\n", p.database.length(), p.database.data()); log_write("found it :) %.*s\n", (int)p.database.length(), p.database.data());
return i; return i;
} }
} }
@@ -352,7 +352,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
sort_items.push_back("Alphabetical"_i18n); sort_items.push_back("Alphabetical"_i18n);
SidebarEntryArray::Items order_items; SidebarEntryArray::Items order_items;
order_items.push_back("Decending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){ options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
@@ -452,9 +452,8 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
if (R_SUCCEEDED(rc)) { if (R_SUCCEEDED(rc)) {
Scan(m_path); Scan(m_path);
} else { } else {
char msg[FS_MAX_PATH]; const auto msg = std::string("Failed to rename file: ") + entry.name;
std::snprintf(msg, sizeof(msg), "Failed to rename file: %s", entry.name); App::Push(std::make_shared<ErrorBox>(rc, msg.c_str()));
App::Push(std::make_shared<ErrorBox>(rc, msg));
} }
} }
})); }));
@@ -478,10 +477,10 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
m_fs->CreateDirectoryRecursivelyWithPath(full_path); m_fs->CreateDirectoryRecursivelyWithPath(full_path);
if (R_SUCCEEDED(m_fs->CreateFile(full_path, 0, 0))) { if (R_SUCCEEDED(m_fs->CreateFile(full_path, 0, 0))) {
log_write("created file: %s\n", full_path); log_write("created file: %s\n", full_path.s);
Scan(m_path); Scan(m_path);
} else { } else {
log_write("failed to create file: %s\n", full_path); log_write("failed to create file: %s\n", full_path.s);
} }
} }
})); }));
@@ -499,10 +498,10 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
} }
if (R_SUCCEEDED(m_fs->CreateDirectoryRecursively(full_path))) { if (R_SUCCEEDED(m_fs->CreateDirectoryRecursively(full_path))) {
log_write("created dir: %s\n", full_path); log_write("created dir: %s\n", full_path.s);
Scan(m_path); Scan(m_path);
} else { } else {
log_write("failed to create dir: %s\n", full_path); log_write("failed to create dir: %s\n", full_path.s);
} }
} }
})); }));
@@ -578,10 +577,10 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
void Menu::Draw(NVGcontext* vg, Theme* theme) { void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
const auto& text_col = theme->elements[ThemeEntryID_TEXT].colour; const auto& text_col = theme->GetColour(ThemeEntryID_TEXT);
if (m_entries_current.empty()) { if (m_entries_current.empty()) {
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, text_col, "Empty..."_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Empty..."_i18n.c_str());
return; return;
} }
@@ -605,19 +604,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
if (e.IsSelected()) { if (e.IsSelected()) {
// gfx::drawText(vg, x - 60.f, y + (h / 2.f) - (48.f / 2), 48.f, "\uE14B", nullptr, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, gfx::Colour::CYAN); gfx::drawText(vg, Vec2{x - 10.f, y + (h / 2.f) - (24.f / 2)}, 24.f, "\uE14B", nullptr, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT_SELECTED));
gfx::drawText(vg, x, y + (h / 2.f) - (24.f / 2), 24.f, "\uE14B", nullptr, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, gfx::Colour::CYAN);
} }
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (m_index == i) { if (m_index == i) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
} else { } else {
if (i == m_index) { if (i != m_entries_current.size() - 1) {
gfx::drawRect(vg, x, y, w, 1.f, text_col); gfx::drawRect(vg, Vec4{x, y + h, w, 1.f}, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
} }
gfx::drawRect(vg, x, y + h, w, 1.f, text_col);
} }
if (e.IsDir()) { if (e.IsDir()) {
@@ -642,12 +639,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
nvgSave(vg); nvgSave(vg);
nvgIntersectScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), h); nvgIntersectScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), h);
gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour); gfx::drawText(vg, x + text_xoffset+65, y + (h / 2.f), 20.f, e.name, NULL, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id));
nvgRestore(vg); nvgRestore(vg);
if (e.IsDir()) { if (e.IsDir()) {
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->elements[text_id].colour, "%zd files"_i18n.c_str(), e.file_count); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%zd files"_i18n.c_str(), e.file_count);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->elements[text_id].colour, "%zd dirs"_i18n.c_str(), e.dir_count); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%zd dirs"_i18n.c_str(), e.dir_count);
} else { } else {
if (!e.time_stamp.is_valid) { if (!e.time_stamp.is_valid) {
const auto path = GetNewPath(e); const auto path = GetNewPath(e);
@@ -656,11 +653,11 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto t = (time_t)(e.time_stamp.modified); const auto t = (time_t)(e.time_stamp.modified);
struct tm tm{}; struct tm tm{};
localtime_r(&t, &tm); localtime_r(&t, &tm);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->elements[text_id].colour, "%02u/%02u/%u", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%02u/%02u/%u", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
if ((double)e.file_size / 1024.0 / 1024.0 <= 0.009) { if ((double)e.file_size / 1024.0 / 1024.0 <= 0.009) {
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->elements[text_id].colour, "%.2f KiB", (double)e.file_size / 1024.0); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f KiB", (double)e.file_size / 1024.0);
} else { } else {
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->elements[text_id].colour, "%.2f MiB", (double)e.file_size / 1024.0 / 1024.0); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f MiB", (double)e.file_size / 1024.0 / 1024.0);
} }
} }
}); });
@@ -779,7 +776,7 @@ void Menu::InstallForwarder() {
} }
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result { auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
log_write("new scan path: %s\n", new_path); log_write("new scan path: %s\n", new_path.s);
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) { if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
const LastFile f(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size()); const LastFile f(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size());
m_previous_highlighted_file.emplace_back(f); m_previous_highlighted_file.emplace_back(f);
@@ -870,7 +867,7 @@ auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
if (assoc_db == PATHS[db_idx].folder || assoc_db == PATHS[db_idx].database) { if (assoc_db == PATHS[db_idx].folder || assoc_db == PATHS[db_idx].database) {
for (const auto& assoc_ext : assoc.ext) { for (const auto& assoc_ext : assoc.ext) {
if (assoc_ext == extension) { if (assoc_ext == extension) {
log_write("found ext: %s assoc_ext: %s assoc.ext: %s\n", assoc.path, assoc_ext.c_str(), extension.c_str()); log_write("found ext: %s assoc_ext: %s assoc.ext: %s\n", assoc.path.s, assoc_ext.c_str(), extension.c_str());
out_entries.emplace_back(assoc); out_entries.emplace_back(assoc);
} }
} }
@@ -888,7 +885,7 @@ auto Menu::FindFileAssocFor() -> std::vector<FileAssocEntry> {
if (assoc.database.empty()) { if (assoc.database.empty()) {
for (const auto& assoc_ext : assoc.ext) { for (const auto& assoc_ext : assoc.ext) {
if (assoc_ext == extension) { if (assoc_ext == extension) {
log_write("found ext: %s\n", assoc.path); log_write("found ext: %s\n", assoc.path.s);
out_entries.emplace_back(assoc); out_entries.emplace_back(assoc);
} }
} }
@@ -1036,14 +1033,14 @@ void Menu::Sort() {
case SortType_Size: { case SortType_Size: {
if (lhs.file_size == rhs.file_size) { if (lhs.file_size == rhs.file_size) {
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0; return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0;
} else if (order == OrderType_Decending) { } else if (order == OrderType_Descending) {
return lhs.file_size > rhs.file_size; return lhs.file_size > rhs.file_size;
} else { } else {
return lhs.file_size < rhs.file_size; return lhs.file_size < rhs.file_size;
} }
} break; } break;
case SortType_Alphabetical: { case SortType_Alphabetical: {
if (order == OrderType_Decending) { if (order == OrderType_Descending) {
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0; return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) < 0;
} else { } else {
return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) > 0; return strncasecmp(lhs.name, rhs.name, sizeof(lhs.name)) > 0;
@@ -1101,7 +1098,6 @@ void Menu::SetIndexFromLastFile(const LastFile& last_file) {
} }
} }
SetIndex(index); SetIndex(index);
log_write("\nnew index: %zu %zu mod: %zu\n", m_index, index % 8);
} }
} }
@@ -1150,7 +1146,7 @@ void Menu::OnDeleteCallback() {
if (p.IsDir()) { if (p.IsDir()) {
pbox->NewTransfer("Scanning "_i18n + full_path); pbox->NewTransfer("Scanning "_i18n + full_path);
if (R_FAILED(get_collections(full_path, p.name, collections))) { if (R_FAILED(get_collections(full_path, p.name, collections))) {
log_write("failed to get dir collection: %s\n", full_path); log_write("failed to get dir collection: %s\n", full_path.s);
return false; return false;
} }
} }
@@ -1168,10 +1164,10 @@ void Menu::OnDeleteCallback() {
const auto full_path = GetNewPath(c.path, p.name); const auto full_path = GetNewPath(c.path, p.name);
pbox->NewTransfer("Deleting "_i18n + full_path); pbox->NewTransfer("Deleting "_i18n + full_path);
if (p.type == FsDirEntryType_Dir) { if (p.type == FsDirEntryType_Dir) {
log_write("deleting dir: %s\n", full_path); log_write("deleting dir: %s\n", full_path.s);
m_fs->DeleteDirectory(full_path); m_fs->DeleteDirectory(full_path);
} else { } else {
log_write("deleting file: %s\n", full_path); log_write("deleting file: %s\n", full_path.s);
m_fs->DeleteFile(full_path); m_fs->DeleteFile(full_path);
} }
} }
@@ -1196,10 +1192,10 @@ void Menu::OnDeleteCallback() {
pbox->NewTransfer("Deleting "_i18n + full_path); pbox->NewTransfer("Deleting "_i18n + full_path);
if (p.IsDir()) { if (p.IsDir()) {
log_write("deleting dir: %s\n", full_path); log_write("deleting dir: %s\n", full_path.s);
m_fs->DeleteDirectory(full_path); m_fs->DeleteDirectory(full_path);
} else { } else {
log_write("deleting file: %s\n", full_path); log_write("deleting file: %s\n", full_path.s);
m_fs->DeleteFile(full_path); m_fs->DeleteFile(full_path);
} }
} }
@@ -1214,8 +1210,6 @@ void Menu::OnDeleteCallback() {
} }
void Menu::OnPasteCallback() { void Menu::OnPasteCallback() {
bool use_progress_box{true};
// check if we only have 1 file / folder and is cut (rename) // check if we only have 1 file / folder and is cut (rename)
if (m_selected_files.size() == 1 && m_selected_type == SelectedType::Cut) { if (m_selected_files.size() == 1 && m_selected_type == SelectedType::Cut) {
const auto& entry = m_selected_files[0]; const auto& entry = m_selected_files[0];
@@ -1265,7 +1259,7 @@ void Menu::OnPasteCallback() {
if (p.IsDir()) { if (p.IsDir()) {
pbox->NewTransfer("Scanning "_i18n + full_path); pbox->NewTransfer("Scanning "_i18n + full_path);
if (R_FAILED(get_collections(full_path, p.name, collections))) { if (R_FAILED(get_collections(full_path, p.name, collections))) {
log_write("failed to get dir collection: %s\n", full_path); log_write("failed to get dir collection: %s\n", full_path.s);
return false; return false;
} }
} }
@@ -1302,7 +1296,7 @@ void Menu::OnPasteCallback() {
const auto src_path = GetNewPath(c.path, p.name); const auto src_path = GetNewPath(c.path, p.name);
const auto dst_path = GetNewPath(base_dst_path, p.name); const auto dst_path = GetNewPath(base_dst_path, p.name);
log_write("creating: %s to %s\n", src_path, dst_path); log_write("creating: %s to %s\n", src_path.s, dst_path.s);
pbox->NewTransfer("Creating "_i18n + dst_path); pbox->NewTransfer("Creating "_i18n + dst_path);
m_fs->CreateDirectory(dst_path); m_fs->CreateDirectory(dst_path);
} }
@@ -1317,7 +1311,7 @@ void Menu::OnPasteCallback() {
const auto dst_path = GetNewPath(base_dst_path, p.name); const auto dst_path = GetNewPath(base_dst_path, p.name);
pbox->NewTransfer("Copying "_i18n + src_path); pbox->NewTransfer("Copying "_i18n + src_path);
log_write("copying: %s to %s\n", src_path, dst_path); log_write("copying: %s to %s\n", src_path.s, dst_path.s);
R_TRY_RESULT(pbox->CopyFile(src_path, dst_path), false); R_TRY_RESULT(pbox->CopyFile(src_path, dst_path), false);
} }
} }
@@ -1417,7 +1411,7 @@ auto Menu::get_collections(const fs::FsPath& path, const fs::FsPath& parent_name
// get a list of all the files / dirs // get a list of all the files / dirs
FsDirCollection collection; FsDirCollection collection;
R_TRY(get_collection(path, parent_name, collection, true, true, false)); R_TRY(get_collection(path, parent_name, collection, true, true, false));
log_write("got collection: %s parent_name: %s files: %zu dirs: %zu\n", path, parent_name, collection.files.size(), collection.dirs.size()); log_write("got collection: %s parent_name: %s files: %zu dirs: %zu\n", path.s, parent_name.s, collection.files.size(), collection.dirs.size());
out.emplace_back(collection); out.emplace_back(collection);
// for (size_t i = 0; i < collection.dirs.size(); i++) { // for (size_t i = 0; i < collection.dirs.size(); i++) {
@@ -1425,7 +1419,7 @@ auto Menu::get_collections(const fs::FsPath& path, const fs::FsPath& parent_name
// use heap as to not explode the stack // use heap as to not explode the stack
const auto new_path = std::make_unique<fs::FsPath>(Menu::GetNewPath(path, p.name)); const auto new_path = std::make_unique<fs::FsPath>(Menu::GetNewPath(path, p.name));
const auto new_parent_name = std::make_unique<fs::FsPath>(Menu::GetNewPath(parent_name, p.name)); const auto new_parent_name = std::make_unique<fs::FsPath>(Menu::GetNewPath(parent_name, p.name));
log_write("trying to get nested collection: %s parent_name: %s\n", *new_path, *new_parent_name); log_write("trying to get nested collection: %s parent_name: %s\n", new_path->s, new_parent_name->s);
R_TRY(get_collections(*new_path, *new_parent_name, out)); R_TRY(get_collections(*new_path, *new_parent_name, out));
} }

View File

@@ -119,7 +119,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
log_write("found zip\n"); log_write("found zip\n");
auto zfile = unzOpen64(temp_file); auto zfile = unzOpen64(temp_file);
if (!zfile) { if (!zfile) {
log_write("failed to open zip: %s\n", temp_file); log_write("failed to open zip: %s\n", temp_file.s);
return false; return false;
} }
ON_SCOPE_EXIT(unzClose(zfile)); ON_SCOPE_EXIT(unzClose(zfile));
@@ -155,29 +155,29 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
Result rc; Result rc;
if (file_path[strlen(file_path) -1] == '/') { if (file_path[strlen(file_path) -1] == '/') {
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", file_path, rc); log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
} else { } else {
if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(file_path)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateDirectoryRecursivelyWithPath(file_path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", file_path, rc); log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path, rc); log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
FsFile f; FsFile f;
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) { if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
log_write("failed to open file: %s 0x%04X\n", file_path, rc); log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(fsFileClose(&f));
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) { if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
log_write("failed to set file size: %s 0x%04X\n", file_path, rc); log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
@@ -386,10 +386,10 @@ void Menu::Update(Controller* controller, TouchInfo* touch) {
void Menu::Draw(NVGcontext* vg, Theme* theme) { void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
const auto& text_col = theme->elements[ThemeEntryID_TEXT].colour; const auto& text_col = theme->GetColour(ThemeEntryID_TEXT);
if (m_entries.empty()) { if (m_entries.empty()) {
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, text_col, "Empty..."_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Empty..."_i18n.c_str());
return; return;
} }
@@ -402,20 +402,19 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (m_index == i) { if (m_index == i) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
} else { } else {
if (i == m_index_offset) { if (i != m_entries.size() - 1) {
gfx::drawRect(vg, x, y, w, 1.f, text_col); gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
} }
gfx::drawRect(vg, x, y + h, w, 1.f, text_col);
} }
nvgSave(vg); nvgSave(vg);
nvgIntersectScissor(vg, x + text_xoffset, y, w-(x+text_xoffset+50), h); nvgIntersectScissor(vg, x + text_xoffset, y, w-(x+text_xoffset+50), h);
gfx::drawTextArgs(vg, x + text_xoffset, y + (h / 2.f), 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour, "%s By %s", e.repo.c_str(), e.owner.c_str()); gfx::drawTextArgs(vg, x + text_xoffset, y + (h / 2.f), 20.f, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id), "%s By %s", e.repo.c_str(), e.owner.c_str());
nvgRestore(vg); nvgRestore(vg);
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f), 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->elements[text_id].colour, "version: %s", e.tag.c_str()); gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f), 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE, theme->GetColour(text_id), "version: %s", e.tag.c_str());
}); });
} }
@@ -429,11 +428,7 @@ void Menu::OnFocusGained() {
void Menu::SetIndex(s64 index) { void Menu::SetIndex(s64 index) {
m_index = index; m_index = index;
if (!m_index) { if (!m_index) {
m_index_offset = 0; m_list->SetYoff(0);
}
if (m_index > m_index_offset && m_index - m_index_offset >= 7) {
m_index_offset = m_index - 7;
} }
SetTitleSubHeading(m_entries[m_index].json_path); SetTitleSubHeading(m_entries[m_index].json_path);
@@ -442,8 +437,6 @@ void Menu::SetIndex(s64 index) {
void Menu::Scan() { void Menu::Scan() {
m_entries.clear(); m_entries.clear();
m_index = 0;
m_index_offset = 0;
// load from romfs first // load from romfs first
if (R_SUCCEEDED(romfsInit())) { if (R_SUCCEEDED(romfsInit())) {

View File

@@ -20,7 +20,7 @@ namespace {
auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath { auto GenerateStarPath(const fs::FsPath& nro_path) -> fs::FsPath {
fs::FsPath out{}; fs::FsPath out{};
const auto dilem = std::strrchr(nro_path.s, '/'); const auto dilem = std::strrchr(nro_path.s, '/');
std::snprintf(out, sizeof(out), "%.*s.%s.star", dilem - nro_path.s + 1, nro_path.s, dilem + 1); std::snprintf(out, sizeof(out), "%.*s.%s.star", int(dilem - nro_path.s + 1), nro_path.s, dilem + 1);
return out; return out;
} }
@@ -83,7 +83,7 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
sort_items.push_back("Size (Star)"_i18n); sort_items.push_back("Size (Star)"_i18n);
SidebarEntryArray::Items order_items; SidebarEntryArray::Items order_items;
order_items.push_back("Decending"_i18n); order_items.push_back("Descending"_i18n);
order_items.push_back("Ascending"_i18n); order_items.push_back("Ascending"_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){
@@ -196,7 +196,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (pos == m_index) { if (pos == m_index) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, v, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
} else { } else {
DrawElement(v, ThemeEntryID_GRID); DrawElement(v, ThemeEntryID_GRID);
} }
@@ -216,9 +216,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
const float font_size = 18; const float font_size = 18;
gfx::drawTextArgs(vg, x + 148, y + 45, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s%s", has_star ? "\u2605 " : "", e.GetName()); 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->elements[text_id].colour, e.GetAuthor()); 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->elements[text_id].colour, e.GetDisplayVersion()); gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
} }
nvgRestore(vg); nvgRestore(vg);
}); });
@@ -237,8 +237,6 @@ void Menu::SetIndex(s64 index) {
m_list->SetYoff(0); m_list->SetYoff(0);
} }
const auto& e = m_entries[m_index];
if (IsStarEnabled()) { if (IsStarEnabled()) {
const auto star_path = GenerateStarPath(m_entries[m_index].path); const auto star_path = GenerateStarPath(m_entries[m_index].path);
if (fs::FsNativeSd().FileExists(star_path)) { if (fs::FsNativeSd().FileExists(star_path)) {
@@ -279,8 +277,8 @@ void Menu::ScanHomebrew() {
struct IniUser { struct IniUser {
std::vector<NroEntry>& entires; std::vector<NroEntry>& entires;
Hbini* ini; Hbini* ini{};
std::string last_section; std::string last_section{};
} ini_user{ m_entries }; } ini_user{ m_entries };
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int { ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
@@ -320,11 +318,6 @@ void Menu::Sort() {
fs::FsPath star_path; fs::FsPath star_path;
for (auto& p : m_entries) { for (auto& p : m_entries) {
p.has_star = fs.FileExists(GenerateStarPath(p.path)); p.has_star = fs.FileExists(GenerateStarPath(p.path));
if (p.has_star == true) {
log_write("found star: %s\n", p.path.s);
} else {
log_write("no star: %s\n", p.path.s);
}
} }
} }
@@ -336,13 +329,13 @@ void Menu::Sort() {
const auto name_cmp = [order](const NroEntry& lhs, const NroEntry& rhs) -> bool { const auto name_cmp = [order](const NroEntry& lhs, const NroEntry& rhs) -> bool {
auto r = strcasecmp(lhs.GetName(), rhs.GetName()); auto r = strcasecmp(lhs.GetName(), rhs.GetName());
if (!r) { if (!r) {
auto r = strcasecmp(lhs.GetAuthor(), rhs.GetAuthor()); r = strcasecmp(lhs.GetAuthor(), rhs.GetAuthor());
if (!r) { if (!r) {
auto r = strcasecmp(lhs.path, rhs.path); r = strcasecmp(lhs.path, rhs.path);
} }
} }
if (order == OrderType_Decending) { if (order == OrderType_Descending) {
return r < 0; return r < 0;
} else { } else {
return r > 0; return r > 0;
@@ -356,6 +349,7 @@ void Menu::Sort() {
} else if (!lhs.has_star.value() && rhs.has_star.value()) { } else if (!lhs.has_star.value() && rhs.has_star.value()) {
return false; return false;
} }
[[fallthrough]];
case SortType_Updated: { case SortType_Updated: {
auto lhs_timestamp = lhs.hbini.timestamp; auto lhs_timestamp = lhs.hbini.timestamp;
auto rhs_timestamp = rhs.hbini.timestamp; auto rhs_timestamp = rhs.hbini.timestamp;
@@ -368,7 +362,7 @@ void Menu::Sort() {
if (lhs_timestamp == rhs_timestamp) { if (lhs_timestamp == rhs_timestamp) {
return name_cmp(lhs, rhs); return name_cmp(lhs, rhs);
} else if (order == OrderType_Decending) { } else if (order == OrderType_Descending) {
return lhs_timestamp > rhs_timestamp; return lhs_timestamp > rhs_timestamp;
} else { } else {
return lhs_timestamp < rhs_timestamp; return lhs_timestamp < rhs_timestamp;
@@ -381,10 +375,11 @@ void Menu::Sort() {
} else if (!lhs.has_star.value() && rhs.has_star.value()) { } else if (!lhs.has_star.value() && rhs.has_star.value()) {
return false; return false;
} }
[[fallthrough]];
case SortType_Size: { case SortType_Size: {
if (lhs.size == rhs.size) { if (lhs.size == rhs.size) {
return name_cmp(lhs, rhs); return name_cmp(lhs, rhs);
} else if (order == OrderType_Decending) { } else if (order == OrderType_Descending) {
return lhs.size > rhs.size; return lhs.size > rhs.size;
} else { } else {
return lhs.size < rhs.size; return lhs.size < rhs.size;
@@ -397,6 +392,7 @@ void Menu::Sort() {
} else if (!lhs.has_star.value() && rhs.has_star.value()) { } else if (!lhs.has_star.value() && rhs.has_star.value()) {
return false; return false;
} }
[[fallthrough]];
case SortType_Alphabetical: { case SortType_Alphabetical: {
return name_cmp(lhs, rhs); return name_cmp(lhs, rhs);
} break; } break;

View File

@@ -258,8 +258,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto scale = std::min(scale_x, scale_y); const auto scale = std::min(scale_x, scale_y);
w = m_irs_width * scale; w = m_irs_width * scale;
h = m_irs_height * scale; h = m_irs_height * scale;
cx = (m_pos.x + m_pos.w / 2.0) - w / 2.0; cx = (m_pos.x + m_pos.w / 2.F) - w / 2.F;
cy = (m_pos.y + m_pos.h / 2.0) - h / 2.0; cy = (m_pos.y + m_pos.h / 2.F) - h / 2.F;
angle = 0; angle = 0;
} break; } break;
case Rotation_90: { case Rotation_90: {
@@ -268,8 +268,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto scale = std::min(scale_x, scale_y); const auto scale = std::min(scale_x, scale_y);
w = m_irs_width * scale; w = m_irs_width * scale;
h = m_irs_height * scale; h = m_irs_height * scale;
cx = (m_pos.x + m_pos.w / 2.0) + h / 2.0; cx = (m_pos.x + m_pos.w / 2.F) + h / 2.F;
cy = (m_pos.y + m_pos.h / 2.0) - w / 2.0; cy = (m_pos.y + m_pos.h / 2.F) - w / 2.F;
angle = 90; angle = 90;
} break; } break;
case Rotation_180: { case Rotation_180: {
@@ -278,8 +278,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto scale = std::min(scale_x, scale_y); const auto scale = std::min(scale_x, scale_y);
w = m_irs_width * scale; w = m_irs_width * scale;
h = m_irs_height * scale; h = m_irs_height * scale;
cx = (m_pos.x + m_pos.w / 2.0) + w / 2.0; cx = (m_pos.x + m_pos.w / 2.F) + w / 2.F;
cy = (m_pos.y + m_pos.h / 2.0) + h / 2.0; cy = (m_pos.y + m_pos.h / 2.F) + h / 2.F;
angle = 180; angle = 180;
} break; } break;
case Rotation_270: { case Rotation_270: {
@@ -288,8 +288,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto scale = std::min(scale_x, scale_y); const auto scale = std::min(scale_x, scale_y);
w = m_irs_width * scale; w = m_irs_width * scale;
h = m_irs_height * scale; h = m_irs_height * scale;
cx = (m_pos.x + m_pos.w / 2.0) - h / 2.0; cx = (m_pos.x + m_pos.w / 2.F) - h / 2.F;
cy = (m_pos.y + m_pos.h / 2.0) + w / 2.0; cy = (m_pos.y + m_pos.h / 2.F) + w / 2.F;
angle = 270; angle = 270;
} break; } break;
} }
@@ -382,7 +382,7 @@ void Menu::LoadDefaultConfig() {
irsGetClusteringProcessorDefaultConfig(&m_clustering_config); irsGetClusteringProcessorDefaultConfig(&m_clustering_config);
irsGetIrLedProcessorDefaultConfig(&m_led_config); irsGetIrLedProcessorDefaultConfig(&m_led_config);
m_tera_config; m_tera_config = {};
m_adaptive_config = {}; m_adaptive_config = {};
m_hand_config = {}; m_hand_config = {};

View File

@@ -54,7 +54,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); auto zfile = unzOpen64(zip_out);
if (!zfile) { if (!zfile) {
log_write("failed to open zip: %s\n", zip_out); log_write("failed to open zip: %s\n", zip_out.s);
return false; return false;
} }
ON_SCOPE_EXIT(unzClose(zfile)); ON_SCOPE_EXIT(unzClose(zfile));
@@ -89,28 +89,32 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
file_path = fs::AppendPath("/", file_path); file_path = fs::AppendPath("/", file_path);
} }
if (std::strstr(file_path, "sphaira.nro")) {
file_path = App::GetExePath();
}
Result rc; Result rc;
if (file_path[strlen(file_path) -1] == '/') { if (file_path[strlen(file_path) -1] == '/') {
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create folder: %s 0x%04X\n", file_path, rc); log_write("failed to create folder: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
} else { } else {
Result rc; Result rc;
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path, rc); log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
FsFile f; FsFile f;
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) { if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
log_write("failed to open file: %s 0x%04X\n", file_path, rc); log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(fsFileClose(&f));
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) { if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
log_write("failed to set file size: %s 0x%04X\n", file_path, rc); log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
@@ -124,7 +128,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
} }
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) { if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
log_write("failed to write file: %s 0x%04X\n", file_path, rc); log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
@@ -146,6 +150,7 @@ MainMenu::MainMenu() {
curl::Url{GITHUB_URL}, curl::Url{GITHUB_URL},
curl::Path{CACHE_PATH}, curl::Path{CACHE_PATH},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::Header{ curl::Header{
{ "Accept", "application/vnd.github+json" }, { "Accept", "application/vnd.github+json" },
}, },
@@ -225,6 +230,7 @@ MainMenu::MainMenu() {
language_items.push_back("Portuguese"_i18n); language_items.push_back("Portuguese"_i18n);
language_items.push_back("Russian"_i18n); language_items.push_back("Russian"_i18n);
language_items.push_back("Swedish"_i18n); language_items.push_back("Swedish"_i18n);
language_items.push_back("Vietnamese"_i18n);
options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [this](){ options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [this](){
SidebarEntryArray::Items theme_items{}; SidebarEntryArray::Items theme_items{};
@@ -274,11 +280,8 @@ MainMenu::MainMenu() {
m_update_state = UpdateState::None; m_update_state = UpdateState::None;
App::Notify("Updated to "_i18n + m_update_version); App::Notify("Updated to "_i18n + m_update_version);
App::Push(std::make_shared<OptionBox>( App::Push(std::make_shared<OptionBox>(
"Restart Sphaira?"_i18n, "Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
"Back"_i18n, "Restart"_i18n, 1, [this](auto op_index){ App::ExitRestart();
if (op_index && *op_index) {
App::ExitRestart();
}
} }
)); ));
} else { } else {
@@ -324,6 +327,11 @@ MainMenu::MainMenu() {
install_items.push_back("System memory"_i18n); install_items.push_back("System memory"_i18n);
install_items.push_back("microSD card"_i18n); install_items.push_back("microSD card"_i18n);
SidebarEntryArray::Items text_scroll_speed_items;
text_scroll_speed_items.push_back("Slow"_i18n);
text_scroll_speed_items.push_back("Normal"_i18n);
text_scroll_speed_items.push_back("Fast"_i18n);
options->Add(std::make_shared<SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [this](bool& enable){ options->Add(std::make_shared<SidebarEntryBool>("Logging"_i18n, App::GetLogEnable(), [this](bool& enable){
App::SetLogEnable(enable); App::SetLogEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n)); }, "Enabled"_i18n, "Disabled"_i18n));
@@ -343,6 +351,10 @@ MainMenu::MainMenu() {
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){ options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
App::SetInstallPrompt(enable); App::SetInstallPrompt(enable);
}, "Enabled"_i18n, "Disabled"_i18n)); }, "Enabled"_i18n, "Disabled"_i18n));
options->Add(std::make_shared<SidebarEntryArray>("Text scroll speed"_i18n, text_scroll_speed_items, [this](s64& index_out){
App::SetTextScrollSpeed(index_out);
}, (s64)App::GetTextScrollSpeed()));
})); }));
}}) }})
); );

View File

@@ -41,7 +41,7 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
#define draw(...) \ #define draw(...) \
gfx::textBounds(vg, 0, 0, bounds, __VA_ARGS__); \ gfx::textBounds(vg, 0, 0, bounds, __VA_ARGS__); \
start_x -= bounds[2] - bounds[0]; \ start_x -= bounds[2] - bounds[0]; \
gfx::drawTextArgs(vg, start_x, start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->elements[ThemeEntryID_TEXT].colour, __VA_ARGS__); \ gfx::drawTextArgs(vg, start_x, start_y, font_size, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), __VA_ARGS__); \
start_x -= spacing; start_x -= spacing;
// draw("version %s", APP_VERSION); // draw("version %s", APP_VERSION);
@@ -58,17 +58,15 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
#undef draw #undef draw
gfx::drawRect(vg, 30.f, 86.f, 1220.f, 1.f, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, 30.f, 86.f, 1220.f, 1.f, theme->GetColour(ThemeEntryID_LINE));
gfx::drawRect(vg, 30.f, 646.0f, 1220.f, 1.f, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, 30.f, 646.0f, 1220.f, 1.f, theme->GetColour(ThemeEntryID_LINE));
nvgFontSize(vg, 28); nvgFontSize(vg, 28);
gfx::textBounds(vg, 0, 0, bounds, m_title.c_str()); gfx::textBounds(vg, 0, 0, bounds, m_title.c_str());
gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str()); gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
gfx::drawTextArgs(vg, 80 + (bounds[2] - bounds[0]) + 10, start_y, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->elements[ThemeEntryID_TEXT].colour, m_title_sub_heading.c_str()); gfx::drawTextArgs(vg, 80 + (bounds[2] - bounds[0]) + 10, start_y, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str());
// gfx::drawTextArgs(vg, 80, 65, 28.f, NVG_ALIGN_LEFT, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str()); gfx::drawTextArgs(vg, 80, 685.f, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), "%s", m_sub_heading.c_str());
// gfx::drawTextArgs(vg, 80, 680.f, 18, NVG_ALIGN_LEFT, theme->elements[ThemeEntryID_TEXT].colour, "%s", m_sub_heading.c_str());
gfx::drawTextArgs(vg, 80, 685.f, 18, NVG_ALIGN_LEFT, theme->elements[ThemeEntryID_TEXT].colour, "%s", m_sub_heading.c_str());
} }
void MenuBase::SetTitle(std::string title) { void MenuBase::SetTitle(std::string title) {

View File

@@ -13,7 +13,7 @@
#include "i18n.hpp" #include "i18n.hpp"
#include <minIni.h> #include <minIni.h>
#include <nanovg/stb_image.h> #include <stb_image.h>
#include <cstring> #include <cstring>
#include <minizip/unzip.h> #include <minizip/unzip.h>
#include <yyjson.h> #include <yyjson.h>
@@ -102,32 +102,10 @@ auto apiBuildUrlDownloadInternal(const std::string& id, bool is_pack) -> std::st
// https://api.themezer.net/?query=query{downloadPack(id:"11"){filename,url,mimetype}} // https://api.themezer.net/?query=query{downloadPack(id:"11"){filename,url,mimetype}}
} }
auto apiBuildUrlDownloadTheme(const ThemeEntry& e) -> std::string {
return apiBuildUrlDownloadInternal(e.id, false);
}
auto apiBuildUrlDownloadPack(const PackListEntry& e) -> std::string { auto apiBuildUrlDownloadPack(const PackListEntry& e) -> std::string {
return apiBuildUrlDownloadInternal(e.id, true); return apiBuildUrlDownloadInternal(e.id, true);
} }
auto apiBuildFilePack(const PackListEntry& e) -> fs::FsPath {
fs::FsPath path;
std::snprintf(path, sizeof(path), "%s/%s By %s/", THEME_FOLDER, e.details.name.c_str(), e.creator.display_name.c_str());
return path;
}
#if 0
auto apiBuildUrlPack(const PackListEntry& e) -> std::string {
char url[2048];
std::snprintf(url, sizeof(url), "https://api.themezer.net/?query=query($id:String!){pack(id:$id){id,creator{display_name},details{name,description},last_updated,categories,dl_count,like_count,themes{id,details{name},layout{id,details{name}},categories,target,preview{original,thumb},last_updated,dl_count,like_count}}}&variables={\"id\":\"%s\"}", e.id.c_str());
return url;
}
#endif
auto apiBuildUrlThemeList(const Config& e) -> std::string {
return apiBuildUrlListInternal(e, false);
}
auto apiBuildUrlListPacks(const Config& e) -> std::string { auto apiBuildUrlListPacks(const Config& e) -> std::string {
return apiBuildUrlListInternal(e, true); return apiBuildUrlListInternal(e, true);
} }
@@ -171,7 +149,6 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
if (!image.image) { if (!image.image) {
log_write("failed to load image from file: %s\n", path.s); log_write("failed to load image from file: %s\n", path.s);
log_write("failed to load image from file: %s\n", path);
return false; return false;
} else { } else {
// log_write("loaded image from file: %s\n", path); // log_write("loaded image from file: %s\n", path);
@@ -189,35 +166,18 @@ void from_json(yyjson_val* json, Creator& e) {
void from_json(yyjson_val* json, Details& e) { void from_json(yyjson_val* json, Details& e) {
JSON_OBJ_ITR( JSON_OBJ_ITR(
JSON_SET_STR(name); JSON_SET_STR(name);
// JSON_SET_STR(description);
); );
} }
void from_json(yyjson_val* json, Preview& e) { void from_json(yyjson_val* json, Preview& e) {
JSON_OBJ_ITR( JSON_OBJ_ITR(
// JSON_SET_STR(original);
JSON_SET_STR(thumb); JSON_SET_STR(thumb);
); );
} }
void from_json(yyjson_val* json, DownloadPack& e) {
JSON_OBJ_ITR(
JSON_SET_STR(filename);
JSON_SET_STR(url);
JSON_SET_STR(mimetype);
);
}
void from_json(yyjson_val* json, ThemeEntry& e) { void from_json(yyjson_val* json, ThemeEntry& e) {
JSON_OBJ_ITR( JSON_OBJ_ITR(
JSON_SET_STR(id); JSON_SET_STR(id);
// JSON_SET_OBJ(creator);
// JSON_SET_OBJ(details);
// JSON_SET_STR(last_updated);
// JSON_SET_UINT(dl_count);
// JSON_SET_UINT(like_count);
// JSON_SET_ARR_STR(categories);
// JSON_SET_STR(target);
JSON_SET_OBJ(preview); JSON_SET_OBJ(preview);
); );
} }
@@ -227,10 +187,6 @@ void from_json(yyjson_val* json, PackListEntry& e) {
JSON_SET_STR(id); JSON_SET_STR(id);
JSON_SET_OBJ(creator); JSON_SET_OBJ(creator);
JSON_SET_OBJ(details); JSON_SET_OBJ(details);
// JSON_SET_STR(last_updated);
// JSON_SET_ARR_STR(categories);
// JSON_SET_UINT(dl_count);
// JSON_SET_UINT(like_count);
JSON_SET_ARR_OBJ(themes); JSON_SET_ARR_OBJ(themes);
); );
} }
@@ -246,7 +202,6 @@ void from_json(yyjson_val* json, Pagination& e) {
void from_json(const std::vector<u8>& data, DownloadPack& e) { void from_json(const std::vector<u8>& data, DownloadPack& e) {
JSON_INIT_VEC(data, "data"); JSON_INIT_VEC(data, "data");
// JSON_GET_OBJ("downloadTheme");
JSON_GET_OBJ("downloadPack"); JSON_GET_OBJ("downloadPack");
JSON_OBJ_ITR( JSON_OBJ_ITR(
JSON_SET_STR(filename); JSON_SET_STR(filename);
@@ -310,14 +265,14 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
// create directories // create directories
fs::FsPath dir_path; fs::FsPath dir_path;
std::snprintf(dir_path, sizeof(dir_path), "%s/%s - By %s", THEME_FOLDER, entry.details.name.c_str(), entry.creator.display_name.c_str()); std::snprintf(dir_path, sizeof(dir_path), "%s/%s - By %s", THEME_FOLDER.s, entry.details.name.c_str(), entry.creator.display_name.c_str());
fs.CreateDirectoryRecursively(dir_path); fs.CreateDirectoryRecursively(dir_path);
// 3. extract the zip // 3. extract the zip
if (!pbox->ShouldExit()) { if (!pbox->ShouldExit()) {
auto zfile = unzOpen64(zip_out); auto zfile = unzOpen64(zip_out);
if (!zfile) { if (!zfile) {
log_write("failed to open zip: %s\n", zip_out); log_write("failed to open zip: %s\n", zip_out.s);
return false; return false;
} }
ON_SCOPE_EXIT(unzClose(zfile)); ON_SCOPE_EXIT(unzClose(zfile));
@@ -352,19 +307,19 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
Result rc; Result rc;
if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) { if (R_FAILED(rc = fs.CreateFile(file_path, info.uncompressed_size, 0)) && rc != FsError_PathAlreadyExists) {
log_write("failed to create file: %s 0x%04X\n", file_path, rc); log_write("failed to create file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
FsFile f; FsFile f;
if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) { if (R_FAILED(rc = fs.OpenFile(file_path, FsOpenMode_Write, &f))) {
log_write("failed to open file: %s 0x%04X\n", file_path, rc); log_write("failed to open file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
ON_SCOPE_EXIT(fsFileClose(&f)); ON_SCOPE_EXIT(fsFileClose(&f));
if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) { if (R_FAILED(rc = fsFileSetSize(&f, info.uncompressed_size))) {
log_write("failed to set file size: %s 0x%04X\n", file_path, rc); log_write("failed to set file size: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
@@ -382,7 +337,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
} }
if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) { if (R_FAILED(rc = fsFileWrite(&f, offset, buf.data(), bytes_read, FsWriteOption_None))) {
log_write("failed to write file: %s 0x%04X\n", file_path, rc); log_write("failed to write file: %s 0x%04X\n", file_path.s, rc);
return false; return false;
} }
@@ -542,13 +497,13 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
}}) }})
); );
m_page_index = 0;
m_pages.resize(1);
PackListDownload();
const Vec4 v{75, 110, 350, 250}; const Vec4 v{75, 110, 350, 250};
const Vec2 pad{10, 10}; const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 6, m_pos, v, pad); m_list = std::make_unique<List>(3, 6, m_pos, v, pad);
m_page_index = 0;
m_pages.resize(1);
PackListDownload();
} }
Menu::~Menu() { Menu::~Menu() {
@@ -581,7 +536,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme); MenuBase::Draw(vg, theme);
if (m_pages.empty()) { if (m_pages.empty()) {
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Empty!"_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Empty!"_i18n.c_str());
return; return;
} }
@@ -589,15 +544,15 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
switch (page.m_ready) { switch (page.m_ready) {
case PageLoadState::None: case PageLoadState::None:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Not Ready..."_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Not Ready..."_i18n.c_str());
return; return;
case PageLoadState::Loading: case PageLoadState::Loading:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Loading"_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Loading"_i18n.c_str());
return; return;
case PageLoadState::Done: case PageLoadState::Done:
break; break;
case PageLoadState::Error: case PageLoadState::Error:
gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, gfx::Colour::YELLOW, "Error loading page!"_i18n.c_str()); gfx::drawTextArgs(vg, SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 36.f, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE, theme->GetColour(ThemeEntryID_TEXT_INFO), "Error loading page!"_i18n.c_str());
return; return;
} }
@@ -612,13 +567,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT; auto text_id = ThemeEntryID_TEXT;
if (pos == m_index) { if (pos == m_index) {
text_id = ThemeEntryID_TEXT_SELECTED; text_id = ThemeEntryID_TEXT_SELECTED;
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
} else { } else {
DrawElement(x, y, w, h, ThemeEntryID_GRID); DrawElement(x, y, w, h, ThemeEntryID_GRID);
} }
const float xoff = (350 - 320) / 2; const float xoff = (350 - 320) / 2;
const float yoff = (350 - 320) / 2;
// lazy load image // lazy load image
if (e.themes.size()) { if (e.themes.size()) {
@@ -638,7 +592,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
switch (image.state) { switch (image.state) {
case ImageDownloadState::None: { case ImageDownloadState::None: {
const auto path = apiBuildIconCache(theme); const auto path = apiBuildIconCache(theme);
log_write("downloading theme!: %s\n", path); log_write("downloading theme!: %s\n", path.s);
const auto url = theme.preview.thumb; const auto url = theme.preview.thumb;
log_write("downloading url: %s\n", url.c_str()); log_write("downloading url: %s\n", url.c_str());
@@ -647,6 +601,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
curl::Url{url}, curl::Url{url},
curl::Path{path}, curl::Path{path},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this, &image](auto& result) { curl::OnComplete{[this, &image](auto& result) {
if (result.success) { if (result.success) {
image.state = ImageDownloadState::Done; image.state = ImageDownloadState::Done;
@@ -683,8 +638,8 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
nvgSave(vg); nvgSave(vg);
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
{ {
gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.details.name.c_str()); gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s", e.details.name.c_str());
gfx::drawTextArgs(vg, x + xoff, y + 180 + 55, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.creator.display_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());
} }
nvgRestore(vg); nvgRestore(vg);
}); });
@@ -733,6 +688,7 @@ void Menu::PackListDownload() {
curl::Url{packList_url}, curl::Url{packList_url},
curl::Path{packlist_path}, curl::Path{packlist_path},
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::StopToken{this->GetToken()},
curl::OnComplete{[this, page_index](auto& result){ curl::OnComplete{[this, page_index](auto& result){
log_write("got themezer data\n"); log_write("got themezer data\n");
if (!result.success) { if (!result.success) {
@@ -757,8 +713,8 @@ void Menu::PackListDownload() {
std::snprintf(subheading, sizeof(subheading), "Page %zu / %zu"_i18n.c_str(), m_page_index+1, m_page_index_max); std::snprintf(subheading, sizeof(subheading), "Page %zu / %zu"_i18n.c_str(), m_page_index+1, m_page_index_max);
SetSubHeading(subheading); SetSubHeading(subheading);
log_write("a.pagination.page: %u\n", a.pagination.page); log_write("a.pagination.page: %zu\n", a.pagination.page);
log_write("a.pagination.page_count: %u\n", a.pagination.page_count); log_write("a.pagination.page_count: %zu\n", a.pagination.page_count);
} }
}); });
} }

View File

@@ -19,15 +19,9 @@ auto NotifEntry::Draw(NVGcontext* vg, Theme* theme, float y) -> bool {
} }
auto NotifEntry::Draw(NVGcontext* vg, Theme* theme) -> void { auto NotifEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
auto overlay_col = theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour; auto text_col = theme->GetColour(ThemeEntryID_TEXT);
auto selected_col = theme->elements[ThemeEntryID_SELECTED].colour;
auto text_col = theme->elements[ThemeEntryID_TEXT].colour;
float font_size = 18.f; float font_size = 18.f;
// overlay_col.a = 0.2f;
// selected_col.a = 0.2f;
// text_col.a = 0.2f;
// auto vg = App::GetVg();
if (!m_bounds_measured) { if (!m_bounds_measured) {
m_bounds_measured = true; m_bounds_measured = true;
m_pos.w = 320.f; m_pos.w = 320.f;
@@ -49,7 +43,7 @@ auto NotifEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
} }
} }
gfx::drawRectOutline(vg, 4.f, overlay_col, m_pos, selected_col); gfx::drawRectOutline(vg, theme, 4.f, m_pos);
gfx::drawText(vg, Vec2{m_pos.x + (m_pos.w / 2.f), m_pos.y + (m_pos.h / 2.f)}, font_size, text_col, m_text.c_str(), NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER); gfx::drawText(vg, Vec2{m_pos.x + (m_pos.w / 2.f), m_pos.y + (m_pos.h / 2.f)}, font_size, text_col, m_text.c_str(), NVG_ALIGN_MIDDLE | NVG_ALIGN_CENTER);
} }

View File

@@ -10,7 +10,7 @@
namespace sphaira::ui::gfx { namespace sphaira::ui::gfx {
namespace { namespace {
static 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"},
std::pair{Button::X, "\uE0E2"}, std::pair{Button::X, "\uE0E2"},
@@ -33,80 +33,45 @@ static constexpr std::array buttons = {
std::pair{Button::R3, "\uE105"}, std::pair{Button::R3, "\uE105"},
}; };
#define F(a) (a/255.f) // turn range 0-255 to 0.f-1.f range
constexpr std::array COLOURS = {
std::pair<Colour, NVGcolor>{Colour::BLACK, { F(45.f), F(45.f), F(45.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::LIGHT_BLACK, { F(50.f), F(50.f), F(50.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::SILVER, { F(128.f), F(128.f), F(128.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::DARK_GREY, { F(70.f), F(70.f), F(70.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::GREY, { F(77.f), F(77.f), F(77.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::WHITE, { F(251.f), F(251.f), F(251.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::CYAN, { F(0.f), F(255.f), F(200.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::TEAL, { F(143.f), F(253.f), F(252.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::BLUE, { F(36.f), F(141.f), F(199.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::LIGHT_BLUE, { F(26.f), F(188.f), F(252.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::YELLOW, { F(255.f), F(177.f), F(66.f), F(255.f) }},
std::pair<Colour, NVGcolor>{Colour::RED, { F(250.f), F(90.f), F(58.f), F(255.f) }}
};
#undef F
// NEW --------------------- // NEW ---------------------
inline void drawRectIntenal(NVGcontext* vg, const Vec4& vec, const NVGcolor& c, bool rounded) { void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGcolor& c, bool rounded) {
nvgBeginPath(vg); nvgBeginPath(vg);
if (rounded) { if (rounded) {
nvgRoundedRect(vg, vec.x, vec.y, vec.w, vec.h, 15); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
} else { } else {
nvgRect(vg, vec.x, vec.y, vec.w, vec.h); nvgRect(vg, v.x, v.y, v.w, v.h);
} }
nvgFillColor(vg, c); nvgFillColor(vg, c);
nvgFill(vg); nvgFill(vg);
} }
inline void drawRectIntenal(NVGcontext* vg, const Vec4& vec, const NVGpaint& p, bool rounded) { void drawRectIntenal(NVGcontext* vg, const Vec4& v, const NVGpaint& p, bool rounded) {
nvgBeginPath(vg); nvgBeginPath(vg);
if (rounded) { if (rounded) {
nvgRoundedRect(vg, vec.x, vec.y, vec.w, vec.h, 15); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
} else { } else {
nvgRect(vg, vec.x, vec.y, vec.w, vec.h); nvgRect(vg, v.x, v.y, v.w, v.h);
} }
nvgFillPaint(vg, p); nvgFillPaint(vg, p);
nvgFill(vg); nvgFill(vg);
} }
inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor& c) { void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v) {
float gradientX, gradientY, color; float gradientX, gradientY, color;
getHighlightAnimation(&gradientX, &gradientY, &color); getHighlightAnimation(&gradientX, &gradientY, &color);
#if 1 const auto strokeWidth = 5.F;
// NVGcolor pulsationColor = nvgRGBAf((color * out_col.r) + (1 - color) * out_col.r, auto v2 = v;
// (color * out_col.g) + (1 - color) * out_col.g, v2.x -= strokeWidth / 2.F;
// (color * out_col.b) + (1 - color) * out_col.b, v2.y -= strokeWidth / 2.F;
// out_col.a);
NVGcolor pulsationColor = nvgRGBAf((color * out_col.r) + (1 - color) * out_col.r,
(color * out_col.g) + (1 - color) * out_col.g,
(color * out_col.b) + (1 - color) * out_col.b,
out_col.a);
drawRectIntenal(vg, {vec.x-size,vec.y-size,vec.w+(size*2.f),vec.h+(size * 2.f)}, pulsationColor, false);
drawRectIntenal(vg, vec, c, false);
#else
const auto strokeWidth = 5.0;
auto v2 = vec;
v2.x -= strokeWidth / 2.0;
v2.y -= strokeWidth / 2.0;
v2.w += strokeWidth; v2.w += strokeWidth;
v2.h += strokeWidth; v2.h += strokeWidth;
const auto corner_radius = 0.5; const auto corner_radius = 0.5F;
// nvgSave(vg); const auto shadow_width = 2.F;
// nvgResetScissor(vg); const auto shadow_offset = 10.F;
const auto shadow_feather = 10.F;
// const auto stroke_width = 5.0f; const auto shadow_opacity = 128.F;
// const auto shadow_corner_radius = 6.0f;
const auto shadow_width = 2.0f;
const auto shadow_offset = 10.0f;
const auto shadow_feather = 10.0f;
const auto shadow_opacity = 128.0f;
// Shadow // Shadow
NVGpaint shadowPaint = nvgBoxGradient(vg, NVGpaint shadowPaint = nvgBoxGradient(vg,
@@ -123,8 +88,8 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
nvgFillPaint(vg, shadowPaint); nvgFillPaint(vg, shadowPaint);
nvgFill(vg); nvgFill(vg);
const auto color1 = nvgRGB(25, 138, 198); const auto color1 = theme->GetColour(ThemeEntryID_HIGHLIGHT_1);
const auto color2 = nvgRGB(137, 241, 242); const auto color2 = theme->GetColour(ThemeEntryID_HIGHLIGHT_2);
const auto borderColor = nvgRGBAf(color2.r, color2.g, color2.b, 0.5); const auto borderColor = nvgRGBAf(color2.r, color2.g, color2.b, 0.5);
const auto transparent = nvgRGBA(0, 0, 0, 0); const auto transparent = nvgRGBA(0, 0, 0, 0);
@@ -160,55 +125,23 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
nvgStrokeWidth(vg, strokeWidth); nvgStrokeWidth(vg, strokeWidth);
nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius); nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius);
nvgStroke(vg); nvgStroke(vg);
drawRectIntenal(vg, {vec.x-size,vec.y-size,vec.w+(size*2.f),vec.h+(size * 2.f)}, pulsationColor, false);
drawRectIntenal(vg, vec, c, true);
nvgBeginPath(vg);
nvgRoundedRect(vg, vec.x, vec.y, vec.w, vec.h, corner_radius);
nvgFillColor(vg, c);
nvgFill(vg);
// nvgRestore(vg);
#endif
} }
inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p) { void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGcolor& c) {
float gradientX, gradientY, color; const auto corner_radius = 0.5;
getHighlightAnimation(&gradientX, &gradientY, &color); drawRectOutlineInternal(vg, theme, size, v);
NVGcolor pulsationColor = nvgRGBAf((color * out_col.r) + (1 - color) * out_col.r,
(color * out_col.g) + (1 - color) * out_col.g,
(color * out_col.b) + (1 - color) * out_col.b,
out_col.a);
drawRectIntenal(vg, {vec.x-size,vec.y-size,vec.w+(size*2.f),vec.h+(size * 2.f)}, pulsationColor, false);
drawRectIntenal(vg, vec, p, false);
}
inline void drawTriangleInternal(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c) {
nvgBeginPath(vg); nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, corner_radius);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgFillColor(vg, c); nvgFillColor(vg, c);
nvgFill(vg); nvgFill(vg);
} }
inline void drawTriangleInternal(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p) { void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgFillPaint(vg, p);
nvgFill(vg);
}
inline void drawTextIntenal(NVGcontext* vg, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor& c) {
nvgBeginPath(vg); nvgBeginPath(vg);
nvgFontSize(vg, size); nvgFontSize(vg, size);
nvgTextAlign(vg, align); nvgTextAlign(vg, align);
nvgFillColor(vg, c); nvgFillColor(vg, c);
nvgText(vg, vec.x, vec.y, str, end); nvgText(vg, v.x, v.y, str, end);
} }
} // namespace } // namespace
@@ -222,15 +155,6 @@ const char* getButton(const Button want) {
std::unreachable(); std::unreachable();
} }
NVGcolor getColour(Colour want) {
for (auto& [key, val] : COLOURS) {
if (key == want) {
return val;
}
}
std::unreachable();
}
void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) { void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const NVGcolor& c, const char* str, ...) {
std::va_list v; std::va_list v;
va_start(v, str); va_start(v, str);
@@ -240,21 +164,7 @@ void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const
drawText(vg, x, y, size, buffer, nullptr, align, c); drawText(vg, x, y, size, buffer, nullptr, align, c);
} }
static void drawImageInternal(NVGcontext* vg, Vec4 v, int texture, int rounded = 0) { void drawImage(NVGcontext* vg, const Vec4& v, int texture) {
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
// drawRect(vg, x, y, w, h, paint);
nvgBeginPath(vg);
// nvgRect(vg, x, y, w, h);
if (rounded == 0) {
nvgRect(vg, v.x, v.y, v.w, v.h);
} else {
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, rounded);
}
nvgFillPaint(vg, paint);
nvgFill(vg);
}
void drawImage(NVGcontext* vg, Vec4 v, int texture) {
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f); const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
drawRect(vg, v, paint, false); drawRect(vg, v, paint, false);
} }
@@ -263,7 +173,7 @@ void drawImage(NVGcontext* vg, float x, float y, float w, float h, int texture)
drawImage(vg, Vec4(x, y, w, h), texture); drawImage(vg, Vec4(x, y, w, h), texture);
} }
void drawImageRounded(NVGcontext* vg, Vec4 v, int texture) { void drawImageRounded(NVGcontext* vg, const Vec4& v, int texture) {
const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f); const auto paint = nvgImagePattern(vg, v.x, v.y, v.w, v.h, 0, texture, 1.f);
nvgBeginPath(vg); nvgBeginPath(vg);
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15); nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
@@ -275,7 +185,7 @@ void drawImageRounded(NVGcontext* vg, float x, float y, float w, float h, int te
drawImageRounded(vg, Vec4(x, y, w, h), texture); drawImageRounded(vg, Vec4(x, y, w, h), texture);
} }
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, 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) {
nvgBeginPath(vg); nvgBeginPath(vg);
nvgFontSize(vg, size); nvgFontSize(vg, size);
nvgTextAlign(vg, align); nvgTextAlign(vg, align);
@@ -283,14 +193,6 @@ void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, NVGc
nvgTextBox(vg, x, y, bound, str, end); nvgTextBox(vg, x, y, bound, str, end);
} }
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, NVGcolor&& c, const char* str, int align, const char* end) {
drawTextBox(vg, x, y, size, bound, c, str, align, end);
}
void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, Colour c, const char* str, int align, const char* end) {
drawTextBox(vg, x, y, size, bound, getColour(c), str, align, end);
}
void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str, ...) { void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str, ...) {
char buf[0x100]; char buf[0x100];
va_list v; va_list v;
@@ -303,171 +205,50 @@ void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str
// NEW----------- // NEW-----------
void dimBackground(NVGcontext* vg) { void dimBackground(NVGcontext* vg) {
// drawRectIntenal(vg, {0.f,0.f,1280.f,720.f}, nvgRGBA(30,30,30,180)); drawRectIntenal(vg, {0.f,0.f,SCREEN_WIDTH,SCREEN_HEIGHT}, nvgRGBA(0, 0, 0, 180), false);
// drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(20, 20, 20, 225), false);
drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(0, 0, 0, 230), false);
}
void drawRect(NVGcontext* vg, float x, float y, float w, float h, Colour c, bool rounded) {
drawRectIntenal(vg, {x,y,w,h}, getColour(c), rounded);
}
void drawRect(NVGcontext* vg, Vec4 vec, Colour c, bool rounded) {
drawRectIntenal(vg, vec, getColour(c), rounded);
} }
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor& c, bool rounded) { void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor& c, bool rounded) {
drawRectIntenal(vg, {x,y,w,h}, c, rounded); drawRectIntenal(vg, {x,y,w,h}, c, rounded);
} }
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGcolor&& c, bool rounded) { void drawRect(NVGcontext* vg, const Vec4& v, const NVGcolor& c, bool rounded) {
drawRectIntenal(vg, {x,y,w,h}, c, rounded); drawRectIntenal(vg, v, c, rounded);
}
void drawRect(NVGcontext* vg, Vec4 vec, const NVGcolor& c, bool rounded) {
drawRectIntenal(vg, vec, c, rounded);
}
void drawRect(NVGcontext* vg, Vec4 vec, const NVGcolor&& c, bool rounded) {
drawRectIntenal(vg, vec, c, rounded);
} }
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint& p, bool rounded) { void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint& p, bool rounded) {
drawRectIntenal(vg, {x,y,w,h}, p, rounded); drawRectIntenal(vg, {x,y,w,h}, p, rounded);
} }
void drawRect(NVGcontext* vg, float x, float y, float w, float h, const NVGpaint&& p, bool rounded) { void drawRect(NVGcontext* vg, const Vec4& v, const NVGpaint& p, bool rounded) {
drawRectIntenal(vg, {x,y,w,h}, p, rounded); drawRectIntenal(vg, v, p, rounded);
} }
void drawRect(NVGcontext* vg, Vec4 vec, const NVGpaint& p, bool rounded) { void drawRectOutline(NVGcontext* vg, const Theme* theme, float size, float x, float y, float w, float h) {
drawRectIntenal(vg, vec, p, rounded); drawRectOutlineInternal(vg, theme, size, {x,y,w,h}, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
} }
void drawRect(NVGcontext* vg, Vec4 vec, const NVGpaint&& p, bool rounded) { void drawRectOutline(NVGcontext* vg, const Theme* theme, float size, const Vec4& v) {
drawRectIntenal(vg, vec, p, rounded); drawRectOutlineInternal(vg, theme, size, v, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, float x, float y, float w, float h, Colour c) {
drawRectOutlineInternal(vg, size, out_col, {x,y,w,h}, getColour(c));
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, Colour c) {
drawRectOutlineInternal(vg, size, out_col, vec, getColour(c));
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor& c) {
drawRectOutlineInternal(vg, size, out_col, {x,y,w,h}, c);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGcolor&& c) {
drawRectOutlineInternal(vg, size, out_col, {x,y,w,h}, c);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor& c) {
drawRectOutlineInternal(vg, size, out_col, vec, c);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGcolor&& c) {
drawRectOutlineInternal(vg, size, out_col, vec, c);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint& p) {
drawRectOutlineInternal(vg, size, out_col, {x,y,w,h}, p);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, float x, float y, float w, float h, const NVGpaint&& p) {
drawRectOutlineInternal(vg, size, out_col, {x,y,w,h}, p);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p) {
drawRectOutlineInternal(vg, size, out_col, vec, p);
}
void drawRectOutline(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint&& p) {
drawRectOutlineInternal(vg, size, out_col, vec, p);
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, Colour c) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, getColour(c));
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor& c) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, c);
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGcolor&& c) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, c);
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, p);
}
void drawTriangle(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint&& p) {
drawTriangleInternal(vg, aX, aY, bX, bY, cX, cY, p);
}
void drawText(NVGcontext* vg, float x, float y, float size, const char* str, const char* end, int align, Colour c) {
drawTextIntenal(vg, {x,y}, size, str, end, align, getColour(c));
}
void drawText(NVGcontext* vg, float x, float y, float size, Colour c, const char* str, int align, const char* end) {
drawTextIntenal(vg, {x,y}, size, str, end, align, getColour(c));
}
void drawText(NVGcontext* vg, Vec2 vec, float size, const char* str, const char* end, int align, Colour c) {
drawTextIntenal(vg, vec, size, str, end, align, getColour(c));
}
void drawText(NVGcontext* vg, Vec2 vec, float size, Colour c, const char* str, int align, const char* end) {
drawTextIntenal(vg, vec, size, str, end, align, getColour(c));
} }
void drawText(NVGcontext* vg, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c) { void drawText(NVGcontext* vg, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor& c) {
drawTextIntenal(vg, {x,y}, size, str, end, align, c); drawTextIntenal(vg, {x,y}, size, str, end, align, c);
} }
void drawText(NVGcontext* vg, float x, float y, float size, const char* str, const char* end, int align, const NVGcolor&& c) {
drawTextIntenal(vg, {x,y}, size, str, end, align, c);
}
void drawText(NVGcontext* vg, float x, float y, float size, const NVGcolor& c, const char* str, int align, const char* end) { void drawText(NVGcontext* vg, float x, float y, float size, const NVGcolor& c, const char* str, int align, const char* end) {
drawTextIntenal(vg, {x,y}, size, str, end, align, c); drawTextIntenal(vg, {x,y}, size, str, end, align, c);
} }
void drawText(NVGcontext* vg, float x, float y, float size, const NVGcolor&& c, const char* str, int align, const char* end) { void drawText(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
drawTextIntenal(vg, {x,y}, size, str, end, align, c); drawTextIntenal(vg, v, size, str, end, align, c);
} }
void drawText(NVGcontext* vg, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor& c) { void drawText(NVGcontext* vg, const Vec2& v, float size, const NVGcolor& c, const char* str, int align, const char* end) {
drawTextIntenal(vg, vec, size, str, end, align, c); drawTextIntenal(vg, v, size, str, end, align, c);
} }
void drawText(NVGcontext* vg, Vec2 vec, float size, const char* str, const char* end, int align, const NVGcolor&& c) { void drawScrollbar(NVGcontext* vg, const Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page) {
drawTextIntenal(vg, vec, size, str, end, align, c);
}
void drawText(NVGcontext* vg, Vec2 vec, float size, const NVGcolor& c, const char* str, int align, const char* end) {
drawTextIntenal(vg, vec, size, str, end, align, c);
}
void drawText(NVGcontext* vg, Vec2 vec, float size, const NVGcolor&& c, const char* str, int align, const char* end) {
drawTextIntenal(vg, vec, size, str, end, align, c);
}
void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, Colour c, const char* str, ...) {
std::va_list v;
va_start(v, str);
char buffer[0x100];
std::vsnprintf(buffer, sizeof(buffer), str, v);
va_end(v);
drawTextIntenal(vg, {x, y}, size, buffer, nullptr, align, getColour(c));
}
void drawScrollbar(NVGcontext* vg, Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page) {
const s64 SCROLL = index_off; const s64 SCROLL = index_off;
const s64 max_entry_display = max_per_page; const s64 max_entry_display = max_per_page;
const s64 entry_total = count; const s64 entry_total = count;
@@ -478,35 +259,34 @@ void drawScrollbar(NVGcontext* vg, Theme* theme, float x, float y, float h, u32
if (entry_total > max_entry_display) { if (entry_total > max_entry_display) {
const float sb_h = 1.f / (float)entry_total * h; const float sb_h = 1.f / (float)entry_total * h;
const float sb_y = SCROLL; const float sb_y = SCROLL;
gfx::drawRect(vg, x, y, scc2, h, theme->elements[ThemeEntryID_GRID].colour, false); gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false);
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, false); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false);
} }
} }
void drawScrollbar(NVGcontext* vg, Theme* theme, u32 index_off, u32 count, u32 max_per_page) { void drawScrollbar(NVGcontext* vg, const Theme* theme, u32 index_off, u32 count, u32 max_per_page) {
// drawScrollbar(vg, SCREEN_WIDTH - 50, 100, 500, index_off, count, max_per_page);
drawScrollbar(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, max_per_page); drawScrollbar(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, max_per_page);
} }
void drawScrollbar2(NVGcontext* vg, Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page) { void drawScrollbar2(NVGcontext* vg, const Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page) {
// round up // round up
if (count % row) { if (count % row) {
count = count + (row - count % row); count = count + (row - count % row);
} }
const float scc2 = 8.0; const float scc2 = 6.0;
const float scw = 2.0; const float scw = 2.0;
// only draw scrollbar if needed // only draw scrollbar if needed
if (count > page) { if (count > page) {
const float sb_h = 1.f / (float)count * h; const float sb_h = 1.f / (float)count * h;
const float sb_y = index_off; const float sb_y = index_off;
gfx::drawRect(vg, x, y, scc2, h, theme->elements[ThemeEntryID_GRID].colour, false); gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false);
gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, false); gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false);
} }
} }
void drawScrollbar2(NVGcontext* vg, Theme* theme, s64 index_off, s64 count, s64 row, s64 page) { void drawScrollbar2(NVGcontext* vg, const Theme* theme, s64 index_off, s64 count, s64 row, s64 page) {
drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page); drawScrollbar2(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, row, page);
} }

View File

@@ -12,10 +12,10 @@ OptionBoxEntry::OptionBoxEntry(const std::string& text, Vec4 pos)
auto OptionBoxEntry::Draw(NVGcontext* vg, Theme* theme) -> void { auto OptionBoxEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
if (m_selected) { if (m_selected) {
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, m_pos, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRectOutline(vg, theme, 4.f, m_pos);
gfx::drawText(vg, m_text_pos, 26.f, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, m_text.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); gfx::drawText(vg, m_text_pos, 26.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_text.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
} else { } else {
gfx::drawText(vg, m_text_pos, 26.f, theme->elements[ThemeEntryID_TEXT].colour, m_text.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); gfx::drawText(vg, m_text_pos, 26.f, theme->GetColour(ThemeEntryID_TEXT), m_text.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
} }
} }
@@ -94,14 +94,14 @@ 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; const float padding = 15;
gfx::dimBackground(vg); gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
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->elements[ThemeEntryID_TEXT].colour, m_message.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); 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);
nvgRestore(vg); nvgRestore(vg);
gfx::drawRect(vg, m_spacer_line, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, m_spacer_line, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
for (auto&p: m_entries) { for (auto&p: m_entries) {
p.Draw(vg, theme); p.Draw(vg, theme);

View File

@@ -22,8 +22,8 @@ PopupList::PopupList(std::string title, Items items, std::string& index_ref)
const auto it = std::find(m_items.cbegin(), m_items.cend(), index_ref); const auto it = std::find(m_items.cbegin(), m_items.cend(), index_ref);
if (it != m_items.cend()) { if (it != m_items.cend()) {
m_index = std::distance(m_items.cbegin(), it); m_index = std::distance(m_items.cbegin(), it);
if (m_index >= 7) { if (m_index >= 6) {
m_index_offset = m_index - 6; m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
} }
} }
@@ -50,8 +50,8 @@ PopupList::PopupList(std::string title, Items items, Callback cb, std::string in
const auto it = std::find(m_items.cbegin(), m_items.cend(), index); const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
if (it != m_items.cend()) { if (it != m_items.cend()) {
SetIndex(std::distance(m_items.cbegin(), it)); SetIndex(std::distance(m_items.cbegin(), it));
if (m_index >= 7) { if (m_index >= 6) {
m_index_offset = m_index - 6; m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
} }
} }
} }
@@ -84,20 +84,21 @@ PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
); );
m_pos.w = 1280.f; m_pos.w = 1280.f;
const float a = std::min(405.f, (60.f * static_cast<float>(m_items.size()))); const float a = std::min(370.f, (60.f * static_cast<float>(m_items.size())));
m_pos.h = 80.f + 140.f + a; m_pos.h = 80.f + 140.f + a;
m_pos.y = 720.f - m_pos.h; m_pos.y = 720.f - m_pos.h;
m_line_top = m_pos.y + 70.f; m_line_top = m_pos.y + 70.f;
m_line_bottom = 720.f - 73.f; m_line_bottom = 720.f - 73.f;
if (m_index >= 7) {
m_index_offset = m_index - 6;
}
Vec4 v{m_block}; Vec4 v{m_block};
v.y = m_line_top + 1.f + 42.f; v.y = m_line_top + 1.f + 42.f;
const Vec4 pos{0, m_line_top, 1280.f, m_line_bottom - m_line_top}; const Vec4 pos{0, m_line_top, 1280.f, m_line_bottom - m_line_top};
m_list = std::make_unique<List>(1, 7, pos, v); m_list = std::make_unique<List>(1, 6, pos, v);
m_list->SetScrollBarPos(1250, m_line_top + 20, m_line_bottom - m_line_top - 40); m_list->SetScrollBarPos(1250, m_line_top + 20, m_line_bottom - m_line_top - 40);
if (m_index >= 6) {
m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
}
} }
auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void { auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
@@ -110,21 +111,21 @@ auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void { auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::dimBackground(vg); gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
gfx::drawText(vg, m_pos + m_title_pos, 24.f, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str()); gfx::drawText(vg, m_pos + m_title_pos, 24.f, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
gfx::drawRect(vg, 30.f, m_line_top, m_line_width, 1.f, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, 30.f, m_line_top, m_line_width, 1.f, theme->GetColour(ThemeEntryID_LINE));
gfx::drawRect(vg, 30.f, m_line_bottom, m_line_width, 1.f, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, 30.f, m_line_bottom, m_line_width, 1.f, theme->GetColour(ThemeEntryID_LINE));
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;
if (m_index == i) { if (m_index == i) {
gfx::drawRect(vg, x - 4.f, y - 4.f, w + 8.f, h + 8.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour); gfx::drawRectOutline(vg, theme, 4.f, v);
gfx::drawRect(vg, x, y, w, h, theme->elements[ThemeEntryID_SELECTED].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(ThemeEntryID_TEXT_SELECTED));
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->elements[ThemeEntryID_TEXT_SELECTED].colour);
} else { } else {
gfx::drawRect(vg, x, y, w, 1.f, theme->elements[ThemeEntryID_TEXT].colour); if (i != m_items.size() - 1) {
gfx::drawRect(vg, x, y + h, w, 1.f, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
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->elements[ThemeEntryID_TEXT].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(ThemeEntryID_TEXT));
} }
}); });
@@ -143,10 +144,6 @@ auto PopupList::OnFocusLost() noexcept -> void {
void PopupList::SetIndex(s64 index) { void PopupList::SetIndex(s64 index) {
m_index = index; m_index = index;
if (m_index > m_index_offset && m_index - m_index_offset >= 6) {
m_index_offset = m_index - 6;
}
} }
} // namespace sphaira::ui } // namespace sphaira::ui

View File

@@ -31,7 +31,6 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
m_pos.h = 430.f; m_pos.h = 430.f;
m_pos.x = 255; m_pos.x = 255;
m_pos.y = 145; m_pos.y = 145;
145 + 430; // 575, 200, 420
m_pos.w = 770.f; m_pos.w = 770.f;
m_pos.h = 295.f; m_pos.h = 295.f;
@@ -52,9 +51,7 @@ ProgressBox::ProgressBox(const std::string& title, ProgressBoxCallback callback,
} }
ProgressBox::~ProgressBox() { ProgressBox::~ProgressBox() {
mutexLock(&m_mutex); m_stop_source.request_stop();
m_exit_requested = true;
mutexUnlock(&m_mutex);
if (R_FAILED(threadWaitForExit(&m_thread))) { if (R_FAILED(threadWaitForExit(&m_thread))) {
log_write("failed to join thread\n"); log_write("failed to join thread\n");
@@ -83,7 +80,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
mutexUnlock(&m_mutex); mutexUnlock(&m_mutex);
gfx::dimBackground(vg); gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
// The pop up shape. // The pop up shape.
// const Vec4 box = { 255, 145, 770, 430 }; // const Vec4 box = { 255, 145, 770, 430 };
@@ -93,19 +90,15 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
// shapes. // shapes.
if (offset && size) { if (offset && size) {
gfx::drawRect(vg, prog_bar, gfx::Colour::SILVER); gfx::drawRect(vg, prog_bar, theme->GetColour(ThemeEntryID_PROGRESSBAR_BACKGROUND));
const u32 percentage = ((double)offset / (double)size) * 100.0; const u32 percentage = ((double)offset / (double)size) * 100.0;
gfx::drawRect(vg, prog_bar.x, prog_bar.y, ((float)offset / (float)size) * prog_bar.w, prog_bar.h, gfx::Colour::CYAN); gfx::drawRect(vg, prog_bar.x, prog_bar.y, ((float)offset / (float)size) * prog_bar.w, prog_bar.h, theme->GetColour(ThemeEntryID_PROGRESSBAR));
// gfx::drawTextArgs(vg, prog_bar.x + 85, prog_bar.y + 40, 20, 0, gfx::Colour::WHITE, "%u%%", percentage); gfx::drawTextArgs(vg, prog_bar.x + prog_bar.w + 10, prog_bar.y, 20, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%u%%", percentage);
gfx::drawTextArgs(vg, prog_bar.x + prog_bar.w + 10, prog_bar.y, 20, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, gfx::Colour::WHITE, "%u%%", percentage);
} }
gfx::drawTextArgs(vg, center_x, m_pos.y + 60, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::WHITE, title.c_str()); gfx::drawTextArgs(vg, center_x, m_pos.y + 60, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
// gfx::drawTextArgs(vg, center_x, 260, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::SILVER, "Please do not remove the gamecard or");
// gfx::drawTextArgs(vg, center_x, 295, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::SILVER, "power off the system whilst installing.");
// gfx::drawTextArgs(vg, center_x, 360, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::WHITE, "%.2f MiB/s", 24.0);
if (!transfer.empty()) { if (!transfer.empty()) {
gfx::drawTextArgs(vg, center_x, prog_bar.y - 15 - 20 * 1.5, 20, NVG_ALIGN_CENTER, gfx::Colour::WHITE, "%s", transfer.c_str()); gfx::drawTextArgs(vg, center_x, prog_bar.y - 15 - 20 * 1.5F, 20, NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), "%s", transfer.c_str());
} }
} }
@@ -129,16 +122,11 @@ auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
} }
void ProgressBox::RequestExit() { void ProgressBox::RequestExit() {
mutexLock(&m_mutex); m_stop_source.request_stop();
m_exit_requested = true;
mutexUnlock(&m_mutex);
} }
auto ProgressBox::ShouldExit() -> bool { auto ProgressBox::ShouldExit() -> bool {
mutexLock(&m_mutex); return m_stop_source.stop_requested();
const auto exit_requested = m_exit_requested;
mutexUnlock(&m_mutex);
return exit_requested;
} }
auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result { auto ProgressBox::CopyFile(const fs::FsPath& src_path, const fs::FsPath& dst_path) -> Result {

View File

@@ -9,9 +9,9 @@ namespace sphaira::ui {
ScrollableText::ScrollableText(const std::string& text, float x, float y, float y_clip, float w, float font_size) ScrollableText::ScrollableText(const std::string& text, float x, float y, float y_clip, float w, float font_size)
: m_font_size{font_size} : m_font_size{font_size}
, m_y_off_base{y} , m_y_off_base{y}
, m_y_off{y}
, m_clip_y{y_clip} , m_clip_y{y_clip}
, m_end_w{w} , m_end_w{w}
, m_y_off{y}
{ {
SetActions( SetActions(
std::make_pair(Button::LS_DOWN, Action{[this](){ std::make_pair(Button::LS_DOWN, Action{[this](){
@@ -94,17 +94,15 @@ void ScrollableText::Draw(NVGcontext* vg, Theme* theme) {
const auto sb_h = 1.f / max_index * scrollbar_size; const auto sb_h = 1.f / max_index * scrollbar_size;
const auto in_clip = m_clip_y / m_step - 1; const auto in_clip = m_clip_y / m_step - 1;
const auto sb_y = m_index; const auto sb_y = m_index;
// gfx::drawRect(vg, banner_vec.x+banner_vec.w-20, m_y_off_base, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour); gfx::drawRect(vg, banner_vec.w, m_y_off_base, 10, scrollbar_size, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND));
// gfx::drawRect(vg, banner_vec.x+banner_vec.w-20+2, m_y_off_base + sb_h * sb_y, 10-4, sb_h + (sb_h * in_clip) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour); gfx::drawRect(vg, banner_vec.w+2, m_y_off_base + sb_h * sb_y, 10-4, sb_h + (sb_h * in_clip) - 4, theme->GetColour(ThemeEntryID_SCROLLBAR));
gfx::drawRect(vg, banner_vec.w, m_y_off_base, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
gfx::drawRect(vg, banner_vec.w+2, m_y_off_base + sb_h * sb_y, 10-4, sb_h + (sb_h * in_clip) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
} }
nvgSave(vg); nvgSave(vg);
nvgScissor(vg, 0, m_y_off_base - m_font_size, 1280, m_clip_y + m_font_size); // clip nvgIntersectScissor(vg, 0, m_y_off_base - m_font_size, 1280, m_clip_y + m_font_size); // clip
nvgTextLineHeight(App::GetVg(), 1.7); nvgTextLineHeight(App::GetVg(), 1.7);
gfx::drawTextBox(vg, banner_vec.x + 40, m_y_off, m_font_size, m_bounds[2] - m_bounds[0], theme->elements[ThemeEntryID_TEXT].colour, m_text.c_str()); gfx::drawTextBox(vg, banner_vec.x + 40, m_y_off, m_font_size, m_bounds[2] - m_bounds[0], theme->GetColour(ThemeEntryID_TEXT), m_text.c_str());
nvgRestore(vg); nvgRestore(vg);
} }

View File

@@ -7,6 +7,14 @@
namespace sphaira::ui { namespace sphaira::ui {
namespace { namespace {
auto GetTextScrollSpeed() -> float {
switch (App::GetTextScrollSpeed()) {
case 0: return 0.5;
default: case 1: return 1.0;
case 2: return 1.5;
}
}
auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 { auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
return Vec4{ return Vec4{
va.x, va.y, va.x, va.y,
@@ -24,14 +32,7 @@ SidebarEntryBase::SidebarEntryBase(std::string&& title)
auto SidebarEntryBase::Draw(NVGcontext* vg, Theme* theme) -> void { auto SidebarEntryBase::Draw(NVGcontext* vg, Theme* theme) -> void {
// draw spacers or highlight box if in focus (selected) // draw spacers or highlight box if in focus (selected)
if (HasFocus()) { if (HasFocus()) {
gfx::drawRect(vg, m_pos, nvgRGB(50,50,50)); gfx::drawRectOutline(vg, theme, 4.f, m_pos);
gfx::drawRect(vg, m_pos, nvgRGB(0,0,0));
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, m_pos, theme->elements[ThemeEntryID_SELECTED].colour);
// gfx::drawRect(vg, m_pos.x - 4.f, m_pos.y - 4.f, m_pos.w + 8.f, m_pos.h + 8.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour);
// gfx::drawRect(vg, m_pos.x, m_pos.y, m_pos.w, m_pos.h, theme->elements[ThemeEntryID_SELECTED].colour);
} else {
gfx::drawRect(vg, m_pos.x, m_pos.y, m_pos.w, 1.f, nvgRGB(81, 81, 81)); // spacer
gfx::drawRect(vg, m_pos.x, m_pos.y + m_pos.h, m_pos.w, 1.f, nvgRGB(81, 81, 81)); // spacer
} }
} }
@@ -60,16 +61,16 @@ auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme); SidebarEntryBase::Draw(vg, theme);
// if (HasFocus()) { // if (HasFocus()) {
// gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); // gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
// } else { // } else {
// } // }
gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
if (m_option == true) { if (m_option == true) {
gfx::drawText(vg, Vec2{m_pos.x + m_pos.w - 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, m_true_str.c_str(), NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); gfx::drawText(vg, Vec2{m_pos.x + m_pos.w - 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_true_str.c_str(), NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
} else { // text info } else { // text info
gfx::drawText(vg, Vec2{m_pos.x + m_pos.w - 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT].colour, m_false_str.c_str(), NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); gfx::drawText(vg, Vec2{m_pos.x + m_pos.w - 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT), m_false_str.c_str(), NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
} }
} }
@@ -90,9 +91,9 @@ auto SidebarEntryCallback::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme); SidebarEntryBase::Draw(vg, theme);
// if (HasFocus()) { // if (HasFocus()) {
// gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); // gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
// } else { // } else {
gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
// } // }
} }
@@ -154,11 +155,59 @@ auto SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme); SidebarEntryBase::Draw(vg, theme);
const auto& text_entry = m_items[m_index]; const auto& text_entry = m_items[m_index];
// const auto& colour = HasFocus() ? theme->elements[ThemeEntryID_TEXT_SELECTED].colour : theme->elements[ThemeEntryID_TEXT].colour;
const auto& colour = theme->elements[ThemeEntryID_TEXT].colour;
gfx::drawText(vg, Vec2{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, colour, m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); // scrolling text
gfx::drawText(vg, Vec2{m_pos.x + m_pos.w - 15.f, m_pos.y + (m_pos.h / 2.f)}, 20.f, theme->elements[ThemeEntryID_TEXT_SELECTED].colour, text_entry.c_str(), NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); // todo: move below in a flexible class and use it for all text drawing.
float bounds[4];
nvgFontSize(vg, 20);
nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgTextBounds(vg, 0, 0, m_title.c_str(), nullptr, bounds);
const float start_x = bounds[2] + 50;
const float max_off = m_pos.w - start_x - 15.f;
auto value_str = m_items[m_index];
nvgTextBounds(vg, 0, 0, value_str.c_str(), nullptr, bounds);
if (HasFocus()) {
const auto scroll_amount = GetTextScrollSpeed();
if (bounds[2] > max_off) {
value_str += " ";
nvgTextBounds(vg, 0, 0, value_str.c_str(), nullptr, bounds);
if (!m_text_yoff) {
m_tick++;
if (m_tick >= 90) {
m_tick = 0;
m_text_yoff += scroll_amount;
}
} else if (bounds[2] > m_text_yoff) {
m_text_yoff += std::min(scroll_amount, bounds[2] - m_text_yoff);
} else {
m_text_yoff = 0;
}
value_str += text_entry;
}
}
const Vec2 key_text_pos{m_pos.x + 15.f, m_pos.y + (m_pos.h / 2.f)};
gfx::drawText(vg, key_text_pos, 20.f, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgSave(vg);
const float xpos = m_pos.x + m_pos.w - 15.f - std::min(max_off, bounds[2]);
nvgIntersectScissor(vg, xpos, GetY(), max_off, GetH());
const Vec2 value_text_pos{xpos - m_text_yoff, m_pos.y + (m_pos.h / 2.f)};
gfx::drawText(vg, value_text_pos, 20.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), value_str.c_str(), NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
nvgRestore(vg);
}
auto SidebarEntryArray::OnFocusGained() noexcept -> void {
Widget::OnFocusGained();
}
auto SidebarEntryArray::OnFocusLost() noexcept -> void {
Widget::OnFocusLost();
m_text_yoff = 0;
} }
Sidebar::Sidebar(std::string title, Side side, Items&& items) Sidebar::Sidebar(std::string title, Side side, Items&& items)
@@ -222,18 +271,23 @@ auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
} }
auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void { auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::drawRect(vg, m_pos, nvgRGBA(0, 0, 0, 220)); gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR));
gfx::drawText(vg, m_title_pos, m_title_size, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str()); gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
if (!m_sub.empty()) { if (!m_sub.empty()) {
gfx::drawTextArgs(vg, m_pos.x + m_pos.w - 30.f, m_title_pos.y + 10.f, 18, NVG_ALIGN_TOP | NVG_ALIGN_RIGHT, theme->elements[ThemeEntryID_TEXT].colour, m_sub.c_str()); gfx::drawTextArgs(vg, m_pos.x + m_pos.w - 30.f, m_title_pos.y + 10.f, 16, NVG_ALIGN_TOP | NVG_ALIGN_RIGHT, theme->GetColour(ThemeEntryID_TEXT_INFO), m_sub.c_str());
} }
gfx::drawRect(vg, m_top_bar, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, m_top_bar, theme->GetColour(ThemeEntryID_LINE));
gfx::drawRect(vg, m_bottom_bar, theme->elements[ThemeEntryID_TEXT].colour); gfx::drawRect(vg, m_bottom_bar, theme->GetColour(ThemeEntryID_LINE));
Widget::Draw(vg, theme); Widget::Draw(vg, theme);
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;
if (i != m_items.size() - 1) {
gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
}
m_items[i]->SetY(y); m_items[i]->SetY(y);
m_items[i]->Draw(vg, theme); m_items[i]->Draw(vg, theme);
}); });
@@ -266,11 +320,6 @@ void Sidebar::SetIndex(s64 index) {
m_items[m_index]->OnFocusLost(); m_items[m_index]->OnFocusLost();
m_index = index; m_index = index;
m_items[m_index]->OnFocusGained(); m_items[m_index]->OnFocusGained();
if (m_index > m_index_offset && m_index - m_index_offset >= 5) {
m_index_offset = m_index - 5;
}
SetupButtons(); SetupButtons();
} }
} }
@@ -279,7 +328,7 @@ void Sidebar::SetupButtons() {
RemoveActions(); RemoveActions();
// add entry actions // add entry actions
for (const auto& [button, action] : m_items[m_index]->GetActions()) { for (const auto& [button, action] : m_items[m_index]->GetActions()) {
SetAction(button, action); SetAction(button, action);
} }

View File

@@ -10,7 +10,7 @@ auto uiButton::Draw(NVGcontext* vg, Theme* theme) -> void {
// gfx::drawRect(vg, m_pos, gfx::Colour::RED); // gfx::drawRect(vg, m_pos, gfx::Colour::RED);
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP); nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
nvgFillColor(vg, theme->elements[ThemeEntryID_TEXT].colour); nvgFillColor(vg, theme->GetColour(ThemeEntryID_TEXT));
nvgFontSize(vg, 20); nvgFontSize(vg, 20);
nvgText(vg, m_hint_pos.x, m_hint_pos.y, m_action.m_hint.c_str(), nullptr); nvgText(vg, m_hint_pos.x, m_hint_pos.y, m_action.m_hint.c_str(), nullptr);
nvgFontSize(vg, 26); nvgFontSize(vg, 26);