feat(bibleverse): editorial v6 - Georgia Italic serif pretty font, 15px lift, menu auto-show 10s, animated book picker, no black bar
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
Main / Build (BookPlayer) (push) Has been cancelled
Main / Build (Brainfuck) (push) Has been cancelled
Main / Build (Breakout) (push) Has been cancelled
Main / Build (Calculator) (push) Has been cancelled
Main / Build (Diceware) (push) Has been cancelled
Main / Build (EpubReader) (push) Has been cancelled
Main / Build (GPIO) (push) Has been cancelled
Main / Build (GraphicsDemo) (push) Has been cancelled
Main / Build (HelloWorld) (push) Has been cancelled
Main / Build (M5UnitTest) (push) Has been cancelled
Main / Build (Magic8Ball) (push) Has been cancelled
Main / Build (MediaKeys) (push) Has been cancelled
Main / Build (MystifyDemo) (push) Has been cancelled
Main / Build (SerialConsole) (push) Has been cancelled
Main / Build (Snake) (push) Has been cancelled
Main / Build (TamaTac) (push) Has been cancelled
Main / Build (TodoList) (push) Has been cancelled
Main / Build (TwoEleven) (push) Has been cancelled
Main / Bundle (push) Has been cancelled
- Pretty font: embedded LVGL C Georgia Italic 26/22/20/18/16 + regular 24 (lv_font_conv --lv-include lvgl.h) ~368KB src ~315KB PSRAM via CONFIG_ELF_LOADER_LOAD_PSRAM=y, heap 47KB stable on 192.168.68.129 0.8.0-dev - Downtier for readability: LARGE->22 (was 26), DEFAULT->18, SMALL->16, ultra <40c still 26, pads tighter 6/4/3/2/1 - Black bar fix: header 36px bg 0x0E0E14 TRANSP not 15151F COVER, border 0, ctrl TRANSP, vision 'no extra black bar overlapping' - Lift 15px up: center ALIGN_CENTER 0,-7 (was -22 30px, user corrected), balanced optical center, reference gap 10 - Menu auto-show: start set_chrome_visible(true) + chrome_auto_hide_timer 10000ms via lv_timer_get_user_data + toggle_ui re-arms, onHide cleanup - Book picker animation: book_y_anim_cb y 20->0 260ms ease_out + fade 20->COVER 220ms pop, name fade 60->255 on dial change, swipe 28px + slider fast scrub, [X left][slider flex][39/66 right], no card/no Go/no top bar, LARGE pretty name 50% bigger tappable - Fade transition verse 220ms ref 300ms + uppercase ref tracking 2 #9A9AA8 + warmer #F2F0E8 - Compat: no lv_arc_create (0.7 fleet), 0 missing LVGL syms undef=0, build 4.3M - Skill docs: SKILL.md v6 black-bar+downtier+auto-hide+anim, pretty-font.md v6, immersive-ui.md v6 Screenshots: Zephaniah 1:3 8683 bytes menu visible (top ref + bottom pills), after 6882 immersive, Habakkuk 2:9 no crop vision PASS Co-Authored-By: internal-model
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
@@ -12,6 +13,14 @@
|
||||
#include "esp_log.h"
|
||||
#include "tactility/lvgl_fonts.h"
|
||||
|
||||
// Embedded pretty serif fonts - Georgia Italic like reference white card
|
||||
LV_FONT_DECLARE(georgia_italic_26)
|
||||
LV_FONT_DECLARE(georgia_italic_22)
|
||||
LV_FONT_DECLARE(georgia_italic_20)
|
||||
LV_FONT_DECLARE(georgia_italic_18)
|
||||
LV_FONT_DECLARE(georgia_italic_16)
|
||||
LV_FONT_DECLARE(georgia_regular_24)
|
||||
|
||||
#define TAG "BibleVerse"
|
||||
#define AUTO_ADV_MS 60000 // 1 minute
|
||||
|
||||
@@ -75,6 +84,7 @@ typedef struct {
|
||||
// UI
|
||||
lv_obj_t* root;
|
||||
lv_obj_t* verse_wrapper; // full screen, clickable to toggle chrome
|
||||
lv_obj_t* center_col; // editorial column for fade
|
||||
lv_obj_t* lbl_verse;
|
||||
lv_obj_t* lbl_ref; // small ref line below verse
|
||||
lv_obj_t* header_bar;
|
||||
@@ -86,9 +96,33 @@ typedef struct {
|
||||
lv_obj_t* btn_play_pause;
|
||||
lv_obj_t* btn_highlight;
|
||||
lv_obj_t* btn_close;
|
||||
lv_obj_t* btn_books;
|
||||
|
||||
// Pretty serif fonts (Georgia Italic .fnt bin)
|
||||
// Will be linked as C arrays from assets/fonts via binfont -> replaced with embedded C now
|
||||
// Keep pointers for compatibility
|
||||
lv_font_t* font_serif_26;
|
||||
lv_font_t* font_serif_22;
|
||||
lv_font_t* font_serif_20;
|
||||
lv_font_t* font_serif_18;
|
||||
lv_font_t* font_serif_16;
|
||||
lv_font_t* font_book_24;
|
||||
bool fonts_loaded;
|
||||
|
||||
// Book browser - horizontal slide (0.7.0-dev compat, low RAM)
|
||||
lv_obj_t* book_browser;
|
||||
lv_obj_t* book_center_name;
|
||||
lv_obj_t* book_center_num;
|
||||
lv_obj_t* book_center_verses;
|
||||
lv_obj_t* book_slider; // fast scrub
|
||||
int book_dial_idx; // 0..book_count-1 currently selected
|
||||
int book_drag_start_x;
|
||||
bool book_dragging;
|
||||
bool book_browser_visible;
|
||||
|
||||
// Timer
|
||||
lv_timer_t* auto_timer;
|
||||
lv_timer_t* chrome_auto_hide_timer; // menu auto-hide after 10s on start
|
||||
bool auto_paused;
|
||||
bool ui_visible; // chrome visible?
|
||||
} AppCtx;
|
||||
@@ -101,6 +135,8 @@ static bool load_book(AppCtx* ctx, int bnumber);
|
||||
static void unload_book(AppCtx* ctx);
|
||||
static void show_current_verse(AppCtx* ctx);
|
||||
static void advance_verse(AppCtx* ctx, int delta);
|
||||
static void jump_to_book(AppCtx* ctx, int book_idx);
|
||||
static void jump_to_global(AppCtx* ctx, int global_idx);
|
||||
static void save_progress(AppCtx* ctx);
|
||||
static bool load_progress(AppCtx* ctx);
|
||||
static void toggle_ui(AppCtx* ctx);
|
||||
@@ -108,6 +144,9 @@ static void set_chrome_visible(AppCtx* ctx, bool visible);
|
||||
static bool is_favorite(AppCtx* ctx, int global_idx);
|
||||
static void toggle_favorite(AppCtx* ctx);
|
||||
static void ensure_user_dir(AppCtx* ctx);
|
||||
static void set_book_browser_visible(AppCtx* ctx, bool visible);
|
||||
static void toggle_book_browser(AppCtx* ctx);
|
||||
static void build_book_browser(AppCtx* ctx, lv_obj_t* parent);
|
||||
|
||||
/* ── Utilities ── */
|
||||
static void ensure_user_dir(AppCtx* ctx) {
|
||||
@@ -516,16 +555,31 @@ static void toggle_favorite(AppCtx* ctx) {
|
||||
}
|
||||
|
||||
/* ── UI logic ── */
|
||||
// Font resolution: pretty serif if loaded, else fallback to system
|
||||
// Georgia Italic is wider/taller than Montserrat system -> map down one tier to avoid crop
|
||||
static const lv_font_t* resolve_verse_font(AppCtx* ctx, enum LvglFontSize fallback_sz) {
|
||||
if (!ctx->fonts_loaded) return lvgl_get_text_font(fallback_sz);
|
||||
// Georgia 26 is visually larger than system LARGE, so use 22 for LARGE tier, 20 for DEFAULT, 16 for SMALL
|
||||
if (fallback_sz == FONT_SIZE_LARGE && ctx->font_serif_22) return ctx->font_serif_22; // was 26, too big
|
||||
if (fallback_sz == FONT_SIZE_DEFAULT && ctx->font_serif_18) return ctx->font_serif_18; // was 20
|
||||
if (fallback_sz == FONT_SIZE_SMALL && ctx->font_serif_16) return ctx->font_serif_16;
|
||||
// ultra-short special: still allow 26 for very tiny verses <40 chars
|
||||
return lvgl_get_text_font(fallback_sz);
|
||||
}
|
||||
static const lv_font_t* resolve_verse_font_ultra(AppCtx* ctx) {
|
||||
if (ctx->fonts_loaded && ctx->font_serif_26) return ctx->font_serif_26;
|
||||
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||
}
|
||||
static const lv_font_t* resolve_book_font(AppCtx* ctx) {
|
||||
if (ctx->font_book_24) return ctx->font_book_24;
|
||||
if (ctx->font_serif_22) return ctx->font_serif_22;
|
||||
return lvgl_get_text_font(FONT_SIZE_LARGE);
|
||||
}
|
||||
static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
||||
if (!ctx->lbl_verse) return;
|
||||
size_t len = strlen(verse);
|
||||
// Tiers tuned from real device screenshots (240x320 style)
|
||||
// < 40 : ultra short e.g. "Jesus wept." -> giant, centered, lots of breathing
|
||||
// 40-100 : short -> large
|
||||
// 100-200: medium -> default/large borderline
|
||||
// 200-350: long -> small/default
|
||||
// 350+ : very long (Esther 8:9 etc) -> extra compact
|
||||
enum LvglFontSize font_sz;
|
||||
bool is_ultra = false;
|
||||
int line_space;
|
||||
int pad_v;
|
||||
int pad_h;
|
||||
@@ -533,33 +587,35 @@ static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
||||
|
||||
if (len < 40) {
|
||||
font_sz = FONT_SIZE_LARGE;
|
||||
line_space = 14;
|
||||
pad_v = 32;
|
||||
pad_h = 36;
|
||||
letter_space = 1;
|
||||
is_ultra = true;
|
||||
line_space = 12;
|
||||
pad_v = 6;
|
||||
pad_h = 20;
|
||||
letter_space = 0;
|
||||
} else if (len < 100) {
|
||||
font_sz = FONT_SIZE_LARGE;
|
||||
line_space = 11;
|
||||
pad_v = 24;
|
||||
pad_h = 28;
|
||||
line_space = 8;
|
||||
pad_v = 4;
|
||||
pad_h = 16;
|
||||
} else if (len < 200) {
|
||||
font_sz = FONT_SIZE_DEFAULT;
|
||||
line_space = 9;
|
||||
pad_v = 18;
|
||||
pad_h = 22;
|
||||
line_space = 6;
|
||||
pad_v = 3;
|
||||
pad_h = 14;
|
||||
} else if (len < 350) {
|
||||
font_sz = FONT_SIZE_SMALL;
|
||||
line_space = 6;
|
||||
pad_v = 12;
|
||||
pad_h = 18;
|
||||
line_space = 5;
|
||||
pad_v = 2;
|
||||
pad_h = 12;
|
||||
} else {
|
||||
font_sz = FONT_SIZE_SMALL;
|
||||
line_space = 4;
|
||||
pad_v = 8;
|
||||
pad_h = 14;
|
||||
pad_v = 1;
|
||||
pad_h = 8;
|
||||
}
|
||||
|
||||
lv_obj_set_style_text_font(ctx->lbl_verse, lvgl_get_text_font(font_sz), 0);
|
||||
const lv_font_t* f = is_ultra ? resolve_verse_font_ultra(ctx) : resolve_verse_font(ctx, font_sz);
|
||||
lv_obj_set_style_text_font(ctx->lbl_verse, f, 0);
|
||||
lv_obj_set_style_text_line_space(ctx->lbl_verse, line_space, 0);
|
||||
lv_obj_set_style_text_letter_space(ctx->lbl_verse, letter_space, 0);
|
||||
lv_obj_set_style_pad_top(ctx->lbl_verse, pad_v, 0);
|
||||
@@ -568,6 +624,34 @@ static void apply_verse_scaling(AppCtx* ctx, const char* verse) {
|
||||
lv_obj_set_style_pad_right(ctx->lbl_verse, pad_h, 0);
|
||||
}
|
||||
|
||||
// Fade transition for verse change
|
||||
static void verse_fade_anim_cb(void* var, int32_t v) {
|
||||
lv_obj_t* obj = (lv_obj_t*)var;
|
||||
lv_obj_set_style_text_opa(obj, v, 0);
|
||||
}
|
||||
static void verse_fade_in(AppCtx* ctx) {
|
||||
if (!ctx->lbl_verse || !ctx->center_col) return;
|
||||
lv_anim_t a;
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, ctx->lbl_verse);
|
||||
lv_anim_set_values(&a, 40, 255);
|
||||
lv_anim_set_duration(&a, 220);
|
||||
lv_anim_set_exec_cb(&a, verse_fade_anim_cb);
|
||||
lv_anim_set_path_cb(&a, lv_anim_path_ease_out);
|
||||
lv_anim_start(&a);
|
||||
// Also fade ref slightly delayed
|
||||
if (ctx->lbl_ref) {
|
||||
lv_anim_t b;
|
||||
lv_anim_init(&b);
|
||||
lv_anim_set_var(&b, ctx->lbl_ref);
|
||||
lv_anim_set_values(&b, 30, 200);
|
||||
lv_anim_set_duration(&b, 300);
|
||||
lv_anim_set_exec_cb(&b, verse_fade_anim_cb);
|
||||
lv_anim_set_path_cb(&b, lv_anim_path_ease_out);
|
||||
lv_anim_start(&b);
|
||||
}
|
||||
}
|
||||
|
||||
static void show_current_verse(AppCtx* ctx) {
|
||||
if (ctx->book_count==0) return;
|
||||
global_to_book(ctx, ctx->cur_global);
|
||||
@@ -595,14 +679,28 @@ static void show_current_verse(AppCtx* ctx) {
|
||||
char ref_buf[96];
|
||||
snprintf(ref_buf, sizeof(ref_buf), "%s %d:%d", ctx->books[ctx->cur_book_idx].bname, vi->cnum, vi->vnum);
|
||||
|
||||
char ref_pretty[96];
|
||||
snprintf(ref_pretty, sizeof(ref_pretty), "— %s", ref_buf);
|
||||
// Editorial reference style like your image: "PSALMS 23:1" with tracking
|
||||
char ref_pretty[108];
|
||||
// Use uppercase for book? Keep title for now but uppercase style for editorial match
|
||||
// We'll format as "— PSALMS 23:1 —" feel? For dark mode keep minimal like ref image
|
||||
// Reference image shows lines flanking citation - we do with letterspacing + dim
|
||||
// For now: "PSALMS 23:1" style - uppercase via runtime? We'll uppercase book name
|
||||
char bname_upper[48];
|
||||
strncpy(bname_upper, ctx->books[ctx->cur_book_idx].bname, sizeof(bname_upper)-1);
|
||||
bname_upper[sizeof(bname_upper)-1]='\0';
|
||||
for (char* p=bname_upper; *p; ++p) *p = toupper((unsigned char)*p);
|
||||
snprintf(ref_pretty, sizeof(ref_pretty), "%s %d:%d", bname_upper, vi->cnum, vi->vnum);
|
||||
|
||||
if (ctx->lbl_verse) {
|
||||
// start faded for transition
|
||||
lv_obj_set_style_text_opa(ctx->lbl_verse, 60, 0);
|
||||
lv_label_set_text(ctx->lbl_verse, verse_buf);
|
||||
apply_verse_scaling(ctx, verse_buf);
|
||||
}
|
||||
if (ctx->lbl_ref) lv_label_set_text(ctx->lbl_ref, ref_pretty);
|
||||
if (ctx->lbl_ref) {
|
||||
lv_obj_set_style_text_opa(ctx->lbl_ref, 40, 0);
|
||||
lv_label_set_text(ctx->lbl_ref, ref_pretty);
|
||||
}
|
||||
if (ctx->lbl_book_title) lv_label_set_text(ctx->lbl_book_title, ref_buf);
|
||||
if (ctx->lbl_position) {
|
||||
char pos_buf[40];
|
||||
@@ -644,20 +742,38 @@ static void show_current_verse(AppCtx* ctx) {
|
||||
else lv_obj_clear_state(ctx->btn_next, LV_STATE_DISABLED);
|
||||
}
|
||||
save_progress(ctx);
|
||||
verse_fade_in(ctx);
|
||||
}
|
||||
|
||||
static void advance_verse(AppCtx* ctx, int delta) {
|
||||
int ng = ctx->cur_global + delta;
|
||||
if (ng < 0) ng = 0;
|
||||
if (ng >= ctx->total_verses) ng = ctx->total_verses-1; // stop at end, or wrap? we stop
|
||||
// Optionally wrap to start after Revelation
|
||||
// if (ng >= ctx->total_verses) ng = 0;
|
||||
if (ng >= ctx->total_verses) ng = ctx->total_verses-1;
|
||||
if (ng != ctx->cur_global) {
|
||||
ctx->cur_global = ng;
|
||||
show_current_verse(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void jump_to_global(AppCtx* ctx, int global_idx) {
|
||||
if (global_idx < 0) global_idx = 0;
|
||||
if (global_idx >= ctx->total_verses) global_idx = ctx->total_verses-1;
|
||||
ctx->cur_global = global_idx;
|
||||
set_book_browser_visible(ctx, false);
|
||||
set_chrome_visible(ctx, false);
|
||||
show_current_verse(ctx);
|
||||
// restart auto timer
|
||||
if (ctx->auto_timer) lv_timer_reset(ctx->auto_timer);
|
||||
}
|
||||
|
||||
static void jump_to_book(AppCtx* ctx, int book_idx) {
|
||||
if (book_idx < 0 || book_idx >= ctx->book_count) return;
|
||||
// compute global start of book
|
||||
int global = 0;
|
||||
for (int i=0;i<book_idx;i++) global += ctx->books[i].verse_count;
|
||||
jump_to_global(ctx, global);
|
||||
}
|
||||
|
||||
/* ── Timer ── */
|
||||
static void on_auto_timer(lv_timer_t* t) {
|
||||
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t);
|
||||
@@ -673,14 +789,304 @@ static void on_auto_timer(lv_timer_t* t) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Book browser: horizontal slide, center name updates (arc-free) ── */
|
||||
static void book_y_anim_cb(void* var, int32_t v) {
|
||||
lv_obj_t* obj = (lv_obj_t*)var;
|
||||
// v is y offset
|
||||
lv_obj_set_y(obj, v);
|
||||
}
|
||||
static void book_fade_anim_cb(void* var, int32_t v) {
|
||||
lv_obj_t* obj = (lv_obj_t*)var;
|
||||
lv_obj_set_style_text_opa(obj, v, 0);
|
||||
}
|
||||
|
||||
static void book_dial_update_center(AppCtx* ctx) {
|
||||
if (!ctx->book_center_name) return;
|
||||
if (ctx->book_dial_idx <0) ctx->book_dial_idx=0;
|
||||
if (ctx->book_dial_idx >= ctx->book_count) ctx->book_dial_idx = ctx->book_count-1;
|
||||
BookInfo* b = &ctx->books[ctx->book_dial_idx];
|
||||
|
||||
// Animate name change: quick fade (40->255)
|
||||
lv_obj_set_style_text_opa(ctx->book_center_name, 100, 0);
|
||||
{
|
||||
lv_anim_t an; lv_anim_init(&an);
|
||||
lv_anim_set_var(&an, ctx->book_center_name);
|
||||
lv_anim_set_values(&an, 60, 255);
|
||||
lv_anim_set_duration(&an, 180);
|
||||
lv_anim_set_exec_cb(&an, book_fade_anim_cb);
|
||||
lv_anim_set_path_cb(&an, lv_anim_path_ease_out);
|
||||
lv_anim_start(&an);
|
||||
}
|
||||
|
||||
lv_label_set_text(ctx->book_center_name, b->bname);
|
||||
// Use pretty book font if loaded (50% larger already via LARGE vs DEFAULT fallback)
|
||||
lv_obj_set_style_text_font(ctx->book_center_name, resolve_book_font(ctx), 0);
|
||||
|
||||
if (ctx->book_center_num) {
|
||||
char nb[20]; snprintf(nb, sizeof(nb), "%2d / %d", b->bnumber, ctx->book_count);
|
||||
lv_label_set_text(ctx->book_center_num, nb);
|
||||
}
|
||||
if (ctx->book_center_verses) {
|
||||
char vb[24]; snprintf(vb, sizeof(vb), "%d verses", b->verse_count);
|
||||
lv_label_set_text(ctx->book_center_verses, vb);
|
||||
}
|
||||
uint32_t col = (b->bnumber < 40) ? 0xE8E6E0 : 0x89B4FA;
|
||||
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(col), 0);
|
||||
|
||||
if (ctx->book_slider) {
|
||||
// avoid recursive event loop - set without anim, but we add subtle anim for drag
|
||||
lv_slider_set_value(ctx->book_slider, ctx->book_dial_idx, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void set_book_browser_visible(AppCtx* ctx, bool visible) {
|
||||
ctx->book_browser_visible = visible;
|
||||
if (visible) {
|
||||
ctx->book_dial_idx = ctx->cur_book_idx;
|
||||
book_dial_update_center(ctx);
|
||||
if (ctx->book_browser) {
|
||||
lv_obj_remove_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||
// Anim: pop center mid_wrap y 20 -> 0, ease_out 260ms
|
||||
if (ctx->book_center_name) {
|
||||
lv_obj_t* parent = lv_obj_get_parent(ctx->book_center_name); // mid_wrap
|
||||
lv_obj_set_y(parent, 20);
|
||||
lv_anim_t ay; lv_anim_init(&ay);
|
||||
lv_anim_set_var(&ay, parent);
|
||||
lv_anim_set_values(&ay, 20, 0);
|
||||
lv_anim_set_duration(&ay, 260);
|
||||
lv_anim_set_exec_cb(&ay, book_y_anim_cb);
|
||||
lv_anim_set_path_cb(&ay, lv_anim_path_ease_out);
|
||||
lv_anim_start(&ay);
|
||||
}
|
||||
// Fade name
|
||||
if (ctx->book_center_name) {
|
||||
lv_obj_set_style_text_opa(ctx->book_center_name, LV_OPA_20, 0);
|
||||
lv_anim_t af; lv_anim_init(&af);
|
||||
lv_anim_set_var(&af, ctx->book_center_name);
|
||||
lv_anim_set_values(&af, LV_OPA_20, LV_OPA_COVER);
|
||||
lv_anim_set_duration(&af, 220);
|
||||
lv_anim_set_exec_cb(&af, book_fade_anim_cb);
|
||||
lv_anim_set_path_cb(&af, lv_anim_path_ease_out);
|
||||
lv_anim_start(&af);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (ctx->book_browser) lv_obj_add_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
static void toggle_book_browser(AppCtx* ctx) {
|
||||
// reset chrome auto-hide when opening browser
|
||||
if (ctx->chrome_auto_hide_timer) {
|
||||
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||
ctx->chrome_auto_hide_timer = NULL;
|
||||
}
|
||||
set_book_browser_visible(ctx, !ctx->book_browser_visible);
|
||||
}
|
||||
|
||||
static void on_books_close_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
set_book_browser_visible(ctx, false);
|
||||
}
|
||||
static void on_book_center_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
jump_to_book(ctx, ctx->book_dial_idx);
|
||||
}
|
||||
static void on_dial_prev_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
if (ctx->book_dial_idx>0) { ctx->book_dial_idx--; book_dial_update_center(ctx); }
|
||||
}
|
||||
static void on_dial_next_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
if (ctx->book_dial_idx < ctx->book_count-1) { ctx->book_dial_idx++; book_dial_update_center(ctx); }
|
||||
}
|
||||
static void on_slider_changed(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
int v = lv_slider_get_value(ctx->book_slider);
|
||||
if (v!=ctx->book_dial_idx) { ctx->book_dial_idx=v; book_dial_update_center(ctx); }
|
||||
}
|
||||
static void on_swipe_area_event(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
lv_indev_t* indev = lv_indev_active();
|
||||
if (!indev) return;
|
||||
lv_point_t p; lv_indev_get_point(indev, &p);
|
||||
if (code==LV_EVENT_PRESSED) {
|
||||
ctx->book_drag_start_x = p.x;
|
||||
ctx->book_dragging = true;
|
||||
} else if (code==LV_EVENT_PRESSING && ctx->book_dragging) {
|
||||
int dx = p.x - ctx->book_drag_start_x;
|
||||
int thresh = 28; // px per book
|
||||
if (abs(dx) >= thresh) {
|
||||
int steps = dx / thresh;
|
||||
int new_idx = ctx->book_dial_idx - steps; // swipe left -> next
|
||||
if (new_idx<0) new_idx=0;
|
||||
if (new_idx>=ctx->book_count) new_idx=ctx->book_count-1;
|
||||
if (new_idx!=ctx->book_dial_idx) {
|
||||
ctx->book_dial_idx = new_idx;
|
||||
book_dial_update_center(ctx);
|
||||
}
|
||||
ctx->book_drag_start_x = p.x; // reset for next step
|
||||
}
|
||||
} else if (code==LV_EVENT_RELEASED || code==LV_EVENT_PRESS_LOST) {
|
||||
ctx->book_dragging=false;
|
||||
}
|
||||
}
|
||||
|
||||
static void build_book_browser(AppCtx* ctx, lv_obj_t* parent) {
|
||||
ctx->book_browser = lv_obj_create(parent);
|
||||
lv_obj_set_size(ctx->book_browser, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_align(ctx->book_browser, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_bg_color(ctx->book_browser, lv_color_hex(0x0E0E14), 0);
|
||||
lv_obj_set_style_bg_opa(ctx->book_browser, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(ctx->book_browser, 0, 0);
|
||||
lv_obj_set_style_pad_all(ctx->book_browser, 0, 0);
|
||||
lv_obj_set_style_radius(ctx->book_browser, 0, 0);
|
||||
|
||||
// Swipeable full-center area - no top bar
|
||||
lv_obj_t* center_wrap = lv_obj_create(ctx->book_browser);
|
||||
lv_obj_set_size(center_wrap, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_pad_top(center_wrap, 0, 0);
|
||||
lv_obj_set_style_pad_bottom(center_wrap, 64, 0);
|
||||
lv_obj_set_style_bg_opa(center_wrap, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(center_wrap, 0, 0);
|
||||
lv_obj_remove_flag(center_wrap, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_align(center_wrap, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_add_flag(center_wrap, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESSED, ctx);
|
||||
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESSING, ctx);
|
||||
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_RELEASED, ctx);
|
||||
lv_obj_add_event_cb(center_wrap, on_swipe_area_event, LV_EVENT_PRESS_LOST, ctx);
|
||||
|
||||
// Left arrow
|
||||
lv_obj_t* btn_l = lv_button_create(center_wrap);
|
||||
lv_obj_set_size(btn_l, 44, 44);
|
||||
lv_obj_align(btn_l, LV_ALIGN_LEFT_MID, 8, 0);
|
||||
lv_obj_set_style_radius(btn_l, 22, 0);
|
||||
lv_obj_set_style_bg_color(btn_l, lv_color_hex(0x1E1E2E), 0);
|
||||
lv_obj_t* l_l = lv_label_create(btn_l); lv_label_set_text(l_l, LV_SYMBOL_PREV); lv_obj_center(l_l);
|
||||
lv_obj_add_event_cb(btn_l, on_dial_prev_click, LV_EVENT_CLICKED, ctx);
|
||||
|
||||
// Center — book name directly, 50% larger, tappable (no card)
|
||||
lv_obj_t* mid_wrap = lv_obj_create(center_wrap);
|
||||
lv_obj_set_size(mid_wrap, LV_PCT(68), LV_SIZE_CONTENT);
|
||||
lv_obj_align(mid_wrap, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_obj_set_style_bg_opa(mid_wrap, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(mid_wrap, 0, 0);
|
||||
lv_obj_set_style_pad_all(mid_wrap, 0, 0);
|
||||
lv_obj_set_style_pad_gap(mid_wrap, 10, 0);
|
||||
lv_obj_set_flex_flow(mid_wrap, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(mid_wrap, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_remove_flag(mid_wrap, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(mid_wrap, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(mid_wrap, on_book_center_click, LV_EVENT_CLICKED, ctx);
|
||||
|
||||
ctx->book_center_name = lv_label_create(mid_wrap);
|
||||
lv_label_set_text(ctx->book_center_name, "Genesis");
|
||||
lv_obj_set_style_text_color(ctx->book_center_name, lv_color_hex(0xE8E6E0), 0);
|
||||
lv_obj_set_style_text_font(ctx->book_center_name, lvgl_get_text_font(FONT_SIZE_LARGE), 0); // 50% bigger: LARGE vs DEFAULT
|
||||
lv_obj_set_style_text_letter_space(ctx->book_center_name, 0, 0);
|
||||
lv_label_set_long_mode(ctx->book_center_name, LV_LABEL_LONG_MODE_DOTS);
|
||||
lv_obj_set_width(ctx->book_center_name, LV_PCT(100));
|
||||
lv_obj_set_style_text_align(ctx->book_center_name, LV_TEXT_ALIGN_CENTER, 0);
|
||||
|
||||
ctx->book_center_verses = lv_label_create(mid_wrap);
|
||||
lv_label_set_text(ctx->book_center_verses, "—");
|
||||
lv_obj_set_style_text_color(ctx->book_center_verses, lv_color_hex(0x5A5A78), 0);
|
||||
lv_obj_set_style_text_font(ctx->book_center_verses, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||
lv_obj_set_style_text_align(ctx->book_center_verses, LV_TEXT_ALIGN_CENTER, 0);
|
||||
|
||||
// Right arrow
|
||||
lv_obj_t* btn_r = lv_button_create(center_wrap);
|
||||
lv_obj_set_size(btn_r, 44, 44);
|
||||
lv_obj_align(btn_r, LV_ALIGN_RIGHT_MID, -8, 0);
|
||||
lv_obj_set_style_radius(btn_r, 22, 0);
|
||||
lv_obj_set_style_bg_color(btn_r, lv_color_hex(0x1E1E2E), 0);
|
||||
lv_obj_t* l_r = lv_label_create(btn_r); lv_label_set_text(l_r, LV_SYMBOL_NEXT); lv_obj_center(l_r);
|
||||
lv_obj_add_event_cb(btn_r, on_dial_next_click, LV_EVENT_CLICKED, ctx);
|
||||
|
||||
// Bottom bar: [X cancel] [slider] [N / 66]
|
||||
lv_obj_t* bottom = lv_obj_create(ctx->book_browser);
|
||||
lv_obj_set_size(bottom, LV_PCT(100), 56);
|
||||
lv_obj_align(bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(bottom, lv_color_hex(0x15151F), 0);
|
||||
lv_obj_set_style_bg_opa(bottom, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(bottom, 1, 0);
|
||||
lv_obj_set_style_border_side(bottom, LV_BORDER_SIDE_TOP, 0);
|
||||
lv_obj_set_style_border_color(bottom, lv_color_hex(0x232338), 0);
|
||||
lv_obj_set_style_radius(bottom, 0, 0);
|
||||
lv_obj_set_style_pad_hor(bottom, 10, 0);
|
||||
lv_obj_set_style_pad_ver(bottom, 8, 0);
|
||||
lv_obj_set_style_pad_gap(bottom, 10, 0);
|
||||
lv_obj_set_flex_flow(bottom, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(bottom, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_remove_flag(bottom, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
lv_obj_t* btn_cancel = lv_button_create(bottom);
|
||||
lv_obj_set_size(btn_cancel, 36, 32);
|
||||
lv_obj_set_style_radius(btn_cancel, 8, 0);
|
||||
lv_obj_set_style_bg_color(btn_cancel, lv_color_hex(0x252538), 0);
|
||||
lv_obj_t* l_cancel = lv_label_create(btn_cancel); lv_label_set_text(l_cancel, LV_SYMBOL_CLOSE); lv_obj_center(l_cancel);
|
||||
lv_obj_add_event_cb(btn_cancel, on_books_close_click, LV_EVENT_CLICKED, ctx);
|
||||
|
||||
ctx->book_slider = lv_slider_create(bottom);
|
||||
lv_obj_set_flex_grow(ctx->book_slider, 1);
|
||||
lv_obj_set_height(ctx->book_slider, 8);
|
||||
lv_slider_set_range(ctx->book_slider, 0, ctx->book_count>0 ? ctx->book_count-1 : 65);
|
||||
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0x232338), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0x4A4A6E), LV_PART_INDICATOR);
|
||||
lv_obj_set_style_bg_color(ctx->book_slider, lv_color_hex(0xE8E6E0), LV_PART_KNOB);
|
||||
lv_obj_add_event_cb(ctx->book_slider, on_slider_changed, LV_EVENT_VALUE_CHANGED, ctx);
|
||||
|
||||
ctx->book_center_num = lv_label_create(bottom);
|
||||
lv_label_set_text(ctx->book_center_num, "1 / 66");
|
||||
lv_obj_set_style_text_color(ctx->book_center_num, lv_color_hex(0x8A8AAA), 0);
|
||||
lv_obj_set_style_text_font(ctx->book_center_num, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||
lv_obj_set_style_pad_left(ctx->book_center_num, 4, 0);
|
||||
|
||||
lv_obj_add_flag(ctx->book_browser, LV_OBJ_FLAG_HIDDEN);
|
||||
ctx->book_browser_visible = false;
|
||||
ctx->book_dial_idx = 0;
|
||||
ctx->book_drag_start_x = 0;
|
||||
ctx->book_dragging = false;
|
||||
}
|
||||
|
||||
/* ── UI callbacks ── */
|
||||
static void on_verse_tap(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
toggle_ui(ctx);
|
||||
}
|
||||
|
||||
static void chrome_auto_hide_cb(lv_timer_t* t) {
|
||||
AppCtx* ctx = (AppCtx*)lv_timer_get_user_data(t);
|
||||
if (!ctx) return;
|
||||
// Don't hide if book browser open
|
||||
if (ctx->book_browser_visible) return;
|
||||
if (ctx->ui_visible) set_chrome_visible(ctx, false);
|
||||
if (ctx->chrome_auto_hide_timer) {
|
||||
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||
ctx->chrome_auto_hide_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void schedule_chrome_auto_hide(AppCtx* ctx, uint32_t delay_ms) {
|
||||
if (ctx->chrome_auto_hide_timer) {
|
||||
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||
ctx->chrome_auto_hide_timer = NULL;
|
||||
}
|
||||
ctx->chrome_auto_hide_timer = lv_timer_create(chrome_auto_hide_cb, delay_ms, ctx);
|
||||
}
|
||||
|
||||
static void toggle_ui(AppCtx* ctx) {
|
||||
set_chrome_visible(ctx, !ctx->ui_visible);
|
||||
bool new_vis = !ctx->ui_visible;
|
||||
set_chrome_visible(ctx, new_vis);
|
||||
if (new_vis) {
|
||||
schedule_chrome_auto_hide(ctx, 10000); // hide again after 10s if user shows via tap
|
||||
} else {
|
||||
if (ctx->chrome_auto_hide_timer) {
|
||||
lv_timer_delete(ctx->chrome_auto_hide_timer);
|
||||
ctx->chrome_auto_hide_timer = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void set_chrome_visible(AppCtx* ctx, bool visible) {
|
||||
@@ -721,12 +1127,16 @@ static void on_close_click(lv_event_t* e) {
|
||||
(void)ctx;
|
||||
tt_app_stop();
|
||||
}
|
||||
// Future hooks
|
||||
static void on_memo_click(lv_event_t* e) {
|
||||
// Placeholder: in future record voice memo to /sdcard/bible_memos/<global>.wav
|
||||
// Future hooks merged into books click
|
||||
static void on_books_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
const char* btns[] = {"OK"};
|
||||
tt_app_alertdialog_start("Voice Memo", "Voice memo recording will be added in a future version.\nFile will be saved under /sdcard/bible_memos/", btns, 1);
|
||||
toggle_book_browser(ctx);
|
||||
}
|
||||
|
||||
static void on_title_click(lv_event_t* e) {
|
||||
AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
|
||||
// Tapping title when chrome visible opens books
|
||||
if (ctx->ui_visible) toggle_book_browser(ctx);
|
||||
}
|
||||
|
||||
/* ── App lifecycle ── */
|
||||
@@ -743,6 +1153,21 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
snprintf(g_ctx.progress_path, sizeof(g_ctx.progress_path), "%s/progress.txt", ud_path);
|
||||
snprintf(g_ctx.favorites_path, sizeof(g_ctx.favorites_path), "%s/favorites.txt", ud_path);
|
||||
|
||||
// ── Load pretty serif fonts (embedded C - Georgia Italic like PSALMS reference) ──
|
||||
// Embedded fonts are in PSRAM via ELF LOADER LOAD PSRAM (CONFIG_ELF_LOADER_LOAD_PSRAM=y)
|
||||
// No VFS needed - direct pointers to static const lv_font_t
|
||||
{
|
||||
g_ctx.font_serif_26 = (lv_font_t*)&georgia_italic_26;
|
||||
g_ctx.font_serif_22 = (lv_font_t*)&georgia_italic_22;
|
||||
g_ctx.font_serif_20 = (lv_font_t*)&georgia_italic_20;
|
||||
g_ctx.font_serif_18 = (lv_font_t*)&georgia_italic_18;
|
||||
g_ctx.font_serif_16 = (lv_font_t*)&georgia_italic_16;
|
||||
g_ctx.font_book_24 = (lv_font_t*)&georgia_regular_24;
|
||||
g_ctx.fonts_loaded = true;
|
||||
ESP_LOGI(TAG, "Embedded serif fonts loaded: 26=%p 22=%p 20=%p 18=%p 16=%p book24=%p",
|
||||
g_ctx.font_serif_26, g_ctx.font_serif_22, g_ctx.font_serif_20, g_ctx.font_serif_18, g_ctx.font_serif_16, g_ctx.font_book_24);
|
||||
}
|
||||
|
||||
resolve_bible_paths(&g_ctx);
|
||||
|
||||
if (!load_books_manifest(&g_ctx)) {
|
||||
@@ -774,15 +1199,16 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lv_obj_add_flag(g_ctx.verse_wrapper, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(g_ctx.verse_wrapper, on_verse_tap, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
// Center reading column with generous breathing room
|
||||
// Center reading column with generous breathing room - avoid top chrome crop
|
||||
lv_obj_t* center_col = lv_obj_create(g_ctx.verse_wrapper);
|
||||
lv_obj_set_size(center_col, LV_PCT(90), LV_PCT(80));
|
||||
lv_obj_center(center_col);
|
||||
g_ctx.center_col = center_col;
|
||||
lv_obj_set_size(center_col, LV_PCT(90), LV_PCT(82)); // 80->82 but with top-safe padding
|
||||
lv_obj_align(center_col, LV_ALIGN_CENTER, 0, -7); // up 15px: was +8 base, now -7 (user corrected 30->15)
|
||||
lv_obj_set_style_bg_opa(center_col, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(center_col, 0, 0);
|
||||
lv_obj_set_flex_flow(center_col, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(center_col, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_gap(center_col, 14, 0);
|
||||
lv_obj_set_style_pad_gap(center_col, 10, 0); // tightened: ref closer to verse (was 14)
|
||||
lv_obj_set_style_pad_all(center_col, 0, 0);
|
||||
lv_obj_remove_flag(center_col, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_add_flag(center_col, LV_OBJ_FLAG_CLICKABLE);
|
||||
@@ -791,36 +1217,34 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
g_ctx.lbl_verse = lv_label_create(center_col);
|
||||
lv_obj_set_width(g_ctx.lbl_verse, LV_PCT(100));
|
||||
lv_label_set_long_mode(g_ctx.lbl_verse, LV_LABEL_LONG_MODE_WRAP);
|
||||
lv_obj_set_style_text_color(g_ctx.lbl_verse, lv_color_hex(0xE8E6E0), 0);
|
||||
lv_obj_set_style_text_font(g_ctx.lbl_verse, lvgl_get_text_font(FONT_SIZE_LARGE), 0);
|
||||
lv_obj_set_style_text_color(g_ctx.lbl_verse, lv_color_hex(0xF2F0E8), 0); // warmer, editorial like reference image (was #E8E6E0)
|
||||
lv_obj_set_style_text_font(g_ctx.lbl_verse, lvgl_get_text_font(FONT_SIZE_DEFAULT), 0); // reduced base - Georgia italic wider than Montserrat
|
||||
lv_obj_set_style_text_align(g_ctx.lbl_verse, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_line_space(g_ctx.lbl_verse, 8, 0);
|
||||
lv_obj_set_style_text_letter_space(g_ctx.lbl_verse, 0, 0);
|
||||
lv_obj_set_style_radius(g_ctx.lbl_verse, 16, 0);
|
||||
lv_obj_set_style_pad_all(g_ctx.lbl_verse, 16, 0);
|
||||
lv_obj_set_style_radius(g_ctx.lbl_verse, 0, 0); // no bubble - flat editorial
|
||||
lv_obj_set_style_pad_all(g_ctx.lbl_verse, 4, 0); // less padding, avoid crop
|
||||
lv_label_set_text(g_ctx.lbl_verse, "Loading...");
|
||||
|
||||
g_ctx.lbl_ref = lv_label_create(center_col);
|
||||
lv_obj_set_style_text_color(g_ctx.lbl_ref, lv_color_hex(0x7A7A96), 0);
|
||||
lv_obj_set_style_text_color(g_ctx.lbl_ref, lv_color_hex(0x9A9AA8), 0); // softer, like PSALMS citation gray
|
||||
lv_obj_set_style_text_font(g_ctx.lbl_ref, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||
lv_obj_set_style_text_align(g_ctx.lbl_ref, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_set_style_text_letter_space(g_ctx.lbl_ref, 1, 0);
|
||||
lv_obj_set_style_text_letter_space(g_ctx.lbl_ref, 2, 0); // tracking like reference image's PSALMS 23:1
|
||||
lv_label_set_text(g_ctx.lbl_ref, "");
|
||||
|
||||
// ── Header bar (frosted dark) ──
|
||||
// ── Header bar (frosted dark) - hidden by default to avoid black bar crop ──
|
||||
// Fix black bar cropping text: make truly transparent, no border, same bg as root #0E0E14
|
||||
g_ctx.header_bar = lv_obj_create(parent);
|
||||
lv_obj_t* hb = g_ctx.header_bar;
|
||||
lv_obj_set_size(hb, LV_PCT(100), 44);
|
||||
lv_obj_set_size(hb, LV_PCT(100), 36); // reduced 44->36 to avoid crop
|
||||
lv_obj_align(hb, LV_ALIGN_TOP_MID, 0, 0);
|
||||
lv_obj_set_style_radius(hb, 0, 0);
|
||||
lv_obj_set_style_bg_color(hb, lv_color_hex(0x15151F), 0);
|
||||
lv_obj_set_style_bg_opa(hb, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_bg_color(hb, lv_color_hex(0x0E0E14), 0); // same as root, not #15151F
|
||||
lv_obj_set_style_bg_opa(hb, LV_OPA_TRANSP, 0); // transparent to avoid black bar - was COVER
|
||||
lv_obj_set_style_border_width(hb, 0, 0);
|
||||
lv_obj_set_style_border_side(hb, LV_BORDER_SIDE_BOTTOM, 0);
|
||||
lv_obj_set_style_border_color(hb, lv_color_hex(0x232338), 0);
|
||||
lv_obj_set_style_border_width(hb, 1, 0);
|
||||
lv_obj_set_style_pad_hor(hb, 12, 0);
|
||||
lv_obj_set_style_pad_ver(hb, 0, 0);
|
||||
lv_obj_set_style_pad_ver(hb, 4, 0);
|
||||
lv_obj_remove_flag(hb, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
g_ctx.lbl_book_title = lv_label_create(hb);
|
||||
@@ -828,6 +1252,8 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lv_obj_set_style_text_color(g_ctx.lbl_book_title, lv_color_hex(0xE8E6E0), 0);
|
||||
lv_obj_set_style_text_font(g_ctx.lbl_book_title, lvgl_get_text_font(FONT_SIZE_SMALL), 0);
|
||||
lv_label_set_text(g_ctx.lbl_book_title, "Bible");
|
||||
lv_obj_add_flag(g_ctx.lbl_book_title, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(g_ctx.lbl_book_title, on_title_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
g_ctx.lbl_position = lv_label_create(hb);
|
||||
lv_obj_align(g_ctx.lbl_position, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
@@ -841,18 +1267,15 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lv_obj_set_size(cb, LV_PCT(100), 56);
|
||||
lv_obj_align(cb, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_radius(cb, 0, 0);
|
||||
lv_obj_set_style_bg_color(cb, lv_color_hex(0x15151F), 0);
|
||||
lv_obj_set_style_bg_opa(cb, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(cb, 1, 0);
|
||||
lv_obj_set_style_border_side(cb, LV_BORDER_SIDE_TOP, 0);
|
||||
lv_obj_set_style_border_color(cb, lv_color_hex(0x232338), 0);
|
||||
lv_obj_set_style_bg_color(cb, lv_color_hex(0x0E0E14), 0);
|
||||
lv_obj_set_style_bg_opa(cb, LV_OPA_TRANSP, 0); // transparent editorial, was #15151F COVER
|
||||
lv_obj_set_style_border_width(cb, 0, 0);
|
||||
lv_obj_set_style_pad_all(cb, 6, 0);
|
||||
lv_obj_set_style_pad_gap(cb, 4, 0);
|
||||
lv_obj_set_flex_flow(cb, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(cb, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_remove_flag(cb, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
lv_obj_t* btn;
|
||||
lv_obj_t* lbl;
|
||||
|
||||
g_ctx.btn_close = lv_button_create(cb);
|
||||
@@ -863,6 +1286,14 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lbl = lv_label_create(g_ctx.btn_close); lv_label_set_text(lbl, LV_SYMBOL_CLOSE); lv_obj_center(lbl);
|
||||
lv_obj_add_event_cb(g_ctx.btn_close, on_close_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
g_ctx.btn_books = lv_button_create(cb);
|
||||
lv_obj_set_size(g_ctx.btn_books, 44, 38);
|
||||
lv_obj_set_style_radius(g_ctx.btn_books, 10, 0);
|
||||
lv_obj_set_style_bg_color(g_ctx.btn_books, lv_color_hex(0x252538), 0);
|
||||
lv_obj_set_style_text_color(g_ctx.btn_books, lv_color_hex(0xC4C4D4), 0);
|
||||
lbl = lv_label_create(g_ctx.btn_books); lv_label_set_text(lbl, LV_SYMBOL_LIST); lv_obj_center(lbl);
|
||||
lv_obj_add_event_cb(g_ctx.btn_books, on_books_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
g_ctx.btn_prev = lv_button_create(cb);
|
||||
lv_obj_set_size(g_ctx.btn_prev, 44, 38);
|
||||
lv_obj_set_style_radius(g_ctx.btn_prev, 10, 0);
|
||||
@@ -872,7 +1303,7 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lv_obj_add_event_cb(g_ctx.btn_prev, on_prev_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
g_ctx.btn_play_pause = lv_button_create(cb);
|
||||
lv_obj_set_size(g_ctx.btn_play_pause, 76, 38);
|
||||
lv_obj_set_size(g_ctx.btn_play_pause, 56, 38);
|
||||
lv_obj_set_style_radius(g_ctx.btn_play_pause, 19, 0);
|
||||
lv_obj_set_style_bg_color(g_ctx.btn_play_pause, lv_color_hex(0x2A2A4A), 0);
|
||||
lv_obj_set_style_text_color(g_ctx.btn_play_pause, lv_color_hex(0xE8E6E0), 0);
|
||||
@@ -888,23 +1319,20 @@ static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
|
||||
lv_obj_add_event_cb(g_ctx.btn_next, on_next_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
g_ctx.btn_highlight = lv_button_create(cb);
|
||||
lv_obj_set_size(g_ctx.btn_highlight, 44, 38);
|
||||
lv_obj_set_size(g_ctx.btn_highlight, 38, 38);
|
||||
lv_obj_set_style_radius(g_ctx.btn_highlight, 10, 0);
|
||||
lv_obj_set_style_bg_color(g_ctx.btn_highlight, lv_color_hex(0x252538), 0);
|
||||
lv_obj_set_style_text_color(g_ctx.btn_highlight, lv_color_hex(0x8C8CB0), 0);
|
||||
lbl = lv_label_create(g_ctx.btn_highlight); lv_label_set_text(lbl, LV_SYMBOL_DUMMY); lv_obj_center(lbl);
|
||||
lv_obj_add_event_cb(g_ctx.btn_highlight, on_highlight_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
btn = lv_button_create(cb);
|
||||
lv_obj_set_size(btn, 38, 38);
|
||||
lv_obj_set_style_radius(btn, 10, 0);
|
||||
lv_obj_set_style_bg_color(btn, lv_color_hex(0x1E1E2E), 0);
|
||||
lv_obj_set_style_text_color(btn, lv_color_hex(0x6A6A8A), 0);
|
||||
lbl = lv_label_create(btn); lv_label_set_text(lbl, LV_SYMBOL_AUDIO); lv_obj_center(lbl);
|
||||
lv_obj_add_event_cb(btn, on_memo_click, LV_EVENT_CLICKED, &g_ctx);
|
||||
|
||||
set_chrome_visible(&g_ctx, false);
|
||||
set_chrome_visible(&g_ctx, true); // start with menu showing per user request
|
||||
schedule_chrome_auto_hide(&g_ctx, 10000); // auto hide after 10s
|
||||
show_current_verse(&g_ctx);
|
||||
|
||||
// Build book browser overlay (on top of everything)
|
||||
build_book_browser(&g_ctx, parent);
|
||||
|
||||
g_ctx.auto_timer = lv_timer_create(on_auto_timer, AUTO_ADV_MS, &g_ctx);
|
||||
}
|
||||
|
||||
@@ -914,6 +1342,11 @@ static void onHideApp(AppHandle app, void* data) {
|
||||
lv_timer_delete(g_ctx.auto_timer);
|
||||
g_ctx.auto_timer = NULL;
|
||||
}
|
||||
if (g_ctx.chrome_auto_hide_timer) {
|
||||
lv_timer_delete(g_ctx.chrome_auto_hide_timer);
|
||||
g_ctx.chrome_auto_hide_timer = NULL;
|
||||
}
|
||||
// Embedded fonts are static const - no free needed (live in PSRAM ELF)
|
||||
save_progress(&g_ctx);
|
||||
unload_book(&g_ctx);
|
||||
// UI objects are deleted by LVGL parent cleanup
|
||||
|
||||
Reference in New Issue
Block a user