feat: add live font preview pane to font selection screen (#2349)

## Summary
* Adds a live font preview pane to the font selection screen so users
can see how a font looks before committing to it.
* Changes
* A preview pane occupying the top 30% of the font selection screen,
rendering sample pangram text in the previewed font
* A two-step confirm flow: first press (enter button) previews the font,
second press selects it
* Back restores the original font settings, so browsing has no side
effects
* Layout dimensions cached in `onEnter()` to avoid redundant
recalculation between `loop()` and `render()`

## Additional Context
* Preview sample text is hardcoded English; didn't want to use AI for
translation as I would not be able to verify the output in most
languages...
* The preview pane reduces visible list height; this is compensated by
passing the reserved height into `getNumberOfItemsPerPage`

---
### AI Usage
Did you use AI tools to help write this code? **PARTIALLY**
AI use for assisting in coding and in writing the PR description.
This commit is contained in:
Paul Delestrac
2026-06-18 18:16:28 -04:00
committed by GitHub
parent 5e990b3991
commit b1131795d8
31 changed files with 176 additions and 41 deletions
+1
View File
@@ -70,6 +70,7 @@ STR_ORIENTATION: "Арыентацыя чытання"
STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі" STR_SIDE_BTN_LAYOUT: "Бакавыя кнопкі"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Арыентаваць пярэднія кнопкі"
STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела" STR_LONG_PRESS_SKIP: "Доўгае націсканне - змена раздзела"
STR_FONT_PREVIEW_TEXT: "У Іўі худы жвавы чорт у зялёнай камізэльцы пабег пад'есці фаршу з юшкай"
STR_FONT_FAMILY: "Шрыфт чытання" STR_FONT_FAMILY: "Шрыфт чытання"
STR_FONT_SIZE: "Памер шрыфту інтэрфейсу" STR_FONT_SIZE: "Памер шрыфту інтэрфейсу"
STR_LINE_SPACING: "Міжрадковы інтэрвал" STR_LINE_SPACING: "Міжрадковы інтэрвал"
+1
View File
@@ -338,6 +338,7 @@ STR_MANAGE_FONTS: "Gestiona els tipus de lletra"
STR_FONT_BROWSER: "Navegador de tipus de lletra" STR_FONT_BROWSER: "Navegador de tipus de lletra"
STR_LOADING_FONT_LIST: "S'està carregant la llista de tipus de lletra..." STR_LOADING_FONT_LIST: "S'està carregant la llista de tipus de lletra..."
STR_NO_FONTS_AVAILABLE: "No hi ha tipus de lletra disponibles" STR_NO_FONTS_AVAILABLE: "No hi ha tipus de lletra disponibles"
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
STR_FONT_INSTALLED: "Tipus de lletra instal·lat!" STR_FONT_INSTALLED: "Tipus de lletra instal·lat!"
STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació del tipus de lletra" STR_FONT_INSTALL_FAILED: "Ha fallat la instal·lació del tipus de lletra"
STR_INSTALLED: "Instal·lat" STR_INSTALLED: "Instal·lat"
+1
View File
@@ -73,6 +73,7 @@ STR_LONG_PRESS_BEHAVIOR: "Chování při dlouhém stisknutí tlačítka"
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP" STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Přeskočení kapitoly" STR_LONG_PRESS_BEHAVIOR_SKIP: "Přeskočení kapitoly"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Změna orientace" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Změna orientace"
STR_FONT_PREVIEW_TEXT: "Příliš žluťoučký kůň úpěl ďábelské ódy"
STR_FONT_FAMILY: "Rodina písem čtečky" STR_FONT_FAMILY: "Rodina písem čtečky"
STR_FONT_SIZE: "Velikost písma rozhraní" STR_FONT_SIZE: "Velikost písma rozhraní"
STR_LINE_SPACING: "Řádkování čtečky" STR_LINE_SPACING: "Řádkování čtečky"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Comportamiento al mantener pulsado el botón"
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado" STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivado"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambio de orientación" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambio de orientación"
STR_FONT_PREVIEW_TEXT: "Høj bly gom vandt fræk sexquiz på wc"
STR_FONT_FAMILY: "Læser skrifttype" STR_FONT_FAMILY: "Læser skrifttype"
STR_FONT_SIZE: "Læser skriftstørrelse" STR_FONT_SIZE: "Læser skriftstørrelse"
STR_LINE_SPACING: "Linjeafstand" STR_LINE_SPACING: "Linjeafstand"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF" STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip" STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
STR_FONT_PREVIEW_TEXT: "Pa's wijze lynx bezag vroom het fikse aquaduct"
STR_FONT_FAMILY: "Lettertype lezer" STR_FONT_FAMILY: "Lettertype lezer"
STR_FONT_SIZE: "Lettergrootte lezer" STR_FONT_SIZE: "Lettergrootte lezer"
STR_LINE_SPACING: "Regelafstand lezer" STR_LINE_SPACING: "Regelafstand lezer"
+1
View File
@@ -79,6 +79,7 @@ STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip" STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
STR_LONG_PRESS_MENU: "Long-press Menu" STR_LONG_PRESS_MENU: "Long-press Menu"
STR_FONT_PREVIEW_TEXT: "The quick brown fox jumps over the lazy dog"
STR_FONT_FAMILY: "Reader Font Family" STR_FONT_FAMILY: "Reader Font Family"
STR_FONT_SIZE: "Reader Font Size" STR_FONT_SIZE: "Reader Font Size"
STR_LINE_SPACING: "Reader Line Spacing" STR_LINE_SPACING: "Reader Line Spacing"
+1
View File
@@ -73,6 +73,7 @@ STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF" STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip" STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
STR_FONT_PREVIEW_TEXT: "Törkylempijävongahdus"
STR_FONT_FAMILY: "Lukijan fonttiperhe" STR_FONT_FAMILY: "Lukijan fonttiperhe"
STR_FONT_SIZE: "Käyttöliittymän fonttikoko" STR_FONT_SIZE: "Käyttöliittymän fonttikoko"
STR_LINE_SPACING: "Lukijan riviväli" STR_LINE_SPACING: "Lukijan riviväli"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Comportement lors d'un appui long"
STR_LONG_PRESS_BEHAVIOR_OFF: "Désactivé" STR_LONG_PRESS_BEHAVIOR_OFF: "Désactivé"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saut de chapitre" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saut de chapitre"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Changement d'orientation" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Changement d'orientation"
STR_FONT_PREVIEW_TEXT: "Portez ce vieux whisky au juge blond qui fume"
STR_FONT_FAMILY: "Police de caractères du lecteur" STR_FONT_FAMILY: "Police de caractères du lecteur"
STR_FONT_SIZE: "Taille police lecteur" STR_FONT_SIZE: "Taille police lecteur"
STR_LINE_SPACING: "Interligne" STR_LINE_SPACING: "Interligne"
+1
View File
@@ -73,6 +73,7 @@ STR_LONG_PRESS_BEHAVIOR: "Verhalten bei langem Tastendruck"
STR_LONG_PRESS_BEHAVIOR_OFF: "AUS" STR_LONG_PRESS_BEHAVIOR_OFF: "AUS"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Kapitel überspringen" STR_LONG_PRESS_BEHAVIOR_SKIP: "Kapitel überspringen"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ausrichtung ändern" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ausrichtung ändern"
STR_FONT_PREVIEW_TEXT: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich"
STR_FONT_FAMILY: "Lese-Schriftfamilie" STR_FONT_FAMILY: "Lese-Schriftfamilie"
STR_FONT_SIZE: "Schriftgröße" STR_FONT_SIZE: "Schriftgröße"
STR_LINE_SPACING: "Lese-Zeilenabstand" STR_LINE_SPACING: "Lese-Zeilenabstand"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "פעולת לחיצה ארוכה"
STR_LONG_PRESS_BEHAVIOR_OFF: "כבוי" STR_LONG_PRESS_BEHAVIOR_OFF: "כבוי"
STR_LONG_PRESS_BEHAVIOR_SKIP: "דלג פרק" STR_LONG_PRESS_BEHAVIOR_SKIP: "דלג פרק"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "שנה כיוון מסך" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "שנה כיוון מסך"
STR_FONT_PREVIEW_TEXT: "דג סקרן שט בים מאוכזב ולפתע מצא חברה"
STR_FONT_FAMILY: "גופן הקריאה" STR_FONT_FAMILY: "גופן הקריאה"
STR_FONT_SIZE: "גודל גופן" STR_FONT_SIZE: "גודל גופן"
STR_LINE_SPACING: "מרווח בין שורות" STR_LINE_SPACING: "מרווח בין שורות"
+1
View File
@@ -74,6 +74,7 @@ STR_ORIENTATION: "Olvasási irány"
STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)" STR_SIDE_BTN_LAYOUT: "Oldalsó gomb elrendezés (olvasó)"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Elülső gombok tájolása" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Elülső gombok tájolása"
STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás" STR_LONG_PRESS_SKIP: "Hosszú nyomás - fejezet ugrás"
STR_FONT_PREVIEW_TEXT: "Egy hűtlen vejét fülöncsípő, dühös mexikói úr Wesselényinél mázol Quitóban"
STR_FONT_FAMILY: "Olvasó betűkészlet" STR_FONT_FAMILY: "Olvasó betűkészlet"
STR_FONT_SIZE: "Olvasó betűméret" STR_FONT_SIZE: "Olvasó betűméret"
STR_LINE_SPACING: "Olvasó sorköz" STR_LINE_SPACING: "Olvasó sorköz"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Press. lunga pul. laterali"
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF" STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Salta capitolo" STR_LONG_PRESS_BEHAVIOR_SKIP: "Salta capitolo"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientamento" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientamento"
STR_FONT_PREVIEW_TEXT: "Pranzo d'acqua fa volti sghembi"
STR_FONT_FAMILY: "Font lettore" STR_FONT_FAMILY: "Font lettore"
STR_FONT_SIZE: "Dimensione font" STR_FONT_SIZE: "Dimensione font"
STR_LINE_SPACING: "Interlinea lettore" STR_LINE_SPACING: "Interlinea lettore"
+1
View File
@@ -69,6 +69,7 @@ STR_ORIENTATION: "Оқу бағдары"
STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)" STR_SIDE_BTN_LAYOUT: "Бүйірлік түймелер орналасуы (оқырман)"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Алдыңғы түймелерді бағдарлау" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Алдыңғы түймелерді бағдарлау"
STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу" STR_LONG_PRESS_SKIP: "Ұзақ басу арқылы тарау өткізу"
STR_FONT_PREVIEW_TEXT: "Канагаттандырылмагандыктарыныздан"
STR_FONT_FAMILY: "Оқырман қаріп тобы" STR_FONT_FAMILY: "Оқырман қаріп тобы"
STR_FONT_SIZE: "Интерфейс қаріп өлшемі" STR_FONT_SIZE: "Интерфейс қаріп өлшемі"
STR_LINE_SPACING: "Оқырман жол аралығы" STR_LINE_SPACING: "Оқырман жол аралығы"
+1
View File
@@ -74,6 +74,7 @@ STR_ORIENTATION: "Orientacija"
STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai" STR_SIDE_BTN_LAYOUT: "Šoniniai mygtukai"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuoti priekinius mygtukus" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Orientuoti priekinius mygtukus"
STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)" STR_LONG_PRESS_SKIP: "Praleisti skyrių (ilgai)"
STR_FONT_PREVIEW_TEXT: "Įlinkdama fechtuotojo špaga sublykčiojusi pragręžė apvalų arbūzą"
STR_FONT_FAMILY: "Šriftas" STR_FONT_FAMILY: "Šriftas"
STR_FONT_SIZE: "Šrifto dydis" STR_FONT_SIZE: "Šrifto dydis"
STR_LINE_SPACING: "Tarpai tarp eilučių" STR_LINE_SPACING: "Tarpai tarp eilučių"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Funkcja długiego przyciśnięcia"
STR_LONG_PRESS_BEHAVIOR_OFF: "Wył." STR_LONG_PRESS_BEHAVIOR_OFF: "Wył."
STR_LONG_PRESS_BEHAVIOR_SKIP: "Przeskocz rozdział" STR_LONG_PRESS_BEHAVIOR_SKIP: "Przeskocz rozdział"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientacja ekranu" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientacja ekranu"
STR_FONT_PREVIEW_TEXT: "Pchnąć w tę łódź jeża lub ośm skrzyń fig"
STR_FONT_FAMILY: "Czcionka" STR_FONT_FAMILY: "Czcionka"
STR_FONT_SIZE: "Rozmiar czcionki" STR_FONT_SIZE: "Rozmiar czcionki"
STR_LINE_SPACING: "Odstępy między wierszami" STR_LINE_SPACING: "Odstępy między wierszami"
+1
View File
@@ -73,6 +73,7 @@ STR_LONG_PRESS_BEHAVIOR: "Comportamento do botão de premir e segurar"
STR_LONG_PRESS_BEHAVIOR_OFF: "Desligado" STR_LONG_PRESS_BEHAVIOR_OFF: "Desligado"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Alterar orientação" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Alterar orientação"
STR_FONT_PREVIEW_TEXT: "Vejo galã sexy pôr quinze kiwis à força em baú achatado"
STR_FONT_FAMILY: "Fonte do leitor" STR_FONT_FAMILY: "Fonte do leitor"
STR_FONT_SIZE: "Tam. fonte UI" STR_FONT_SIZE: "Tam. fonte UI"
STR_LINE_SPACING: "Espaçamento entre linhas" STR_LINE_SPACING: "Espaçamento entre linhas"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Comportament buton apăsat lung"
STR_LONG_PRESS_BEHAVIOR_OFF: "Dezactivat" STR_LONG_PRESS_BEHAVIOR_OFF: "Dezactivat"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Sărire capitol" STR_LONG_PRESS_BEHAVIOR_SKIP: "Sărire capitol"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Schimbă orientarea" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Schimbă orientarea"
STR_FONT_PREVIEW_TEXT: "Încă vând gem, whisky bej și tequila roz, preț fix"
STR_FONT_FAMILY: "Familie font lectură" STR_FONT_FAMILY: "Familie font lectură"
STR_FONT_SIZE: "Dimensiune font" STR_FONT_SIZE: "Dimensiune font"
STR_LINE_SPACING: "Spaţiere între rânduri" STR_LINE_SPACING: "Spaţiere între rânduri"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Долгое нажатие"
STR_LONG_PRESS_BEHAVIOR_OFF: "Ничего" STR_LONG_PRESS_BEHAVIOR_OFF: "Ничего"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Пропуск главы" STR_LONG_PRESS_BEHAVIOR_SKIP: "Пропуск главы"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Изменить ориентацию" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Изменить ориентацию"
STR_FONT_PREVIEW_TEXT: "Съешь ещё этих мягких французских булок, да выпей же чаю"
STR_FONT_FAMILY: "Шрифт чтения" STR_FONT_FAMILY: "Шрифт чтения"
STR_FONT_SIZE: "Размер шрифта интерфейса" STR_FONT_SIZE: "Размер шрифта интерфейса"
STR_LINE_SPACING: "Межстрочный интервал" STR_LINE_SPACING: "Межстрочный интервал"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Správanie pri dlhom stlačení tlačidla"
STR_LONG_PRESS_BEHAVIOR_OFF: "VYP" STR_LONG_PRESS_BEHAVIOR_OFF: "VYP"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Preskočiť kapitolu" STR_LONG_PRESS_BEHAVIOR_SKIP: "Preskočiť kapitolu"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Zmeniť orientáciu" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Zmeniť orientáciu"
STR_FONT_PREVIEW_TEXT: "Vypätá dcéra grófa Maxwella s IQ nižším ako kôň núti čeľaď hrýzť hŕbu jabĺk"
STR_FONT_FAMILY: "Rodina písiem čítačky" STR_FONT_FAMILY: "Rodina písiem čítačky"
STR_FONT_SIZE: "Veľkosť písma rozhrania" STR_FONT_SIZE: "Veľkosť písma rozhrania"
STR_LINE_SPACING: "Riadkovanie čítačky" STR_LINE_SPACING: "Riadkovanie čítačky"
+1
View File
@@ -74,6 +74,7 @@ STR_ORIENTATION: "Orientacija branja"
STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov" STR_SIDE_BTN_LAYOUT: "Razpored stranskih gumbov"
STR_FRONT_BTN_FOLLOW_ORIENTATION: "Usmeri sprednje gumbe" STR_FRONT_BTN_FOLLOW_ORIENTATION: "Usmeri sprednje gumbe"
STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja" STR_LONG_PRESS_SKIP: "Dolgi pritisk za preskok poglavja"
STR_FONT_PREVIEW_TEXT: "V kožuščku hudobnega fanta stopiclja mizar"
STR_FONT_FAMILY: "Pisava bralnika" STR_FONT_FAMILY: "Pisava bralnika"
STR_FONT_SIZE: "Velikost pisave" STR_FONT_SIZE: "Velikost pisave"
STR_LINE_SPACING: "Razmik med vrsticami" STR_LINE_SPACING: "Razmik med vrsticami"
+1
View File
@@ -77,6 +77,7 @@ STR_LONG_PRESS_BEHAVIOR: "Al mantener pulsado un botón"
STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada" STR_LONG_PRESS_BEHAVIOR_OFF: "No hacer nada"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítulo"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambiar orient." STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Cambiar orient."
STR_FONT_PREVIEW_TEXT: "Benjamín pidió una bebida de kiwi y fresa. Noé, sin vergüenza, la más exquisita champaña del menú"
STR_FONT_FAMILY: "Tipografía" STR_FONT_FAMILY: "Tipografía"
STR_FONT_SIZE: "Tamaño" STR_FONT_SIZE: "Tamaño"
STR_LINE_SPACING: "Interlineado" STR_LINE_SPACING: "Interlineado"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Beteende vid lång knapptryckning"
STR_LONG_PRESS_BEHAVIOR_OFF: "AV" STR_LONG_PRESS_BEHAVIOR_OFF: "AV"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Hoppa över kapitel" STR_LONG_PRESS_BEHAVIOR_SKIP: "Hoppa över kapitel"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ändra orientering" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Ändra orientering"
STR_FONT_PREVIEW_TEXT: "Flygande bäckasiner söka hwila på mjuka tuvor"
STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj" STR_FONT_FAMILY: "Eboksläsarens typsnittsfamilj"
STR_FONT_SIZE: "Eboksläsarens typsnittsstorlek" STR_FONT_SIZE: "Eboksläsarens typsnittsstorlek"
STR_LINE_SPACING: "Eboksläsarens linjemellanrum" STR_LINE_SPACING: "Eboksläsarens linjemellanrum"
+1
View File
@@ -72,6 +72,7 @@ STR_LONG_PRESS_BEHAVIOR: "Long-press button behavior"
STR_LONG_PRESS_BEHAVIOR_OFF: "OFF" STR_LONG_PRESS_BEHAVIOR_OFF: "OFF"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip" STR_LONG_PRESS_BEHAVIOR_SKIP: "Chapter skip"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Orientation change"
STR_FONT_PREVIEW_TEXT: "Pijamalı hasta yağız şoföre çabucak güvendi"
STR_FONT_FAMILY: "Okuyucu Yazı Tipi Ailesi" STR_FONT_FAMILY: "Okuyucu Yazı Tipi Ailesi"
STR_FONT_SIZE: "Arayüz Yazı Boyutu" STR_FONT_SIZE: "Arayüz Yazı Boyutu"
STR_LINE_SPACING: "Okuyucu Satır Aralığı" STR_LINE_SPACING: "Okuyucu Satır Aralığı"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Поведінка при довгому настик
STR_LONG_PRESS_BEHAVIOR_OFF: "Немає" STR_LONG_PRESS_BEHAVIOR_OFF: "Немає"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Наступ. розділ (утримув.)" STR_LONG_PRESS_BEHAVIOR_SKIP: "Наступ. розділ (утримув.)"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Зміна орієнтації екрану" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Зміна орієнтації екрану"
STR_FONT_PREVIEW_TEXT: "Єхидна, ґава, їжак ще й шиплячі плазуни бігцем форсують Янцзи"
STR_FONT_FAMILY: "Шрифт" STR_FONT_FAMILY: "Шрифт"
STR_FONT_SIZE: "Розмір шрифту" STR_FONT_SIZE: "Розмір шрифту"
STR_LINE_SPACING: "Міжрядковий інтервал" STR_LINE_SPACING: "Міжрядковий інтервал"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Comportament de prémer llargament el botó"
STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat" STR_LONG_PRESS_BEHAVIOR_OFF: "Desactivat"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols" STR_LONG_PRESS_BEHAVIOR_SKIP: "Saltar capítols"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Canvi d'orientació"
STR_FONT_PREVIEW_TEXT: "Jove xef, porti whisky amb quinze glaçons d'hidrogen, coi!"
STR_FONT_FAMILY: "Família de fonts" STR_FONT_FAMILY: "Família de fonts"
STR_FONT_SIZE: "Grandària de la lletra (UI)" STR_FONT_SIZE: "Grandària de la lletra (UI)"
STR_LINE_SPACING: "Interlineat del lector" STR_LINE_SPACING: "Interlineat del lector"
+1
View File
@@ -78,6 +78,7 @@ STR_LONG_PRESS_BEHAVIOR: "Hành vi nhấn giữ nút"
STR_LONG_PRESS_BEHAVIOR_OFF: "TẮT" STR_LONG_PRESS_BEHAVIOR_OFF: "TẮT"
STR_LONG_PRESS_BEHAVIOR_SKIP: "Nhảy chương" STR_LONG_PRESS_BEHAVIOR_SKIP: "Nhảy chương"
STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Đổi hướng" STR_LONG_PRESS_BEHAVIOR_ORIENTATION: "Đổi hướng"
STR_FONT_PREVIEW_TEXT: "Trường quê em do bố của em xây kĩ nên sạch và đẹp lắm"
STR_FONT_FAMILY: "Phông chữ trình đọc" STR_FONT_FAMILY: "Phông chữ trình đọc"
STR_FONT_SIZE: "Cỡ chữ trình đọc" STR_FONT_SIZE: "Cỡ chữ trình đọc"
STR_LINE_SPACING: "Giãn dòng trình đọc" STR_LINE_SPACING: "Giãn dòng trình đọc"
+126 -39
View File
@@ -1,13 +1,36 @@
#include "FontSelectionActivity.h" #include "FontSelectionActivity.h"
#include <FontCacheManager.h>
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <I18n.h> #include <I18n.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include "CrossPointSettings.h" #include "CrossPointSettings.h"
#include "MappedInputManager.h" #include "MappedInputManager.h"
#include "SdCardFontSystem.h"
#include "components/UITheme.h" #include "components/UITheme.h"
#include "fontIds.h" #include "fontIds.h"
namespace {
constexpr const char* ELLIPSIS_UTF8 = "\xe2\x80\xa6";
int findCurrentFontIndex(const SdCardFontRegistry* registry, const char* sdFontFamilyName, uint8_t fontFamily) {
if (sdFontFamilyName[0] != '\0' && registry) {
const auto& families = registry->getFamilies();
for (int i = 0; i < static_cast<int>(families.size()); i++) {
if (families[i].name == sdFontFamilyName) {
return CrossPointSettings::BUILTIN_FONT_COUNT + i;
}
}
}
return fontFamily < CrossPointSettings::BUILTIN_FONT_COUNT ? fontFamily : 0;
}
} // namespace
FontSelectionActivity::FontSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, FontSelectionActivity::FontSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const SdCardFontRegistry* registry) const SdCardFontRegistry* registry)
: Activity("FontSelect", renderer, mappedInput), registry_(registry) {} : Activity("FontSelect", renderer, mappedInput), registry_(registry) {}
@@ -15,12 +38,22 @@ FontSelectionActivity::FontSelectionActivity(GfxRenderer& renderer, MappedInputM
void FontSelectionActivity::onEnter() { void FontSelectionActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
// Build combined font list: built-in + SD card fonts // Get metrics and calculate layout dimensions
metrics_ = UITheme::getInstance().getMetrics();
afterHeader = metrics_.topPadding + metrics_.headerHeight + metrics_.verticalSpacing;
bottomReserved = metrics_.buttonHintsHeight + metrics_.verticalSpacing;
usableHeight = renderer.getScreenHeight() - afterHeader - bottomReserved;
previewHeight = usableHeight * metrics_.previewHeightPercent / 100;
originalFontFamily_ = SETTINGS.fontFamily;
strncpy(originalSdFontFamilyName_, SETTINGS.sdFontFamilyName, sizeof(originalSdFontFamilyName_) - 1);
originalSdFontFamilyName_[sizeof(originalSdFontFamilyName_) - 1] = '\0';
fonts_.clear(); fonts_.clear();
fonts_.reserve(CrossPointSettings::BUILTIN_FONT_COUNT + (registry_ ? registry_->getFamilyCount() : 0)); fonts_.reserve(CrossPointSettings::BUILTIN_FONT_COUNT + (registry_ ? registry_->getFamilyCount() : 0));
fonts_.push_back({I18N.get(StrId::STR_NOTO_SERIF), true, 0}); fonts_.push_back({I18N.get(StrId::STR_NOTO_SERIF), true, static_cast<uint8_t>(CrossPointSettings::NOTOSERIF)});
fonts_.push_back({I18N.get(StrId::STR_NOTO_SANS), true, 1}); fonts_.push_back({I18N.get(StrId::STR_NOTO_SANS), true, static_cast<uint8_t>(CrossPointSettings::NOTOSANS)});
if (registry_) { if (registry_) {
const auto& families = registry_->getFamilies(); const auto& families = registry_->getFamilies();
@@ -29,19 +62,8 @@ void FontSelectionActivity::onEnter() {
} }
} }
// Find current selection selectedIndex_ = findCurrentFontIndex(registry_, SETTINGS.sdFontFamilyName, SETTINGS.fontFamily);
selectedIndex_ = 0; previewFontIndex_ = selectedIndex_;
if (SETTINGS.sdFontFamilyName[0] != '\0' && registry_) {
const auto& families = registry_->getFamilies();
for (int i = 0; i < static_cast<int>(families.size()); i++) {
if (families[i].name == SETTINGS.sdFontFamilyName) {
selectedIndex_ = CrossPointSettings::BUILTIN_FONT_COUNT + i;
break;
}
}
} else {
selectedIndex_ = SETTINGS.fontFamily < CrossPointSettings::BUILTIN_FONT_COUNT ? SETTINGS.fontFamily : 0;
}
requestUpdate(); requestUpdate();
} }
@@ -50,17 +72,40 @@ void FontSelectionActivity::onExit() { Activity::onExit(); }
void FontSelectionActivity::loop() { void FontSelectionActivity::loop() {
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
SETTINGS.fontFamily = originalFontFamily_;
strncpy(SETTINGS.sdFontFamilyName, originalSdFontFamilyName_, sizeof(SETTINGS.sdFontFamilyName) - 1);
SETTINGS.sdFontFamilyName[sizeof(SETTINGS.sdFontFamilyName) - 1] = '\0';
sdFontSystem.ensureLoaded(renderer);
finish(); finish();
return; return;
} }
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
handleSelection(); if (selectedIndex_ == previewFontIndex_) {
handleSelection();
} else {
previewFontIndex_ = selectedIndex_;
const auto& font = fonts_[selectedIndex_];
if (font.isBuiltin) {
SETTINGS.fontFamily = font.settingIndex;
SETTINGS.sdFontFamilyName[0] = '\0';
} else if (registry_) {
const int sdIdx = font.settingIndex - CrossPointSettings::BUILTIN_FONT_COUNT;
const auto& families = registry_->getFamilies();
if (sdIdx < static_cast<int>(families.size())) {
strncpy(SETTINGS.sdFontFamilyName, families[sdIdx].name.c_str(), sizeof(SETTINGS.sdFontFamilyName) - 1);
SETTINGS.sdFontFamilyName[sizeof(SETTINGS.sdFontFamilyName) - 1] = '\0';
sdFontSystem.ensureLoaded(renderer);
}
}
requestUpdate();
}
return; return;
} }
const int listSize = static_cast<int>(fonts_.size()); const int listSize = static_cast<int>(fonts_.size());
const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false); const int pageItems =
UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, previewHeight + metrics_.verticalSpacing);
buttonNavigator_.onNextRelease([this, listSize] { buttonNavigator_.onNextRelease([this, listSize] {
selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, listSize); selectedIndex_ = ButtonNavigator::nextIndex(selectedIndex_, listSize);
@@ -89,7 +134,7 @@ void FontSelectionActivity::handleSelection() {
SETTINGS.fontFamily = font.settingIndex; SETTINGS.fontFamily = font.settingIndex;
SETTINGS.sdFontFamilyName[0] = '\0'; SETTINGS.sdFontFamilyName[0] = '\0';
} else if (registry_) { } else if (registry_) {
int sdIdx = font.settingIndex - CrossPointSettings::BUILTIN_FONT_COUNT; const int sdIdx = font.settingIndex - CrossPointSettings::BUILTIN_FONT_COUNT;
const auto& families = registry_->getFamilies(); const auto& families = registry_->getFamilies();
if (sdIdx < static_cast<int>(families.size())) { if (sdIdx < static_cast<int>(families.size())) {
strncpy(SETTINGS.sdFontFamilyName, families[sdIdx].name.c_str(), sizeof(SETTINGS.sdFontFamilyName) - 1); strncpy(SETTINGS.sdFontFamilyName, families[sdIdx].name.c_str(), sizeof(SETTINGS.sdFontFamilyName) - 1);
@@ -99,39 +144,81 @@ void FontSelectionActivity::handleSelection() {
finish(); finish();
} }
void FontSelectionActivity::renderPreviewPane(int top, int height, int fontId, const char* fontName) const {
const int left = metrics_.previewPadding;
const int width = renderer.getScreenWidth() - (metrics_.previewPadding * 2);
if (width <= 0 || height <= 0) return;
const int labelFontId = UI_10_FONT_ID;
const int labelH = renderer.getTextHeight(labelFontId);
const int labelGap = 4;
const int labelReserved = labelH + labelGap + metrics_.previewPadding;
char labelBuf[128];
snprintf(labelBuf, sizeof(labelBuf), "%s \"%s\"", tr(STR_PREVIEW), fontName ? fontName : "");
const int labelY = top + height - metrics_.previewPadding - labelH;
renderer.drawText(labelFontId, left, labelY, labelBuf);
if (fontId == 0) return;
const int lineH = renderer.getTextHeight(fontId);
if (lineH <= 0) return;
const int innerHeight = height - metrics_.previewPadding - labelReserved;
const int maxLines = std::max(1, innerHeight / (lineH + 2));
const char* previewText = I18N.get(StrId::STR_FONT_PREVIEW_TEXT);
if (auto* fcm = renderer.getFontCacheManager()) {
char prewarmBuf[256];
snprintf(prewarmBuf, sizeof(prewarmBuf), "%s %s", previewText, ELLIPSIS_UTF8);
fcm->prewarmCache(fontId, prewarmBuf, 0x01);
}
const auto lines = renderer.wrappedText(fontId, previewText, width, maxLines);
int y = top + metrics_.previewPadding;
const int textBottomLimit = top + height - labelReserved;
for (const auto& line : lines) {
if (y + lineH > textBottomLimit) break;
renderer.drawText(fontId, left, y, line.c_str());
y += lineH + 2;
}
}
void FontSelectionActivity::render(RenderLock&&) { void FontSelectionActivity::render(RenderLock&&) {
renderer.clearScreen(); renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
const auto& metrics = UITheme::getInstance().getMetrics();
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_FONT_FAMILY)); GUI.drawHeader(renderer, Rect{0, metrics_.topPadding, pageWidth, metrics_.headerHeight}, tr(STR_FONT_FAMILY));
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; const int previewTop = afterHeader;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing; const int listTop = previewTop + previewHeight + metrics_.verticalSpacing;
const int listHeight = usableHeight - previewHeight - metrics_.verticalSpacing;
// Determine which font index is currently active (to mark as "Selected") const int previewFontId = SETTINGS.getReaderFontId();
int currentFontIndex = 0; const char* previewFontName = (previewFontIndex_ >= 0 && previewFontIndex_ < static_cast<int>(fonts_.size()))
if (SETTINGS.sdFontFamilyName[0] != '\0' && registry_) { ? fonts_[previewFontIndex_].name.c_str()
const auto& families = registry_->getFamilies(); : nullptr;
for (int i = 0; i < static_cast<int>(families.size()); i++) { renderPreviewPane(previewTop, previewHeight, previewFontId, previewFontName);
if (families[i].name == SETTINGS.sdFontFamilyName) {
currentFontIndex = CrossPointSettings::BUILTIN_FONT_COUNT + i;
break;
}
}
} else {
currentFontIndex = SETTINGS.fontFamily < CrossPointSettings::BUILTIN_FONT_COUNT ? SETTINGS.fontFamily : 0;
}
renderer.drawLine(0, listTop - metrics_.verticalSpacing / 2, pageWidth, listTop - metrics_.verticalSpacing / 2);
const int currentFontIndex = findCurrentFontIndex(registry_, originalSdFontFamilyName_, originalFontFamily_);
GUI.drawList( GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(fonts_.size()), selectedIndex_, renderer, Rect{0, listTop, pageWidth, listHeight}, static_cast<int>(fonts_.size()), selectedIndex_,
[this](int index) { return fonts_[index].name; }, nullptr, nullptr, [this](int index) { return fonts_[index].name; }, nullptr, nullptr,
[this, currentFontIndex](int index) -> std::string { return index == currentFontIndex ? tr(STR_SELECTED) : ""; }, [this, currentFontIndex](int index) -> std::string {
if (index == previewFontIndex_ && index != currentFontIndex) return tr(STR_PREVIEW);
if (index == currentFontIndex) return tr(STR_SELECTED);
return "";
},
true); true);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); const bool onPreviewed = selectedIndex_ == previewFontIndex_;
const char* confirmLabel = onPreviewed ? tr(STR_SELECT) : tr(STR_PREVIEW);
const auto labels = mappedInput.mapLabels(tr(STR_BACK), confirmLabel, tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(); renderer.displayBuffer();
@@ -2,10 +2,12 @@
#include <SdCardFontRegistry.h> #include <SdCardFontRegistry.h>
#include <cstdint>
#include <string> #include <string>
#include <vector> #include <vector>
#include "activities/Activity.h" #include "activities/Activity.h"
#include "components/themes/BaseTheme.h"
#include "util/ButtonNavigator.h" #include "util/ButtonNavigator.h"
class FontSelectionActivity final : public Activity { class FontSelectionActivity final : public Activity {
@@ -20,15 +22,26 @@ class FontSelectionActivity final : public Activity {
private: private:
void handleSelection(); void handleSelection();
int getFontIdForPreview(int index) const;
void renderPreviewPane(int top, int height, int fontId, const char* fontName) const;
struct FontEntry { struct FontEntry {
std::string name; std::string name;
bool isBuiltin; bool isBuiltin;
uint8_t settingIndex; // index used by valueSetter uint8_t settingIndex;
}; };
const SdCardFontRegistry* registry_; const SdCardFontRegistry* registry_;
ButtonNavigator buttonNavigator_; ButtonNavigator buttonNavigator_;
std::vector<FontEntry> fonts_; std::vector<FontEntry> fonts_;
int selectedIndex_ = 0; int selectedIndex_ = 0;
int previewFontIndex_ = 0;
uint8_t originalFontFamily_ = 0;
char originalSdFontFamilyName_[32] = {};
ThemeMetrics metrics_ = {};
int afterHeader = 0;
int bottomReserved = 0;
int usableHeight = 0;
int previewHeight = 0;
}; };
+5
View File
@@ -32,6 +32,9 @@ struct ThemeMetrics {
int headerHeight; int headerHeight;
int verticalSpacing; int verticalSpacing;
int previewPadding;
int previewHeightPercent;
int contentSidePadding; int contentSidePadding;
int listRowHeight; int listRowHeight;
int listWithSubtitleRowHeight; int listWithSubtitleRowHeight;
@@ -111,6 +114,8 @@ constexpr ThemeMetrics values = {.batteryWidth = 15,
.batteryBarHeight = 20, .batteryBarHeight = 20,
.headerHeight = 45, .headerHeight = 45,
.verticalSpacing = 10, .verticalSpacing = 10,
.previewPadding = 12,
.previewHeightPercent = 30,
.contentSidePadding = 20, .contentSidePadding = 20,
.listRowHeight = 30, .listRowHeight = 30,
.listWithSubtitleRowHeight = 50, .listWithSubtitleRowHeight = 50,
+2
View File
@@ -12,6 +12,8 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
.batteryBarHeight = 40, .batteryBarHeight = 40,
.headerHeight = 84, .headerHeight = 84,
.verticalSpacing = 16, .verticalSpacing = 16,
.previewPadding = 12,
.previewHeightPercent = 30,
.contentSidePadding = 20, .contentSidePadding = 20,
.listRowHeight = 40, .listRowHeight = 40,
.listWithSubtitleRowHeight = 60, .listWithSubtitleRowHeight = 60,
@@ -11,6 +11,8 @@ constexpr ThemeMetrics values = {.batteryWidth = 15,
.batteryBarHeight = 20, .batteryBarHeight = 20,
.headerHeight = 45, .headerHeight = 45,
.verticalSpacing = 10, .verticalSpacing = 10,
.previewPadding = 12,
.previewHeightPercent = 30,
.contentSidePadding = 20, .contentSidePadding = 20,
.listRowHeight = 42, .listRowHeight = 42,
.listWithSubtitleRowHeight = 69, .listWithSubtitleRowHeight = 69,