27 Commits
touch ... 0.6.0

Author SHA1 Message Date
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 2587 additions and 2075 deletions

View File

@@ -19,6 +19,7 @@
"Details": "Details",
"Update": "Update",
"Remove": "Entfernen",
"Restore": "",
"Download": "Download",
"Next Page": "Nächste Seite",
"Prev Page": "Vorherige Seite",
@@ -26,6 +27,9 @@
"Star": "Favorit",
"System memory": "System-Speicher",
"microSD card": "microSD-Karte",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Yes": "Ja",
"No": "Nein",
"Enabled": "Aktiviert",
@@ -54,7 +58,6 @@
"Asc": "Aufst.",
"Menu Options": "Menü-Optionen",
"Header": "Header",
"Theme": "Theme",
"Theme Options": "Theme-Optionen",
"Select Theme": "Theme auswählen",
@@ -114,6 +117,8 @@
"Create Folder": "Ordner erstellen",
"Set Folder Name": "Ordnernamen eingeben",
"View as text (unfinished)": "Als Text anzeigen (Beta)",
"Ignore read only": "",
"Mount": "",
"Empty...": "Leer...",
"Open with DayBreak?": "Mit DayBreak öffnen?",
"Launch ": "Starten ",
@@ -230,6 +235,12 @@
"Updated to ": "Aktualisiert auf ",
"Restart Sphaira?": "Sphaira neustarten?",
"Failed to download update": "Update-Download fehlgeschlagen",
"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?": "Ausgewählte Dateien löschen?",
"Completely remove ": "Vollständig entfernen ",
"Are you sure you want to delete ": "Wirklich löschen ",

View File

@@ -19,6 +19,7 @@
"Details": "Details",
"Update": "Update",
"Remove": "Remove",
"Restore": "Restore",
"Download": "Download",
"Next Page": "Next Page",
"Prev Page": "Prev Page",
@@ -26,6 +27,9 @@
"Star": "Star",
"System memory": "System memory",
"microSD card": "microSD card",
"Sd": "Sd",
"Image System memory": "Image System memory",
"Image microSD card": "Image microSD card",
"Yes": "Yes",
"No": "No",
"Enabled": "Enabled",
@@ -54,7 +58,6 @@
"Asc": "Asc",
"Menu Options": "Menu Options",
"Header": "Header",
"Theme": "Theme",
"Theme Options": "Theme Options",
"Select Theme": "Select Theme",
@@ -114,6 +117,8 @@
"Create Folder": "Create Folder",
"Set Folder Name": "Set Folder Name",
"View as text (unfinished)": "View as text (unfinished)",
"Ignore read only": "Ignore read only",
"Mount": "Mount",
"Empty...": "Empty...",
"Open with DayBreak?": "Open with DayBreak?",
"Launch ": "Launch ",
@@ -230,6 +235,12 @@
"Updated to ": "Updated to ",
"Restart Sphaira?": "Restart Sphaira?",
"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?",
"Completely remove ": "Completely remove ",
"Are you sure you want to delete ": "Are you sure you want to delete ",

View File

@@ -19,6 +19,7 @@
"Details": "Detalles",
"Update": "Actualizar",
"Remove": "Borrar",
"Restore": "",
"Download": "Descargar",
"Next Page": "Página siguiente",
"Prev Page": "Página anterior",
@@ -26,6 +27,9 @@
"Star": "Favorito",
"System memory": "Memoria de sistema",
"microSD card": "microSD",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Yes": "Sí",
"No": "No",
"Enabled": "Activado",
@@ -54,7 +58,6 @@
"Asc": "Ascendente",
"Menu Options": "Opciones de menú",
"Header": "Encabezado",
"Theme": "Tema",
"Theme Options": "Opciones de tema",
"Select Theme": "Seleccionar tema",
@@ -114,6 +117,8 @@
"Create Folder": "Crear carpeta",
"Set Folder Name": "Establecer nombre de carpeta",
"View as text (unfinished)": "Ver como texto (sin terminar)",
"Ignore read only": "",
"Mount": "",
"Empty...": "Vacío...",
"Open with DayBreak?": "¿Abrir con DayBreak?",
"Launch ": "Abrir ",
@@ -230,6 +235,12 @@
"Updated to ": "Actualizado a ",
"Restart Sphaira?": "¿Reiniciar Sphaira?",
"Failed to download update": "Fallo al descargar actualización",
"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?": "¿Eliminar archivos seleccionados?",
"Completely remove ": "Eliminar completamente",
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",

View File

@@ -19,6 +19,7 @@
"Details": "Détails",
"Update": "Mise à jour",
"Remove": "Supprimer",
"Restore": "Restaurer",
"Download": "Télécharger",
"Next Page": "Page Suiv.",
"Prev Page": "Page Préc.",
@@ -26,6 +27,9 @@
"Star": "Ajouter aux favories",
"System memory": "Mémoire système",
"microSD card": "Carte microSD",
"Sd": "Sd",
"Image System memory": "Image de la mémoire System",
"Image microSD card": "Image de la Carte microSD",
"Yes": "Oui",
"No": "Non",
"Enabled": "Activé(e)",
@@ -54,7 +58,6 @@
"Asc": "Croissant",
"Menu Options": "Options des Menus",
"Header": "En-tête",
"Theme": "Thème",
"Theme Options": "Options de Thème",
"Select Theme": "Choisir un Thème",
@@ -114,6 +117,8 @@
"Create Folder": "Créer un Dossier",
"Set Folder Name": "Nommer Le Dossier",
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
"Ignore read only": "Ignorer lecture seule",
"Mount": "Monter",
"Empty...": "Vide...",
"Open with DayBreak?": "Ouvrir avec DayBreak?",
"Launch ": "Lancer ",
@@ -230,6 +235,12 @@
"Updated to ": "Mis à jour vers ",
"Restart Sphaira?": "Redémarrer Sphaira?",
"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?",
"Completely remove ": "Supprimer totalement ",
"Are you sure you want to delete ": "Êtes-vous sûr de vouloir supprimer ",

View File

