From b078df52e9b6f0ac8fea6497b4e97a74bfa3e8f2 Mon Sep 17 00:00:00 2001 From: "Michael A. Cojocari" Date: Wed, 30 Sep 2026 20:54:15 -0400 Subject: [PATCH] feat(ui): lock screen shows the battery, laid out for every panel; P4 BOOT button locks Lock screen (all boards, #579): the default view is now drawn from parts instead of the old 320x240 bitmap with the padlock and wordmark baked in, which cover-scaled into a jumble on any other panel shape. WADAMESH sits at the top, the clock below it (clear of the status bar), a smaller padlock in the centre with the unread count to its left and a new battery readout to its right, each centred between the padlock and the screen edge. Short panels get a smaller wordmark and padlock. The clock's burn-in drift now moves around its new place instead of snapping it back to y=30 every minute. T-Deck Pro keeps its black-on-white e-paper colours; custom wallpapers are unchanged. T-Display P4: the BOOT button (GPIO35) now works like the Wio Tracker L2's wake button -- a press wakes or lights the lock screen, a 2 s hold locks or unlocks -- so the P4 gets the lock screen and the "Lock when screen off" setting. It gets no Power-off row: GPIO35 cannot wake the P4 from deep sleep. Also: the chat list's per-row gear clears the list scrollbar on large panels. Co-Authored-By: Claude Opus 5.5 --- DEVICES.md | 5 +- src/ui-touch/UITask.cpp | 329 +++++++++++++++++++------------- src/ui-touch/device_caps.h | 16 +- tdisplay_p4/main/CMakeLists.txt | 5 + 4 files changed, 213 insertions(+), 142 deletions(-) diff --git a/DEVICES.md b/DEVICES.md index 80e59a6..d0ac4ff 100644 --- a/DEVICES.md +++ b/DEVICES.md @@ -81,7 +81,10 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases validated at boot; if it reports lost integrity, the same optional saved-Wi-Fi cold-boot sync offered on T-Deck is available in Clock settings. New in beta_38; report anything that feels off. -- **T-Display P4**: touch UI with the on-screen keyboard. The optional clip-on +- **T-Display P4**: touch UI with the on-screen keyboard. The BOOT button wakes + the screen (or lights the lock screen) and holding it for two seconds locks or + unlocks, as the Wio Tracker L2's wake button does; "Lock when screen off" is + under Settings, Lock screen. The optional clip-on keyboard expansion can be attached or removed while running and is picked up within a couple of seconds; while it is on, it types into the focused field and the on-screen keyboard stays down (tap the field again to bring it up). diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index cd9415e..dcee592 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -49,14 +49,6 @@ static void* wadaMp3Scratch() { return s_wada_mp3_scratch; } #include // esp_deep_sleep_start / ext0 wakeup for the power-off menu #include // rtc_gpio_pullup_en — hold the wake pin's level in deep sleep #include "assets/lockscreen_placeholder_jpg.h" // seeded to SPIFFS /lock/placeholder.jpg on first boot (PNG decode is broken on this board) - #if CAP_LOCK_SCREEN - #if !defined(HAS_TDECK_PRO) - #include "assets/lockscreen_wallpaper_rgb565.h" // crisp pre-dithered default lock-screen wallpaper (no JPEG banding) - #endif - #if defined(TLORA_PAGER) - #include "assets/lockscreen_wallpaper_pager_rgb565.h" // native 480x222 crop/layout for this board's wide/short panel - #endif - #endif #include #include #include // nvs_get_stats() for the About-tab NVS usage line @@ -14039,6 +14031,8 @@ static void lockOnScreenOffToggleCb(lv_event_t* e) { const char* unlock_hint = on ? TR("Locks when screen off\n(double-press d-pad center to unlock)") : TR("Screen-off just dims"); #elif defined(HAS_WIO_TRACKER_L2) const char* unlock_hint = on ? TR("Locks when screen off\n(hold the wake button to unlock)") : TR("Screen-off just dims"); +#elif defined(HAS_TDISPLAY_P4) + const char* unlock_hint = on ? TR("Locks when screen off\n(hold the BOOT button to unlock)") : TR("Screen-off just dims"); #elif defined(HAS_TANMATSU) const char* unlock_hint = on ? TR("Locks when screen off\n(press Volume Down to unlock)") : TR("Screen-off just dims"); #elif defined(TLORA_PAGER) @@ -16126,10 +16120,8 @@ static void buildDeviceSettings(int sec) { #endif // HAS_TDECK_GT911 || HAS_THINKNODE_M9 // Auto-lock on screen-off: when the idle timeout dims the screen, also hard-lock so the - // touchscreen is inert (Tim: pocket-taps while dark). NOT on the P4 — it has no unlock path - // (no button/trackball and no touch-unlock overlay yet), so a hard lock there is a power-cycle - // trap. The P4 still dims on idle (screen-off wakes on touch); a proper P4 lock is a follow-up. -#if !defined(HAS_TDISPLAY_P4) + // touchscreen is inert (Tim: pocket-taps while dark). Every board here has a deliberate + // unlock control; the P4's is holding its BOOT button. { int h = settingsRowLabel(body, y, 6, TR("Lock when screen off"), COLOR_SUB, nullptr, 56); lv_obj_t* sw = lv_switch_create(body); @@ -16147,7 +16139,6 @@ static void buildDeviceSettings(int sec) { #endif y += LV_MAX(40, h + 12); } -#endif } @@ -41385,6 +41376,22 @@ static void formatChatRowTime(char* buf, size_t cap, uint32_t ts) { else strftime(buf, cap, "%d/%m/%y", &tmv); } +// How far in from the row's right edge the per-row settings gear sits. On the +// large panels the list's scrollbar is drawn over that edge and covered the +// gear, so clear it: the scrollbar's own width and edge gap, plus a margin. +static lv_coord_t threadGearRightInset(lv_obj_t* row) { +#if CAP_LARGE_SCREEN + for (lv_obj_t* o = row ? lv_obj_get_parent(row) : nullptr; o; o = lv_obj_get_parent(o)) { + if (!lv_obj_has_flag(o, LV_OBJ_FLAG_SCROLLABLE)) continue; + return lv_obj_get_style_width(o, LV_PART_SCROLLBAR) + + lv_obj_get_style_pad_right(o, LV_PART_SCROLLBAR) + 6; + } +#else + (void)row; +#endif + return 2; +} + static void refreshChatList(LvChatPanel& p) { if (!g_lv.task || !p.list_cont) return; @@ -41481,13 +41488,14 @@ static void refreshChatList(LvChatPanel& p) { // Per-row settings gear on the far right — opens the thread-settings sheet (same as a long-press // and the in-chat cog). Tapping it swallows the gesture so the row's CLICKED can't open the chat. const lv_coord_t gear_w = 28; + const lv_coord_t gear_r = threadGearRightInset(btn); { lv_obj_t* gear = lv_btn_create(btn); lv_obj_remove_style_all(gear); lv_obj_add_flag(gear, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_add_flag(gear, NAV_HMOVE_FLAG); // keyboard nav: reach the gear with RIGHT (not UP/DOWN); keeps the row itself focusable so the chat opens lv_obj_set_size(gear, gear_w, 30); - lv_obj_align(gear, LV_ALIGN_RIGHT_MID, -2, 0); + lv_obj_align(gear, LV_ALIGN_RIGHT_MID, -gear_r, 0); lv_obj_add_event_cb(gear, threadGearCb, LV_EVENT_CLICKED, &p.ctx_store[i]); lv_obj_t* gl = lv_label_create(gear); lv_label_set_text(gl, LV_SYMBOL_SETTINGS); @@ -41496,7 +41504,7 @@ static void refreshChatList(LvChatPanel& p) { lv_obj_center(gl); } // Last-message time, just left of the gear; the unread badge + @ sit to its left. - const lv_coord_t time_x = (lv_coord_t)(-(10 + gear_w)); + const lv_coord_t time_x = (lv_coord_t)(-(8 + gear_r + gear_w)); char tbuf[16]; formatChatRowTime(tbuf, sizeof(tbuf), ts); lv_coord_t time_w = 0; @@ -41652,13 +41660,14 @@ static void refreshChatList(LvChatPanel& p) { // Per-row settings gear on the far right (same behaviour as the compact rows). const lv_coord_t gear_w = 28; + const lv_coord_t gear_r = threadGearRightInset(btn); { lv_obj_t* gear = lv_btn_create(btn); lv_obj_remove_style_all(gear); lv_obj_add_flag(gear, LV_OBJ_FLAG_IGNORE_LAYOUT); lv_obj_add_flag(gear, NAV_HMOVE_FLAG); lv_obj_set_size(gear, gear_w, kThreadRowH - 8); - lv_obj_align(gear, LV_ALIGN_RIGHT_MID, -2, 0); + lv_obj_align(gear, LV_ALIGN_RIGHT_MID, -gear_r, 0); lv_obj_add_event_cb(gear, threadGearCb, LV_EVENT_CLICKED, &p.ctx_store[i]); lv_obj_t* gl = lv_label_create(gear); lv_label_set_text(gl, LV_SYMBOL_SETTINGS); @@ -41668,7 +41677,7 @@ static void refreshChatList(LvChatPanel& p) { } // Time, top-right (left of the gear). - const lv_coord_t time_x = (lv_coord_t)(-(10 + gear_w)); + const lv_coord_t time_x = (lv_coord_t)(-(8 + gear_r + gear_w)); char tbuf[16]; formatChatRowTime(tbuf, sizeof(tbuf), ts); lv_coord_t time_w = 0; @@ -43491,10 +43500,9 @@ static unsigned long s_lock_unread_ms = 0; // 1 Hz poll limiter #if defined(HAS_TDECK_PRO) static bool s_lock_epaper_default = false; // built-in white lock view vs custom wallpaper #endif -#if defined(TLORA_PAGER) -static lv_obj_t* s_lock_status = nullptr; // "Screen locked" -- tracked for lockscreenHide() cleanup -static lv_obj_t* s_lock_hint = nullptr; // unlock hint -- tracked for lockscreenHide() cleanup -#endif +static lv_coord_t s_lock_clock_y = 30; // the clock's place (lockscreenShow); the drift moves around it +static lv_obj_t* s_lock_batt = nullptr; // battery glyph + % to the right of the padlock +static int s_lock_batt_key = -1; // last level drawn (redraw guard; 1000 = charging) // How long the trackball must be held to unlock, in ms. static const unsigned long kLockUnlockHoldMs = 1000; @@ -43574,20 +43582,9 @@ static void lockscreenUpdateClock() { // the panel. Deterministic from the minute, so it also moves on every reveal. const int dx = (mm % 5) * 3 - 6; // -6 … +6 px const int dy = ((mm / 5) % 3) * 4 - 4; // -4 … +4 px -#if defined(TLORA_PAGER) - // Same anti-burn-in drift, but around this board's top-LEFT clock position - // (lockscreenShow()'s TOP_LEFT/6,30) instead of T-Deck's TOP_MID -- without - // this override every periodic clock update (this function runs on every - // minute rollover) silently snapped the clock back to horizontally - // centered, undoing the top-left placement the moment it first ticked. - // The unread badge stays in its own right-column spot (300,70) -- it - // doesn't ride with the clock on this layout, so no drift needed there. - lv_obj_align(s_lock_clock, LV_ALIGN_TOP_LEFT, 6 + dx, 30 + dy); -#else - lv_obj_align(s_lock_clock, LV_ALIGN_TOP_MID, dx, 30 + dy); - // The unread badge rides along with the same drift so it never parks either. - if (s_lock_unread) lv_obj_align(s_lock_unread, LV_ALIGN_TOP_MID, dx, 68 + dy); -#endif + // Around the place lockscreenShow() gave it: re-aligning to a fixed y here + // would put it back under the status bar / wordmark on the first tick. + lv_obj_align(s_lock_clock, LV_ALIGN_TOP_MID, dx, s_lock_clock_y + dy); s_lock_clock_min = mm; } @@ -43605,6 +43602,22 @@ static void lockscreenUpdateUnread() { lv_obj_clear_flag(s_lock_unread, LV_OBJ_FLAG_HIDDEN); } +// The battery readout beside the padlock: the status bar's glyph, plus the +// percentage unless charging (the bolt says it then, as in the status bar). +static void lockscreenUpdateBattery() { + if (!s_lock_batt) return; + const uint16_t mv = batteryMvSmoothed(); + const bool charging = batteryIsCharging(mv); + const int pct = batteryPercentFromMv(mv); + const int key = charging ? 1000 : pct; + if (key == s_lock_batt_key) return; + s_lock_batt_key = key; + char b[24]; + if (charging || pct < 0) snprintf(b, sizeof b, "%s", batteryGlyphForMv(mv)); + else snprintf(b, sizeof b, "%s %d%%", batteryGlyphForMv(mv), pct); + lv_label_set_text(s_lock_batt, b); +} + static void lockscreenUnlockPopupHide() { if (s_unlock_popup) { lv_obj_del(s_unlock_popup); s_unlock_popup = nullptr; s_unlock_count = nullptr; } } @@ -43660,8 +43673,13 @@ static void lockscreenShow() { const lv_coord_t sh = lv_disp_get_ver_res(nullptr); char wpath[TOUCH_LOCK_WALLPAPER_MAXLEN]; touchPrefsGetLockWallpaper(wpath, sizeof wpath); + // The default view is drawn from parts -- wordmark, clock, padlock -- rather + // than the old 320x240 bitmap with the padlock and wordmark baked in. Cover- + // scaled onto any other panel shape, that bitmap cropped the wordmark and + // zoomed everything into one jumble in the middle, under the clock. + const bool native = !strcmp(wpath, "/lock/placeholder.jpg"); #if defined(HAS_TDECK_PRO) - s_lock_epaper_default = !strcmp(wpath, "/lock/placeholder.jpg"); + s_lock_epaper_default = native; #endif s_lock_root = lv_obj_create(lv_layer_top()); @@ -43669,10 +43687,16 @@ static void lockscreenShow() { lv_obj_set_size(s_lock_root, sw, sh); lv_obj_set_pos(s_lock_root, 0, 0); #if defined(HAS_TDECK_PRO) - lv_obj_set_style_bg_color(s_lock_root, - s_lock_epaper_default ? lv_color_white() : lv_color_black(), LV_PART_MAIN); + // 1-bit e-paper: the default view is black on white. + lv_obj_set_style_bg_color(s_lock_root, native ? lv_color_white() : lv_color_black(), LV_PART_MAIN); #else - lv_obj_set_style_bg_color(s_lock_root, lv_color_black(), LV_PART_MAIN); + if (native) { // the old wallpaper's dark gradient + lv_obj_set_style_bg_color(s_lock_root, lv_color_hex(0x1C232B), LV_PART_MAIN); + lv_obj_set_style_bg_grad_color(s_lock_root, lv_color_hex(0x080B0F), LV_PART_MAIN); + lv_obj_set_style_bg_grad_dir(s_lock_root, LV_GRAD_DIR_VER, LV_PART_MAIN); + } else { + lv_obj_set_style_bg_color(s_lock_root, lv_color_black(), LV_PART_MAIN); + } #endif lv_obj_set_style_bg_opa(s_lock_root, LV_OPA_COVER, LV_PART_MAIN); lv_obj_clear_flag(s_lock_root, LV_OBJ_FLAG_SCROLLABLE); @@ -43682,7 +43706,7 @@ static void lockscreenShow() { // glow around the lock view. lv_obj_add_flag(s_lock_root, NAV_SKIP_FLAG); - // Wallpaper, scaled to cover the screen (crop overflow, never letterbox). + // A custom wallpaper, scaled to cover the screen (crop overflow, never letterbox). int ww = 0, wh = 0; const uint8_t* wall_data = nullptr; if (s_lock_wall) { @@ -43697,37 +43721,7 @@ static void lockscreenShow() { lvglPsramFree(s_lock_wall); s_lock_wall = nullptr; } - if (!strcmp(wpath, "/lock/placeholder.jpg")) { - // Default: the pre-dithered RGB565 embed, drawn straight from flash — crisp, - // with no JPEG round-trip to re-introduce gradient banding. -#if defined(HAS_TDECK_PRO) - // The shared default is a 320x240 landscape bitmap. Cover-scaling it onto - // this 240x320 portrait panel crops both ends of WADAMESH and magnifies its - // padlock. Draw a native monochrome wordmark instead: no crop, no giant icon. - lv_obj_t* brand = lv_label_create(s_lock_root); - lv_label_set_text(brand, "WADAMESH"); - lv_obj_set_width(brand, sw - 24); - lv_obj_set_style_text_align(brand, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); - lv_obj_set_style_text_font(brand, &lv_font_unscii_16, LV_PART_MAIN); - lv_obj_set_style_text_color(brand, lv_color_black(), LV_PART_MAIN); - lv_obj_set_style_text_letter_space(brand, 2, LV_PART_MAIN); - lv_obj_align(brand, LV_ALIGN_CENTER, 0, 6); -#elif defined(TLORA_PAGER) - // Native 480x222 crop of the same icon+wordmark, repositioned to the left - // (see lockscreen_wallpaper_pager_rgb565.h) -- the shared 320x240 art's - // cover-fill on this much wider/shorter panel zoomed it ~1.5x and center- - // cropped ~70px off top+bottom, shoving it into the fixed-position text - // labels below (reported/photographed). Being screen-native, this needs - // no crop math at all: the cover-zoom below naturally computes ~1:1. - wall_data = (const uint8_t*)lockscreen_wallpaper_pager_rgb565; - ww = LOCKSCREEN_WALLPAPER_PAGER_W; - wh = LOCKSCREEN_WALLPAPER_PAGER_H; -#else - wall_data = (const uint8_t*)lockscreen_wallpaper_rgb565; - ww = LOCKSCREEN_WALLPAPER_W; - wh = LOCKSCREEN_WALLPAPER_H; -#endif - } else { + if (!native) { s_lock_wall = lockscreenDecodeWallpaper(&ww, &wh); // custom wallpaper (JPEG) wall_data = s_lock_wall; } @@ -43743,10 +43737,7 @@ static void lockscreenShow() { lv_img_set_antialias(img, true); lv_img_set_pivot(img, ww / 2, wh / 2); lv_obj_clear_flag(img, LV_OBJ_FLAG_CLICKABLE); - // Background wallpaper: same cover-fill-and-center treatment on every - // board, pager included -- only the text labels below get a pager- - // specific layout (they're what was actually unreadable/colliding; the - // wallpaper crop itself is unchanged from how it's always looked). + // Same cover-fill-and-center treatment on every board. uint32_t zx = (uint32_t)sw * 256u / (uint32_t)ww; uint32_t zy = (uint32_t)sh * 256u / (uint32_t)wh; uint32_t zoom = (zx > zy) ? zx : zy; // cover @@ -43754,11 +43745,12 @@ static void lockscreenShow() { lv_img_set_zoom(img, (uint16_t)zoom); lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); } - #if defined(HAS_TDECK_PRO) // The built-in Pro/Max lock view is black on white for clean 1-bit e-paper. // Custom wallpapers keep the prior white overlay text for contrast. - const lv_color_t col = s_lock_epaper_default ? lv_color_black() : lv_color_white(); + const lv_color_t col = native ? lv_color_black() : lv_color_white(); + const lv_color_t brand_col = col; + const lv_color_t lock_col = col; #elif defined(TLORA_PAGER) // Force a guaranteed-visible white here rather than the shared, user- // customizable touchPrefsGetLockTextColor() -- on this board that pref was @@ -43766,56 +43758,140 @@ static void lockscreenShow() { // background) even at the shared soft-white default (0xE6F2FF), so this // board gets pure white instead of chasing a per-device pref/storage // question for a cosmetic lock screen. - const lv_color_t col = lv_color_hex(0xFFFFFFu); + const lv_color_t col = lv_color_hex(0xFFFFFFu); + const lv_color_t brand_col = lv_color_white(); + const lv_color_t lock_col = lv_color_hex(0x3D9BFF); // the old wallpaper's padlock blue #else - const lv_color_t col = lv_color_hex(touchPrefsGetLockTextColor()); + const lv_color_t col = lv_color_hex(touchPrefsGetLockTextColor()); + const lv_color_t brand_col = lv_color_white(); + const lv_color_t lock_col = lv_color_hex(0x3D9BFF); // the old wallpaper's padlock blue #endif + // Layout, top down from the status bar: WADAMESH (default view only), the + // clock, then a centre row [unread count] padlock [battery] with "Screen + // locked" under it, and the unlock hint at the bottom. The short panels (222- + // 320 px tall) get a smaller wordmark and padlock and tighter gaps; the centre + // row sits at mid-screen unless that would reach the clock. + const bool short_panel = sh < 400; + const lv_coord_t gap = short_panel ? 4 : 14; + const lv_font_t* brand_font = short_panel ? &lv_font_montserrat_16 : &lv_font_montserrat_28; + const lv_coord_t lock_w = short_panel ? 40 : 64; + const lv_coord_t lock_h = short_panel ? 48 : 78; + lv_coord_t y = STATUSBAR_H + gap; + + if (native) { + lv_obj_t* brand = lv_label_create(s_lock_root); + lv_label_set_text(brand, "WADAMESH"); + lv_obj_set_style_text_font(brand, brand_font, LV_PART_MAIN); + lv_obj_set_style_text_color(brand, brand_col, LV_PART_MAIN); + lv_obj_set_style_text_letter_space(brand, short_panel ? 2 : 3, LV_PART_MAIN); + lv_obj_align(brand, LV_ALIGN_TOP_MID, 0, y); + y += lv_font_get_line_height(brand_font) + gap; + } + s_lock_clock = lv_label_create(s_lock_root); lv_label_set_text(s_lock_clock, "--:--"); lv_obj_set_style_text_font(s_lock_clock, &lv_font_montserrat_28, LV_PART_MAIN); lv_obj_set_style_text_color(s_lock_clock, col, LV_PART_MAIN); -#if defined(TLORA_PAGER) - // Fixed-width box spanning the same 192px the composited icon occupies - // (lockscreen_wallpaper_pager_rgb565.h, pasted at x=6 width=192) with - // centered text, instead of just left-anchoring the label -- "10:37" and - // "8:05" are different pixel widths, so anchoring by the label's own LEFT - // edge left the clock's actual visual center drifting with the text - // instead of lining up with the icon below it. Centering within a box of - // the icon's own width keeps the two centers matched regardless of what - // the clock displays. - lv_obj_set_width(s_lock_clock, 192); - lv_obj_set_style_text_align(s_lock_clock, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); - lv_obj_align(s_lock_clock, LV_ALIGN_TOP_LEFT, 6, 30); // top-left corner, below the 22 px status bar -#else - lv_obj_align(s_lock_clock, LV_ALIGN_TOP_MID, 0, 30); // below the 22 px status bar -#endif + // The clock drifts +/-4 px around this for burn-in (lockscreenUpdateClock), so + // it sits that much lower to keep clear of the wordmark above. + s_lock_clock_y = y + 4; + lv_obj_align(s_lock_clock, LV_ALIGN_TOP_MID, 0, s_lock_clock_y); + y = s_lock_clock_y + 4 + lv_font_get_line_height(&lv_font_montserrat_28) + (short_panel ? 6 : 18); s_lock_clock_min = -1; s_lock_clock_current = -1; + // Centre row anchor: the padlock's box (drawn on the default view only; a + // custom wallpaper keeps its own picture, and the row keeps its place). + lv_obj_t* lock = lv_obj_create(s_lock_root); + lv_obj_remove_style_all(lock); + lv_obj_set_size(lock, lock_w, lock_h); + lv_obj_clear_flag(lock, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE); + const lv_coord_t lock_y = LV_MAX((lv_coord_t)(sh / 2 - lock_h / 2), y); + lv_obj_align(lock, LV_ALIGN_TOP_MID, 0, lock_y); + if (native) { + // Shackle: the top half of a ring, with legs down into the body. + const lv_coord_t ring = lock_w * 5 / 8; + const lv_coord_t stroke = short_panel ? 4 : 5; + const lv_coord_t body_y = lock_h * 7 / 16; + lv_obj_t* arc = lv_arc_create(lock); + lv_obj_remove_style_all(arc); + lv_obj_set_size(arc, ring, ring); + lv_obj_align(arc, LV_ALIGN_TOP_MID, 0, 0); + lv_arc_set_bg_angles(arc, 180, 360); + lv_obj_set_style_arc_width(arc, stroke, LV_PART_MAIN); + lv_obj_set_style_arc_color(arc, lock_col, LV_PART_MAIN); + lv_obj_set_style_arc_opa(arc, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_arc_opa(arc, LV_OPA_TRANSP, LV_PART_INDICATOR); + lv_obj_set_style_bg_opa(arc, LV_OPA_TRANSP, LV_PART_KNOB); + lv_obj_clear_flag(arc, LV_OBJ_FLAG_CLICKABLE); + for (int side = 0; side < 2; ++side) { + lv_obj_t* leg = lv_obj_create(lock); + lv_obj_remove_style_all(leg); + lv_obj_set_size(leg, stroke, body_y - ring / 2 + 2); + lv_obj_set_style_bg_color(leg, lock_col, LV_PART_MAIN); + lv_obj_set_style_bg_opa(leg, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_pos(leg, (lock_w - ring) / 2 + (side ? ring - stroke : 0), ring / 2); + } + // Body, with a keyhole. + lv_obj_t* body = lv_obj_create(lock); + lv_obj_remove_style_all(body); + lv_obj_set_size(body, lock_w, lock_h - body_y); + lv_obj_set_pos(body, 0, body_y); + lv_obj_set_style_radius(body, short_panel ? 6 : 10, LV_PART_MAIN); +#if defined(HAS_TDECK_PRO) + lv_obj_set_style_bg_color(body, lv_color_white(), LV_PART_MAIN); +#else + lv_obj_set_style_bg_color(body, lv_color_hex(0x141A21), LV_PART_MAIN); +#endif + lv_obj_set_style_bg_opa(body, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_set_style_border_color(body, lock_col, LV_PART_MAIN); + lv_obj_set_style_border_width(body, short_panel ? 3 : 4, LV_PART_MAIN); + const lv_coord_t hole_d = short_panel ? 7 : 10; + lv_obj_t* hole = lv_obj_create(body); + lv_obj_remove_style_all(hole); + lv_obj_set_size(hole, hole_d, hole_d); + lv_obj_set_style_radius(hole, LV_RADIUS_CIRCLE, LV_PART_MAIN); + lv_obj_set_style_bg_color(hole, lock_col, LV_PART_MAIN); + lv_obj_set_style_bg_opa(hole, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_align(hole, LV_ALIGN_CENTER, 0, -hole_d / 2); + lv_obj_t* slot = lv_obj_create(body); + lv_obj_remove_style_all(slot); + lv_obj_set_size(slot, short_panel ? 3 : 4, hole_d + 2); + lv_obj_set_style_bg_color(slot, lock_col, LV_PART_MAIN); + lv_obj_set_style_bg_opa(slot, LV_OPA_COVER, LV_PART_MAIN); + lv_obj_align(slot, LV_ALIGN_CENTER, 0, hole_d / 2 - 1); + } + + // The two side readouts each get the whole space between the padlock and their + // screen edge, text centred in it: messages on the left, battery on the right. + const lv_coord_t side_w = LV_MAX(40, (lv_coord_t)((sw - lock_w) / 2)); s_lock_unread = lv_label_create(s_lock_root); lv_obj_set_style_text_font(s_lock_unread, &g_font_16, LV_PART_MAIN); lv_obj_set_style_text_color(s_lock_unread, col, LV_PART_MAIN); -#if defined(TLORA_PAGER) - // Positioned once the hint label exists below -- see the bottom-right stack - // built after `hint` is created. -#else - lv_obj_align(s_lock_unread, LV_ALIGN_TOP_MID, 0, 68); -#endif + lv_obj_set_width(s_lock_unread, side_w); + lv_obj_set_style_text_align(s_lock_unread, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + lv_obj_align_to(s_lock_unread, lock, LV_ALIGN_OUT_LEFT_MID, 0, 0); lv_obj_add_flag(s_lock_unread, LV_OBJ_FLAG_HIDDEN); s_lock_unread_n = -1; + + s_lock_batt = lv_label_create(s_lock_root); + lv_obj_set_style_text_font(s_lock_batt, &g_font_16, LV_PART_MAIN); + lv_obj_set_style_text_color(s_lock_batt, col, LV_PART_MAIN); + lv_obj_set_width(s_lock_batt, side_w); + lv_obj_set_style_text_align(s_lock_batt, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); + lv_obj_align_to(s_lock_batt, lock, LV_ALIGN_OUT_RIGHT_MID, 0, 0); + s_lock_batt_key = -1; + lockscreenUpdateClock(); lockscreenUpdateUnread(); + lockscreenUpdateBattery(); lv_obj_t* st = lv_label_create(s_lock_root); lv_label_set_text(st, TR("Screen locked")); lv_obj_set_style_text_font(st, &g_font_16, LV_PART_MAIN); lv_obj_set_style_text_color(st, col, LV_PART_MAIN); -#if defined(TLORA_PAGER) - s_lock_status = st; // positioned once the hint label exists below -- see the bottom-right stack -#else - lv_obj_align(st, LV_ALIGN_TOP_MID, 0, 190); -#endif + lv_obj_align_to(st, lock, LV_ALIGN_OUT_BOTTOM_MID, 0, short_panel ? 6 : 18); lv_obj_t* hint = lv_label_create(s_lock_root); useChainedFont(hint); @@ -43835,6 +43911,8 @@ static void lockscreenShow() { lv_label_set_text(hint, TR("double-press d-pad center to unlock")); #elif defined(HAS_WIO_TRACKER_L2) lv_label_set_text(hint, TR("hold the wake button to unlock")); +#elif defined(HAS_TDISPLAY_P4) + lv_label_set_text(hint, TR("hold the BOOT button to unlock")); #else lv_label_set_text(hint, TR("hold the trackball to unlock")); #endif @@ -43853,26 +43931,13 @@ static void lockscreenShow() { #else lv_obj_set_style_text_opa(hint, LV_OPA_70, LV_PART_MAIN); #endif -#if defined(TLORA_PAGER) - lv_obj_align(hint, LV_ALIGN_BOTTOM_RIGHT, -6, -8); // bottom-right corner, clear of the icon/clock column above - s_lock_hint = hint; - // Message count + "Screen locked" stack right-aligned directly above the hint, - // each anchored to the one below it (not a fixed y) so the group holds - // together and stays right-aligned regardless of label width or whether the - // unread badge is visible. - lv_obj_align_to(st, hint, LV_ALIGN_OUT_TOP_RIGHT, 0, -4); - lv_obj_align_to(s_lock_unread, st, LV_ALIGN_OUT_TOP_RIGHT, 0, -4); -#else lv_obj_align(hint, LV_ALIGN_BOTTOM_MID, 0, -8); -#endif } static void lockscreenHide() { lockscreenUnlockPopupHide(); if (s_lock_root) { lv_obj_del(s_lock_root); s_lock_root = nullptr; s_lock_clock = nullptr; s_lock_unread = nullptr; -#if defined(TLORA_PAGER) - s_lock_status = nullptr; s_lock_hint = nullptr; -#endif + s_lock_batt = nullptr; } if (s_lock_wall) { lv_img_cache_invalidate_src(&s_lock_wall_dsc); lvglPsramFree(s_lock_wall); s_lock_wall = nullptr; } s_lock_clock_min = -1; @@ -43901,7 +43966,11 @@ static void serviceLockscreen() { if (mm != s_lock_clock_min || (current ? 1 : 0) != s_lock_clock_current) lockscreenUpdateClock(); unsigned long now = millis(); - if (now - s_lock_unread_ms >= 1000) { s_lock_unread_ms = now; lockscreenUpdateUnread(); } + if (now - s_lock_unread_ms >= 1000) { + s_lock_unread_ms = now; + lockscreenUpdateUnread(); + lockscreenUpdateBattery(); + } } #endif // CAP_LOCK_SCREEN @@ -47331,7 +47400,7 @@ static void powerRebootCb(lv_event_t* e) { // finds the hardware switch — and that is what the T-Display P4 did: no wake // source armed at all, under a toast promising a trackball it does not have // (#310). Boards without the symbol get no Power-off row (see openPowerMenu). -#if defined(PIN_USER_BTN) && !defined(HAS_WIO_TRACKER_L2) +#if defined(PIN_USER_BTN) && !defined(HAS_WIO_TRACKER_L2) && !defined(HAS_TDISPLAY_P4) static void powerOffCb(lv_event_t* e) { if (lv_event_get_code(e) != LV_EVENT_CLICKED) return; closePowerMenu(); @@ -47533,8 +47602,9 @@ static void openPowerMenu() { lv_obj_center(l); return b; }; -#if !defined(PIN_USER_BTN) || defined(HAS_WIO_TRACKER_L2) +#if !defined(PIN_USER_BTN) || defined(HAS_WIO_TRACKER_L2) || defined(HAS_TDISPLAY_P4) // No "Power off" here: nothing on this board can wake it from deep sleep. + // (The P4's BOOT button is GPIO35, not one of its LP GPIOs, so it cannot.) // The M9 has a power-cut slider and reset, neither a wakeable GPIO; the L2's // wake button is behind an I2C expander, which cannot wake the ESP32-S3; the // T-Display P4 and Tanmatsu have no user button either. Offering a software @@ -64241,9 +64311,10 @@ void UITask::loop() { s_tb_click_press = tb_pressed && !s_tb_wake_consume; if (s_tb_click_press) { s_tb_last_active_ms = now; noteUserInput(); } } -#elif defined(HAS_WIO_TRACKER_L2) +#elif defined(HAS_WIO_TRACKER_L2) || defined(HAS_TDISPLAY_P4) // Either physical button wakes/reveals immediately. Continuing to hold for // two seconds toggles the hard lock; releasing early cancels the transition. + // The T-Display P4's BOOT button (GPIO35) works the same way. static constexpr uint32_t kL2WakeHoldMs = 2000; static uint32_t s_l2_btn_down_ms = 0; static bool s_l2_press_active = false; @@ -64358,19 +64429,15 @@ void UITask::loop() { // every click. Signed keeps a slightly-ahead stamp negative (= "just had input"). if (_screen_timeout_ms > 0 && !_screen_off && (int32_t)(now - _last_input_ms) >= (int32_t)_screen_timeout_ms) { -#if !defined(HAS_TDISPLAY_P4) - // The P4 has no unlock path (no button/overlay), so it must NEVER hard-lock, even if an old - // pref left the flag set — it always takes the plain screen-off branch below, which wakes on - // touch. (Guards the trap; the "Lock when screen off" toggle is also hidden on the P4.) if (s_lock_on_screen_off && !_manual_lock) { // "Lock when screen off": idle dim also hard-locks, so the touchscreen is // inert until a deliberate unlock (trackball hold on the T-Deck, BOOT - // press on the V4) — Tim's request to stop pocket-taps while dark. The - // !_manual_lock guard stops the Tanmatsu's lit lock screen from re-firing - // lockScreen() (and re-lighting) on every idle tick. + // press on the V4, holding BOOT on the P4) — Tim's request to stop + // pocket-taps while dark. The !_manual_lock guard stops the Tanmatsu's + // lit lock screen from re-firing lockScreen() (and re-lighting) on every + // idle tick. lockScreen(); } else -#endif { touchScreenBacklight(false); setCpuForScreen(false); // idle dim (no lock) -> drop to 80 MHz too diff --git a/src/ui-touch/device_caps.h b/src/ui-touch/device_caps.h index 3eb72de..d480cae 100644 --- a/src/ui-touch/device_caps.h +++ b/src/ui-touch/device_caps.h @@ -142,16 +142,12 @@ #define CAP_FILESYSTEM 1 // SD_MMC + internal FFat 'storage' #define CAP_GPS 1 // L76K #define CAP_OTA 1 // standalone dual-OTA app - // 0, for the same reason as the V4-R8 above: nothing here is wired. There is no - // way to REACH the lock (the control-center Lock button is HAS_TDECK_GT911-only, - // and the pager/Tanmatsu triggers are their own boards), no unlock gesture (every - // unlock path is a trackball / Vol- / d-pad / wake-button branch, and the on-screen - // hint falls through to "hold the trackball", which this board does not have), and - // the DSEC_LOCK settings body is gated to the T-Deck and M9 -- so declaring the cap - // only produced an empty Lock screen settings card (#451). Turning this back on is - // a feature: a touch reveal + hold-to-unlock path, a P4 hint string, and the - // DSEC_LOCK gates. Until then, do not advertise it. - #define CAP_LOCK_SCREEN 0 + // The BOOT button (GPIO35, PIN_USER_BTN) works like the Wio Tracker L2's wake + // button: a press wakes the screen or lights the lock screen, holding it 2 s + // locks / unlocks. That is both the way to reach the lock and the deliberate + // unlock #451 found missing; the lock hint names the button, and the Lock screen + // settings page carries the "Lock when screen off" switch (as on the L2). + #define CAP_LOCK_SCREEN 1 #elif defined(ATTAKY_MESH_SERIES) #define CAP_TOUCH 1 diff --git a/tdisplay_p4/main/CMakeLists.txt b/tdisplay_p4/main/CMakeLists.txt index 91457c9..17af63b 100644 --- a/tdisplay_p4/main/CMakeLists.txt +++ b/tdisplay_p4/main/CMakeLists.txt @@ -97,6 +97,11 @@ set(WADA_DEFS # Detected at runtime; the on-screen keyboard stays while it is away (P4Keyboard.h). --- HAS_TDISPLAY_P4_KEYBOARD=1 + # --- BOOT button (GPIO35, active low) as the wake/lock button, like the Wio Tracker L2's: + # a press wakes (or lights the lock screen), a 2 s hold locks / unlocks. GPIO35 is not an + # LP GPIO, so it cannot wake deep sleep: UITask keeps the Power-off row off this board. --- + PIN_USER_BTN=35 + # --- Board I2C_1 (XL9535 + touch + PCF8563 RTC + BQ27220 gauge) --- PIN_BOARD_SDA=7 PIN_BOARD_SCL=8 XL9535_SDA=7 XL9535_SCL=8 XL9535_INT_PIN=5