@@ -1,31 +1,35 @@
{
"[Applet Mode]": "[Modalità applet]",
"No Internet": "Niente Internet",
"Files": "",
"Apps": "",
"Store": "",
"Files": "File",
"Apps": "App",
"Store": "Store",
"Menu": "Menu",
"Options": "Opzioni",
"OK": "",
"OK": "OK",
"Back": "Indietro",
"Select": "",
"Select": "Seleziona",
"Open": "Apri",
"Launch": "Lancia",
"Info": "Informazioni",
"Install": "Installa",
"Delete": "Elimina",
"Restart": "",
"Changelog": "",
"Details": "",
"Update": "",
"Remove": "",
"Restart": "Riavvia",
"Changelog": "Patch notes",
"Details": "Dettagli",
"Update": "Aggiorna",
"Remove": "Rimuovi",
"Restore": "Ripristina",
"Download": "Download",
"Next Page": "Pagina successiva",
"Prev Page": "Pagina precedente",
"Unstar": "",
"Star": "",
"System memory": "",
"microSD card": "",
"Unstar": "Rimuovi dai preferiti",
"Star": "Aggiungi ai preferiti",
"System memory": "Memoria di sistema",
"microSD card": "Scheda microSD",
"Sd": "SD",
"Image System memory": "Immagine memoria di sistema",
"Image microSD card": "Immagine scheda microSD",
"Yes": "Sì",
"No": "No",
"Enabled": "Abilitato",
@@ -40,12 +44,12 @@
"Updated": "Aggiornato",
"Updated (Star)": "",
"Downloads": "Download",
"Size": "Misurare",
"Size (Star)": "",
"Size": "Dimensione",
"Size (Star)": "Dimensione (Preferiti)",
"Alphabetical": "Alfabetico",
"Alphabetical (Star)": "",
"Likes": "",
"ID": "",
"Alphabetical (Star)": "Alfabetico (Preferiti)",
"Likes": "Mi Piace",
"ID": "ID",
"Decending": "Decrescente",
"Descending (down)": "Decrescente",
"Desc": "Decrescente",
@@ -54,7 +58,6 @@
"Asc": "Crescente",
"Menu Options": "Opzioni menu",
"Header": "Intestazione",
"Theme": "Tema",
"Theme Options": "Opzioni tema",
"Select Theme": "Seleziona tema",
@@ -65,13 +68,13 @@
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "",
"Nxlink Upload": "",
"Nxlink Finished": "",
"Switch-Handheld!": "",
"Switch-Docked!": "",
"Nxlink Connected": "Nxlink connesso",
"Nxlink Upload": "Nxlink upload",
"Nxlink Finished": "Nxlink finito",
"Switch-Handheld!": "Switch Portatile",
"Switch-Docked!": "Switch Dock",
"Language": "Lingua",
"Auto": "",
"Auto": "Auto",
"English": "English",
"Japanese": "日本語",
"French": "Français",
@@ -89,9 +92,9 @@
"Misc": "Varie",
"Misc Options": "Opzioni varie",
"Web": "Rete",
"Install forwarders": "",
"Install location": "",
"Show install warning": "",
"Install forwarders": "Installa forwarder",
"Install location": "Installa posizione",
"Show install warning": "Mostra avvertimento installazione",
"FileBrowser": "FileBrowser",
"%zd files": "%zd files",
@@ -102,23 +105,25 @@
"Hidden Last": "Ultimo nascosto",
"Cut": "Taglia",
"Copy": "Copia",
"Paste": "",
"Paste ": "",
" file(s)?": "",
"Paste": "Incolla",
"Paste ": "Incolla ",
" file(s)?": "(i)file?",
"Rename": "Rinomina",
"Set New File Name": "",
"Set New File Name": "Imposta nuovo nome",
"Advanced": "Avanzato",
"Advanced Options": "Opzioni avanzate",
"Create File": "Crea file",
"Set File Name": "",
"Set File Name": "Imposta nome",
"Create Folder": "Crea cartella",
"Set Folder Name": "",
"Set Folder Name": "Imposta nome",
"View as text (unfinished)": "Visualizza come testo (non finito)",
"Empty...": "",
"Open with DayBreak?": "",
"Launch ": "",
"Launch option for: ": "",
"Select launcher for: ": "",
"Ignore read only": "Ignora read only",
"Mount": "Monta",
"Empty...": "Vuoto...",
"Open with DayBreak?": "Vuoi aprire con Daybreak?",
"Launch ": "Lancia",
"Launch option for: ": "Lancia opzione per",
"Select launcher for: ": "Scegli launcher per",
"Homebrew": "Homebrew",
"Homebrew Options": "Opzioni Homebrew",
@@ -230,6 +235,12 @@
"Updated to ": "",
"Restart Sphaira?": "",
"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?": "",
"Completely remove ": "",
"Are you sure you want to delete ": "Sei sicuro di voler eliminare? ",

View File

@@ -19,6 +19,7 @@
"Details": "詳細",
"Update": "アップデート",
"Remove": "除去",
"Restore": "復元",
"Download": "ダウンロード",
"Next Page": "次のページ",
"Prev Page": "前のページ",
@@ -26,6 +27,9 @@
"Star": "お気に入り",
"System memory": "システムメモリ",
"microSD card": "SDメモリーカード",
"Sd": "SDメモリーカード",
"Image System memory": "システムメモリイメージ",
"Image microSD card": "SDイメージ",
"Yes": "はい",
"No": "いいえ",
"Enabled": "",
@@ -54,7 +58,6 @@
"Asc": "上昇",
"Menu Options": "メニュー設定",
"Header": "ヘッダー",
"Theme": "テーマ",
"Theme Options": "テーマ設定",
"Select Theme": "テーマを選ぶ",
@@ -114,6 +117,8 @@
"Create Folder": "フォルダーの作成",
"Set Folder Name": "名前を入力",
"View as text (unfinished)": "テキストとして表示 (未完成)",
"Ignore read only": "読み取り専用を無視する",
"Mount": "マウント",
"Empty...": "このフォルダーは空です",
"Open with DayBreak?": "DayBreakで開きますか?",
"Launch ": "起動しますか",
@@ -230,6 +235,12 @@
"Updated to ": "アップデート: ",
"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が見つかりません\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?": "本当に削除しますか?",
"Completely remove ": "除去しますか ",
"Are you sure you want to delete ": "消去してもよろしいですか ",

View File

@@ -19,6 +19,7 @@
"Details": "상세",
"Update": "업데이트",
"Remove": "제거",
"Restore": "복원",
"Download": "다운로드",
"Next Page": "다음 페이지",
"Prev Page": "이전 페이지",
@@ -26,6 +27,9 @@
"Star": "즐겨찾기",
"System memory": "낸드 저장소",
"microSD card": "SD 카드",
"Sd": "SD 카드",
"Image System memory": "낸드 이미지",
"Image microSD card": "SD 이미지",
"Yes": "예",
"No": "아니요",
"Enabled": "",
@@ -54,7 +58,6 @@
"Asc": "오름차순",
"Menu Options": "메뉴",
"Header": "헤더",
"Theme": "테마",
"Theme Options": "테마 옵션",
"Select Theme": "테마 선택",
@@ -85,13 +88,13 @@
"Russian": "Русский",
"Swedish": "Svenska",
"Logging": "로깅",
"Replace hbmenu on exit": "hbmenu  sphaira 교체",
"Replace hbmenu on exit": "종료 시 hbmenu 교체",
"Misc": "기타",
"Misc Options": "기타",
"Misc Options": "기타 옵션",
"Web": "웹 브라우저",
"Install forwarders": "바로가기 설치",
"Install location": "설치 위치",
"Show install warning": "설치 경고 표시",
"Show install warning": "경고 메시지",
"FileBrowser": "파일 탐색기",
"%zd files": "%zd 개 파일",
@@ -114,6 +117,8 @@
"Create Folder": "새 폴더",
"Set Folder Name": "폴더명 입력",
"View as text (unfinished)": "텍스트로 보기 (미완성)",
"Ignore read only": "읽기 전용 설정 무시",
"Mount": "마운트",
"Empty...": "비어있음...",
"Open with DayBreak?": "DayBreak로 열까요?",
"Launch ": "실행할까요 ",
@@ -124,7 +129,7 @@
"Homebrew Options": "홈브류 옵션",
"Hide Sphaira": "Sphaira 숨기기",
"Install Forwarder": "바로가기 설치",
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 바로가기 설치 밴 위험이 있습니다!",
"WARNING: Installing forwarders will lead to a ban!": "경고: 시스낸드에 바로가기 설치 시, 밴 위험이 있습니다!",
"Installing Forwarder": "바로가기 설치",
"Creating Program": "프로그램 생성",
"Creating Control": "컨트롤 생성",
@@ -203,7 +208,7 @@
"GitHub": "GitHub",
"Downloading json": "JSON에서 다운로드",
"Select asset to download for ": "다운로드 아이템 선택 ",
"Select asset to download for ": "다운로드 아이템: ",
"Installing ": "설치 ",
"Uninstalling ": "설치 제거 ",
@@ -230,6 +235,12 @@
"Updated to ": "업데이트: ",
"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 ": "정말 삭제할까요 ",

View File

@@ -19,6 +19,7 @@
"Details": "",
"Update": "",
"Remove": "",
"Restore": "",
"Download": "Downloaden",
"Next Page": "Volgende pagina",
"Prev Page": "Vorige pagina",
@@ -26,6 +27,9 @@
"Star": "",
"System memory": "",
"microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Yes": "Ja",
"No": "Nee",
"Enabled": "Ingeschakeld",
@@ -54,7 +58,6 @@
"Asc": "Oplopend",
"Menu Options": "Menu-opties",
"Header": "Koptekst",
"Theme": "Thema",
"Theme Options": "Thema Opties",
"Select Theme": "Selecteer Thema",
@@ -114,6 +117,8 @@
"Create Folder": "Map maken",
"Set Folder Name": "",
"View as text (unfinished)": "Bekijk als tekst (onvoltooid)",
"Ignore read only": "",
"Mount": "",
"Empty...": "",
"Open with DayBreak?": "",
"Launch ": "",
@@ -230,6 +235,12 @@
"Updated to ": "",
"Restart Sphaira?": "",
"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?": "",
"Completely remove ": "",
"Are you sure you want to delete ": "Weet u zeker dat u wilt verwijderen ",

View File

@@ -15,17 +15,21 @@
"Install": "Instalar",
"Delete": "Excluir",
"Restart": "Reiniciar",
"Changelog": "Changelog",
"Changelog": "Alterações",
"Details": "Detalhes",
"Update": "Atualizar",
"Remove": "Remover",
"Download": "Download",
"Restore": "Restaurar",
"Download": "Baixar",
"Next Page": "Próxima página",
"Prev Page": "Página anterior",
"Unstar": "Desfavoritar",
"Star": "Favoritar",
"System memory": "Memória do console",
"microSD card": "Cartão microSD",
"Sd": "SD",
"Image System memory": "Imagem (Memória do console)",
"Image microSD card": "Imagem (Cartão microSD)",
"Yes": "Sim",
"No": "Não",
"Enabled": "Habilitado",
@@ -54,7 +58,6 @@
"Asc": "Asc.",
"Menu Options": "Opções do menu",
"Header": "Cabeçalho",
"Theme": "Tema",
"Theme Options": "Opções de tema",
"Select Theme": "Selecionar tema",
@@ -84,7 +87,7 @@
"Portuguese": "Português",
"Russian": "Русский",
"Swedish": "Svenska",
"Logging": "Logging",
"Logging": "Registro de depuração",
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
"Misc": "Diversos",
"Misc Options": "Opções diversas",
@@ -114,6 +117,8 @@
"Create Folder": "Criar pasta",
"Set Folder Name": "Definir novo nome da pasta",
"View as text (unfinished)": "Ver como texto (inacabado)",
"Ignore read only": "Ignorar somente leitura",
"Mount": "Montar",
"Empty...": "Vazio...",
"Open with DayBreak?": "Abrir com DayBreak?",
"Launch ": "Iniciar",
@@ -124,7 +129,7 @@
"Homebrew Options": "Opções do Homebrew",
"Hide Sphaira": "Esconder Sphaira",
"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",
"Creating Program": "Criando Program",
"Creating Control": "Criando Control",
@@ -201,9 +206,9 @@
"Bad Page": "Página inválida",
"Download theme?": "Baixar tema?",
"GitHub": "",
"Downloading json": "",
"Select asset to download for ": "",
"GitHub": "GitHub",
"Downloading json": "Baixando JSON",
"Select asset to download for ": "Selecione o recurso para baixar em ",
"Installing ": "Instalando ",
"Uninstalling ": "Desinstalando ",
@@ -217,8 +222,8 @@
"Copying ": "Copiando ",
"Trying to load ": "Tentando carregar ",
"Downloading ": "Baixando ",
"Downloaded ": "",
"Removed ": "",
"Downloaded ": "Baixado",
"Removed ": "Removido",
"Checking MD5": "Checando MD5",
"Loading...": "Carregando...",
"Loading": "Carregando",
@@ -230,6 +235,12 @@
"Updated to ": "Atualizado para ",
"Restart Sphaira?": "Reiniciar Sphaira?",
"Failed to download update": "Falha ao baixar a atualização",
"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?": "Deletar arquivos selecionados?",
"Completely remove ": "Remover completamente ",
"Are you sure you want to delete ": "Você tem certeza que quer deletar ",

View File

@@ -19,6 +19,7 @@
"Details": "",
"Update": "",
"Remove": "",
"Restore": "",
"Download": "Скачать",
"Next Page": "Следующая страница",
"Prev Page": "Предыдущая страница",
@@ -26,6 +27,9 @@
"Star": "",
"System memory": "",
"microSD card": "",
"Sd": "",
"Image System memory": "",
"Image microSD card": "",
"Yes": "Да",
"No": "Нет",
"Enabled": "Включено",
@@ -54,7 +58,6 @@
"Asc": "По возрастанию",
"Menu Options": "Параметры меню",
"Header": "Заголовок",
"Theme": "Тема",
"Theme Options": "Параметры темы",
"Select Theme": "Выберите тему",
@@ -114,6 +117,8 @@
"Create Folder": "Создать папку",
"Set Folder Name": "",
"View as text (unfinished)": "Посмотреть как текст (незакончено)",
"Ignore read only": "",
"Mount": "",
"Empty...": "",
"Open with DayBreak?": "",
"Launch ": "",
@@ -230,6 +235,12 @@
"Updated to ": "",
"Restart Sphaira?": "",
"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?": "",
"Completely remove ": "",
"Are you sure you want to delete ": "Вы уверены, что хотите удалить ",

View File

@@ -1,6 +1,6 @@
{
"[Applet Mode]": "[Applet-läge]",
"No Internet": "Ingen internetanslutning",
"No Internet": "Ingen Internetanslutning",
"Files": "Filer",
"Apps": "Appar",
"Store": "Butik",
@@ -14,47 +14,50 @@
"Info": "Info",
"Install": "Installera",
"Delete": "Radera",
"Restart": "",
"Restart": "Starta om",
"Changelog": "Ändringslogg",
"Details": "Detaljer",
"Update": "Uppdatera",
"Remove": "Ta bort",
"Restore": "Återställ",
"Download": "Ladda ner",
"Next Page": "Nästa sida",
"Prev Page": "Föregående sida",
"Unstar": "",
"Star": "",
"System memory": "",
"microSD card": "",
"Unstar": "Avmarkera stjärna",
"Star": "Markera stjärna",
"System memory": "Systemminne",
"microSD card": "microSD-kort",
"Sd": "Sd",
"Image System memory": "Avbild Systemminne",
"Image microSD card": "Avbild microSD-kort",
"Yes": "Ja",
"No": "Nej",
"Enabled": "Aktiverad",
"Disabled": "Avaktiverad",
"Disabled": "Inaktiverad",
"Sort By": "Sortera efter",
"Sort Options": "Sorteringsalternativ",
"Filter": "Filter",
"Filter": "Filtrera",
"Sort": "Sortera",
"Order": "Ordning",
"Search": "Sök",
"Updated": "Uppdaterad",
"Updated (Star)": "",
"Updated (Star)": "Uppdaterad (Stjärna)",
"Downloads": "Nedladdningar",
"Size": "Storlek",
"Size (Star)": "",
"Size (Star)": "Storlek (Stjärna)",
"Alphabetical": "Alfabetisk",
"Alphabetical (Star)": "",
"Alphabetical (Star)": "Alfabetisk (Stjärna)",
"Likes": "Gillar",
"ID": "ID",
"Decending": "Fallande",
"Descending (down)": "Fallande (nedåt)",
"Desc": "Fallande",
"Desc": "Fall",
"Ascending": "Stigande",
"Ascending (Up)": "Stigande (uppåt)",
"Asc": "Stigande",
"Asc": "Stig",
"Menu Options": "Menyalternativ",
"Header": "Rubrik",
"Theme": "Tema",
"Theme Options": "Temaalternativ",
"Select Theme": "Välj tema",
@@ -66,10 +69,10 @@
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink Connected": "Nxlink ansluten",
"Nxlink Upload": "Nxlink uppladdning",
"Nxlink Upload": "Nxlink överför",
"Nxlink Finished": "Nxlink klar",
"Switch-Handheld!": "",
"Switch-Docked!": "",
"Switch-Handheld!": "Switch Handhållen!",
"Switch-Docked!": "Switch Dockad!",
"Language": "Språk",
"Auto": "Auto",
"English": "Engelska",
@@ -89,9 +92,9 @@
"Misc": "Övrigt",
"Misc Options": "Övriga alternativ",
"Web": "Webb",
"Install forwarders": "",
"Install location": "",
"Show install warning": "",
"Install forwarders": "Installera genvägar",
"Install location": "Installationsplats",
"Show install warning": "Visa installationsvarning",
"FileBrowser": "Filbläddrare",
"%zd files": "%zd filer",
@@ -114,28 +117,30 @@
"Create Folder": "Skapa mapp",
"Set Folder Name": "Ange mappnamn",
"View as text (unfinished)": "Visa som text (ofärdig)",
"Ignore read only": "Ignorera skrivskydd",
"Mount": "Montera",
"Empty...": "Tom...",
"Open with DayBreak?": "Öppna med DayBreak?",
"Launch ": "",
"Launch ": "Starta ",
"Launch option for: ": "Startalternativ för: ",
"Select launcher for: ": "",
"Select launcher for: ": "Välj startprogram för: ",
"Homebrew": "Homebrew",
"Homebrew Options": "Homebrew-alternativ",
"Hide Sphaira": "Dölj Sphaira",
"Install Forwarder": "Installera forwarder",
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera forwarders leder till en avstängning!",
"Installing Forwarder": "Installerar forwarder",
"Install Forwarder": "Installera genväg",
"WARNING: Installing forwarders will lead to a ban!": "VARNING: Att installera genvägar kan leda till avstängning!",
"Installing Forwarder": "Installerar genväg",
"Creating Program": "Skapar program",
"Creating Control": "Skapar kontroll",
"Creating Meta": "Skapar meta",
"Creating Meta": "Skapar metadata",
"Writing Nca": "Skriver Nca",
"Updating ncm databse": "Uppdaterar ncm-databas",
"Pushing application record": "Lägger till applikationspost",
"Pushing application record": "Skickar programpost",
"Installed!": "Installerad!",
"Failed to install forwarder": "Misslyckades med att installera forwarder",
"Unstarred ": "",
"Starred ": "",
"Failed to install forwarder": "Misslyckades att installera genväg",
"Unstarred ": "Avmarkerad ",
"Starred ": "Markerad ",
"AppStore": "AppStore",
"Filter: %s | Sort: %s | Order: %s": "Filter: %s | Sortering: %s | Ordning: %s",
@@ -151,21 +156,21 @@
"category: %s": "kategori: %s",
"extracted: %.2f MiB": "extraherad: %.2f MiB",
"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",
"Irs": "Irs",
"Ambient Noise Level: ": "Omgivningsljudnivå: ",
"Controller": "Kontroll",
"Controller": "Kontroller",
"Pad ": "Handkontroll ",
" (Available)": " (Tillgänglig)",
" (Unsupported)": "",
" (Unsupported)": " (Ej stödd)",
" (Unconnected)": " (Ej ansluten)",
"HandHeld": "Handhållen",
"Rotation": "Rotation",
"0 (Sideways)": "0 (Sido)",
"0 (Sideways)": "0 (Sidan)",
"90 (Flat)": "90 (Platt)",
"180 (-Sideways)": "180 (-Sido)",
"180 (-Sideways)": "180 (-Sidan)",
"270 (Upside down)": "270 (Upp och ner)",
"Colour": "Färg",
"Grey": "Grå",
@@ -175,8 +180,8 @@
"Blue": "Blå",
"Light Target": "Ljusmål",
"All leds": "Alla lysdioder",
"Bright group": "Ljusstark grupp",
"Dim group": "Dämpad grupp",
"Bright group": "Ljus grupp",
"Dim group": "Dimma grupp",
"None": "Ingen",
"Gain": "Förstärkning",
"Negative Image": "Negativ bild",
@@ -188,9 +193,9 @@
"80x60": "80×60",
"40x30": "40×30",
"20x15": "20×15",
"Trimming Format": "Trimformat",
"External Light Filter": "Extern ljusfilter",
"Load Default": "Ladda standardinställningar",
"Trimming Format": "Trimningsformat",
"External Light Filter": "Externt ljusfilter",
"Load Default": "Ladda standard",
"Themezer": "Themezer",
"Themezer Options": "Themezer-alternativ",
@@ -201,9 +206,9 @@
"Bad Page": "Ogiltig sida",
"Download theme?": "Ladda ner tema?",
"GitHub": "",
"Downloading json": "",
"Select asset to download for ": "",
"GitHub": "GitHub",
"Downloading json": "Laddar ner JSON",
"Select asset to download for ": "Välj tillgång att ladda ner för ",
"Installing ": "Installerar ",
"Uninstalling ": "Avinstallerar ",
@@ -215,24 +220,30 @@
"Scanning ": "Skannar ",
"Creating ": "Skapar ",
"Copying ": "Kopierar ",
"Trying to load ": "",
"Trying to load ": "Försöker ladda ",
"Downloading ": "Laddar ner ",
"Downloaded ": "",
"Removed ": "",
"Downloaded ": "Nedladdad ",
"Removed ": "Borttagen ",
"Checking MD5": "Kontrollerar MD5",
"Loading...": "Laddar...",
"Loading": "Laddar",
"Empty!": "Tomt!",
"Not Ready...": "Ej redo...",
"Not Ready...": "Inte redo...",
"Error loading page!": "Fel vid laddning av sida!",
"Update avaliable: ": "Uppdatering tillgänglig: ",
"Download update: ": "Ladda ner uppdatering: ",
"Updated to ": "",
"Restart Sphaira?": "",
"Failed to download update": "Misslyckades med att ladda ner uppdatering",
"Updated to ": "Uppdaterad till ",
"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?",
"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 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.",
}

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

@@ -0,0 +1,250 @@
{
"[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",
"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",
"Decending": "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",
"Vietnamese": "Việt Nam",
"Japanese": "日本語",
"French": "Français",
"German": "Deutsch",
"Italian": "Italiano",
"Spanish": "Español",
"Chinese": "中文",
"Korean": "한국어",
"Dutch": "Dutch",
"Portuguese": "Português",
"Russian": "Русский",
"Swedish": "Svenska",
"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",
"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 ",
"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": "信息",
"Install": "安装",
"Delete": "删除",
"Restart": "",
"Restart": "重启",
"Changelog": "更新日志",
"Details": "详情",
"Update": "更新",
"Remove": "删除",
"Restore": "恢复",
"Download": "下载",
"Next Page": "下一页",
"Prev Page": "上一页",
@@ -26,6 +27,9 @@
"Star": "星标",
"System memory": "主机内存",
"microSD card": "SD卡",
"Sd": "SD卡",
"Image System memory": "主机内存图像",
"Image microSD card": "SD卡图像",
"Yes": "是",
"No": "否",
"Enabled": "启用",
@@ -54,7 +58,6 @@
"Asc": "升序",
"Menu Options": "菜单选项",
"Header": "标题",
"Theme": "主题",
"Theme Options": "主题选项",
"Select Theme": "选择主题",
@@ -64,12 +67,12 @@
"Network Options": "网络选项",
"Ftp": "FTP",
"Mtp": "MTP",
"Nxlink": "Nxlink",
"Nxlink": "Nxlink插件提交",
"Nxlink Connected": "Nxlink 已连接",
"Nxlink Upload": "Nxlink 上传中",
"Nxlink Finished": "Nxlink 已结束",
"Switch-Handheld!": "",
"Switch-Docked!": "",
"Switch-Handheld!": "切换至掌机模式!",
"Switch-Docked!": "切换至底座模式!",
"Language": "语言",
"Auto": "自动",
"English": "English",
@@ -86,8 +89,8 @@
"Swedish": "Svenska",
"Logging": "日志",
"Replace hbmenu on exit": "退出后用Sphaira替换hbmenu",
"Misc": "杂项",
"Misc Options": "杂项设置",
"Misc": "拓展",
"Misc Options": "拓展设置",
"Web": "网页浏览器",
"Install forwarders": "允许安装前端应用",
"Install location": "安装位置",
@@ -114,11 +117,13 @@
"Create Folder": "新建文件夹",
"Set Folder Name": "输入文件夹名",
"View as text (unfinished)": "以文本形式查看(未完善)",
"Ignore read only": "忽略只读",
"Mount": "挂载",
"Empty...": "空...",
"Open with DayBreak?": "使用DayBreak打开",
"Launch ": "",
"Launch ": "启动 ",
"Launch option for: ": "启动选项:",
"Select launcher for: ": "",
"Select launcher for: ": "选择启动器用于:",
"Homebrew": "应用列表",
"Homebrew Options": "应用选项",
@@ -134,8 +139,8 @@
"Pushing application record": "正在推送应用记录",
"Installed!": "安装完成!",
"Failed to install forwarder": "前端应用安装失败",
"Unstarred ": "",
"Starred ": "",
"Unstarred ": "取消星标 ",
"Starred ": "已星标 ",
"AppStore": "应用商店",
"Filter: %s | Sort: %s | Order: %s": "筛选: %s | 排序: %s | 顺序: %s",
@@ -178,7 +183,7 @@
"Bright group": "亮色组",
"Dim group": "暗色组",
"None": "无",
"Gain": "增益",
"Gain": "曝光",
"Negative Image": "负片图像",
"Normal image": "正常图像",
"Negative image": "负片图像",
@@ -201,9 +206,9 @@
"Bad Page": "错误的页面",
"Download theme?": "下载该主题?",
"GitHub": "",
"Downloading json": "",
"Select asset to download for ": "",
"GitHub": "GitHub",
"Downloading json": "正在下载 json",
"Select asset to download for ": "选择要下载的资源用于 ",
"Installing ": "正在安装 ",
"Uninstalling ": "正在卸载 ",
@@ -215,24 +220,30 @@
"Scanning ": "正在扫描 ",
"Creating ": "正在创建 ",
"Copying ": "正在复制 ",
"Trying to load ": "",
"Trying to load ": "尝试加载 ",
"Downloading ": "正在下载 ",
"Downloaded ": "",
"Removed ": "",
"Downloaded ": "已下载 ",
"Removed ": "已移除 ",
"Checking MD5": "正在校验 MD5",
"Loading...": "加载中...",
"Loading": "加载中",
"Empty!": "空!",
"Empty!": "空空如野",
"Not Ready...": "尚未准备好...",
"Error loading page!": "页面加载失败!",
"Update avaliable: ": "有可用更新!",
"Download update: ": "下载更新:",
"Updated to ": "",
"Restart Sphaira?": "",
"Updated to ": "更新至 ",
"Restart Sphaira?": "重启 Sphaira",
"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?": "删除选中的文件?",
"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.": ""
"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]
name=Abyss
author=TotalJustice
version=1.0.0
; unused currently
preview=romfs:/theme/preview.jpg
version=1.1.0
inherit=romfs:/themes/base_black_theme.ini
[theme]
background=0x0f111aff
background = 0x0f111a
grid = 0x0f115c30
selected=0x0f115cff
selected_overlay=0x529cffff
text=0xffbc41ff
text_selected=0x529cffff
popup = 0x0f115c
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
line = 0xffbc41
line_seperator = 0xffbc41
text = 0xffbc41
text_info = 0xd79f36
text_selected = 0x529cff
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]
name=Black
author=TotalJustice
version=1.0.0
preview=romfs:/theme/preview.jpg
[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
version=1.1.0
inherit=romfs:/themes/base_black_theme.ini

View File

@@ -1,23 +1,13 @@
[meta]
name=OLED Black
author=iTotalJustice/Sanras
version=1.0.0
preview=romfs:/theme/preview.jpg
author=TotalJustice/Sanras
version=1.1.0
inherit=romfs:/themes/base_black_theme.ini
[theme]
background=0x000000ff
cursor=romfs:/theme/cursor.png
cursor_drag=romfs:/theme/cursor_drag.png
background = 0x000000
grid = 0x46464640
selected=0x323232ff
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
popup = 0x323232
text = 0xfbfbfb
text_selected = 0x00ffc8
selected_background = 0x323232

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)
set(sphaira_VERSION 0.5.0)
set(sphaira_VERSION 0.6.0)
project(sphaira
VERSION ${sphaira_VERSION}
@@ -51,13 +51,13 @@ add_executable(sphaira
source/ui/notification.cpp
source/ui/nvg_util.cpp
source/ui/option_box.cpp
source/ui/option_list.cpp
source/ui/popup_list.cpp
source/ui/progress_box.cpp
source/ui/scrollable_text.cpp
source/ui/scrollbar.cpp
source/ui/sidebar.cpp
source/ui/widget.cpp
source/ui/list.cpp
source/ui/bubbles.cpp
source/app.cpp
source/download.cpp
@@ -96,7 +96,7 @@ FetchContent_Declare(libhaze
FetchContent_Declare(libpulsar
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
GIT_TAG d729be3
GIT_TAG de656e4
)
FetchContent_Declare(nanovg
@@ -116,7 +116,7 @@ FetchContent_Declare(yyjson
FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 63ec295
GIT_TAG 11cac8b
)
set(MININI_LIB_NAME minIni)
@@ -227,39 +227,6 @@ set_target_properties(libhaze PROPERTIES
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)
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})

View File

@@ -25,6 +25,7 @@ enum SoundEffect {
SoundEffect_Startup,
SoundEffect_Install,
SoundEffect_Error,
SoundEffect_MAX,
};
enum class LaunchType {
@@ -33,7 +34,9 @@ enum class LaunchType {
Forwader_Sphaira,
};
// todo: why is this global???
void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
void DrawElement(const Vec4&, ThemeEntryID id);
class App {
public:
@@ -56,8 +59,8 @@ public:
static void NotifyFlashLed();
static auto GetThemeMetaList() -> std::span<ThemeMeta>;
static void SetTheme(u64 theme_index);
static auto GetThemeIndex() -> u64;
static void SetTheme(s64 theme_index);
static auto GetThemeIndex() -> s64;
static auto GetDefaultImage(int* w = nullptr, int* h = nullptr) -> int;
@@ -77,6 +80,7 @@ public:
static auto GetThemeShuffleEnable() -> bool;
static auto GetThemeMusicEnable() -> bool;
static auto GetLanguage() -> long;
static auto GetTextScrollSpeed() -> long;
static void SetMtpEnable(bool enable);
static void SetFtpEnable(bool enable);
@@ -89,6 +93,7 @@ public:
static void SetThemeShuffleEnable(bool enable);
static void SetThemeMusicEnable(bool enable);
static void SetLanguage(long index);
static void SetTextScrollSpeed(long index);
static auto Install(OwoConfig& config) -> Result;
static auto Install(ui::ProgressBox* pbox, OwoConfig& config) -> Result;
@@ -102,9 +107,9 @@ public:
// void DrawElement(float x, float y, float w, float h, ui::ThemeEntryID id);
auto LoadElementImage(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 ScanThemes(const std::string& path);
void ScanThemeEntries();
@@ -143,7 +148,7 @@ public:
Theme m_theme{};
fs::FsPath theme_path{};
std::size_t m_theme_index{};
s64 m_theme_index{};
bool m_quit{};
@@ -158,9 +163,11 @@ public:
option::OptionBool m_theme_shuffle{INI_SECTION, "theme_shuffle", false};
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
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[24]{};
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
private: // from nanovg decko3d example by adubbz
static constexpr unsigned NumFramebuffers = 2;

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

@@ -10,7 +10,6 @@ public:
ErrorBox(Result code, const std::string& message);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
private:

View File

@@ -0,0 +1,57 @@
#pragma once
#include "ui/object.hpp"
namespace sphaira::ui {
struct List final : Object {
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
using TouchCallback = std::function<void(s64 index)>;
List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad = {});
void OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback);
void Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
auto SetScrollBarPos(float x, float y, float h) {
m_scrollbar.x = x;
m_scrollbar.y = y;
m_scrollbar.h = h;
}
auto ScrollDown(s64& index, s64 step, s64 count) -> bool;
auto ScrollUp(s64& index, s64 step, s64 count) -> bool;
auto GetYoff() const {
return m_yoff;
}
void SetYoff(float y = 0) {
m_yoff = y;
}
auto GetMaxY() const {
return m_v.h + m_pad.y;
}
private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override {}
auto ClampY(float y, s64 count) const -> float;
private:
const s64 m_row;
const s64 m_page;
Vec4 m_v;
Vec2 m_pad;
Vec4 m_scrollbar{};
// current y offset.
float m_yoff{};
// in progress y offset, used when scrolling.
float m_y_prog{};
};
} // namespace sphaira::ui

View File

@@ -2,6 +2,7 @@
#include "ui/menus/menu_base.hpp"
#include "ui/scrollable_text.hpp"
#include "ui/list.hpp"
#include "nro.hpp"
#include "fs.hpp"
#include <span>
@@ -77,7 +78,7 @@ struct EntryMenu final : MenuBase {
// void OnFocusGained() override;
void ShowChangelogAction();
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void UpdateOptions();
@@ -97,10 +98,10 @@ private:
const LazyImage& m_default_icon;
Menu& m_menu;
std::size_t m_index{}; // where i am in the array
s64 m_index{}; // where i am in the array
std::vector<Option> m_options;
LazyImage m_banner;
std::vector<LazyImage> m_screens;
std::unique_ptr<List> m_list;
std::shared_ptr<ScrollableText> m_details;
std::shared_ptr<ScrollableText> m_changelog;
@@ -149,7 +150,7 @@ struct FeedbackMenu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
@@ -157,8 +158,7 @@ private:
const std::vector<Entry>& m_package_entries;
LazyImage& m_default_image;
std::vector<FeedbackEntry> m_entries;
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
s64 m_index{}; // where i am in the array
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
};
@@ -170,7 +170,7 @@ struct Menu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void ScanHomebrew();
void Sort();
@@ -201,19 +201,19 @@ private:
SortType m_sort{SortType::SortType_Updated};
OrderType m_order{OrderType::OrderType_Decending};
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
s64 m_index{}; // where i am in the array
LazyImage m_default_image;
LazyImage m_update;
LazyImage m_get;
LazyImage m_local;
LazyImage m_installed;
ImageDownloadState m_repo_download_state{ImageDownloadState::None};
std::unique_ptr<List> m_list;
std::string m_search_term;
std::string m_author_term;
u64 m_entry_search_jump_back{};
u64 m_entry_author_jump_back{};
s64 m_entry_search_jump_back{};
s64 m_entry_author_jump_back{};
bool m_is_search{};
bool m_is_author{};
bool m_dirty{}; // if set, does a sort

View File

@@ -23,8 +23,8 @@ private:
std::unique_ptr<ScrollableText> m_scroll_text;
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
s64 m_start{};
s64 m_index{}; // where i am in the array
};
} // namespace sphaira::ui::menu::fileview

View File

@@ -1,6 +1,7 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/list.hpp"
#include "nro.hpp"
#include "fs.hpp"
#include "option.hpp"
@@ -92,9 +93,9 @@ struct FileAssocEntry {
struct LastFile {
fs::FsPath name;
u64 index;
u64 offset;
u64 entries_count;
s64 index;
float offset;
s64 entries_count;
};
struct FsDirCollection {
@@ -119,7 +120,7 @@ struct Menu final : MenuBase {
}
private:
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void InstallForwarder();
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
@@ -131,7 +132,7 @@ private:
return GetNewPath(m_path, entry.name);
}
auto GetNewPath(u64 index) const -> fs::FsPath {
auto GetNewPath(s64 index) const -> fs::FsPath {
return GetNewPath(m_path, GetEntry(index).name);
}
@@ -239,6 +240,7 @@ private:
std::vector<u32> m_entries_index_search; // files found via search
std::span<u32> m_entries_current;
std::unique_ptr<List> m_list;
std::optional<fs::FsPath> m_daybreak_path;
// search options
@@ -255,9 +257,8 @@ private:
// if it does, the index becomes that file.
std::vector<LastFile> m_previous_highlighted_file;
fs::FsPath m_selected_path;
std::size_t m_index{};
std::size_t m_index_offset{};
std::size_t m_selected_count{};
s64 m_index{};
s64 m_selected_count{};
SelectedType m_selected_type{SelectedType::None};
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};

View File

@@ -1,6 +1,7 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/list.hpp"
#include "fs.hpp"
#include "option.hpp"
#include <vector>
@@ -49,7 +50,7 @@ struct Menu final : MenuBase {
void OnFocusGained() override;
private:
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void Scan();
void LoadEntriesFromPath(const fs::FsPath& path);
@@ -66,8 +67,8 @@ private:
private:
std::vector<Entry> m_entries;
std::size_t m_index{};
std::size_t m_index_offset{};
s64 m_index{};
std::unique_ptr<List> m_list;
};
} // namespace sphaira::ui::menu::gh

View File

@@ -1,6 +1,7 @@
#pragma once
#include "ui/menus/menu_base.hpp"
#include "ui/list.hpp"
#include "nro.hpp"
#include "fs.hpp"
#include "option.hpp"
@@ -29,7 +30,7 @@ struct Menu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void InstallHomebrew();
void ScanHomebrew();
void Sort();
@@ -50,8 +51,8 @@ private:
static constexpr inline const char* INI_SECTION = "homebrew";
std::vector<NroEntry> m_entries;
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
s64 m_index{}; // where i am in the array
std::unique_ptr<List> m_list;
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Decending};

View File

@@ -61,7 +61,7 @@ private:
Rotation m_rotation{Rotation_90};
Colour m_colour{Colour_Grey};
int m_image{};
std::size_t m_index{};
s64 m_index{};
};
} // namespace sphaira::ui::menu::irs

View File

@@ -34,8 +34,7 @@ struct MainMenu final : Widget {
private:
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
void AddOnLPress();
void AddOnRPress();
void AddOnLRPress();
private:
std::shared_ptr<homebrew::Menu> m_homebrew_menu{};

View File

@@ -21,9 +21,6 @@ struct MenuBase : Widget {
void SetTitleSubHeading(std::string sub_heading);
void SetSubHeading(std::string sub_heading);
static auto ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool;
static auto ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool;
private:
void UpdateVars();

View File

@@ -2,6 +2,7 @@
#include "ui/menus/menu_base.hpp"
#include "ui/scrollable_text.hpp"
#include "ui/list.hpp"
#include "option.hpp"
#include <span>
@@ -164,8 +165,11 @@ struct Menu final : MenuBase {
void Draw(NVGcontext* vg, Theme* theme) override;
void OnFocusGained() override;
void SetIndex(std::size_t index) {
void SetIndex(s64 index) {
m_index = index;
if (!m_index) {
m_list->SetYoff(0);
}
}
// void SetSearch(const std::string& term);
@@ -180,13 +184,13 @@ private:
static constexpr inline u32 MAX_ON_PAGE = 16; // same as website
std::vector<PageEntry> m_pages;
std::size_t m_page_index{};
std::size_t m_page_index_max{1};
s64 m_page_index{};
s64 m_page_index_max{1};
std::string m_search{};
std::size_t m_start{};
std::size_t m_index{}; // where i am in the array
s64 m_index{}; // where i am in the array
std::unique_ptr<List> m_list;
// options
option::OptionLong m_sort{INI_SECTION, "sort", 0};

View File

@@ -19,7 +19,6 @@ public:
auto IsDone() const noexcept { return m_count == 0; }
private:
void OnLayoutChange() override;
void Draw(NVGcontext* vg, Theme* theme) override;
private:
@@ -34,7 +33,6 @@ public:
NotifMananger() = default;
~NotifMananger() = default;
void OnLayoutChange() override;
void Draw(NVGcontext* vg, Theme* theme) override;
void Push(const NotifEntry& entry);

View File

@@ -1,90 +1,41 @@
#pragma once
#include "nanovg.h"
#include "ui/widget.hpp"
#include "ui/types.hpp"
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*, 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*, Vec4 v, int texture);
auto getColour(Colour c) -> NVGcolor;
void drawImageRounded(NVGcontext*, const Vec4& v, int texture);
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*, Vec4 vec, const NVGcolor& c, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGcolor&& c, bool rounded = false);
void drawRect(NVGcontext*, const Vec4& v, 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*, Vec4 vec, const NVGpaint& p, bool rounded = false);
void drawRect(NVGcontext*, Vec4 vec, const NVGpaint&& p, bool rounded = false);
void drawRect(NVGcontext*, const Vec4& v, 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*, float size, const NVGcolor& out_col, Vec4 vec, Colour 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, 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 drawRectOutline(NVGcontext*, const Theme*, float size, float x, float y, float w, float h);
void drawRectOutline(NVGcontext*, const Theme*, float size, const Vec4& v);
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 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*, Vec2 vec, 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*, 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 drawText(NVGcontext*, const Vec2& v, 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 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, 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 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 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*;
void drawButton(NVGcontext* vg, float x, float y, float size, Button button);
void drawButtons(NVGcontext* vg, const Widget::Actions& actions, const NVGcolor& c, float start_x = 1220.f);
void drawScrollbar(NVGcontext*, const Theme*, 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 drawDimBackground(NVGcontext* vg);
void drawScrollbar2(NVGcontext*, const Theme*, float x, float y, float h, 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 getHighlightAnimation(float* gradientX, float* gradientY, float* color);

View File

@@ -9,8 +9,6 @@ public:
Object() = default;
virtual ~Object() = default;
// virtual auto OnLayoutChange() -> void = 0;
virtual auto OnLayoutChange() -> void {};
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void = 0;
auto GetPos() const noexcept {

View File

@@ -12,7 +12,6 @@ public:
OptionBoxEntry(const std::string& text, Vec4 pos);
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
auto OnLayoutChange() -> void override {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Selected(bool enable) -> void;
@@ -28,25 +27,25 @@ private:
// todo: support upto 4 options.
class OptionBox final : public Widget {
public:
using Callback = std::function<void(std::optional<std::size_t> index)>;
using Callback = std::function<void(std::optional<s64> index)>;
using Option = std::string;
using Options = std::vector<Option>;
public:
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}); // confirm
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb); // yesno
OptionBox(const std::string& message, const Option& a, const Option& b, std::size_t index, Callback cb); // yesno
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb); // yesno
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, Callback cb); // tri
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, std::size_t index, Callback cb); // tri
OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb); // tri
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private:
auto Setup(std::size_t index) -> void; // common setup values
auto Setup(s64 index) -> void; // common setup values
void SetIndex(s64 index);
private:
std::string m_message;
@@ -54,7 +53,7 @@ private:
Vec4 m_spacer_line{};
std::size_t m_index{};
s64 m_index{};
std::vector<OptionBoxEntry> m_entries;
};

View File

@@ -1,27 +0,0 @@
#pragma once
#include "ui/widget.hpp"
#include <optional>
namespace sphaira::ui {
class OptionList final : public Widget {
public:
using Options = std::vector<std::pair<std::string, std::function<void()>>>;
public:
OptionList(Options _options);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
protected:
Options m_options;
std::size_t m_index{};
private:
};
} // namespace sphaira::ui

View File

@@ -1,7 +1,7 @@
#pragma once
#include "ui/widget.hpp"
#include "ui/scrollbar.hpp"
#include "ui/list.hpp"
#include <optional>
namespace sphaira::ui {
@@ -9,21 +9,23 @@ namespace sphaira::ui {
class PopupList final : public Widget {
public:
using Items = std::vector<std::string>;
using Callback = std::function<void(std::optional<std::size_t>)>;
using Callback = std::function<void(std::optional<s64>)>;
public:
explicit PopupList(std::string title, Items items, Callback cb, std::size_t index = 0);
explicit PopupList(std::string title, Items items, Callback cb, s64 index = 0);
PopupList(std::string title, Items items, Callback cb, std::string index);
PopupList(std::string title, Items items, std::string& index_str_ref, std::size_t& index);
PopupList(std::string title, Items items, std::string& index_str_ref, s64& index);
PopupList(std::string title, Items items, std::string& index_ref);
PopupList(std::string title, Items items, std::size_t& index_ref);
PopupList(std::string title, Items items, s64& index_ref);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private:
void SetIndex(s64 index);
private:
static constexpr Vec2 m_title_pos{70.f, 28.f};
static constexpr Vec4 m_block{280.f, 110.f, 720.f, 60.f};
@@ -33,17 +35,13 @@ private:
std::string m_title;
Items m_items;
Callback m_callback;
std::size_t m_index; // index in list array
std::size_t m_index_offset{}; // drawing from array start
s64 m_index; // index in list array
// std::size_t& index_ref;
// std::string& index_str_ref;
std::unique_ptr<List> m_list;
float m_selected_y{};
float m_yoff{};
float m_line_top{};
float m_line_bottom{};
ScrollBar m_scrollbar;
};
} // namespace sphaira::ui

View File

@@ -22,7 +22,7 @@ struct ProgressBox final : Widget {
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
auto UpdateTransfer(u64 offset, u64 size) -> ProgressBox&;
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
void RequestExit();
auto ShouldExit() -> bool;
@@ -55,8 +55,8 @@ private:
ProgressBoxDoneCallback m_done{};
std::string m_title{};
std::string m_transfer{};
u64 m_size{};
u64 m_offset{};
s64 m_size{};
s64 m_offset{};
bool m_exit_requested{};
};

View File

@@ -1,34 +0,0 @@
#pragma once
#include "ui/widget.hpp"
namespace sphaira::ui {
class ScrollBar final : public Widget {
public:
enum class Direction { DOWN, UP };
public:
ScrollBar() = default;
ScrollBar(Vec4 bounds, float entry_height, std::size_t entries);
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
auto OnLayoutChange() -> void override;
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto Setup(Vec4 bounds, float entry_height, std::size_t entries) -> void;
auto Move(Direction direction) -> void;
private:
auto Setup() -> void;
private:
Vec4 m_bounds{};
std::size_t m_entries{};
std::size_t m_index{};
float m_entry_height{};
float m_step_size{};
bool m_should_draw{false};
};
} // namespace sphaira::ui

View File

@@ -1,6 +1,7 @@
#pragma once
#include "ui/widget.hpp"
#include "ui/list.hpp"
#include <memory>
namespace sphaira::ui {
@@ -9,11 +10,9 @@ class SidebarEntryBase : public Widget {
public:
SidebarEntryBase(std::string&& title);
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
virtual auto OnLayoutChange() -> void override {}
protected:
std::string m_title;
Vec2 m_offset{};
};
class SidebarEntryBool final : public SidebarEntryBase {
@@ -24,9 +23,9 @@ public:
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", std::string false_str = "Off");
SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off");
private:
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
private:
bool m_option;
Callback m_callback;
std::string m_true_str;
@@ -50,20 +49,24 @@ class SidebarEntryArray final : public SidebarEntryBase {
public:
using Items = std::vector<std::string>;
using ListCallback = std::function<void()>;
using Callback = std::function<void(std::size_t& index)>;
using Callback = std::function<void(s64& index)>;
public:
explicit SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index = 0);
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0);
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
SidebarEntryArray(std::string title, Items items, std::string& index);
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
private:
Items m_items;
ListCallback m_list_callback;
Callback m_callback;
std::size_t m_index;
s64 m_index;
s64 m_tick{};
float m_text_yoff{};
};
template <typename T>
@@ -101,33 +104,30 @@ public:
Sidebar(std::string title, std::string sub, Side side);
auto Update(Controller* controller, TouchInfo* touch) -> void override;
auto OnLayoutChange() -> void override {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
auto OnFocusGained() noexcept -> void override;
auto OnFocusLost() noexcept -> void override;
void Add(std::shared_ptr<SidebarEntryBase> entry);
void AddSpacer();
void AddHeader(std::string name);
private:
void SetIndex(std::size_t index);
void SetIndex(s64 index);
void SetupButtons();
private:
std::string m_title;
std::string m_sub;
Side m_side;
Items m_items;
std::size_t m_index{};
std::size_t m_index_offset{};
s64 m_index{};
std::unique_ptr<List> m_list;
Vec4 m_top_bar{};
Vec4 m_bottom_bar{};
Vec2 m_title_pos{};
Vec4 m_base_pos{};
float m_selected_y{};
static constexpr float m_title_size{28.f};
// static constexpr Vec2 box_size{380.f, 70.f};
static constexpr Vec2 m_box_size{400.f, 70.f};

View File

@@ -162,21 +162,55 @@ struct ElementEntry {
};
enum ThemeEntryID {
// colour of the background, can be an image.
ThemeEntryID_BACKGROUND,
// colour of the grid background (homebrew, appstore), can be an image.
ThemeEntryID_GRID,
ThemeEntryID_SELECTED,
ThemeEntryID_SELECTED_OVERLAY,
ThemeEntryID_TEXT,
ThemeEntryID_TEXT_SELECTED,
// background colour of a popup.
ThemeEntryID_POPUP,
// colour of the error text / button.
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_VIDEO,
ThemeEntryID_ICON_IMAGE,
ThemeEntryID_ICON_FILE,
ThemeEntryID_ICON_FOLDER,
ThemeEntryID_ICON_ZIP,
ThemeEntryID_ICON_GAME,
ThemeEntryID_ICON_NRO,
ThemeEntryID_MAX,
@@ -186,49 +220,43 @@ struct ThemeMeta {
std::string name;
std::string author;
std::string version;
std::string ini_path;
fs::FsPath inherit;
fs::FsPath ini_path;
};
struct Theme {
std::string name;
std::string author;
std::string version;
fs::FsPath path;
ThemeMeta meta;
PLSR_BFSTM music;
ElementEntry elements[ThemeEntryID_MAX];
// NVGcolor background; // bg
// NVGcolor lines; // grid lines
// NVGcolor spacer; // lines in popup box
// NVGcolor text; // text colour
// NVGcolor text_info; // description text
NVGcolor selected; // selected colours
// NVGcolor overlay; // popup overlay colour
// void DrawElement(float x, float y, float w, float h, ThemeEntryID id);
auto GetColour(ThemeEntryID id) const {
return elements[id].colour;
}
};
enum class TouchState {
Start, // set when touch has started
Touching, // set when touch is held longer than 1 frame
Stop, // set after touch is released
None, // set when there is no touch
};
// enum class TouchGesture {
// None,
// Tap,
// Scroll,
// };
struct TouchInfo {
s32 initial_x;
s32 initial_y;
HidTouchState initial;
HidTouchState cur;
s32 cur_x;
s32 cur_y;
auto in_range(const Vec4& v) const -> bool {
return cur.x >= v.x && cur.x <= v.x + v.w && cur.y >= v.y && cur.y <= v.y + v.h;
}
s32 prev_x;
s32 prev_y;
u32 finger_id;
auto in_range(s32 x, s32 y, s32 w, s32 h) const -> bool {
return in_range(Vec4(x, y, w, h));
}
bool is_touching;
bool is_tap;
bool is_scroll;
bool is_clicked;
bool is_end;
};
enum class Button : u64 {

View File

@@ -8,7 +8,20 @@
namespace sphaira::ui {
struct uiButton final : Object {
uiButton(Button button, Action action) : m_button{button}, m_action{action} {}
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
Button m_button;
Action m_action;
Vec4 m_button_pos{};
Vec4 m_hint_pos{};
};
struct Widget : public Object {
using Actions = std::map<Button, Action>;
using uiButtons = std::vector<uiButton>;
virtual ~Widget() = default;
virtual void Update(Controller* controller, TouchInfo* touch);
@@ -49,6 +62,8 @@ struct Widget : public Object {
m_actions.clear();
}
auto FireAction(Button button, u8 type = ActionType::DOWN) -> bool;
void SetPop(bool pop = true) {
m_pop = pop;
}
@@ -57,9 +72,14 @@ struct Widget : public Object {
return m_pop;
}
using Actions = std::map<Button, Action>;
// using Actions = std::unordered_map<Button, Action>;
auto SetUiButtonPos(Vec2 pos) {
m_button_pos = pos;
}
auto GetUiButtons() const -> uiButtons;
Actions m_actions;
Vec2 m_button_pos{1220, 675};
bool m_focus{false};
bool m_pop{false};
};

View File

@@ -1,6 +1,7 @@
#include "ui/menus/main_menu.hpp"
#include "ui/error_box.hpp"
#include "ui/option_box.hpp"
#include "ui/bubbles.hpp"
#include "app.hpp"
#include "log.hpp"
@@ -33,6 +34,45 @@ extern "C" {
namespace sphaira {
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};
};
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{};
void deko3d_error_cb(void* userData, const char* context, DkResult result, const char* message) {
@@ -203,6 +243,95 @@ auto GetNroIcon(const std::vector<u8>& nro_icon) -> std::vector<u8> {
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);
}
}
}
void haze_callback(const HazeCallbackData *data) {
App::NotifyFlashLed();
evman::push(*data, false);
@@ -391,12 +520,12 @@ auto App::GetThemeMetaList() -> std::span<ThemeMeta> {
return g_app->m_theme_meta_entries;
}
void App::SetTheme(u64 theme_index) {
g_app->LoadTheme(g_app->m_theme_meta_entries[theme_index].ini_path.c_str());
void App::SetTheme(s64 theme_index) {
g_app->LoadTheme(g_app->m_theme_meta_entries[theme_index]);
g_app->m_theme_index = theme_index;
}
auto App::GetThemeIndex() -> u64 {
auto App::GetThemeIndex() -> s64 {
return g_app->m_theme_index;
}
@@ -456,6 +585,10 @@ auto App::GetLanguage() -> long {
return g_app->m_language.Get();
}
auto App::GetTextScrollSpeed() -> long {
return g_app->m_text_scroll_speed.Get();
}
void App::SetNxlinkEnable(bool enable) {
if (App::GetNxlinkEnable() != enable) {
g_app->m_nxlink_enabled.Set(enable);
@@ -626,8 +759,21 @@ void App::SetLanguage(long index) {
if (App::GetLanguage() != index) {
g_app->m_language.Set(index);
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 {
R_TRY(romfsInit());
@@ -701,41 +847,82 @@ void App::ExitRestart() {
void App::Poll() {
m_controller.Reset();
HidTouchScreenState state{};
hidGetTouchScreenStates(&state, 1);
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_Swipe) {
log_write("[SWIPE] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Tap) {
log_write("[TAP] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Press) {
log_write("[PRESS] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Cancel) {
log_write("[CANCEL] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Complete) {
log_write("[COMPLETE] got gesture type: %d direction: %d sampling_number: %d context_number: %d\n", gesture.type, gesture.direction, gesture.sampling_number, gesture.context_number);
}
else if (gesture_count && gesture.type == HidGestureType_Pan) {
log_write("[PAN] got gesture sampling_number: %d context_number: %d x: %d y: %d dx: %d dy: %d vx: %.2f vy: %.2f count: %d\n", 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) {
m_touch_info.initial = m_touch_info.cur = state.touches[0];
m_touch_info.is_touching = true;
m_touch_info.is_tap = true;
} else if (state.count >= 1 && m_touch_info.is_touching) {
m_touch_info.cur = state.touches[0];
if (m_touch_info.is_tap &&
(std::abs((s32)m_touch_info.initial.x - (s32)m_touch_info.cur.x) > 20 ||
std::abs((s32)m_touch_info.initial.y - (s32)m_touch_info.cur.y) > 20)) {
m_touch_info.is_tap = false;
m_touch_info.is_scroll = true;
}
} else if (m_touch_info.is_touching) {
m_touch_info.is_touching = false;
m_touch_info.is_scroll = false;
if (m_touch_info.is_tap) {
m_touch_info.is_clicked = true;
} else {
m_touch_info.is_end = true;
}
}
// todo: better implement this to match hos
if (!m_touch_info.is_touching && !m_touch_info.is_clicked) {
padUpdate(&m_pad);
m_controller.m_kdown = padGetButtonsDown(&m_pad);
m_controller.m_kheld = padGetButtons(&m_pad);
m_controller.m_kup = padGetButtonsUp(&m_pad);
m_controller.UpdateButtonHeld(static_cast<u64>(Button::ANY_DIRECTION));
HidTouchScreenState touch_state{};
hidGetTouchScreenStates(&touch_state, 1);
if (touch_state.count == 1 && !m_touch_info.is_touching) {
m_touch_info.initial_x = m_touch_info.prev_x = m_touch_info.cur_x = touch_state.touches[0].x;
m_touch_info.initial_y = m_touch_info.prev_y = m_touch_info.cur_y = touch_state.touches[0].y;
m_touch_info.finger_id = touch_state.touches[0].finger_id;
m_touch_info.is_touching = true;
m_touch_info.is_tap = true;
// PlaySoundEffect(SoundEffect_Limit);
} else if (touch_state.count >= 1 && m_touch_info.is_touching && m_touch_info.finger_id == touch_state.touches[0].finger_id) {
m_touch_info.prev_x = m_touch_info.cur_x;
m_touch_info.prev_y = m_touch_info.cur_y;
m_touch_info.cur_x = touch_state.touches[0].x;
m_touch_info.cur_y = touch_state.touches[0].y;
if (m_touch_info.is_tap &&
(std::abs(m_touch_info.initial_x - m_touch_info.cur_x) > 20 ||
std::abs(m_touch_info.initial_y - m_touch_info.cur_y) > 20)) {
m_touch_info.is_tap = false;
}
} else if (m_touch_info.is_touching) {
m_touch_info.is_touching = false;
// check if we clicked on anything, if so, handle it
if (m_touch_info.is_tap) {
// todo:
}
}
}
@@ -798,6 +985,7 @@ void App::Draw() {
}
m_notif_manager.Draw(vg, &m_theme);
ui::bubble::Draw(vg, &m_theme);
nvgResetTransform(vg);
nvgEndFrame(this->vg);
@@ -809,17 +997,27 @@ auto App::GetVg() -> NVGcontext* {
}
void DrawElement(float x, float y, float w, float h, ThemeEntryID id) {
DrawElement({x, y, w, h}, id);
}
void DrawElement(const Vec4& v, ThemeEntryID id) {
const auto& e = g_app->m_theme.elements[id];
switch (e.type) {
case ElementType::None: {
} break;
case ElementType::Texture: {
const auto paint = nvgImagePattern(g_app->vg, x, y, w, h, 0, e.texture, 1.f);
ui::gfx::drawRect(g_app->vg, x, y, w, h, paint);
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);
} break;
case ElementType::Colour: {
ui::gfx::drawRect(g_app->vg, x, y, w, h, e.colour);
ui::gfx::drawRect(g_app->vg, v, e.colour);
} break;
}
}
@@ -840,120 +1038,87 @@ auto App::LoadElementColour(std::string_view value) -> ElementEntry {
if (value.starts_with("0x")) {
value = value.substr(2);
} else if (value.starts_with('#')) {
value = value.substr(1);
} else {
return {};
}
char* end;
u32 c = std::strtoul(value.data(), &end, 16);
if (!c && value.data() == end) {
return {};
}
// force alpha bit if not already set.
if (value.length() <= 6) {
c <<= 8;
c |= 0xFF;
}
const u32 c = std::strtol(value.data(), nullptr, 16);
if (c) {
entry.colour = nvgRGBA((c >> 24) & 0xFF, (c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF);
entry.type = ElementType::Colour;
}
return entry;
}
auto App::LoadElement(std::string_view value) -> ElementEntry {
auto App::LoadElement(std::string_view value, ElementType type) -> ElementEntry {
if (value.size() <= 1) {
return {};
}
if (type == ElementType::None || type == ElementType::Colour) {
// most assets are colours, so prioritise this first
if (auto e = LoadElementColour(value); e.type != ElementType::None) {
return e;
}
}
if (type == ElementType::None || type == ElementType::Texture) {
if (auto e = LoadElementImage(value); e.type != ElementType::None) {
return e;
}
if (auto e = LoadElementColour(value); e.type != ElementType::None) {
return e;
}
return {};
}
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]) {
plsrPlayerFree(m_sound_ids[SoundEffect_Music]);
m_sound_ids[SoundEffect_Music] = nullptr;
plsrBFSTMClose(&m_theme.music);
}
for (auto& e : m_theme.elements) {
if (e.type == ElementType::Texture) {
nvgDeleteImage(vg, e.texture);
}
e.type = ElementType::None;
}
}
void App::LoadTheme(const fs::FsPath& path) {
m_theme = {};
}
void App::LoadTheme(const ThemeMeta& meta) {
// reset theme
CloseTheme();
m_theme.path = path;
const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
auto app = static_cast<App*>(UserData);
auto& theme = app->m_theme;
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;
};
ThemeData theme_data{};
LoadThemeInternal(meta, theme_data);
m_theme.meta = meta;
if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit());
if (!ini_browse(cb, this, path)) {
log_write("failed to open ini: %s\n", path);
} else {
log_write("opened ini: %s\n", path);
// load all assets / colours.
for (auto& e : THEME_ENTRIES) {
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);
}
}
}
}
}
@@ -983,42 +1148,12 @@ void App::ScanThemes(const std::string& path) {
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{};
char buf[FS_MAX_PATH]{};
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;
if (LoadThemeMeta(full_path, meta)) {
m_theme_meta_entries.emplace_back(meta);
}
}
}
void App::ScanThemeEntries() {
// load from romfs first
@@ -1129,6 +1264,7 @@ App::App(const char* argv0) {
ON_SCOPE_EXIT(romfsUnmount("qlaunch"));
plsrPlayerInit();
plsrBFSAROpen("qlaunch:/sound/qlaunch.bfsar", &m_qlaunch_bfsar);
ON_SCOPE_EXIT(plsrBFSARClose(&m_qlaunch_bfsar));
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconFocus", &m_sound_ids[SoundEffect_Focus]);
plsrPlayerLoadSoundByName(&m_qlaunch_bfsar, "SeGameIconScroll", &m_sound_ids[SoundEffect_Scroll]);
@@ -1147,16 +1283,29 @@ App::App(const char* argv0) {
ScanThemeEntries();
fs::FsPath theme_path{};
constexpr fs::FsPath default_theme_path{"romfs:/themes/abyss_theme.ini"};
if (App::GetThemeShuffleEnable() && m_theme_meta_entries.size()) {
theme_path = m_theme_meta_entries[randomGet64() % m_theme_meta_entries.size()].ini_path;
} 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
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;
break;
}
@@ -1165,6 +1314,7 @@ App::App(const char* argv0) {
appletHook(&m_appletHookCookie, appplet_hook_calback, this);
hidInitializeTouchScreen();
hidInitializeGesture();
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
// padInitializeDefault(&m_pad);
padInitializeAny(&m_pad);
@@ -1211,6 +1361,9 @@ App::App(const char* argv0) {
}
}
// soon (tm)
// ui::bubble::Init();
App::Push(std::make_shared<ui::menu::main::MainMenu>());
log_write("finished app constructor\n");
}
@@ -1234,6 +1387,8 @@ App::~App() {
i18n::exit();
curl::Exit();
ui::bubble::Exit();
// 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.
m_widgets.clear();
@@ -1241,7 +1396,7 @@ App::~App() {
appletUnhook(&m_appletHookCookie);
ini_puts("config", "theme", m_theme.path, CONFIG_PATH);
ini_puts("config", "theme", m_theme.meta.ini_path, CONFIG_PATH);
CloseTheme();

View File

@@ -38,7 +38,7 @@ Mutex g_mutex_share[CURL_LOCK_DATA_LAST]{};
struct DataStruct {
std::vector<u8> data;
u64 offset{};
s64 offset{};
FsFile f{};
s64 file_offset{};
};

View File

@@ -77,6 +77,7 @@ bool init(long index) {
case 10: setLanguage = SetLanguage_PT; break; // "Portuguese"
case 11: setLanguage = SetLanguage_RU; break; // "Russian"
case 12: lang_name = "se"; break; // "Swedish"
case 13: lang_name = "vi"; break; // "Vietnamese"
}
switch (setLanguage) {

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

@@ -1134,24 +1134,20 @@ auto ErrorBox::Update(Controller* controller, TouchInfo* touch) -> void {
Widget::Update(controller, touch);
}
auto ErrorBox::OnLayoutChange() -> void {
}
auto ErrorBox::Draw(NVGcontext* vg, Theme* theme) -> void {
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 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, 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, 325, 23, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->elements[ThemeEntryID_TEXT].colour, "%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, 415, 20, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, gfx::Colour::SILVER, "https://github.com/ITotalJustice/sphaira/issues");
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, box, theme->elements[ThemeEntryID_SELECTED].colour);
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, 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->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->GetColour(ThemeEntryID_TEXT), "%s", m_message.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, theme->GetColour(ThemeEntryID_TEXT_INFO), "https://github.com/ITotalJustice/sphaira/issues");
gfx::drawRectOutline(vg, theme, 4.f, box);
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

189
sphaira/source/ui/list.cpp Normal file
View File

@@ -0,0 +1,189 @@
#include "ui/list.hpp"
#include "ui/nvg_util.hpp"
#include "app.hpp"
#include "log.hpp"
namespace sphaira::ui {
List::List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad)
: m_row{row}
, m_page{page}
, m_v{v}
, m_pad{pad} {
m_pos = pos;
SetScrollBarPos(SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200);
}
auto List::ClampY(float y, s64 count) const -> float {
float y_max = 0;
if (count >= m_page) {
// round up
if (count % m_row) {
count = count + (m_row - count % m_row);
}
y_max = (count - m_page) / m_row * GetMaxY();
}
if (y < 0) {
y = 0;
} else if (y > y_max) {
y = y_max;
}
return y;
}
void List::OnUpdate(Controller* controller, TouchInfo* touch, s64 count, TouchCallback callback) {
if (touch->is_clicked && touch->in_range(GetPos())) {
auto v = m_v;
v.y -= ClampY(m_yoff + m_y_prog, count);
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
if (v.y > GetY() + GetH()) {
break;
}
const auto x = v.x;
for (; i < count; i++, v.x += v.w + m_pad.x) {
// only draw if full x is in bounds
if (v.x + v.w > GetX() + GetW()) {
break;
}
// skip anything not visible
if (v.y + v.h < GetY()) {
continue;
}
Vec4 vv = v;
// if not drawing, only return clipped v as its used for touch
vv.w = std::min(v.x + v.w, m_pos.x + m_pos.w) - v.x;
vv.h = std::min(v.y + v.h, m_pos.y + m_pos.h) - v.y;
if (touch->in_range(vv)) {
callback(i);
return;
}
}
v.x = x;
}
} else if (touch->is_scroll && touch->in_range(GetPos())) {
m_y_prog = (float)touch->initial.y - (float)touch->cur.y;
} else if (touch->is_end) {
m_yoff = ClampY(m_yoff + m_y_prog, count);
m_y_prog = 0;
}
}
void List::Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const {
const auto yoff = ClampY(m_yoff + m_y_prog, count);
const s64 start = yoff / GetMaxY() * m_row;
gfx::drawScrollbar2(vg, theme, m_scrollbar.x, m_scrollbar.y, m_scrollbar.h, start, count, m_row, m_page);
auto v = m_v;
v.y -= yoff;
nvgSave(vg);
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
for (s64 i = 0; i < count; v.y += v.h + m_pad.y) {
if (v.y > GetY() + GetH()) {
break;
}
const auto x = v.x;
for (; i < count; i++, v.x += v.w + m_pad.x) {
// only draw if full x is in bounds
if (v.x + v.w > GetX() + GetW()) {
break;
}
// skip anything not visible
if (v.y + v.h < GetY()) {
continue;
}
callback(vg, theme, v, i);
}
v.x = x;
}
nvgRestore(vg);
}
auto List::ScrollDown(s64& index, s64 step, s64 count) -> bool {
const auto old_index = index;
if (!count) {
return false;
}
if (index + step < count) {
index += step;
} else {
index = count - 1;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
s64 delta = index - old_index;
s64 start = m_yoff / GetMaxY() * m_row;
while (index < start) {
start -= m_row;
m_yoff -= GetMaxY();
}
if (index - start >= m_page) {
do {
start += m_row;
delta -= m_row;
m_yoff += GetMaxY();
} while (delta > 0 && start + m_page < count);
}
return true;
}
return false;
}
auto List::ScrollUp(s64& index, s64 step, s64 count) -> bool {
const auto old_index = index;
if (!count) {
return false;
}
if (index >= step) {
index -= step;
} else {
index = 0;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
s64 start = m_yoff / GetMaxY() * m_row;
while (index < start) {
start -= m_row;
m_yoff -= GetMaxY();
}
while (index - start >= m_page && start + m_page < count) {
start += m_row;
m_yoff += GetMaxY();
}
return true;
}
return false;
}
} // namespace sphaira::ui

View File

@@ -274,7 +274,7 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, float x, f
if (iw > w || ih > h) {
crop = true;
nvgSave(vg);
nvgScissor(vg, x, y, w, h);
nvgIntersectScissor(vg, x, y, w, h);
}
if (rounded_image) {
gfx::drawImageRounded(vg, ix, iy, iw, ih, i.image);
@@ -711,6 +711,11 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu)
SetSubHeading(m_entry.binary);
SetSubHeading(m_entry.description);
UpdateOptions();
// todo: see Draw()
// const Vec4 v{75, 110, 370, 155};
// const Vec2 pad{10, 10};
// m_list = std::make_unique<List>(3, 3, v, pad);
}
EntryMenu::~EntryMenu() {
@@ -728,11 +733,13 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
constexpr Vec4 line_vec(30, 86, 1220, 646);
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 grid_vec(icon_vec.x - 50, line_vec.y + 1, line_vec.w, line_vec.h - line_vec.y - 1);
// nvgSave(vg);
// 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));
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_entry.image, m_default_icon, icon_vec);
@@ -741,20 +748,21 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
const float text_inc_y = 32;
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;
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;
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;
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;
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;
// for (const auto& option : m_options) {
const auto& text_col = theme->elements[ThemeEntryID_TEXT].colour;
const auto& text_col = theme->GetColour(ThemeEntryID_TEXT);
// todo: rewrite this mess and use list
constexpr float mm = 0;//20;
constexpr Vec4 block{968.f + mm, 110.f, 256.f - mm*2, 60.f};
constexpr float text_xoffset{15.f};
@@ -768,15 +776,10 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) {
auto text_id = ThemeEntryID_TEXT;
if (m_index == i) {
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);
} 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::drawRectOutline(vg, theme, 4.f, Vec4{x, y, w, h});
}
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(ThemeEntryID_TEXT), option.display_text.c_str());
y -= block.h + 18;
}
@@ -858,7 +861,7 @@ void EntryMenu::UpdateOptions() {
SetIndex(0);
}
void EntryMenu::SetIndex(std::size_t index) {
void EntryMenu::SetIndex(s64 index) {
m_index = index;
const auto option = m_options[m_index];
if (option.confirm_text.empty()) {
@@ -886,8 +889,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/banners");
fs.CreateDirectoryRecursively("/switch/sphaira/cache/appstore/screens");
// m_span = m_entries;
this->SetActions(
std::make_pair(Button::RIGHT, Action{[this](){
if (m_entries_current.empty()) {
@@ -912,22 +913,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
}
}}),
std::make_pair(Button::DOWN, Action{[this](){
if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
if (m_list->ScrollDown(m_index, 3, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
if (m_list->ScrollUp(m_index, 3, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::R2, Action{[this](){
if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
if (m_list->ScrollDown(m_index, 9, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::L2, Action{[this](){
if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
if (m_list->ScrollUp(m_index, 9, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
@@ -962,17 +963,17 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
order_items.push_back("Decending"_i18n);
order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Filter"_i18n, filter_items, [this, filter_items](s64& index_out){
SetFilter((Filter)index_out);
}, (std::size_t)m_filter));
}, (s64)m_filter));
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
SetSort((SortType)index_out);
}, (std::size_t)m_sort));
}, (s64)m_sort));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
SetOrder((OrderType)index_out);
}, (std::size_t)m_order));
}, (s64)m_order));
options->Add(std::make_shared<SidebarEntryCallback>("Search"_i18n, [this](){
std::string out;
@@ -1005,6 +1006,9 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
m_sort = (SortType)ini_getl(INI_SECTION, "sort", m_sort, App::CONFIG_PATH);
m_order = (OrderType)ini_getl(INI_SECTION, "order", m_order, App::CONFIG_PATH);
const Vec4 v{75, 110, 370, 155};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
Sort();
}
@@ -1014,41 +1018,35 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
if (m_index == i) {
FireAction(Button::A);
} else {
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(i);
}
});
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
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;
}
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;
}
const u64 SCROLL = m_start;
const u64 max_entry_display = 9;
const u64 nro_total = m_entries_current.size();
const u64 cursor_pos = m_index;
// only draw scrollbar if needed
if (nro_total > max_entry_display) {
const auto scrollbar_size = 500.f;
const auto sb_h = 3.f / (float)nro_total * scrollbar_size;
const auto sb_y = SCROLL / 3.f;
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
}
// max images per frame, in order to not hit io / gpu too hard.
const int image_load_max = 2;
int image_load_count = 0;
for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) {
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
m_list->Draw(vg, theme, m_entries_current.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
const auto& [x, y, w, h] = v;
const auto index = m_entries_current[pos];
auto& e = m_entries[index];
auto& image = e.image;
@@ -1106,9 +1104,9 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
auto text_id = ThemeEntryID_TEXT;
if (pos == cursor_pos) {
if (pos == m_index) {
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 {
DrawElement(x, y, w, h, ThemeEntryID_GRID);
}
@@ -1120,12 +1118,12 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
// gfx::drawImage(vg, x + 20, y + 20, image_size, image_size_h, image.image ? image.image : m_default_image);
nvgSave(vg);
nvgScissor(vg, x, y, w - 30.f, h); // clip
nvgIntersectScissor(vg, v.x, v.y, w - 30.f, h); // clip
{
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 + 80, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, 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 + 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->GetColour(text_id), e.author.c_str());
gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.version.c_str());
}
nvgRestore(vg);
@@ -1144,8 +1142,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
gfx::drawImageRounded(vg, x + w - 30.f, y + 110, i_size, i_size, m_update.image);
break;
}
}
}
});
}
void Menu::OnFocusGained() {
@@ -1174,21 +1171,16 @@ void Menu::OnFocusGained() {
if (m_dirty) {
m_dirty = false;
const auto& current_entry = m_entries[m_entries_current[m_index]];
// m_start = 0;
// m_index = 0;
log_write("\nold index: %zu start: %zu\n", m_index, m_start);
// old index: 19 start: 12
Sort();
for (u32 i = 0; i < m_entries_current.size(); i++) {
if (current_entry.name == m_entries[m_entries_current[i]].name) {
SetIndex(i);
if (i >= 9) {
m_start = (i - 9) / 3 * 3 + 3;
m_list->SetYoff((((i - 9) + 3) / 3) * m_list->GetMaxY());
} else {
m_start = 0;
m_list->SetYoff(0);
}
log_write("\nnew index: %zu start: %zu\n", m_index, m_start);
break;
}
}
@@ -1196,10 +1188,10 @@ void Menu::OnFocusGained() {
}
}
void Menu::SetIndex(std::size_t index) {
void Menu::SetIndex(s64 index) {
m_index = index;
if (!m_index) {
m_start = 0;
m_list->SetYoff(0);
}
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries_current.size()));
@@ -1394,9 +1386,10 @@ void Menu::SetSearch(const std::string& term) {
SetFilter(m_filter);
SetIndex(m_entry_search_jump_back);
if (m_entry_search_jump_back >= 9) {
m_start = (m_entry_search_jump_back - 9) / 3 * 3 + 3;
m_list->SetYoff(0);
m_list->SetYoff((((m_entry_search_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
} else {
m_start = 0;
m_list->SetYoff(0);
}
}});
@@ -1427,11 +1420,12 @@ void Menu::SetAuthor() {
} else {
SetFilter(m_filter);
}
SetIndex(m_entry_author_jump_back);
if (m_entry_author_jump_back >= 9) {
m_start = (m_entry_author_jump_back - 9) / 3 * 3 + 3;
m_list->SetYoff((((m_entry_author_jump_back - 9) + 3) / 3) * m_list->GetMaxY());
} else {
m_start = 0;
m_list->SetYoff(0);
}
}});

View File

@@ -246,22 +246,22 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
}
}}),
std::make_pair(Button::DOWN, Action{[this](){
if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
if (m_list->ScrollDown(m_index, 1, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
if (m_list->ScrollUp(m_index, 1, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
if (m_list->ScrollDown(m_index, 8, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_LEFT, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
if (m_list->ScrollUp(m_index, 8, m_entries_current.size())) {
SetIndex(m_index);
}
}}),
@@ -355,12 +355,12 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
order_items.push_back("Decending"_i18n);
order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this](s64& index_out){
m_sort.Set(index_out);
SortAndFindLastFile();
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this](s64& index_out){
m_order.Set(index_out);
SortAndFindLastFile();
}, m_order.Get()));
@@ -542,7 +542,7 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
mount_items.push_back("Image System memory"_i18n);
mount_items.push_back("Image microSD card"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Mount"_i18n, mount_items, [this](s64& index_out){
App::PopToMenu();
m_mount.Set(index_out);
SetFs("/", index_out);
@@ -551,6 +551,9 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
}})
);
const Vec4 v{75, GetY() + 1.f + 42.f, 1220.f-45.f*2, 60};
m_list = std::make_unique<List>(1, 8, m_pos, v);
fs::FsPath buf;
ini_gets("paths", "last_path", "/", buf, sizeof(buf), App::CONFIG_PATH);
SetFs(buf, m_mount.Get());
@@ -562,47 +565,30 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
m_list->OnUpdate(controller, touch, m_entries_current.size(), [this](auto i) {
if (m_index == i) {
FireAction(Button::A);
} else {
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(i);
}
});
}
void Menu::Draw(NVGcontext* vg, Theme* 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()) {
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;
}
const u64 SCROLL = m_index_offset;
constexpr u64 max_entry_display = 8;
const u64 entry_total = m_entries_current.size();
// only draw scrollbar if needed
if (entry_total > max_entry_display) {
const auto scrollbar_size = 500.f;
const auto sb_h = 1.f / (float)entry_total * scrollbar_size;
const auto sb_y = SCROLL;
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, gfx::getColour(gfx::Colour::BLACK));
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * (max_entry_display - 1)) - 4, gfx::getColour(gfx::Colour::SILVER));
}
// constexpr Vec4 line_top{30.f, 86.f, 1220.f, 1.f};
// constexpr Vec4 line_bottom{30.f, 646.f, 1220.f, 1.f};
// constexpr Vec4 block{280.f, 110.f, 720.f, 60.f};
constexpr Vec4 block{75.f, 110.f, 1220.f-45.f*2, 60.f};
constexpr float text_xoffset{15.f};
// todo: cleanup
const float x = block.x;
float y = GetY() + 1.f + 42.f;
const float h = block.h;
const float w = block.w;
nvgSave(vg);
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
for (std::size_t i = m_index_offset; i < m_entries_current.size(); i++) {
m_list->Draw(vg, theme, m_entries_current.size(), [this, text_col](auto* vg, auto* theme, auto v, auto i) {
const auto& [x, y, w, h] = v;
auto& e = GetEntry(i);
if (e.IsDir()) {
@@ -619,19 +605,17 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
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, x, y + (h / 2.f) - (24.f / 2), 24.f, "\uE14B", nullptr, NVG_ALIGN_RIGHT | 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));
}
auto text_id = ThemeEntryID_TEXT;
if (m_index == i) {
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 {
if (i == m_index_offset) {
gfx::drawRect(vg, x, y, w, 1.f, text_col);
if (i != m_entries_current.size() - 1) {
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()) {
@@ -655,14 +639,13 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
nvgSave(vg);
const auto txt_clip = std::min(GetY() + GetH(), y + h) - y;
nvgScissor(vg, x + text_xoffset+65, y, w-(x+text_xoffset+65+50), txt_clip);
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);
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->GetColour(text_id));
nvgRestore(vg);
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_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_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->GetColour(text_id), "%zd dirs"_i18n.c_str(), e.dir_count);
} else {
if (!e.time_stamp.is_valid) {
const auto path = GetNewPath(e);
@@ -671,21 +654,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
const auto t = (time_t)(e.time_stamp.modified);
struct tm 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) {
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 {
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);
}
}
y += h;
if (!InYBounds(y)) {
break;
}
}
nvgRestore(vg);
});
}
void Menu::OnFocusGained() {
@@ -705,10 +681,10 @@ void Menu::OnFocusGained() {
}
}
void Menu::SetIndex(std::size_t index) {
void Menu::SetIndex(s64 index) {
m_index = index;
if (!m_index) {
m_index_offset = 0;
m_list->SetYoff();
}
if (!m_entries_current.empty() && !GetEntry().checked_internal_extension && GetEntry().extension == "zip") {
@@ -803,16 +779,14 @@ void Menu::InstallForwarder() {
auto Menu::Scan(const fs::FsPath& new_path, bool is_walk_up) -> Result {
log_write("new scan path: %s\n", new_path);
if (!is_walk_up && !m_path.empty() && !m_entries_current.empty()) {
const LastFile f{GetEntry().name, m_index, m_index_offset, 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);
}
log_write("\nold index: %zu start: %zu\n", m_index, m_index_offset);
m_path = new_path;
m_entries.clear();
m_index = 0;
m_index_offset = 0;
m_list->SetYoff(0);
SetTitleSubHeading(m_path);
if (m_selected_type == SelectedType::None) {
@@ -1090,7 +1064,7 @@ void Menu::Sort() {
void Menu::SortAndFindLastFile() {
std::optional<LastFile> last_file;
if (!m_path.empty() && !m_entries_current.empty()) {
last_file = LastFile{GetEntry().name, m_index, m_index_offset, m_entries_current.size()};
last_file = LastFile(GetEntry().name, m_index, m_list->GetYoff(), m_entries_current.size());
}
Sort();
@@ -1111,21 +1085,21 @@ void Menu::SetIndexFromLastFile(const LastFile& last_file) {
}
}
if (index >= 0) {
if ((u64)index == last_file.index && m_entries_current.size() == last_file.entries_count) {
m_index_offset = last_file.offset;
if (index == last_file.index && m_entries_current.size() == last_file.entries_count) {
m_list->SetYoff(last_file.offset);
log_write("index is the same as last time\n");
} else {
// file position changed!
log_write("file position changed\n");
// guesstimate where the position is
if (index >= 8) {
m_index_offset = (index - 8) + 1;
m_list->SetYoff(((index - 8) + 1) * m_list->GetMaxY());
} else {
m_index_offset = 0;
m_list->SetYoff(0);
}
}
SetIndex(index);
log_write("\nnew index: %zu start: %zu mod: %zu\n", m_index, m_index_offset, index % 8);
log_write("\nnew index: %zu %zu mod: %zu\n", m_index, index % 8);
}
}

View File

@@ -182,7 +182,7 @@ auto DownloadApp(ProgressBox* pbox, const GhApiAsset& gh_asset, const AssetEntry
}
std::vector<char> buf(chunk_size);
u64 offset{};
s64 offset{};
while (offset < info.uncompressed_size) {
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
@@ -248,22 +248,22 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
this->SetActions(
std::make_pair(Button::DOWN, Action{[this](){
if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
if (m_list->ScrollDown(m_index, 1, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
if (m_list->ScrollUp(m_index, 1, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_RIGHT, Action{[this](){
if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
if (m_list->ScrollDown(m_index, 8, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_LEFT, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
if (m_list->ScrollUp(m_index, 8, m_entries.size())) {
SetIndex(m_index);
}
}}),
@@ -363,6 +363,9 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
SetPop();
}})
);
const Vec4 v{75, GetY() + 1.f + 42.f, 1220.f-45.f*2, 60};
m_list = std::make_unique<List>(1, 8, m_pos, v);
}
Menu::~Menu() {
@@ -370,75 +373,49 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
if (m_index == i) {
FireAction(Button::A);
} else {
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(i);
}
});
}
void Menu::Draw(NVGcontext* vg, Theme* 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()) {
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;
}
const u64 SCROLL = m_index_offset;
constexpr u64 max_entry_display = 8;
const u64 entry_total = m_entries.size();
// only draw scrollbar if needed
if (entry_total > max_entry_display) {
const auto scrollbar_size = 500.f;
const auto sb_h = 1.f / (float)entry_total * scrollbar_size;
const auto sb_y = SCROLL;
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, gfx::getColour(gfx::Colour::BLACK));
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * (max_entry_display - 1)) - 4, gfx::getColour(gfx::Colour::SILVER));
}
// constexpr Vec4 line_top{30.f, 86.f, 1220.f, 1.f};
// constexpr Vec4 line_bottom{30.f, 646.f, 1220.f, 1.f};
// constexpr Vec4 block{280.f, 110.f, 720.f, 60.f};
constexpr Vec4 block{75.f, 110.f, 1220.f-45.f*2, 60.f};
constexpr float text_xoffset{15.f};
// todo: cleanup
const float x = block.x;
float y = GetY() + 1.f + 42.f;
const float h = block.h;
const float w = block.w;
nvgSave(vg);
nvgScissor(vg, GetX(), GetY(), GetW(), GetH());
for (std::size_t i = m_index_offset; i < m_entries.size(); i++) {
m_list->Draw(vg, theme, m_entries.size(), [this, text_col](auto* vg, auto* theme, auto v, auto i) {
const auto& [x, y, w, h] = v;
auto& e = m_entries[i];
auto text_id = ThemeEntryID_TEXT;
if (m_index == i) {
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 {
if (i == m_index_offset) {
gfx::drawRect(vg, x, y, w, 1.f, text_col);
if (i != m_entries.size() - 1) {
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);
const auto txt_clip = std::min(GetY() + GetH(), y + h) - y;
nvgScissor(vg, x + text_xoffset, y, w-(x+text_xoffset+50), txt_clip);
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());
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->GetColour(text_id), "%s By %s", e.repo.c_str(), e.owner.c_str());
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());
y += h;
if (!InYBounds(y)) {
break;
}
}
nvgRestore(vg);
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());
});
}
void Menu::OnFocusGained() {
@@ -448,10 +425,10 @@ void Menu::OnFocusGained() {
}
}
void Menu::SetIndex(std::size_t index) {
void Menu::SetIndex(s64 index) {
m_index = index;
if (!m_index) {
m_index_offset = 0;
m_list->SetYoff(0);
}
SetTitleSubHeading(m_entries[m_index].json_path);
@@ -460,8 +437,6 @@ void Menu::SetIndex(std::size_t index) {
void Menu::Scan() {
m_entries.clear();
m_index = 0;
m_index_offset = 0;
// load from romfs first
if (R_SUCCEEDED(romfsInit())) {

View File

@@ -43,22 +43,22 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
}
}}),
std::make_pair(Button::DOWN, Action{[this](){
if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries.size())) {
if (m_list->ScrollDown(m_index, 3, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries.size())) {
if (m_list->ScrollUp(m_index, 3, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::R2, Action{[this](){
if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries.size())) {
if (m_list->ScrollDown(m_index, 9, m_entries.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::L2, Action{[this](){
if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries.size())) {
if (m_list->ScrollUp(m_index, 9, m_entries.size())) {
SetIndex(m_index);
}
}}),
@@ -86,12 +86,12 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
order_items.push_back("Decending"_i18n);
order_items.push_back("Ascending"_i18n);
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
m_sort.Set(index_out);
SortAndFindLastFile();
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
m_order.Set(index_out);
SortAndFindLastFile();
}, m_order.Get()));
@@ -141,6 +141,10 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
}
}})
);
const Vec4 v{75, 110, 370, 155};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 9, m_pos, v, pad);
}
Menu::~Menu() {
@@ -153,32 +157,25 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
m_list->OnUpdate(controller, touch, m_entries.size(), [this](auto i) {
if (m_index == i) {
FireAction(Button::A);
} else {
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(i);
}
});
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
const u64 SCROLL = m_start;
const u64 max_entry_display = 9;
const u64 nro_total = m_entries.size();
const u64 cursor_pos = m_index;
fs::FsNativeSd fs;
// only draw scrollbar if needed
if (nro_total > max_entry_display) {
const auto scrollbar_size = 500.f;
const auto sb_h = 3.f / (float)nro_total * scrollbar_size;
const auto sb_y = SCROLL / 3.f;
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
}
// max images per frame, in order to not hit io / gpu too hard.
const int image_load_max = 2;
int image_load_count = 0;
for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) {
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
m_list->Draw(vg, theme, m_entries.size(), [this, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
const auto& [x, y, w, h] = v;
auto& e = m_entries[pos];
// lazy load image
@@ -197,35 +194,34 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
}
auto text_id = ThemeEntryID_TEXT;
if (pos == cursor_pos) {
if (pos == m_index) {
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 {
DrawElement(x, y, w, h, ThemeEntryID_GRID);
DrawElement(v, ThemeEntryID_GRID);
}
const float image_size = 115;
gfx::drawImageRounded(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage());
nvgSave(vg);
nvgScissor(vg, x, y, w - 30.f, h); // clip
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
{
bool has_star = false;
if (IsStarEnabled()) {
if (!e.has_star.has_value()) {
e.has_star = fs.FileExists(GenerateStarPath(e.path));
e.has_star = fs::FsNativeSd().FileExists(GenerateStarPath(e.path));
}
has_star = e.has_star.value();
}
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 + 80, font_size, NVG_ALIGN_LEFT, theme->elements[text_id].colour, 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 + 45, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), "%s%s", has_star ? "\u2605 " : "", e.GetName());
gfx::drawTextArgs(vg, x + 148, y + 80, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetAuthor());
gfx::drawTextArgs(vg, x + 148, y + 115, font_size, NVG_ALIGN_LEFT, theme->GetColour(text_id), e.GetDisplayVersion());
}
nvgRestore(vg);
}
}
});
}
void Menu::OnFocusGained() {
@@ -235,10 +231,10 @@ void Menu::OnFocusGained() {
}
}
void Menu::SetIndex(std::size_t index) {
void Menu::SetIndex(s64 index) {
m_index = index;
if (!m_index) {
m_start = 0;
m_list->SetYoff(0);
}
const auto& e = m_entries[m_index];
@@ -324,11 +320,6 @@ void Menu::Sort() {
fs::FsPath star_path;
for (auto& p : m_entries) {
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);
}
}
}
@@ -428,9 +419,9 @@ void Menu::SortAndFindLastFile() {
if (index >= 0) {
// guesstimate where the position is
if (index >= 9) {
m_start = (index - 9) / 3 * 3 + 3;
m_list->SetYoff((((index - 9) + 3) / 3) * m_list->GetMaxY());
} else {
m_start = 0;
m_list->SetYoff(0);
}
SetIndex(index);
}

View File

@@ -139,44 +139,44 @@ Menu::Menu() : MenuBase{"Irs"_i18n} {
format_str.emplace_back("20x15"_i18n);
}
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Controller"_i18n, controller_str, [this](s64& index){
irsStopImageProcessor(m_entries[m_index].m_handle);
m_index = index;
UpdateConfig(&m_config);
}, m_index));
options->Add(std::make_shared<SidebarEntryArray>("Rotation"_i18n, rotation_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Rotation"_i18n, rotation_str, [this](s64& index){
m_rotation = (Rotation)index;
}, m_rotation));
options->Add(std::make_shared<SidebarEntryArray>("Colour"_i18n, colour_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Colour"_i18n, colour_str, [this](s64& index){
m_colour = (Colour)index;
updateColourArray();
}, m_colour));
options->Add(std::make_shared<SidebarEntryArray>("Light Target"_i18n, light_target_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Light Target"_i18n, light_target_str, [this](s64& index){
m_config.light_target = index;
UpdateConfig(&m_config);
}, m_config.light_target));
options->Add(std::make_shared<SidebarEntryArray>("Gain"_i18n, gain_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Gain"_i18n, gain_str, [this](s64& index){
m_config.gain = GAIN_MIN + index;
UpdateConfig(&m_config);
}, m_config.gain - GAIN_MIN));
options->Add(std::make_shared<SidebarEntryArray>("Negative Image"_i18n, is_negative_image_used_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Negative Image"_i18n, is_negative_image_used_str, [this](s64& index){
m_config.is_negative_image_used = index;
UpdateConfig(&m_config);
}, m_config.is_negative_image_used));
options->Add(std::make_shared<SidebarEntryArray>("Format"_i18n, format_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Format"_i18n, format_str, [this](s64& index){
m_config.orig_format = index;
m_config.trimming_format = index;
UpdateConfig(&m_config);
}, m_config.orig_format));
if (hosversionAtLeast(4,0,0)) {
options->Add(std::make_shared<SidebarEntryArray>("Trimming Format"_i18n, format_str, [this](std::size_t& index){
options->Add(std::make_shared<SidebarEntryArray>("Trimming Format"_i18n, format_str, [this](s64& index){
// you cannot set trim a larger region than the source
if (index < m_config.orig_format) {
index = m_config.orig_format;

View File

@@ -89,6 +89,10 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
file_path = fs::AppendPath("/", file_path);
}
if (std::strstr(file_path, "sphaira.nro")) {
file_path = App::GetExePath();
}
Result rc;
if (file_path[strlen(file_path) -1] == '/') {
if (R_FAILED(rc = fs.CreateDirectoryRecursively(file_path)) && rc != FsError_PathAlreadyExists) {
@@ -115,7 +119,7 @@ auto InstallUpdate(ProgressBox* pbox, const std::string url, const std::string v
}
std::vector<char> buf(chunk_size);
u64 offset{};
s64 offset{};
while (offset < info.uncompressed_size) {
const auto bytes_read = unzReadCurrentFile(zfile, buf.data(), buf.size());
if (bytes_read <= 0) {
@@ -205,9 +209,6 @@ MainMenu::MainMenu() {
}
});
AddOnLPress();
AddOnRPress();
this->SetActions(
std::make_pair(Button::START, Action{App::Exit}),
std::make_pair(Button::Y, Action{"Menu"_i18n, [this](){
@@ -228,9 +229,8 @@ MainMenu::MainMenu() {
language_items.push_back("Portuguese"_i18n);
language_items.push_back("Russian"_i18n);
language_items.push_back("Swedish"_i18n);
language_items.push_back("Vietnamese"_i18n);
options->AddHeader("Header"_i18n);
options->AddSpacer();
options->Add(std::make_shared<SidebarEntryCallback>("Theme"_i18n, [this](){
SidebarEntryArray::Items theme_items{};
const auto theme_meta = App::GetThemeMetaList();
@@ -241,7 +241,7 @@ MainMenu::MainMenu() {
auto options = std::make_shared<Sidebar>("Theme Options"_i18n, Sidebar::Side::LEFT);
ON_SCOPE_EXIT(App::Push(options));
options->Add(std::make_shared<SidebarEntryArray>("Select Theme"_i18n, theme_items, [this, theme_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Select Theme"_i18n, theme_items, [this, theme_items](s64& index_out){
App::SetTheme(index_out);
}, App::GetThemeIndex()));
@@ -279,12 +279,9 @@ MainMenu::MainMenu() {
m_update_state = UpdateState::None;
App::Notify("Updated to "_i18n + m_update_version);
App::Push(std::make_shared<OptionBox>(
"Restart Sphaira?"_i18n,
"Back"_i18n, "Restart"_i18n, 1, [this](auto op_index){
if (op_index && *op_index) {
"Press OK to restart Sphaira"_i18n, "OK"_i18n, [](auto){
App::ExitRestart();
}
}
));
} else {
App::Push(std::make_shared<ui::ErrorBox>(MAKERESULT(351, 1), "Failed to download update"_i18n));
@@ -294,9 +291,9 @@ MainMenu::MainMenu() {
}
}));
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Language"_i18n, language_items, [this](s64& index_out){
App::SetLanguage(index_out);
}, (std::size_t)App::GetLanguage()));
}, (s64)App::GetLanguage()));
options->Add(std::make_shared<SidebarEntryCallback>("Misc"_i18n, [this](){
auto options = std::make_shared<Sidebar>("Misc Options"_i18n, Sidebar::Side::LEFT);
@@ -329,6 +326,11 @@ MainMenu::MainMenu() {
install_items.push_back("System memory"_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){
App::SetLogEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n));
@@ -341,13 +343,17 @@ MainMenu::MainMenu() {
App::SetInstallEnable(enable);
}, "Enabled"_i18n, "Disabled"_i18n));
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Install location"_i18n, install_items, [this](s64& index_out){
App::SetInstallSdEnable(index_out);
}, (std::size_t)App::GetInstallSdEnable()));
}, (s64)App::GetInstallSdEnable()));
options->Add(std::make_shared<SidebarEntryBool>("Show install warning"_i18n, App::GetInstallPrompt(), [this](bool& enable){
App::SetInstallPrompt(enable);
}, "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()));
}));
}})
);
@@ -357,6 +363,8 @@ MainMenu::MainMenu() {
m_app_store_menu = std::make_shared<appstore::Menu>(m_homebrew_menu->GetHomebrewList());
m_current_menu = m_homebrew_menu;
AddOnLRPress();
for (auto [button, action] : m_actions) {
m_current_menu->SetAction(button, action);
}
@@ -389,17 +397,11 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
if (m_current_menu == m_homebrew_menu) {
m_current_menu = menu;
RemoveAction(b);
if (b == Button::L) {
AddOnRPress();
} else {
AddOnLPress();
}
} else {
m_current_menu = m_homebrew_menu;
AddOnRPress();
AddOnLPress();
}
AddOnLRPress();
m_current_menu->OnFocusGained();
for (auto [button, action] : m_actions) {
@@ -407,18 +409,20 @@ void MainMenu::OnLRPress(std::shared_ptr<MenuBase> menu, Button b) {
}
}
void MainMenu::AddOnLPress() {
void MainMenu::AddOnLRPress() {
if (m_current_menu != m_filebrowser_menu) {
const auto label = m_current_menu == m_homebrew_menu ? "Files" : "Apps";
SetAction(Button::L, Action{i18n::get(label), [this]{
OnLRPress(m_filebrowser_menu, Button::L);
}});
}
void MainMenu::AddOnRPress() {
if (m_current_menu != m_app_store_menu) {
const auto label = m_current_menu == m_homebrew_menu ? "Store" : "Apps";
SetAction(Button::R, Action{i18n::get(label), [this]{
OnLRPress(m_app_store_menu, Button::R);
}});
}
}
} // namespace sphaira::ui::menu::main

View File

@@ -41,7 +41,7 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
#define draw(...) \
gfx::textBounds(vg, 0, 0, bounds, __VA_ARGS__); \
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;
// draw("version %s", APP_VERSION);
@@ -58,17 +58,15 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) {
#undef draw
gfx::drawRect(vg, 30.f, 86.f, 1220.f, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
gfx::drawRect(vg, 30.f, 646.0f, 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->GetColour(ThemeEntryID_LINE));
nvgFontSize(vg, 28);
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 + (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, 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->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, 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());
gfx::drawTextArgs(vg, 80, 685.f, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), "%s", m_sub_heading.c_str());
}
void MenuBase::SetTitle(std::string title) {
@@ -103,61 +101,4 @@ void MenuBase::UpdateVars() {
m_poll_timestamp.Update();
}
auto MenuBase::ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool {
const auto old_index = index;
if (!size) {
return false;
}
if (index + step < size) {
index += step;
} else {
index = size - 1;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
s64 delta = index - old_index;
if (index - start >= page) {
do {
start += row;
delta -= row;
} while (delta > 0 && start + page < size);
}
return true;
}
return false;
}
auto MenuBase::ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool {
const auto old_index = index;
if (!size) {
return false;
}
if (index >= step) {
index -= step;
} else {
index = 0;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
// if ()
while (index < start) {
// log_write("moved up\n");
start -= row;
}
return true;
}
return false;
}
} // namespace sphaira::ui::menu

View File

@@ -369,7 +369,7 @@ auto InstallTheme(ProgressBox* pbox, const PackListEntry& entry) -> bool {
}
std::vector<char> buf(chunk_size);
u64 offset{};
s64 offset{};
while (offset < info.uncompressed_size) {
if (pbox->ShouldExit()) {
return false;
@@ -429,25 +429,25 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
}}),
std::make_pair(Button::DOWN, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperDown(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
if (m_list->ScrollDown(m_index, 3, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperUp(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
if (m_list->ScrollUp(m_index, 3, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::R2, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperDown(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
if (m_list->ScrollDown(m_index, 6, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::L2, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperUp(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
if (m_list->ScrollUp(m_index, 6, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
@@ -492,14 +492,14 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
InvalidateAllPages();
}, "Enabled"_i18n, "Disabled"_i18n));
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Sort"_i18n, sort_items, [this, sort_items](s64& index_out){
if (m_sort.Get() != index_out) {
m_sort.Set(index_out);
InvalidateAllPages();
}
}, m_sort.Get()));
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](std::size_t& index_out){
options->Add(std::make_shared<SidebarEntryArray>("Order"_i18n, order_items, [this, order_items](s64& index_out){
if (m_order.Get() != index_out) {
m_order.Set(index_out);
InvalidateAllPages();
@@ -542,6 +542,10 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
}})
);
const Vec4 v{75, 110, 350, 250};
const Vec2 pad{10, 10};
m_list = std::make_unique<List>(3, 6, m_pos, v, pad);
m_page_index = 0;
m_pages.resize(1);
PackListDownload();
@@ -553,13 +557,31 @@ Menu::~Menu() {
void Menu::Update(Controller* controller, TouchInfo* touch) {
MenuBase::Update(controller, touch);
if (m_pages.empty()) {
return;
}
const auto& page = m_pages[m_page_index];
if (page.m_ready != PageLoadState::Done) {
return;
}
m_list->OnUpdate(controller, touch, page.m_packList.size(), [this](auto i) {
if (m_index == i) {
FireAction(Button::A);
} else {
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(i);
}
});
}
void Menu::Draw(NVGcontext* vg, Theme* theme) {
MenuBase::Draw(vg, theme);
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;
}
@@ -567,48 +589,30 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
switch (page.m_ready) {
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;
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;
case PageLoadState::Done:
break;
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;
}
const u64 SCROLL = m_start;
const u64 max_entry_display = 9;
const u64 nro_total = page.m_packList.size();// m_entries_current.size();
const u64 cursor_pos = m_index;
// only draw scrollbar if needed
if (nro_total > max_entry_display) {
const auto scrollbar_size = 500.f;
const auto sb_h = 3.f / (float)(nro_total + 3) * scrollbar_size;
const auto sb_y = SCROLL / 3.f;
gfx::drawRect(vg, SCREEN_WIDTH - 50, 100, 10, scrollbar_size, theme->elements[ThemeEntryID_GRID].colour);
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
}
// max images per frame, in order to not hit io / gpu too hard.
const int image_load_max = 2;
int image_load_count = 0;
nvgSave(vg);
nvgScissor(vg, 30, 87, 1220 - 30, 646 - 87); // clip
for (u64 i = 0, pos = SCROLL, y = 110, w = 350, h = 250; pos < nro_total && i < max_entry_display; y += h + 10) {
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
const auto index = pos;
auto& e = page.m_packList[index];
m_list->Draw(vg, theme, page.m_packList.size(), [this, &page, &image_load_count](auto* vg, auto* theme, auto v, auto pos) {
const auto& [x, y, w, h] = v;
auto& e = page.m_packList[pos];
auto text_id = ThemeEntryID_TEXT;
if (pos == cursor_pos) {
if (pos == m_index) {
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 {
DrawElement(x, y, w, h, ThemeEntryID_GRID);
}
@@ -676,12 +680,14 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
gfx::drawImageRounded(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage());
}
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 + 55, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.creator.display_name.c_str());
nvgSave(vg);
nvgIntersectScissor(vg, x, y, w - 30.f, h); // clip
{
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->GetColour(text_id), "%s", e.creator.display_name.c_str());
}
}
nvgRestore(vg);
});
}
void Menu::OnFocusGained() {
@@ -704,7 +710,7 @@ void Menu::PackListDownload() {
SetSubHeading(subheading);
m_index = 0;
m_start = 0;
m_list->SetYoff(0);
// already downloaded
if (m_pages[m_page_index].m_ready != PageLoadState::None) {

View File

@@ -11,10 +11,6 @@ NotifEntry::NotifEntry(std::string text, Side side)
, m_side{side} {
}
auto NotifEntry::OnLayoutChange() -> void {
}
auto NotifEntry::Draw(NVGcontext* vg, Theme* theme, float y) -> bool {
m_pos.y = y;
Draw(vg, theme);
@@ -23,15 +19,9 @@ auto NotifEntry::Draw(NVGcontext* vg, Theme* theme, float y) -> bool {
}
auto NotifEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
auto overlay_col = theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour;
auto selected_col = theme->elements[ThemeEntryID_SELECTED].colour;
auto text_col = theme->elements[ThemeEntryID_TEXT].colour;
auto text_col = theme->GetColour(ThemeEntryID_TEXT);
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) {
m_bounds_measured = true;
m_pos.w = 320.f;
@@ -53,15 +43,10 @@ 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);
}
auto NotifMananger::OnLayoutChange() -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
}
auto NotifMananger::Draw(NVGcontext* vg, Theme* theme) -> void {
mutexLock(&m_mutex);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));

View File

@@ -33,63 +33,41 @@ static constexpr std::array buttons = {
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 ---------------------
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);
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 {
nvgRect(vg, vec.x, vec.y, vec.w, vec.h);
nvgRect(vg, v.x, v.y, v.w, v.h);
}
nvgFillColor(vg, c);
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);
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 {
nvgRect(vg, vec.x, vec.y, vec.w, vec.h);
nvgRect(vg, v.x, v.y, v.w, v.h);
}
nvgFillPaint(vg, p);
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;
getHighlightAnimation(&gradientX, &gradientY, &color);
const auto strokeWidth = 5.0;
auto v2 = vec;
auto v2 = v;
v2.x -= strokeWidth / 2.0;
v2.y -= strokeWidth / 2.0;
v2.w += strokeWidth;
v2.h += strokeWidth;
const auto corner_radius = 0.5;
nvgSave(vg);
nvgResetScissor(vg);
// const auto stroke_width = 5.0f;
// 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;
@@ -110,8 +88,8 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
nvgFillPaint(vg, shadowPaint);
nvgFill(vg);
const auto color1 = nvgRGB(25, 138, 198);
const auto color2 = nvgRGB(137, 241, 242);
const auto color1 = theme->GetColour(ThemeEntryID_HIGHLIGHT_1);
const auto color2 = theme->GetColour(ThemeEntryID_HIGHLIGHT_2);
const auto borderColor = nvgRGBAf(color2.r, color2.g, color2.b, 0.5);
const auto transparent = nvgRGBA(0, 0, 0, 0);
@@ -147,54 +125,32 @@ inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor&
nvgStrokeWidth(vg, strokeWidth);
nvgRoundedRect(vg, v2.x, v2.y, v2.w, v2.h, corner_radius);
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);
}
inline void drawRectOutlineInternal(NVGcontext* vg, float size, const NVGcolor& out_col, Vec4 vec, const NVGpaint& p) {
float gradientX, gradientY, color;
getHighlightAnimation(&gradientX, &gradientY, &color);
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) {
void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGcolor& c) {
const auto corner_radius = 0.5;
drawRectOutlineInternal(vg, theme, size, v);
nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, corner_radius);
nvgFillColor(vg, c);
nvgFill(vg);
}
inline void drawTriangleInternal(NVGcontext* vg, float aX, float aY, float bX, float bY, float cX, float cY, const NVGpaint& p) {
void drawRectOutlineInternal(NVGcontext* vg, const Theme* theme, float size, const Vec4& v, const NVGpaint& p) {
const auto corner_radius = 0.5;
drawRectOutlineInternal(vg, theme, size, v);
nvgBeginPath(vg);
nvgMoveTo(vg, aX, aY);
nvgLineTo(vg, bX, bY);
nvgLineTo(vg, cX, cY);
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, corner_radius);
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) {
void drawTextIntenal(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& c) {
nvgBeginPath(vg);
nvgFontSize(vg, size);
nvgTextAlign(vg, align);
nvgFillColor(vg, c);
nvgText(vg, vec.x, vec.y, str, end);
nvgText(vg, v.x, v.y, str, end);
}
} // namespace
@@ -208,15 +164,6 @@ const char* getButton(const Button want) {
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, ...) {
std::va_list v;
va_start(v, str);
@@ -226,7 +173,7 @@ void drawTextArgs(NVGcontext* vg, float x, float y, float size, int align, const
drawText(vg, x, y, size, buffer, nullptr, align, c);
}
static void drawImageInternal(NVGcontext* vg, Vec4 v, int texture, int rounded = 0) {
static void drawImageInternal(NVGcontext* vg, const Vec4& v, int texture, int rounded = 0) {
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);
@@ -240,7 +187,7 @@ static void drawImageInternal(NVGcontext* vg, Vec4 v, int texture, int rounded =
nvgFill(vg);
}
void drawImage(NVGcontext* vg, Vec4 v, int texture) {
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, v, paint, false);
}
@@ -249,7 +196,7 @@ void drawImage(NVGcontext* vg, float x, float y, float w, float h, int 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);
nvgBeginPath(vg);
nvgRoundedRect(vg, v.x, v.y, v.w, v.h, 15);
@@ -261,7 +208,7 @@ void drawImageRounded(NVGcontext* vg, float x, float y, float w, float h, int te
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);
nvgFontSize(vg, size);
nvgTextAlign(vg, align);
@@ -269,14 +216,6 @@ void drawTextBox(NVGcontext* vg, float x, float y, float size, float bound, NVGc
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, ...) {
char buf[0x100];
va_list v;
@@ -289,222 +228,89 @@ void textBounds(NVGcontext* vg, float x, float y, float *bounds, const char* str
// NEW-----------
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,1920.f,1080.f}, nvgRGBA(20, 20, 20, 225), false);
drawRectIntenal(vg, {0.f,0.f,1920.f,1080.f}, nvgRGBA(0, 0, 0, 220), 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);
drawRectIntenal(vg, {0.f,0.f,SCREEN_WIDTH,SCREEN_HEIGHT}, nvgRGBA(0, 0, 0, 180), false);
}
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);
}
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);
}
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, const Vec4& v, const NVGcolor& c, bool rounded) {
drawRectIntenal(vg, v, c, 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);
}
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);
void drawRect(NVGcontext* vg, const Vec4& v, const NVGpaint& p, bool rounded) {
drawRectIntenal(vg, v, p, rounded);
}
void drawRect(NVGcontext* vg, Vec4 vec, const NVGpaint& p, bool rounded) {
drawRectIntenal(vg, vec, p, rounded);
void drawRectOutline(NVGcontext* vg, const Theme* theme, float size, float x, float y, float w, float h) {
drawRectOutlineInternal(vg, theme, size, {x,y,w,h}, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
}
void drawRect(NVGcontext* vg, Vec4 vec, const NVGpaint&& p, bool rounded) {
drawRectIntenal(vg, vec, p, rounded);
}
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 drawRectOutline(NVGcontext* vg, const Theme* theme, float size, const Vec4& v) {
drawRectOutlineInternal(vg, theme, size, v, theme->GetColour(ThemeEntryID_SELECTED_BACKGROUND));
}
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 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) {
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) {
drawTextIntenal(vg, {x,y}, size, str, end, align, c);
void drawText(NVGcontext* vg, const Vec2& v, float size, const char* str, const char* end, int align, const NVGcolor& 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) {
drawTextIntenal(vg, vec, size, str, end, align, c);
void drawText(NVGcontext* vg, const Vec2& v, float size, const NVGcolor& c, const char* str, int align, const char* end) {
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) {
drawTextIntenal(vg, vec, size, str, end, align, c);
}
void drawScrollbar(NVGcontext* vg, const Theme* theme, float x, float y, float h, u32 index_off, u32 count, u32 max_per_page) {
const s64 SCROLL = index_off;
const s64 max_entry_display = max_per_page;
const s64 entry_total = count;
const float scc2 = 8.0;
const float scw = 2.0;
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 drawButton(NVGcontext* vg, float x, float y, float size, Button button) {
drawText(vg, x, y, size, getButton(button), nullptr, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE, getColour(Colour::WHITE));
}
void drawButtons(NVGcontext* vg, const Widget::Actions& _actions, const NVGcolor& c, float start_x) {
nvgBeginPath(vg);
nvgFontSize(vg, 24.f);
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
nvgFillColor(vg, c);
float x = start_x;
const float y = 675.f;
float bounds[4]{};
// swaps L/R position, idc how shit this is, it's called once per frame.
std::vector<std::pair<Button, Action>> actions;
actions.reserve(_actions.size());
for (const auto a: _actions) {
// swap
if (a.first == Button::R && actions.size() && actions.back().first == Button::L) {
const auto s = actions.back();
actions.back() = a;
actions.emplace_back(s);
} else {
actions.emplace_back(a);
// only draw scrollbar if needed
if (entry_total > max_entry_display) {
const float sb_h = 1.f / (float)entry_total * h;
const float sb_y = SCROLL;
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->GetColour(ThemeEntryID_SCROLLBAR), false);
}
}
for (const auto& [button, action] : actions) {
if (action.IsHidden() || action.m_hint.empty()) {
continue;
void drawScrollbar(NVGcontext* vg, const Theme* theme, u32 index_off, u32 count, u32 max_per_page) {
drawScrollbar(vg, theme, SCREEN_WIDTH - 50, 100, SCREEN_HEIGHT-200, index_off, count, max_per_page);
}
nvgFontSize(vg, 20.f);
nvgTextBounds(vg, x, y, action.m_hint.c_str(), nullptr, bounds);
auto len = bounds[2] - bounds[0];
nvgText(vg, x, y, action.m_hint.c_str(), nullptr);
x -= len + 8.f;
nvgFontSize(vg, 26.f);
nvgTextBounds(vg, x, y - 7.f, getButton(button), nullptr, bounds);
len = bounds[2] - bounds[0];
nvgText(vg, x, y - 4.f, getButton(button), nullptr);
x -= len + 34.f;
void drawScrollbar2(NVGcontext* vg, const Theme* theme, float x, float y, float h, s64 index_off, s64 count, s64 row, s64 page) {
// round up
if (count % row) {
count = count + (row - count % row);
}
const float scc2 = 6.0;
const float scw = 2.0;
// only draw scrollbar if needed
if (count > page) {
const float sb_h = 1.f / (float)count * h;
const float sb_y = index_off;
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->GetColour(ThemeEntryID_SCROLLBAR), false);
}
}
// from gc installer
void drawDimBackground(NVGcontext* vg) {
// drawRect(vg, 0, 0, 1920, 1080, nvgRGBA(20, 20, 20, 225));
drawRect(vg, 0, 0, 1920, 1080, nvgRGBA(0, 0, 0, 220));
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);
}
#define HIGHLIGHT_SPEED 350.0

View File

@@ -12,10 +12,10 @@ OptionBoxEntry::OptionBoxEntry(const std::string& text, Vec4 pos)
auto OptionBoxEntry::Draw(NVGcontext* vg, Theme* theme) -> void {
if (m_selected) {
gfx::drawRectOutline(vg, 4.f, theme->elements[ThemeEntryID_SELECTED_OVERLAY].colour, m_pos, theme->elements[ThemeEntryID_SELECTED].colour);
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::drawRectOutline(vg, theme, 4.f, m_pos);
gfx::drawText(vg, m_text_pos, 26.f, theme->GetColour(ThemeEntryID_TEXT_SELECTED), m_text.c_str(), NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
} 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);
}
}
@@ -45,7 +45,7 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
}
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, std::size_t index, Callback cb)
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb)
: m_message{message}
, m_callback{cb} {
@@ -70,7 +70,7 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
}
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, std::size_t index, Callback cb)
OptionBox::OptionBox(const std::string& message, const Option& a, const Option& b, const Option& c, s64 index, Callback cb)
: m_message{message}
, m_callback{cb} {
@@ -79,39 +79,29 @@ OptionBox::OptionBox(const std::string& message, const Option& a, const Option&
auto OptionBox::Update(Controller* controller, TouchInfo* touch) -> void {
Widget::Update(controller, touch);
// if (!controller->GotDown(Button::ANY_HORIZONTAL)) {
// return;
// }
// const auto old_index = m_index;
// if (controller->GotDown(Button::LEFT) && m_index) {
// m_index--;
// } else if (controller->GotDown(Button::RIGHT) && m_index < (m_entries.size() - 1)) {
// m_index++;
// }
// if (old_index != m_index) {
// m_entries[old_index].Selected(false);
// m_entries[m_index].Selected(true);
// }
if (touch->is_clicked) {
for (s64 i = 0; i < m_entries.size(); i++) {
auto& e = m_entries[i];
if (touch->in_range(e.GetPos())) {
SetIndex(i);
FireAction(Button::A);
break;
}
}
}
auto OptionBox::OnLayoutChange() -> void {
}
auto OptionBox::Draw(NVGcontext* vg, Theme* theme) -> void {
const float padding = 15;
gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour);
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
nvgSave(vg);
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);
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) {
p.Draw(vg, theme);
@@ -128,26 +118,20 @@ auto OptionBox::OnFocusLost() noexcept -> void {
SetHidden(true);
}
auto OptionBox::Setup(std::size_t index) -> void {
m_index = std::min(m_entries.size() - 1, index);
auto OptionBox::Setup(s64 index) -> void {
m_index = std::min<s64>(m_entries.size() - 1, index);
m_entries[m_index].Selected(true);
m_spacer_line = Vec4{m_pos.x, m_pos.y + 220.f - 2.f, m_pos.w, 2.f};
SetActions(
std::make_pair(Button::LEFT, Action{[this](){
if (m_index) {
m_entries[m_index].Selected(false);
m_index--;
m_entries[m_index].Selected(true);
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(m_index - 1);
}
}}),
std::make_pair(Button::RIGHT, Action{[this](){
if (m_index < (m_entries.size() - 1)) {
m_entries[m_index].Selected(false);
m_index++;
m_entries[m_index].Selected(true);
App::PlaySoundEffect(SoundEffect_Focus);
SetIndex(m_index + 1);
}
}}),
std::make_pair(Button::A, Action{[this](){
@@ -161,4 +145,12 @@ auto OptionBox::Setup(std::size_t index) -> void {
);
}
void OptionBox::SetIndex(s64 index) {
if (m_index != index) {
m_entries[m_index].Selected(false);
m_index = index;
m_entries[m_index].Selected(true);
}
}
} // namespace sphaira::ui

View File

@@ -1,33 +0,0 @@
#include "ui/option_list.hpp"
#include "app.hpp"
#include "ui/nvg_util.hpp"
#include "i18n.hpp"
namespace sphaira::ui {
OptionList::OptionList(Options options)
: m_options{std::move(options)} {
SetAction(Button::A, Action{"Select"_i18n, [this](){
const auto& [_, func] = m_options[m_index];
func();
SetPop();
}});
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
}
auto OptionList::Update(Controller* controller, TouchInfo* touch) -> void {
}
auto OptionList::OnLayoutChange() -> void {
}
auto OptionList::Draw(NVGcontext* vg, Theme* theme) -> void {
}
} // namespace sphaira::ui

View File

@@ -5,7 +5,7 @@
namespace sphaira::ui {
PopupList::PopupList(std::string title, Items items, std::string& index_str_ref, std::size_t& index_ref)
PopupList::PopupList(std::string title, Items items, std::string& index_str_ref, s64& index_ref)
: PopupList{std::move(title), std::move(items), Callback{}, index_ref} {
m_callback = [&index_str_ref, &index_ref, this](auto op_idx) {
@@ -22,7 +22,9 @@ PopupList::PopupList(std::string title, Items items, std::string& index_ref)
const auto it = std::find(m_items.cbegin(), m_items.cend(), index_ref);
if (it != m_items.cend()) {
m_index = std::distance(m_items.cbegin(), it);
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
if (m_index >= 6) {
m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
}
}
m_callback = [&index_ref, this](auto op_idx) {
@@ -32,7 +34,7 @@ PopupList::PopupList(std::string title, Items items, std::string& index_ref)
};
}
PopupList::PopupList(std::string title, Items items, std::size_t& index_ref)
PopupList::PopupList(std::string title, Items items, s64& index_ref)
: PopupList{std::move(title), std::move(items), Callback{}, index_ref} {
m_callback = [&index_ref, this](auto op_idx) {
@@ -47,28 +49,29 @@ PopupList::PopupList(std::string title, Items items, Callback cb, std::string in
const auto it = std::find(m_items.cbegin(), m_items.cend(), index);
if (it != m_items.cend()) {
m_index = std::distance(m_items.cbegin(), it);
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
SetIndex(std::distance(m_items.cbegin(), it));
if (m_index >= 6) {
m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
}
}
}
PopupList::PopupList(std::string title, Items items, Callback cb, std::size_t index)
PopupList::PopupList(std::string title, Items items, Callback cb, s64 index)
: m_title{std::move(title)}
, m_items{std::move(items)}
, m_callback{cb}
, m_index{index} {
m_pos.w = 1280.f;
const float a = std::min(405.f, (60.f * static_cast<float>(m_items.size())));
m_pos.h = 80.f + 140.f + a;
m_pos.y = 720.f - m_pos.h;
m_line_top = m_pos.y + 70.f;
m_line_bottom = 720.f - 73.f;
m_selected_y = m_line_top + 1.f + 42.f + (static_cast<float>(m_index) * m_block.h);
m_scrollbar.Setup(Vec4{1220.f, m_line_top, 1.f, m_line_bottom - m_line_top}, m_block.h, m_items.size());
SetActions(
this->SetActions(
std::make_pair(Button::DOWN, Action{[this](){
if (m_list->ScrollDown(m_index, 1, m_items.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
if (m_list->ScrollUp(m_index, 1, m_items.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::A, Action{"Select"_i18n, [this](){
if (m_callback) {
m_callback(m_index);
@@ -76,84 +79,56 @@ PopupList::PopupList(std::string title, Items items, Callback cb, std::size_t in
SetPop();
}}),
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
if (m_callback) {
m_callback(std::nullopt);
}
SetPop();
}})
);
m_pos.w = 1280.f;
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.y = 720.f - m_pos.h;
m_line_top = m_pos.y + 70.f;
m_line_bottom = 720.f - 73.f;
Vec4 v{m_block};
v.y = m_line_top + 1.f + 42.f;
const Vec4 pos{0, m_line_top, 1280.f, m_line_bottom - m_line_top};
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);
if (m_index >= 6) {
m_list->SetYoff((m_index - 5) * m_list->GetMaxY());
}
}
auto PopupList::Update(Controller* controller, TouchInfo* touch) -> void {
Widget::Update(controller, touch);
if (!controller->GotDown(Button::ANY_VERTICAL)) {
return;
}
const auto old_index = m_index;
if (controller->GotDown(Button::DOWN) && m_index < (m_items.size() - 1)) {
m_index++;
m_selected_y += m_block.h;
} else if (controller->GotDown(Button::UP) && m_index != 0) {
m_index--;
m_selected_y -= m_block.h;
}
if (old_index != m_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
OnLayoutChange();
}
}
auto PopupList::OnLayoutChange() -> void {
if ((m_selected_y + m_block.h) > m_line_bottom) {
m_selected_y -= m_block.h;
m_index_offset++;
m_scrollbar.Move(ScrollBar::Direction::DOWN);
} else if (m_selected_y <= m_line_top) {
m_selected_y += m_block.h;
m_index_offset--;
m_scrollbar.Move(ScrollBar::Direction::UP);
}
// LOG("sely: %.2f, index_off: %lu\n", m_selected_y, m_index_offset);
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
SetIndex(i);
FireAction(Button::A);
});
}
auto PopupList::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::dimBackground(vg);
gfx::drawRect(vg, m_pos, theme->elements[ThemeEntryID_SELECTED].colour);
gfx::drawText(vg, m_pos + m_title_pos, 24.f, theme->elements[ThemeEntryID_TEXT].colour, 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_bottom, m_line_width, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_POPUP));
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->GetColour(ThemeEntryID_LINE));
gfx::drawRect(vg, 30.f, m_line_bottom, m_line_width, 1.f, theme->GetColour(ThemeEntryID_LINE));
// todo: cleanup
const float x = m_block.x;
float y = m_line_top + 1.f + 42.f;
const float h = m_block.h;
const float w = m_block.w;
nvgSave(vg);
nvgScissor(vg, 0, m_line_top, 1280.f, m_line_bottom - m_line_top);
for (std::size_t i = m_index_offset; i < m_items.size(); ++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;
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::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->elements[ThemeEntryID_TEXT_SELECTED].colour);
gfx::drawRectOutline(vg, theme, 4.f, v);
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));
} else {
gfx::drawRect(vg, x, y, w, 1.f, theme->elements[ThemeEntryID_TEXT].colour);
gfx::drawRect(vg, x, y + h, w, 1.f, 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->elements[ThemeEntryID_TEXT].colour);
if (i != m_items.size() - 1) {
gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
}
y += h;
if (y > m_line_bottom) {
break;
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));
}
}
nvgRestore(vg);
});
m_scrollbar.Draw(vg, theme);
Widget::Draw(vg, theme);
}
@@ -167,4 +142,8 @@ auto PopupList::OnFocusLost() noexcept -> void {
SetHidden(true);
}
void PopupList::SetIndex(s64 index) {
m_index = index;
}
} // namespace sphaira::ui

View File

@@ -83,7 +83,7 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
mutexUnlock(&m_mutex);
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.
// const Vec4 box = { 255, 145, 770, 430 };
@@ -93,19 +93,15 @@ auto ProgressBox::Draw(NVGcontext* vg, Theme* theme) -> void {
// shapes.
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;
gfx::drawRect(vg, prog_bar.x, prog_bar.y, ((float)offset / (float)size) * prog_bar.w, prog_bar.h, gfx::Colour::CYAN);
// 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, gfx::Colour::WHITE, "%u%%", percentage);
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 + prog_bar.w + 10, prog_bar.y, 20, NVG_ALIGN_LEFT | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), "%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, 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);
gfx::drawTextArgs(vg, center_x, m_pos.y + 60, 25, NVG_ALIGN_CENTER | NVG_ALIGN_TOP, theme->GetColour(ThemeEntryID_TEXT), title.c_str());
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.5, 20, NVG_ALIGN_CENTER, theme->GetColour(ThemeEntryID_TEXT), "%s", transfer.c_str());
}
}
@@ -119,7 +115,7 @@ auto ProgressBox::NewTransfer(const std::string& transfer) -> ProgressBox& {
return *this;
}
auto ProgressBox::UpdateTransfer(u64 offset, u64 size) -> ProgressBox& {
auto ProgressBox::UpdateTransfer(s64 offset, s64 size) -> ProgressBox& {
mutexLock(&m_mutex);
m_size = size;
m_offset = offset;

View File

@@ -94,17 +94,15 @@ void ScrollableText::Draw(NVGcontext* vg, Theme* theme) {
const auto sb_h = 1.f / max_index * scrollbar_size;
const auto in_clip = m_clip_y / m_step - 1;
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.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, 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);
gfx::drawRect(vg, banner_vec.w, m_y_off_base, 10, scrollbar_size, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND));
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));
}
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);
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);
}

View File

@@ -1,68 +0,0 @@
#include "ui/scrollbar.hpp"
#include "ui/nvg_util.hpp"
namespace sphaira::ui {
ScrollBar::ScrollBar(Vec4 bounds, float entry_height, std::size_t entries)
: m_bounds{bounds}
, m_entries{entries}
, m_entry_height{entry_height} {
Setup();
}
auto ScrollBar::OnLayoutChange() -> void {
}
auto ScrollBar::Draw(NVGcontext* vg, Theme* theme) -> void {
if (m_should_draw) {
gfx::drawRect(vg, m_pos, gfx::Colour::RED);
}
}
auto ScrollBar::Setup(Vec4 bounds, float entry_height, std::size_t entries) -> void {
m_bounds = bounds;
m_entry_height = entry_height;
m_entries = entries;
Setup();
}
auto ScrollBar::Setup() -> void {
m_bounds.y += 5.f;
m_bounds.h -= 10.f;
const float total_size = (m_entry_height) * static_cast<float>(m_entries);
if (total_size > m_bounds.h) {
m_step_size = total_size / m_entries;
m_pos.x = m_bounds.x;
m_pos.y = m_bounds.y;
m_pos.w = 2.f;
m_pos.h = total_size - m_bounds.h;
m_should_draw = true;
// LOG("total size: %.2f\n", total_size);
// LOG("step size: %.2f\n", m_step_size);
// LOG("pos y: %.2f\n", m_pos.y);
// LOG("pos h: %.2f\n", m_pos.h);
} else {
// LOG("not big enough for scroll total: %.2f bounds: %.2f\n", total_size, bounds.h);
}
}
auto ScrollBar::Move(Direction direction) -> void {
switch (direction) {
case Direction::DOWN:
if (m_index < (m_entries - 1)) {
m_index++;
m_pos.y += m_step_size;
}
break;
case Direction::UP:
if (m_index != 0) {
m_index--;
m_pos.y -= m_step_size;
}
break;
}
}
} // namespace sphaira::ui

View File

@@ -7,13 +7,20 @@
namespace sphaira::ui {
namespace {
struct SidebarSpacer : SidebarEntryBase {
};
struct SidebarHeader : SidebarEntryBase {
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 {
return Vec4{
va.x, va.y,
va.w, vb.y - va.y
};
}
} // namespace
@@ -25,14 +32,7 @@ SidebarEntryBase::SidebarEntryBase(std::string&& title)
auto SidebarEntryBase::Draw(NVGcontext* vg, Theme* theme) -> void {
// draw spacers or highlight box if in focus (selected)
if (HasFocus()) {
gfx::drawRect(vg, m_pos, nvgRGB(50,50,50));
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
gfx::drawRectOutline(vg, theme, 4.f, m_pos);
}
}
@@ -61,16 +61,16 @@ auto SidebarEntryBool::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme);
// 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 {
// }
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) {
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
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);
}
}
@@ -91,9 +91,9 @@ auto SidebarEntryCallback::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme);
// 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 {
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);
// }
}
@@ -127,7 +127,7 @@ SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb
}
}
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, std::size_t index)
SidebarEntryArray::SidebarEntryArray(std::string title, Items items, Callback cb, s64 index)
: SidebarEntryBase{std::forward<std::string>(title)}
, m_items{std::move(items)}
, m_callback{cb}
@@ -155,11 +155,59 @@ auto SidebarEntryArray::Draw(NVGcontext* vg, Theme* theme) -> void {
SidebarEntryBase::Draw(vg, theme);
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);
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);
// scrolling text
// 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)
@@ -191,24 +239,12 @@ Sidebar::Sidebar(std::string title, std::string sub, Side side, Items&& items)
m_title_pos = Vec2{m_pos.x + 30.f, m_pos.y + 40.f};
m_base_pos = Vec4{GetX() + 30.f, GetY() + 170.f, m_pos.w - (30.f * 2.f), 70.f};
// each item has it's own Action, but we take over B
SetAction(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}});
// set button positions
SetUiButtonPos({m_pos.x + m_pos.w - 60.f, 675});
m_selected_y = m_base_pos.y;
if (!m_items.empty()) {
// setup positions
m_selected_y = m_base_pos.y;
// for (auto&p : m_items) {
// p->SetPos(m_base_pos);
// m_base_pos.y += m_base_pos.h;
// }
// // give focus to first entry.
// m_items[m_index]->OnFocusGained();
}
const Vec4 pos = DistanceBetweenY(m_top_bar, m_bottom_bar);
m_list = std::make_unique<List>(1, 6, pos, m_base_pos);
m_list->SetScrollBarPos(GetX() + GetW() - 20, m_base_pos.y - 10, pos.h - m_base_pos.y + 48);
}
Sidebar::Sidebar(std::string title, std::string sub, Side side)
@@ -217,77 +253,44 @@ Sidebar::Sidebar(std::string title, std::string sub, Side side)
auto Sidebar::Update(Controller* controller, TouchInfo* touch) -> void {
m_items[m_index]->Update(controller, touch);
Widget::Update(controller, touch);
// if touched out of bounds, pop the sidebar and all widgets below it.
if (touch->is_clicked && !touch->in_range(GetPos())) {
App::PopToMenu();
} else {
m_list->OnUpdate(controller, touch, m_items.size(), [this](auto i) {
SetIndex(i);
FireAction(Button::A);
});
}
if (m_items[m_index]->ShouldPop()) {
SetPop();
}
const auto old_index = m_index;
if (controller->GotDown(Button::ANY_DOWN) && m_index < (m_items.size() - 1)) {
m_index++;
m_selected_y += m_box_size.y;
} else if (controller->GotDown(Button::ANY_UP) && m_index != 0) {
m_index--;
m_selected_y -= m_box_size.y;
}
// if we moved
if (m_index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
m_items[old_index]->OnFocusLost();
m_items[m_index]->OnFocusGained();
// move offset
if ((m_selected_y + m_box_size.y) >= m_bottom_bar.y) {
m_selected_y -= m_box_size.y;
m_index_offset++;
// LOG("move down\n");
} else if (m_selected_y <= m_top_bar.y) {
// LOG("move up sely %.2f top %.2f\n", m_selected_y, m_top_bar.y);
m_selected_y += m_box_size.y;
m_index_offset--;
}
}
}
auto DistanceBetweenY(Vec4 va, Vec4 vb) -> Vec4 {
return Vec4{
va.x, va.y,
va.w, vb.y - va.y
};
}
auto Sidebar::Draw(NVGcontext* vg, Theme* theme) -> void {
gfx::drawRect(vg, m_pos, nvgRGBA(0, 0, 0, 220));
gfx::drawText(vg, m_title_pos, m_title_size, theme->elements[ThemeEntryID_TEXT].colour, m_title.c_str());
gfx::drawRect(vg, m_pos, theme->GetColour(ThemeEntryID_SIDEBAR));
gfx::drawText(vg, m_title_pos, m_title_size, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str());
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, 18, NVG_ALIGN_TOP | NVG_ALIGN_RIGHT, theme->GetColour(ThemeEntryID_TEXT), m_sub.c_str());
}
gfx::drawRect(vg, m_top_bar, theme->elements[ThemeEntryID_TEXT].colour);
gfx::drawRect(vg, m_bottom_bar, theme->elements[ThemeEntryID_TEXT].colour);
gfx::drawRect(vg, m_top_bar, theme->GetColour(ThemeEntryID_LINE));
gfx::drawRect(vg, m_bottom_bar, theme->GetColour(ThemeEntryID_LINE));
const auto dist = DistanceBetweenY(m_top_bar, m_bottom_bar);
nvgSave(vg);
nvgScissor(vg, dist.x, dist.y, dist.w, dist.h);
Widget::Draw(vg, theme);
// for (std::size_t i = m_index_offset; i < m_items.size(); ++i) {
// m_items[i]->Draw(vg, theme);
// }
m_list->Draw(vg, theme, m_items.size(), [this](auto* vg, auto* theme, auto v, auto i) {
const auto& [x, y, w, h] = v;
for (auto&p : m_items) {
p->Draw(vg, theme);
if (i != m_items.size() - 1) {
gfx::drawRect(vg, x, y + h, w, 1.f, theme->GetColour(ThemeEntryID_LINE_SEPARATOR));
}
nvgRestore(vg);
// draw the buttons. fetch the actions from current item and insert into array.
Actions draw_actions{m_actions};
const auto& actions_ref = m_items[m_index]->GetActions();
draw_actions.insert(actions_ref.cbegin(), actions_ref.cend());
gfx::drawButtons(vg, draw_actions, theme->elements[ThemeEntryID_TEXT].colour, m_pos.x + m_pos.w - 60.f);
m_items[i]->SetY(y);
m_items[i]->Draw(vg, theme);
});
}
auto Sidebar::OnFocusGained() noexcept -> void {
@@ -303,23 +306,51 @@ auto Sidebar::OnFocusLost() noexcept -> void {
void Sidebar::Add(std::shared_ptr<SidebarEntryBase> entry) {
m_items.emplace_back(entry);
m_items.back()->SetPos(m_base_pos);
m_base_pos.y += m_base_pos.h;
// for (auto&p : m_items) {
// p->SetPos(base_pos);
// m_base_pos.y += m_base_pos.h;
// }
// give focus to first entry.
if (m_items.size() == 1) {
m_items[m_index]->OnFocusGained();
SetupButtons();
}
}
void Sidebar::AddSpacer() {
void Sidebar::SetIndex(s64 index) {
// if we moved
if (m_index != index) {
m_items[m_index]->OnFocusLost();
m_index = index;
m_items[m_index]->OnFocusGained();
SetupButtons();
}
}
void Sidebar::AddHeader(std::string name) {
void Sidebar::SetupButtons() {
RemoveActions();
// add entry actions
for (const auto& [button, action] : m_items[m_index]->GetActions()) {
SetAction(button, action);
}
// add default actions, overriding if needed.
this->SetActions(
std::make_pair(Button::DOWN, Action{[this](){
auto index = m_index;
if (m_list->ScrollDown(index, 1, m_items.size())) {
SetIndex(index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
auto index = m_index;
if (m_list->ScrollUp(index, 1, m_items.size())) {
SetIndex(index);
}
}}),
// each item has it's own Action, but we take over B
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
SetPop();
}})
);
}
} // namespace sphaira::ui

View File

@@ -1,9 +1,22 @@
#include "ui/widget.hpp"
#include "ui/nvg_util.hpp"
#include "app.hpp"
#include "log.hpp"
namespace sphaira::ui {
auto uiButton::Draw(NVGcontext* vg, Theme* theme) -> void {
// enable to see button region
// gfx::drawRect(vg, m_pos, gfx::Colour::RED);
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
nvgFillColor(vg, theme->GetColour(ThemeEntryID_TEXT));
nvgFontSize(vg, 20);
nvgText(vg, m_hint_pos.x, m_hint_pos.y, m_action.m_hint.c_str(), nullptr);
nvgFontSize(vg, 26);
nvgText(vg, m_button_pos.x, m_button_pos.y, gfx::getButton(m_button), nullptr);
}
void Widget::Update(Controller* controller, TouchInfo* touch) {
for (const auto& [button, action] : m_actions) {
if ((action.m_type & ActionType::DOWN) && controller->GotDown(button)) {
@@ -11,28 +24,36 @@ void Widget::Update(Controller* controller, TouchInfo* touch) {
App::PlaySoundEffect(SoundEffect_Focus);
}
action.Invoke(true);
break;
}
else if ((action.m_type & ActionType::UP) && controller->GotUp(button)) {
action.Invoke(false);
break;
}
else if ((action.m_type & ActionType::HELD) && controller->GotHeld(button)) {
action.Invoke(true);
break;
}
}
auto draw_actions = GetUiButtons();
for (auto& e : draw_actions) {
if (touch->is_clicked && touch->in_range(e.GetPos())) {
log_write("got click: %s\n", e.m_action.m_hint.c_str());
FireAction(e.m_button);
break;
}
}
}
void Widget::Draw(NVGcontext* vg, Theme* theme) {
Actions draw_actions;
auto draw_actions = GetUiButtons();
for (const auto& [button, action] : m_actions) {
if (!action.IsHidden()) {
draw_actions.emplace(button, action);
for (auto& e : draw_actions) {
e.Draw(vg, theme);
}
}
gfx::drawButtons(vg, draw_actions, theme->elements[ThemeEntryID_TEXT].colour);
}
auto Widget::HasAction(Button button) const -> bool {
return m_actions.contains(button);
}
@@ -47,4 +68,67 @@ void Widget::RemoveAction(Button button) {
}
}
auto Widget::FireAction(Button b, u8 type) -> bool {
for (const auto& [button, action] : m_actions) {
if (button == b && (action.m_type & type)) {
App::PlaySoundEffect(SoundEffect_Focus);
action.Invoke(true);
return true;
}
}
return false;
}
auto Widget::GetUiButtons() const -> uiButtons {
auto vg = App::GetVg();
auto [x, y] = m_button_pos;
uiButtons draw_actions;
draw_actions.reserve(m_actions.size());
// build array
for (const auto& [button, action] : m_actions) {
if (action.IsHidden() || action.m_hint.empty()) {
continue;
}
uiButton ui_button{button, action};
// swap
if (button == Button::R && draw_actions.size() && draw_actions.back().m_button == Button::L) {
const auto s = draw_actions.back();
draw_actions.back().m_button = button;
draw_actions.back().m_action = action;
draw_actions.emplace_back(s);
} else {
draw_actions.emplace_back(ui_button);
}
}
float bounds[4]{};
for (auto& e : draw_actions) {
nvgTextAlign(vg, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP);
nvgFontSize(vg, 20.f);
nvgTextBounds(vg, x, y, e.m_action.m_hint.c_str(), nullptr, bounds);
auto len = bounds[2] - bounds[0];
e.m_hint_pos = {x, 675, len, 20};
x -= len + 8.f;
nvgFontSize(vg, 26.f);
nvgTextBounds(vg, x, y - 7.f, gfx::getButton(e.m_button), nullptr, bounds);
len = bounds[2] - bounds[0];
e.m_button_pos = {x, 675 - 4.f, len, 26};
x -= len + 34.f;
e.SetPos(e.m_button_pos);
e.SetX(e.GetX() - 40);
e.SetW(e.m_hint_pos.x - e.m_button_pos.x + len + 25);
e.SetY(e.GetY() - 18);
e.SetH(26 + 18 * 2);
}
return draw_actions;
}
} // namespace sphaira::ui