mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-11-12 02:27:35 +00:00
Touch UI follow-ups 4: M9 composer, Wio time sync, P4 one-row header, keyboard/emoji fixes
- M9 chat: move # and emoji buttons left of the text input (QR | # | emoji | text | Send) - Wio Tracker L2: enable Clock & time sync settings (CAP_BOOT_TIME_SYNC / BootTimeSync) - P4: lift chat composer above the bottom screen curve - P4: one-row status bar (no BLE icon, no battery %), title left, clock beside icons; bottom-aligned Wi-Fi/signal/battery glyphs; stacked chat header only while a chat is open - Touch keyboard: non-letter keys use theme text colour and letter-key font in dark mode - T-Deck Pro: remove "Roll to highlight" hint from emoji picker (fixes overlapping text) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
39f342c67e
commit
4844a9bd90
6 files changed
+347
-191
No files matched your search
+3
-1
@@ -98,7 +98,9 @@ USB) and the [GitHub releases](https://github.com/ALLFATHER-BV/wadamesh/releases
|
||||
mode manually and retry, and please report it.
|
||||
- **Seeed Wio Tracker L2**: pre-release touch target under active bring-up. Builds are for
|
||||
development and hardware validation only; no public release artifact is
|
||||
promised until the port is verified.
|
||||
promised until the port is verified. It has no battery-backed clock, so the
|
||||
same optional saved-Wi-Fi cold-boot time sync offered on T-Deck is available
|
||||
in Clock settings.
|
||||
- **CrowPanel Advance 3.5**: `crowpanel_35_companion_radio_touch` is a
|
||||
16 MB flash / 8 MB OPI PSRAM build for the ILI9488/GT911 panel and an SX1262
|
||||
in the expansion slot. It starts upright in 480x320 landscape; Display
|
||||
|
||||
@@ -97,8 +97,9 @@ appears at the right edge. Press **Right** to select it, then **OK** to jump to
|
||||
the newest message.
|
||||
|
||||
Channels, direct messages, and room conversations place **#** and emoji to the
|
||||
right of the message input. Press Right to move from the input to **#**, and
|
||||
Right again to move to emoji. Press **OK** on either button to open its picker.
|
||||
left of the message input. With the caret at the start of the input, press Left
|
||||
to move to emoji, and Left again to move to **#**; Right walks back to the
|
||||
input. Press **OK** on either button to open its picker.
|
||||
|
||||
In the symbol grid, use all four arrows to move the highlight and **OK** to
|
||||
insert the selected character.
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
// the M9 (RTC integrity can be lost across its hard power cut). Keep the runtime
|
||||
// gate as narrow as the UI capability gate so a migrated preference file cannot
|
||||
// unexpectedly start Wi-Fi on another board.
|
||||
// The T-Display P4 joins on its esp-hosted build (see CAP_BOOT_TIME_SYNC).
|
||||
#if defined(HAS_TDECK_GT911) || defined(HAS_THINKNODE_M9) || \
|
||||
// The T-Display P4 joins on its esp-hosted build (see CAP_BOOT_TIME_SYNC), and the
|
||||
// Wio Tracker L2 (no RTC) joins too.
|
||||
#if defined(HAS_TDECK_GT911) || defined(HAS_THINKNODE_M9) || defined(HAS_WIO_TRACKER_L2) || \
|
||||
(defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED)
|
||||
#define BOOT_TIME_SYNC_SUPPORTED 1
|
||||
#else
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
//
|
||||
// These exist because the self-contained app modules (SnakeGame, the Lua host) hand-rolled
|
||||
// the chrome against a hardcoded `kTopBar = 22`. STATUSBAR_H is a runtime value — SC(22)
|
||||
// once the UI scale is above 100%, and SB_TOP_PAD + SB_ROW*2 on the T-Display P4's
|
||||
// two-row bar — so on those boards the pages were positioned too high, which put their
|
||||
// once the UI scale is above 100%, and SB_TOP_PAD + SB_ROW on the T-Display P4's
|
||||
// round-cornered bar — so on those boards the pages were positioned too high, which put their
|
||||
// own title and close button UNDERNEATH the real status bar with no reachable exit. Going
|
||||
// through here instead means a page can never disagree with the bar about its own height.
|
||||
|
||||
|
||||
+333
-182
@@ -2011,18 +2011,18 @@ constexpr int SWIPE_SCROLL_STEP = 90;
|
||||
// once at boot in begin(), before the UI is built. Stays 22 on the non-scaled boards.
|
||||
static lv_coord_t STATUSBAR_H = 22;
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Two-row status bar for the round-cornered phone panel: row 1 (top) holds the app name +
|
||||
// clock, row 2 (bottom) holds the wifi/ble/sd/signal/battery cluster. A top pad clears the
|
||||
// corner arc; a horizontal inset keeps the end glyphs out of the left/right arcs. STATUSBAR_H
|
||||
// is set to (top pad + two rows) at boot so every page reserves the taller bar, and the bar
|
||||
// stays this fixed height in ALL states (no *2 doubling — the tall personalities reuse the
|
||||
// two rows). Text tops (ROW1_Y / ROW2_Y) are tuned so the small g_font glyphs sit centred.
|
||||
// One-row status bar for the round-cornered phone panel: the profile name / page title on
|
||||
// the left, then the clock, Wi-Fi, signal and battery packed on the right, all on one line.
|
||||
// A top pad clears the corner arc; a horizontal inset keeps the end glyphs out of the
|
||||
// left/right arcs. STATUSBAR_H is set to (top pad + one row) at boot. An open chat is the
|
||||
// one exception: its header (title over subtitle) grows the bar to SB_CHAT_H.
|
||||
static constexpr lv_coord_t SB_ROW = 22; // one status-bar row
|
||||
static constexpr lv_coord_t SB_TOP_PAD = 8; // clear the top corner arc
|
||||
static constexpr lv_coord_t SB_INSET_X = 16; // left/right corner safe-area
|
||||
static constexpr lv_coord_t SB_ROW1_Y = SB_TOP_PAD + 2; // ~10 text top, row 1 (clock)
|
||||
static constexpr lv_coord_t SB_ROW2_Y = SB_TOP_PAD + SB_ROW + 3; // ~33 text top, row 2 (name + status)
|
||||
static void statusBarLayoutTwoRow(int slide); // defined just after buildGlobalStatusBar
|
||||
static constexpr lv_coord_t SB_ROW1_Y = SB_TOP_PAD + 2; // ~10 text top of the row (clock)
|
||||
static constexpr lv_coord_t SB_CHAT_H = SB_TOP_PAD + SB_ROW * 2; // open-chat header height
|
||||
static bool s_round_chat_bar = false; // bar grown to SB_CHAT_H
|
||||
static void statusBarLayoutOneRow(); // defined just after buildGlobalStatusBar
|
||||
#endif
|
||||
// True while the Reader/Web page is collapsed and showing its URL in the status bar's
|
||||
// title zone — updateGlobalStatusBar then hides the clock to make room for the URL.
|
||||
@@ -2093,9 +2093,9 @@ static void (*s_apppage_close)() = nullptr;
|
||||
// right at the top of its content, which the glass row used to sit over).
|
||||
static bool s_apppage_slim = false;
|
||||
#if CAP_ROUND_CORNERS
|
||||
// The round-panel bar is already two rows tall in every state — the tall personalities
|
||||
// (settings title, inbox actions, open chat) reuse the two rows rather than doubling it.
|
||||
static inline lv_coord_t statusBarCurH() { return STATUSBAR_H; }
|
||||
// The round-panel bar is one row in every state except an open chat (see SB_CHAT_H);
|
||||
// settings / tool pages keep their "‹ Title" on that one row rather than doubling it.
|
||||
static inline lv_coord_t statusBarCurH() { return s_round_chat_bar ? SB_CHAT_H : STATUSBAR_H; }
|
||||
#else
|
||||
static inline lv_coord_t statusBarCurH() { return s_statusbar_tall ? (lv_coord_t)(STATUSBAR_H * 2) : STATUSBAR_H; }
|
||||
#endif
|
||||
@@ -2346,8 +2346,22 @@ static inline bool chatComposerTwoRow() {
|
||||
static inline lv_coord_t chatComposerTopRowH() {
|
||||
return chatComposerTwoRow() ? (lv_coord_t)(CHAT_COMP_BTN_P4 + 4) : 0;
|
||||
}
|
||||
// Portrait P4: the full-width text box would otherwise sit under the bottom corner
|
||||
// arcs, so the whole composer rides this far above the screen's bottom edge (the
|
||||
// row's bottom padding). Matches the corner safe-area the status bar uses.
|
||||
static inline lv_coord_t chatComposerBottomLift() {
|
||||
#if CAP_ROUND_CORNERS
|
||||
return chatComposerTwoRow() ? SB_INSET_X : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
// Composer row height not available to the text box: row padding, the top row, the lift.
|
||||
static inline lv_coord_t chatComposerChromeH() {
|
||||
return 4 + chatComposerTopRowH() + chatComposerBottomLift();
|
||||
}
|
||||
static inline lv_coord_t chatComposerBaseH() {
|
||||
if (chatComposerTwoRow()) return CHAT_COMP_H + chatComposerTopRowH();
|
||||
if (chatComposerTwoRow()) return CHAT_COMP_H + chatComposerTopRowH() + chatComposerBottomLift();
|
||||
#if defined(TLORA_PAGER)
|
||||
// Preserve the 34-px Small composer, then add enough chrome around the live
|
||||
// font line for Medium/Large without globally scaling the short viewport.
|
||||
@@ -2499,16 +2513,18 @@ static inline lv_coord_t chatComposerTaW(bool channel_mode) {
|
||||
return width;
|
||||
}
|
||||
// Most boards use a DOUBLE-height status bar in a chat (thread name on the lower
|
||||
// row + a centred cog). The round panel already has two physical rows, while the
|
||||
// short Pager keeps its back/cog/title in the regular single row. chatBarH() is
|
||||
// the full visual height used by channel/blocked sheets.
|
||||
#if CAP_ROUND_CORNERS || defined(TLORA_PAGER)
|
||||
// row + a centred cog). The round panel grows its one-row bar to SB_CHAT_H for the
|
||||
// chat header, while the short Pager keeps its back/cog/title in the regular single
|
||||
// row. chatBarH() is the full visual height used by channel/blocked sheets.
|
||||
#if CAP_ROUND_CORNERS
|
||||
static inline lv_coord_t chatBarH() { return SB_CHAT_H; }
|
||||
#elif defined(TLORA_PAGER)
|
||||
static inline lv_coord_t chatBarH() { return STATUSBAR_H; }
|
||||
#else
|
||||
static inline lv_coord_t chatBarH() { return (lv_coord_t)(STATUSBAR_H * 2); }
|
||||
#endif
|
||||
#if defined(HAS_TDECK_PRO) || defined(HELTEC_LORA_V4_R8)
|
||||
// E-paper and R8 content start below the complete status-bar hitbox.
|
||||
#if defined(HAS_TDECK_PRO) || defined(HELTEC_LORA_V4_R8) || CAP_ROUND_CORNERS
|
||||
// E-paper, R8 and the round panel start chat content below the complete chat header.
|
||||
static inline lv_coord_t chatContentTop(){ return chatBarH(); }
|
||||
#else
|
||||
static inline lv_coord_t chatContentTop(){ return STATUSBAR_H; }
|
||||
@@ -8789,7 +8805,7 @@ static void chatComposerAutoGrow(LvChatPanel* p) {
|
||||
// V4 with the keyboard down) it sits at the screen bottom.
|
||||
const bool kb = g_lv.keyboard && !lv_obj_has_flag(g_lv.keyboard, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_height(p->composer_row, s_comp_h);
|
||||
lv_obj_set_height(p->composer_ta, s_comp_h - 4 - chatComposerTopRowH());
|
||||
lv_obj_set_height(p->composer_ta, s_comp_h - chatComposerChromeH());
|
||||
lv_obj_set_y(p->composer_row, kb ? chatCompYKb() : chatCompYOpen());
|
||||
lv_obj_set_height(p->msgs, kb ? chatMsgHKb() : chatMsgHOpen());
|
||||
// Grow the bottom inset with the composer so the newest bubble keeps clearing it.
|
||||
@@ -8817,7 +8833,7 @@ static void chatComposerPlaceButtons(LvChatPanel* p) {
|
||||
const lv_coord_t b = CHAT_COMP_BTN_P4;
|
||||
const lv_coord_t gap = 6;
|
||||
const lv_coord_t right = chatComposerSendInset();
|
||||
const lv_coord_t ta_h = s_comp_h - 4 - chatComposerTopRowH();
|
||||
const lv_coord_t ta_h = s_comp_h - chatComposerChromeH();
|
||||
const lv_coord_t kMinLift = SB_TOP_PAD - 2;
|
||||
lv_coord_t lift = (ta_h - b) / 2;
|
||||
if (lift < kMinLift) lift = kMinLift;
|
||||
@@ -8844,6 +8860,7 @@ static void chatComposerApplyRows(LvChatPanel* p) {
|
||||
lv_obj_set_size(p->send_btn, b, b);
|
||||
lv_obj_set_style_radius(p->qr_btn, b / 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_radius(p->emoji_btn, b / 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_bottom(p->composer_row, 2 + chatComposerBottomLift(), LV_PART_MAIN);
|
||||
// Two-row: the top row's left end is free; the near-limit counter moves there so
|
||||
// it doesn't sit on the emoji button.
|
||||
if (p->composer_cnt) {
|
||||
@@ -10692,7 +10709,10 @@ static void openEmojiPicker(lv_obj_t* ta, const char* const* items = k_emoji_ite
|
||||
lv_obj_set_pos(title, 2, 0);
|
||||
addCloseXBadge(card, emojiSheetCloseCb);
|
||||
|
||||
// Hint line: how to use the board's primary picker controls.
|
||||
// Hint line: how to use the board's primary picker controls. The T-Deck Pro has no
|
||||
// trackball or arrow keys to drive the selector (picks are by touch), so none there.
|
||||
lv_coord_t grid_y = 26;
|
||||
#if !defined(HAS_TDECK_PRO)
|
||||
lv_obj_t* hint = lv_label_create(card);
|
||||
#if defined(HAS_M9_KEYBOARD)
|
||||
lv_label_set_text(hint, TR("Arrows \xE2\x80\xA2 OK"));
|
||||
@@ -10701,14 +10721,31 @@ static void openEmojiPicker(lv_obj_t* ta, const char* const* items = k_emoji_ite
|
||||
#endif
|
||||
lv_obj_set_style_text_color(hint, lv_color_hex(COLOR_SUB), LV_PART_MAIN);
|
||||
lv_obj_set_style_text_font(hint, &g_font_12, LV_PART_MAIN);
|
||||
lv_obj_align(hint, LV_ALIGN_TOP_RIGHT, -24, 4);
|
||||
// Title and hint share the top line when they fit left of the close badge; a narrow
|
||||
// card gives the hint its own line under the title instead.
|
||||
{
|
||||
const char* tt = lv_label_get_text(title);
|
||||
const char* ht = lv_label_get_text(hint);
|
||||
const lv_coord_t title_w = lv_txt_get_width(tt, strlen(tt), &g_font_14, 0, LV_TEXT_FLAG_NONE);
|
||||
const lv_coord_t hint_w = lv_txt_get_width(ht, strlen(ht), &g_font_12, 0, LV_TEXT_FLAG_NONE);
|
||||
if (2 + title_w + 8 + hint_w + 24 <= cardw - 16) {
|
||||
lv_obj_align(hint, LV_ALIGN_TOP_RIGHT, -24, 4);
|
||||
} else {
|
||||
lv_obj_set_width(hint, cardw - 16 - 2 - 24);
|
||||
lv_label_set_long_mode(hint, LV_LABEL_LONG_DOT);
|
||||
const lv_coord_t hy = lv_font_get_line_height(&g_font_14) + 2;
|
||||
lv_obj_set_pos(hint, 2, hy);
|
||||
grid_y = hy + lv_font_get_line_height(&g_font_12) + 6;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Scrollable grid of glyph buttons.
|
||||
const lv_coord_t grid_w = cardw - 16;
|
||||
lv_obj_t* grid = lv_obj_create(card);
|
||||
lv_obj_remove_style_all(grid);
|
||||
lv_obj_set_size(grid, grid_w, cardh - 16 - 26);
|
||||
lv_obj_set_pos(grid, 0, 26);
|
||||
lv_obj_set_size(grid, grid_w, cardh - 16 - grid_y);
|
||||
lv_obj_set_pos(grid, 0, grid_y);
|
||||
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
|
||||
lv_obj_set_style_pad_row(grid, 4, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(grid, 4, LV_PART_MAIN);
|
||||
@@ -12707,9 +12744,9 @@ static void openAdvertPage() {
|
||||
statusBarSetTall(true);
|
||||
updateGlobalStatusBar();
|
||||
#if CAP_ROUND_CORNERS
|
||||
// The round panel's status bar is already its full two rows and never doubles when
|
||||
// "tall", so the extra STATUSBAR_H below only left an empty band between the bar
|
||||
// and the Home button. The root already starts below the bar.
|
||||
// The round panel's status bar is one row and never doubles when "tall", so the
|
||||
// extra STATUSBAR_H below only left an empty band between the bar and the Home
|
||||
// button. The root already starts below the bar.
|
||||
const int top = 8;
|
||||
#else
|
||||
const int top = STATUSBAR_H + 8; // clear the tall bar's second row, which overlaps the root
|
||||
@@ -38828,8 +38865,9 @@ static void makeChatDetail(LvChatPanel& p) {
|
||||
lv_obj_set_style_pad_all(p.msgs, 6, LV_PART_MAIN);
|
||||
// The status bar's glass lower row floats over the TOP of the list on boards
|
||||
// with a two-row chat header. Pager keeps the chat header in one regular row,
|
||||
// so retaining that old row-sized inset only wastes message space.
|
||||
#if defined(TLORA_PAGER) || defined(HAS_TDECK_PRO) || defined(HELTEC_LORA_V4_R8)
|
||||
// so retaining that old row-sized inset only wastes message space. The round
|
||||
// panel's chat content already starts below its whole header (chatContentTop).
|
||||
#if defined(TLORA_PAGER) || defined(HAS_TDECK_PRO) || defined(HELTEC_LORA_V4_R8) || CAP_ROUND_CORNERS
|
||||
lv_obj_set_style_pad_top(p.msgs, 6, LV_PART_MAIN);
|
||||
#else
|
||||
lv_obj_set_style_pad_top(p.msgs, STATUSBAR_H + 6, LV_PART_MAIN);
|
||||
@@ -38954,15 +38992,11 @@ static void makeChatDetail(LvChatPanel& p) {
|
||||
const bool has_symbol_chip = chatHasSymbolChip(p.channel_mode);
|
||||
const lv_coord_t send_sz = chatComposerSendSz();
|
||||
const lv_coord_t comp_ta_w = chatComposerTaW(p.channel_mode);
|
||||
const lv_coord_t comp_ta_x = has_symbol_chip
|
||||
? chip_sz + chip_gap
|
||||
: 2 * chip_sz + 2 * chip_gap;
|
||||
const lv_coord_t symbol_x = has_symbol_chip
|
||||
? comp_ta_x + comp_ta_w + chip_gap
|
||||
: 0;
|
||||
const lv_coord_t symbol_x = has_symbol_chip ? chip_sz + chip_gap : 0;
|
||||
const lv_coord_t emoji_x = has_symbol_chip
|
||||
? symbol_x + chip_sz + chip_gap
|
||||
: chip_sz + chip_gap;
|
||||
const lv_coord_t comp_ta_x = emoji_x + chip_sz + chip_gap;
|
||||
|
||||
// Macro picker button: a compact chip taps to a popup grid of user-
|
||||
// defined quick-reply presets. Saves typing for stock phrases ("ok",
|
||||
@@ -39053,9 +39087,9 @@ static void makeChatDetail(LvChatPanel& p) {
|
||||
#endif
|
||||
|
||||
p.composer_ta = lv_textarea_create(p.composer_row);
|
||||
// M9 conversations: QR | message input | # | emoji | Send. Other boards
|
||||
// M9 conversations: QR | # | emoji | message input | Send. Other boards
|
||||
// retain QR | emoji | message input | Send.
|
||||
lv_obj_set_size(p.composer_ta, comp_ta_w, composer_h - 4 - chatComposerTopRowH());
|
||||
lv_obj_set_size(p.composer_ta, comp_ta_w, composer_h - chatComposerChromeH());
|
||||
// Bottom-aligned so the box grows UPWARD as the message wraps to more lines.
|
||||
lv_obj_align(p.composer_ta, LV_ALIGN_BOTTOM_LEFT, comp_ta_x, 0);
|
||||
styleCard(p.composer_ta);
|
||||
@@ -48354,6 +48388,21 @@ static bool m9ChatMoveMessage(bool down) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move d-pad focus to one of the chat composer's controls (# / emoji / text box).
|
||||
// If it isn't in the nav group yet, queue it for the next group rebuild.
|
||||
static void m9FocusChatControl(lv_obj_t* obj, bool edit) {
|
||||
if (lv_obj_get_group(obj) == s_nav_group) {
|
||||
s_m9_focus_pending = nullptr;
|
||||
lv_group_focus_obj(obj);
|
||||
} else {
|
||||
s_m9_focus_pending = obj;
|
||||
navMarkDirty();
|
||||
navMaybeRebuild();
|
||||
}
|
||||
s_nav_ta_editing = edit;
|
||||
s_nav_show = !edit;
|
||||
}
|
||||
|
||||
static bool m9HandleArrowKey(int key, lv_obj_t* ta) {
|
||||
if (!s_kbd_nav) return false;
|
||||
// An open dropdown LIST owns the arrows (mirrors Tanmatsu's navPump capture):
|
||||
@@ -48492,6 +48541,22 @@ static bool m9HandleArrowKey(int key, lv_obj_t* ta) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Chat composer (QR | # | emoji | text | Send): LEFT walks text → emoji → #.
|
||||
if (LvChatPanel* chat = navOpenChatPanel();
|
||||
chat && chat->composer_ta && chat->symbol_btn && chat->emoji_btn &&
|
||||
lv_obj_is_valid(chat->symbol_btn) && lv_obj_is_valid(chat->emoji_btn)) {
|
||||
lv_obj_t* focused = s_nav_group ? lv_group_get_focused(s_nav_group) : nullptr;
|
||||
if (focused == chat->emoji_btn || s_m9_focus_pending == chat->emoji_btn) {
|
||||
m9FocusChatControl(chat->symbol_btn, false);
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
}
|
||||
if (ta == chat->composer_ta && lv_textarea_get_cursor_pos(ta) == 0) {
|
||||
m9FocusChatControl(chat->emoji_btn, false);
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (ta) {
|
||||
// Caret already at the start: fall through to focus-move so arrows
|
||||
// always eventually LEAVE the field (a silent boundary no-op read as
|
||||
@@ -48546,33 +48611,20 @@ static bool m9HandleArrowKey(int key, lv_obj_t* ta) {
|
||||
navMarkDirty();
|
||||
navMaybeRebuild();
|
||||
}
|
||||
} else if (LvChatPanel* chat = navOpenChatPanel();
|
||||
chat && chat->symbol_btn && lv_obj_is_valid(chat->symbol_btn) &&
|
||||
(ta == chat->composer_ta || navFocusedTextarea() == chat->composer_ta)) {
|
||||
s_nav_ta_editing = false;
|
||||
if (lv_obj_get_group(chat->symbol_btn) == s_nav_group) {
|
||||
s_m9_focus_pending = nullptr;
|
||||
s_nav_show = true;
|
||||
lv_group_focus_obj(chat->symbol_btn);
|
||||
} else {
|
||||
s_m9_focus_pending = chat->symbol_btn;
|
||||
navMarkDirty();
|
||||
navMaybeRebuild();
|
||||
}
|
||||
} else if (LvChatPanel* chat = navOpenChatPanel();
|
||||
chat && chat->symbol_btn && chat->emoji_btn &&
|
||||
lv_obj_is_valid(chat->symbol_btn) && lv_obj_is_valid(chat->emoji_btn) &&
|
||||
((s_nav_group && lv_group_get_focused(s_nav_group) == chat->symbol_btn) ||
|
||||
s_m9_focus_pending == chat->symbol_btn)) {
|
||||
if (lv_obj_get_group(chat->emoji_btn) == s_nav_group) {
|
||||
s_m9_focus_pending = nullptr;
|
||||
s_nav_show = true;
|
||||
lv_group_focus_obj(chat->emoji_btn);
|
||||
} else {
|
||||
s_m9_focus_pending = chat->emoji_btn;
|
||||
navMarkDirty();
|
||||
navMaybeRebuild();
|
||||
}
|
||||
m9FocusChatControl(chat->emoji_btn, false);
|
||||
} else if (LvChatPanel* chat = navOpenChatPanel();
|
||||
chat && chat->emoji_btn && chat->composer_ta &&
|
||||
lv_obj_is_valid(chat->emoji_btn) && lv_obj_is_valid(chat->composer_ta) &&
|
||||
((s_nav_group && lv_group_get_focused(s_nav_group) == chat->emoji_btn) ||
|
||||
s_m9_focus_pending == chat->emoji_btn)) {
|
||||
m9FocusChatControl(chat->composer_ta, true);
|
||||
if (g_lv.task) g_lv.task->noteUserInput();
|
||||
return true;
|
||||
} else if (ta) {
|
||||
navMoveDir(NAV_RIGHT); // caret was already at end — same as LEFT
|
||||
}
|
||||
@@ -54524,10 +54576,10 @@ static void buildGlobalStatusBar() {
|
||||
// LEFT-aligned group. ~70% of the double-bar height so there's breathing room
|
||||
// above/below; soft rounded corners + a dimmer edge so they don't pop.
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Round panel: the bar is a fixed two rows (no doubling), so size these to sit on
|
||||
// ROW 2's left, inside the corner inset — the status cluster occupies row-2 right.
|
||||
// Round panel: one row, so these sit on its left inside the corner inset (they are
|
||||
// only ever shown on the Pager; kept clear of the clock line regardless).
|
||||
const lv_coord_t BH = 18, BW = 30, GAP = 6, BX0 = SB_INSET_X;
|
||||
const lv_coord_t BY = SB_TOP_PAD + SB_ROW + (SB_ROW - BH) / 2;
|
||||
const lv_coord_t BY = SB_TOP_PAD + (SB_ROW - BH) / 2;
|
||||
#elif defined(TLORA_PAGER)
|
||||
// Match the compact status cluster instead of spanning most of the two-row
|
||||
// header. Keep all three actions in the first status-bar row, sharing its
|
||||
@@ -54860,7 +54912,6 @@ static void buildGlobalStatusBar() {
|
||||
lv_color_hex(s_theme_high_contrast ? COLOR_SUB : COLOR_SECONDARY_ACTION), LV_PART_MAIN);
|
||||
g_statusbar.sig_bars[i] = b;
|
||||
}
|
||||
#if !CAP_ROUND_CORNERS
|
||||
// The SD activity light rides in the bars' empty top-left corner, above the
|
||||
// shortest bar: a flicker of card I/O never moves the icons beside it (in its
|
||||
// own slot it opened a gap the width of a slot between Bluetooth and Wi-Fi).
|
||||
@@ -54870,7 +54921,6 @@ static void buildGlobalStatusBar() {
|
||||
lv_obj_set_style_radius(g_statusbar.sd_icon, 2, LV_PART_MAIN);
|
||||
lv_obj_align(g_statusbar.sd_icon, LV_ALIGN_TOP_LEFT, 0, 0); // replaces its right-middle anchor
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Keyboard layout indicator — sits left of the clock (only shown while typing).
|
||||
@@ -54898,72 +54948,109 @@ static void buildGlobalStatusBar() {
|
||||
lv_obj_add_flag(g_statusbar.dim, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
#if CAP_ROUND_CORNERS
|
||||
statusBarLayoutTwoRow(0); // place the clock (row 1) + the status cluster (row 2)
|
||||
// No battery % or Bluetooth glyph on this panel (see statusBarLayoutOneRow).
|
||||
if (g_statusbar.batt_pct) lv_obj_add_flag(g_statusbar.batt_pct, LV_OBJ_FLAG_HIDDEN);
|
||||
if (g_statusbar.ble_icon) lv_obj_add_flag(g_statusbar.ble_icon, LV_OBJ_FLAG_HIDDEN);
|
||||
statusBarLayoutOneRow();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Two-row status-bar layout for the round-cornered phone panel. Row 1 (top): the clock
|
||||
// at the right inset (+ keyboard-layout tag to its left, + chat back/cog on the left).
|
||||
// Row 2 (bottom): the battery pinned at the right inset, with the signal/wifi/sd/ble
|
||||
// cluster to its left — that cluster slides right by `slide` px when the %-column is
|
||||
// hidden while charging (mirrors the square-panel slide). The app-name / page-title /
|
||||
// chat thread-name (left_label) is placed by updateGlobalStatusBar. Per-item y nudges
|
||||
// centre the different glyph heights within their row. Base X offsets mirror the
|
||||
// single-row builder so the horizontal spacing is unchanged; +SB_INSET_X keeps the end
|
||||
// glyphs clear of the corner arcs.
|
||||
// Right edge (px from the bar's right) of the row-2 sub-battery cluster: after the widest
|
||||
// the percentage can get ("100%" in its font), not a fixed 64 px -- with the scaled font
|
||||
// the signal box sat on top of "100%".
|
||||
static bool s_crumb_capped = false; // round panel: breadcrumb width capped on row 2
|
||||
static int statusClusterStart() {
|
||||
const lv_font_t* pct_font = &g_font_12;
|
||||
if (g_statusbar.batt_pct) pct_font = lv_obj_get_style_text_font(g_statusbar.batt_pct, LV_PART_MAIN);
|
||||
const int pct_w = lv_txt_get_width("100%", 4, pct_font, 0, LV_TEXT_FLAG_NONE);
|
||||
return LV_MAX(64, 26 + pct_w + 6);
|
||||
}
|
||||
// Left edge of the whole row-2 status cluster, measured from the bar's right edge.
|
||||
static int statusClusterLeftExtent() { return statusClusterStart() + 62 + 14 + SB_INSET_X; }
|
||||
// One-row status-bar layout for the round-cornered phone panel. Right to left from the
|
||||
// corner inset: battery, signal bars (+ the SD light riding on them), Wi-Fi, the DND
|
||||
// moon, then the clock. The device has Bluetooth hardware but wadamesh does not drive
|
||||
// it there, so no Bluetooth glyph; the battery shows no %. The profile name / page
|
||||
// title (left_label) sits on the same line at the left, placed by updateGlobalStatusBar.
|
||||
static bool s_crumb_capped = false; // round panel: breadcrumb width capped short of the clock
|
||||
static lv_coord_t s_sb_right_w = 0; // px from the bar's right edge to the clock's left edge
|
||||
static lv_coord_t s_sb_line_bot = 0; // the shared bottom line (y) of the one-row bar
|
||||
|
||||
static void statusBarLayoutTwoRow(int slide) {
|
||||
const int ins = SB_INSET_X;
|
||||
// Row 2 — status cluster, right→left, evenly spaced. Battery pinned at the inset;
|
||||
// the sub-battery cluster (signal/sd/wifi/ble) sits to its left and slides right by
|
||||
// `slide` when the %-column hides while charging. y nudges centre each glyph in the
|
||||
// row (montserrat_14 battery sits a touch higher than the 12px glyphs; sig/sd dots
|
||||
// drop a few px). The scrolling profile name shares this row on the left (placed by
|
||||
// updateGlobalStatusBar).
|
||||
if (g_statusbar.batt_icon) lv_obj_align(g_statusbar.batt_icon, LV_ALIGN_TOP_RIGHT, -(2 + ins), SB_ROW2_Y - 2);
|
||||
if (g_statusbar.batt_pct) lv_obj_align(g_statusbar.batt_pct, LV_ALIGN_TOP_RIGHT, -(26 + ins), SB_ROW2_Y);
|
||||
const int cl = statusClusterStart();
|
||||
// Charging hides the % column; the cluster then closes up to 32 px from the battery
|
||||
// (the historical position), whatever width the % column had reserved.
|
||||
if (slide) slide = cl - 32;
|
||||
if (g_statusbar.sig_box) lv_obj_align(g_statusbar.sig_box, LV_ALIGN_TOP_RIGHT, -(cl + ins - slide), SB_ROW2_Y + 2);
|
||||
if (g_statusbar.sd_icon) lv_obj_align(g_statusbar.sd_icon, LV_ALIGN_TOP_RIGHT, -(cl + 22 + ins - slide), SB_ROW2_Y + 4);
|
||||
if (g_statusbar.conn_icon) lv_obj_align(g_statusbar.conn_icon, LV_ALIGN_TOP_RIGHT, -(cl + 38 + ins - slide), SB_ROW2_Y);
|
||||
if (g_statusbar.ble_icon) lv_obj_align(g_statusbar.ble_icon, LV_ALIGN_TOP_RIGHT, -(cl + 62 + ins - slide), SB_ROW2_Y);
|
||||
// Row 1 — clock at the right inset (+ keyboard-layout tag to its left when typing).
|
||||
if (g_statusbar.clock) lv_obj_align(g_statusbar.clock, LV_ALIGN_TOP_RIGHT, -ins, SB_ROW1_Y);
|
||||
if (g_statusbar.layout_label) lv_obj_align(g_statusbar.layout_label, LV_ALIGN_TOP_RIGHT, -(48 + ins), SB_ROW1_Y);
|
||||
// Row 1 — chat back chevron + channel-settings cog on the left (shown only in a chat).
|
||||
if (g_statusbar.chat_back) lv_obj_align(g_statusbar.chat_back, LV_ALIGN_TOP_LEFT, ins, SB_ROW1_Y);
|
||||
if (g_statusbar.chan_gear) lv_obj_align(g_statusbar.chan_gear, LV_ALIGN_TOP_LEFT, ins + 24, SB_ROW1_Y);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Row 2 has only STATUSBAR_H - SB_ROW2_Y below its text top. At the Large/Huge
|
||||
// UI sizes g_font_14 is taller than that, and the bar clipped the name's
|
||||
// descenders (g, y, p). Give the label the largest font whose whole line fits.
|
||||
static void statusBarFitRow2Font(lv_obj_t* l) {
|
||||
const lv_coord_t room = STATUSBAR_H - SB_ROW2_Y;
|
||||
const lv_font_t* const ladder[] = { &g_font_14, &g_font_12, &lv_font_montserrat_14, &lv_font_montserrat_12 };
|
||||
const lv_font_t* pick = ladder[3];
|
||||
for (const lv_font_t* f : ladder) {
|
||||
if (lv_font_get_line_height(f) <= room) { pick = f; break; }
|
||||
// Where a status glyph's ink starts and ends, measured down from its object's top.
|
||||
// The glyphs mix fonts (16 px battery, 14 px Wi-Fi, 12 px moon), each with its own
|
||||
// line box and baseline, so aligning the boxes leaves the drawn shapes at different
|
||||
// heights. Labels use their first glyph; the signal bars are a plain box.
|
||||
static void sbGlyphInk(lv_obj_t* o, lv_coord_t& top, lv_coord_t& bot) {
|
||||
if (!lv_obj_check_type(o, &lv_label_class)) {
|
||||
top = 0;
|
||||
bot = lv_obj_get_style_height(o, LV_PART_MAIN);
|
||||
return;
|
||||
}
|
||||
lv_obj_set_style_text_font(l, pick, LV_PART_MAIN);
|
||||
const lv_font_t* f = lv_obj_get_style_text_font(o, LV_PART_MAIN);
|
||||
const lv_coord_t base = lv_obj_get_style_pad_top(o, LV_PART_MAIN) + lv_font_get_line_height(f) - f->base_line;
|
||||
top = bot = base;
|
||||
const char* t = lv_label_get_text(o);
|
||||
if (!t || !t[0]) return;
|
||||
uint32_t i = 0;
|
||||
const uint32_t c = _lv_txt_encoded_next(t, &i);
|
||||
lv_font_glyph_dsc_t g;
|
||||
if (!lv_font_get_glyph_dsc(f, &g, c, 0)) return;
|
||||
bot = base - g.ofs_y;
|
||||
top = bot - g.box_h;
|
||||
}
|
||||
// A text label's baseline, measured down from its object's top.
|
||||
static lv_coord_t sbBaseline(lv_obj_t* o) {
|
||||
const lv_font_t* f = lv_obj_get_style_text_font(o, LV_PART_MAIN);
|
||||
return lv_obj_get_style_pad_top(o, LV_PART_MAIN) + lv_font_get_line_height(f) - f->base_line;
|
||||
}
|
||||
// Width of a status object for its current text (valid before the next layout pass).
|
||||
static lv_coord_t sbObjW(lv_obj_t* o) {
|
||||
if (!lv_obj_check_type(o, &lv_label_class)) return lv_obj_get_style_width(o, LV_PART_MAIN);
|
||||
const char* t = lv_label_get_text(o);
|
||||
return lv_txt_get_width(t, strlen(t), lv_obj_get_style_text_font(o, LV_PART_MAIN), 0, LV_TEXT_FLAG_NONE)
|
||||
+ lv_obj_get_style_pad_left(o, LV_PART_MAIN) + lv_obj_get_style_pad_right(o, LV_PART_MAIN);
|
||||
}
|
||||
// Place o with its right edge `right` px in from the bar's right and its ink bottom on y = bottom.
|
||||
static void sbAlignBottom(lv_obj_t* o, lv_coord_t right, lv_coord_t bottom) {
|
||||
lv_coord_t t, b;
|
||||
sbGlyphInk(o, t, b);
|
||||
lv_obj_align(o, LV_ALIGN_TOP_RIGHT, -right, bottom - b);
|
||||
}
|
||||
// Pack the right-hand glyphs leftwards from x (px in from the bar's right), every one's
|
||||
// ink bottom on one line chosen so the tallest shown is centred below the arc pad.
|
||||
// Returns that line; x comes back at the left edge of the last glyph placed.
|
||||
static lv_coord_t statusBarPackRound(bool links, bool dnd, lv_coord_t& x) {
|
||||
struct Slot { lv_obj_t* o; lv_coord_t gap; bool show; };
|
||||
const Slot order[] = {
|
||||
{ g_statusbar.batt_icon, 0, true },
|
||||
{ g_statusbar.sig_box, SBX(8), links },
|
||||
{ g_statusbar.conn_icon, SBX(7), links },
|
||||
{ g_statusbar.dnd_icon, SBX(7), dnd },
|
||||
};
|
||||
auto shown = [](const Slot& sl) {
|
||||
if (!sl.show || !sl.o || lv_obj_has_flag(sl.o, LV_OBJ_FLAG_HIDDEN)) return false;
|
||||
return !lv_obj_check_type(sl.o, &lv_label_class) || lv_label_get_text(sl.o)[0] != '\0';
|
||||
};
|
||||
lv_coord_t ink_h = 0;
|
||||
for (const Slot& sl : order) {
|
||||
if (!shown(sl)) continue;
|
||||
lv_coord_t t, b;
|
||||
sbGlyphInk(sl.o, t, b);
|
||||
ink_h = LV_MAX(ink_h, b - t);
|
||||
}
|
||||
const lv_coord_t bot = (SB_TOP_PAD + statusBarCurH()) / 2 + ink_h / 2;
|
||||
bool first = true;
|
||||
for (const Slot& sl : order) {
|
||||
if (!shown(sl)) continue;
|
||||
if (!first) x += sl.gap;
|
||||
sbAlignBottom(sl.o, x, bot);
|
||||
x += sbObjW(sl.o);
|
||||
first = false;
|
||||
}
|
||||
return bot;
|
||||
}
|
||||
// The whole one-row bar's right side: the glyphs, then the clock on the same line.
|
||||
static void statusBarLayoutOneRow() {
|
||||
lv_coord_t x = SB_INSET_X + 2;
|
||||
const lv_coord_t bot = statusBarPackRound(true, true, x);
|
||||
if (g_statusbar.clock && !lv_obj_has_flag(g_statusbar.clock, LV_OBJ_FLAG_HIDDEN)) {
|
||||
x += 10;
|
||||
lv_obj_align(g_statusbar.clock, LV_ALIGN_TOP_RIGHT, -x, bot - sbBaseline(g_statusbar.clock));
|
||||
x += sbObjW(g_statusbar.clock);
|
||||
if (g_statusbar.async_icon)
|
||||
lv_obj_align_to(g_statusbar.async_icon, g_statusbar.clock, LV_ALIGN_OUT_LEFT_MID, -4, 0);
|
||||
}
|
||||
s_sb_right_w = x;
|
||||
s_sb_line_bot = bot;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -55038,12 +55125,34 @@ static void uiFitTrailingDots(const char* src, const lv_font_t* f, lv_coord_t ma
|
||||
|
||||
static void chatHeaderApply(bool chat_open) {
|
||||
if (!g_statusbar.chat_avatar) return;
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Round P4 panel: inset from the corner arcs and centred below the top-arc pad.
|
||||
// Portrait drops the connectivity glyphs so the header fits one line; landscape has
|
||||
// the room to keep Wi-Fi / signal beside the battery (never Bluetooth or a %).
|
||||
const bool keep_links = chatLandscape();
|
||||
const lv_coord_t inset = SB_INSET_X, ymid = SB_TOP_PAD / 2;
|
||||
#else
|
||||
const bool keep_links = false;
|
||||
const lv_coord_t inset = 0, ymid = 0;
|
||||
#endif
|
||||
lv_obj_t* radios[] = { g_statusbar.conn_icon, g_statusbar.ble_icon, g_statusbar.sd_icon,
|
||||
g_statusbar.dnd_icon, g_statusbar.sig_box, g_statusbar.sleep_icon };
|
||||
auto isLink = [](lv_obj_t* o) {
|
||||
return o == g_statusbar.conn_icon || o == g_statusbar.ble_icon || o == g_statusbar.sig_box;
|
||||
};
|
||||
static int s_radio_state = -1;
|
||||
const int radio_state = chat_open ? (keep_links ? 2 : 1) : 0;
|
||||
if (radio_state != s_radio_state) {
|
||||
s_radio_state = radio_state;
|
||||
for (lv_obj_t* o : radios) {
|
||||
if (!o) continue;
|
||||
const bool hide = chat_open && !(keep_links && isLink(o));
|
||||
lv_obj_set_style_opa(o, hide ? LV_OPA_TRANSP : LV_OPA_COVER, LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
static bool s_was_open = false;
|
||||
if (chat_open != s_was_open) {
|
||||
s_was_open = chat_open;
|
||||
for (lv_obj_t* o : radios) if (o) lv_obj_set_style_opa(o, chat_open ? LV_OPA_TRANSP : LV_OPA_COVER, LV_PART_MAIN);
|
||||
if (chat_open) {
|
||||
lv_obj_clear_flag(g_statusbar.chat_avatar, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(g_statusbar.chat_sub, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -55053,8 +55162,15 @@ static void chatHeaderApply(bool chat_open) {
|
||||
lv_obj_add_flag(g_statusbar.chat_sub, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(g_statusbar.chat_head, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_set_height(g_statusbar.left_label, LV_SIZE_CONTENT); // the title's one-line box (below)
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Hand the one-row bar its own layout back (the round bar never translates).
|
||||
lv_obj_set_width(g_statusbar.left_label, LV_SIZE_CONTENT);
|
||||
lv_label_set_long_mode(g_statusbar.left_label, LV_LABEL_LONG_WRAP);
|
||||
statusBarLayoutOneRow();
|
||||
#else
|
||||
for (lv_obj_t* o : { g_statusbar.clock, g_statusbar.batt_icon, g_statusbar.batt_pct })
|
||||
if (o) lv_obj_set_style_translate_y(o, -(lv_coord_t)(s_statusbar_tall ? STATUSBAR_H / 2 : 0), LV_PART_MAIN);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -55095,19 +55211,36 @@ static void chatHeaderApply(bool chat_open) {
|
||||
// Layout across the full height of the tall bar.
|
||||
const lv_coord_t bar_h = statusBarCurH();
|
||||
const lv_coord_t av_sz = lv_obj_get_style_width(g_statusbar.chat_avatar, LV_PART_MAIN);
|
||||
const lv_coord_t x_av = SC(24), x_text = x_av + av_sz + 8;
|
||||
const lv_coord_t x_back = inset ? inset : 6;
|
||||
const lv_coord_t x_av = x_back + SC(24) - 6, x_text = x_av + av_sz + 8;
|
||||
lv_obj_set_style_translate_y(g_statusbar.chat_avatar, 0, LV_PART_MAIN);
|
||||
lv_obj_align(g_statusbar.chat_avatar, LV_ALIGN_LEFT_MID, x_av, 0);
|
||||
lv_obj_align(g_statusbar.chat_avatar, LV_ALIGN_LEFT_MID, x_av, ymid);
|
||||
if (g_statusbar.chat_back) {
|
||||
lv_obj_set_style_text_color(g_statusbar.chat_back, lv_color_hex(COLOR_TEXT), LV_PART_MAIN);
|
||||
lv_obj_align(g_statusbar.chat_back, LV_ALIGN_LEFT_MID, 6, 0);
|
||||
lv_obj_align(g_statusbar.chat_back, LV_ALIGN_LEFT_MID, x_back, ymid);
|
||||
}
|
||||
// Right side: battery, then the clock just left of it, centred on the bar.
|
||||
for (lv_obj_t* o : { g_statusbar.clock, g_statusbar.batt_icon, g_statusbar.batt_pct })
|
||||
if (o) lv_obj_set_style_translate_y(o, 0, LV_PART_MAIN);
|
||||
#if CAP_ROUND_CORNERS
|
||||
// The one-row bar's layout is re-packed here on the taller header's centre line:
|
||||
// battery, then (landscape) signal and Wi-Fi.
|
||||
lv_coord_t pct_w = inset + 2;
|
||||
const lv_coord_t head_bot = statusBarPackRound(keep_links, false, pct_w);
|
||||
#else
|
||||
const char* pct = g_statusbar.batt_pct ? lv_label_get_text(g_statusbar.batt_pct) : "";
|
||||
const lv_coord_t pct_w = (pct && pct[0]) ? lv_txt_get_width(pct, strlen(pct), &g_font_12, 0, LV_TEXT_FLAG_NONE) + SBX(22) : SBX(20);
|
||||
lv_obj_align(g_statusbar.clock, LV_ALIGN_RIGHT_MID, -(pct_w + 8), 0);
|
||||
#endif
|
||||
#if CAP_ROUND_CORNERS
|
||||
// The clock's digits sit on the same bottom line as the icons beside it.
|
||||
{
|
||||
lv_obj_align(g_statusbar.clock, LV_ALIGN_TOP_RIGHT, -(pct_w + 8), head_bot - sbBaseline(g_statusbar.clock));
|
||||
}
|
||||
if (g_statusbar.async_icon)
|
||||
lv_obj_align_to(g_statusbar.async_icon, g_statusbar.clock, LV_ALIGN_OUT_LEFT_MID, -4, 0);
|
||||
#else
|
||||
lv_obj_align(g_statusbar.clock, LV_ALIGN_RIGHT_MID, -(pct_w + 8), ymid);
|
||||
#endif
|
||||
const char* clk = lv_label_get_text(g_statusbar.clock);
|
||||
const lv_coord_t clk_w = lv_txt_get_width(clk, strlen(clk), lv_obj_get_style_text_font(g_statusbar.clock, LV_PART_MAIN), 0, LV_TEXT_FLAG_NONE);
|
||||
const lv_coord_t text_w = lv_obj_get_style_width(g_statusbar.root, LV_PART_MAIN) - x_text - (pct_w + 8 + clk_w + 10);
|
||||
@@ -55125,15 +55258,15 @@ static void chatHeaderApply(bool chat_open) {
|
||||
uiFitTrailingDots(s_chat_title, &g_font_semi_14, LV_MAX((lv_coord_t)40, text_w), ttl, sizeof ttl);
|
||||
if (strcmp(lv_label_get_text(g_statusbar.left_label), ttl) != 0) lv_label_set_text(g_statusbar.left_label, ttl);
|
||||
}
|
||||
const lv_coord_t top = (bar_h - title_h - sub_h) / 2;
|
||||
const lv_coord_t top = (bar_h - title_h - sub_h) / 2 + ymid;
|
||||
lv_obj_align(g_statusbar.left_label, LV_ALIGN_TOP_LEFT, x_text, top);
|
||||
lv_obj_set_style_translate_y(g_statusbar.chat_sub, 0, LV_PART_MAIN);
|
||||
lv_obj_set_size(g_statusbar.chat_sub, LV_MAX((lv_coord_t)40, text_w), sub_h);
|
||||
lv_obj_align(g_statusbar.chat_sub, LV_ALIGN_TOP_LEFT, x_text, top + title_h);
|
||||
// The hit area for chat settings: avatar and both lines.
|
||||
lv_obj_set_style_translate_y(g_statusbar.chat_head, 0, LV_PART_MAIN);
|
||||
lv_obj_set_size(g_statusbar.chat_head, x_text - x_av + LV_MAX((lv_coord_t)40, text_w), bar_h);
|
||||
lv_obj_align(g_statusbar.chat_head, LV_ALIGN_LEFT_MID, x_av, 0);
|
||||
lv_obj_set_size(g_statusbar.chat_head, x_text - x_av + LV_MAX((lv_coord_t)40, text_w), bar_h - 2 * ymid);
|
||||
lv_obj_align(g_statusbar.chat_head, LV_ALIGN_LEFT_MID, x_av, ymid);
|
||||
}
|
||||
|
||||
// The right-hand indicators, packed leftwards from the battery with one gap between
|
||||
@@ -55141,7 +55274,7 @@ static void chatHeaderApply(bool chat_open) {
|
||||
// hole. Fixed slots left the Bluetooth glyph a slot and more away from the Wi-Fi.
|
||||
// Right to left: the battery, its %, the signal bars, Wi-Fi, Bluetooth, the DND moon,
|
||||
// the keyboard-layout tag. updateGlobalStatusBar runs it only when one of them
|
||||
// appears, goes or changes width. The round two-row bar has its own layout.
|
||||
// appears, goes or changes width. The round one-row bar has its own (statusBarLayoutOneRow).
|
||||
static uint32_t s_sb_pack_sig = 0; // what the last pack saw; the clock's overlap guard follows it
|
||||
static void statusBarPackRight() {
|
||||
if (!g_statusbar.root) return;
|
||||
@@ -55274,6 +55407,13 @@ static void updateGlobalStatusBar() {
|
||||
const bool want_tall = (s_settings_open_cat >= 0) || (s_apppage_title && !s_apppage_slim) || chat_open;
|
||||
#endif
|
||||
if (want_tall != s_statusbar_tall) statusBarSetTall(want_tall);
|
||||
#if CAP_ROUND_CORNERS
|
||||
// One row everywhere except an open chat, whose title + subtitle header is taller.
|
||||
if (chat_open != s_round_chat_bar) {
|
||||
s_round_chat_bar = chat_open;
|
||||
lv_obj_set_height(g_statusbar.root, statusBarCurH());
|
||||
}
|
||||
#endif
|
||||
// Glass lower row on double-height bars (settings detail, inbox/chat overview,
|
||||
// open chat) so the tall bar looks consistent everywhere it appears. Switch the
|
||||
// root's own fill transparent so only the fade backdrop paints the bar, then show
|
||||
@@ -55281,7 +55421,7 @@ static void updateGlobalStatusBar() {
|
||||
// built state — opaque root + hidden fade — already matches the inactive case).
|
||||
// EXCEPT while the channel-settings / blocked-users sheets are open: those are a new
|
||||
// page over the chat, so the bar goes SOLID (no glass revealing the chat behind it).
|
||||
// The round panel keeps a solid two-row bar because there is no doubled lower row;
|
||||
// The round panel keeps a solid bar because there is no doubled lower row;
|
||||
// T-Deck Pro also stays solid because its monochrome e-paper cannot render glass.
|
||||
#if defined(HAS_TDECK_PRO)
|
||||
const bool fade_active = false;
|
||||
@@ -55312,8 +55452,8 @@ static void updateGlobalStatusBar() {
|
||||
// Per-element vertical placement in the tall bar. Default = TOP row (system content,
|
||||
// same place as the regular bar). Exceptions: the settings back+title stays CENTRED;
|
||||
// an open chat's thread name drops to the LOWER row and its cog centres across both.
|
||||
// On the round panel the two rows come from each child's BASE align (set by
|
||||
// statusBarLayoutTwoRow), not from a per-tick shift — so never translate there.
|
||||
// The round panel's one row comes from each child's BASE align (set by
|
||||
// statusBarLayoutOneRow), not from a per-tick shift — so never translate there.
|
||||
const lv_coord_t up = (s_statusbar_tall && !CAP_ROUND_CORNERS) ? -(lv_coord_t)(STATUSBAR_H / 2) : 0;
|
||||
const uint32_t nch = lv_obj_get_child_cnt(g_statusbar.root);
|
||||
for (uint32_t i = 0; i < nch; ++i) {
|
||||
@@ -55356,12 +55496,10 @@ static void updateGlobalStatusBar() {
|
||||
// In a chat the thread name (+ unread prefix) is CENTRED on the lower row (the back +
|
||||
// cog sit at the far left); otherwise it's left-aligned per the usual layout.
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Round panel placement of the left zone:
|
||||
// - open chat: thread name centred on ROW 1 (back/cog on row-1 left, clock row-1 right).
|
||||
// - settings/tool page: "‹ Title" on ROW 2 left, its top level with the battery
|
||||
// readout on the right (and clear of the clock on row 1).
|
||||
// - home / other tabs: the scrolling profile name (or ✉ unread badge) sits on ROW 2,
|
||||
// the SAME line as the battery/status cluster (per request), left-aligned.
|
||||
// Round panel placement of the left zone (an open chat is laid out by chatHeaderApply):
|
||||
// the "‹ Title" of a settings/tool page, or the scrolling profile name (or ✉ unread
|
||||
// badge), sits at the left on the bar's one line, its baseline on the icons' bottom
|
||||
// line, capped short of the clock.
|
||||
if (s_crumb_capped && !(s_settings_open_cat >= 0 || s_apppage_title)) {
|
||||
// Leaving a settings/tool page: drop the breadcrumb's width cap (the home tab's
|
||||
// marquee config, applied further down, sets its own when it is the home tab).
|
||||
@@ -55369,17 +55507,16 @@ static void updateGlobalStatusBar() {
|
||||
lv_label_set_long_mode(g_statusbar.left_label, LV_LABEL_LONG_WRAP);
|
||||
s_crumb_capped = false;
|
||||
}
|
||||
if (in_chan_chat) {
|
||||
lv_obj_align(g_statusbar.left_label, LV_ALIGN_TOP_MID, 0, SB_ROW1_Y);
|
||||
} else if (s_settings_open_cat >= 0 || s_apppage_title) {
|
||||
lv_obj_align(g_statusbar.left_label, LV_ALIGN_TOP_LEFT, SB_INSET_X, SB_ROW2_Y);
|
||||
// Sharing row 2 with the status cluster: cap the title short of it (ellipsis).
|
||||
const int crumb_w = lv_obj_get_width(g_statusbar.root) - SB_INSET_X - statusClusterLeftExtent() - 6;
|
||||
lv_obj_set_width(g_statusbar.left_label, LV_MAX(40, crumb_w));
|
||||
lv_label_set_long_mode(g_statusbar.left_label, LV_LABEL_LONG_DOT);
|
||||
s_crumb_capped = true;
|
||||
} else {
|
||||
lv_obj_align(g_statusbar.left_label, LV_ALIGN_TOP_LEFT, SB_INSET_X, SB_ROW2_Y);
|
||||
if (!in_chan_chat) {
|
||||
lv_obj_align(g_statusbar.left_label, LV_ALIGN_TOP_LEFT, SB_INSET_X,
|
||||
s_sb_line_bot - sbBaseline(g_statusbar.left_label));
|
||||
if (s_settings_open_cat >= 0 || s_apppage_title) {
|
||||
// Sharing the row with the clock and icons: cap the title short of them (ellipsis).
|
||||
const int crumb_w = lv_obj_get_width(g_statusbar.root) - SB_INSET_X - s_sb_right_w - 12;
|
||||
lv_obj_set_width(g_statusbar.left_label, LV_MAX(40, crumb_w));
|
||||
lv_label_set_long_mode(g_statusbar.left_label, LV_LABEL_LONG_DOT);
|
||||
s_crumb_capped = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (in_chan_chat) {
|
||||
@@ -55456,8 +55593,8 @@ static void updateGlobalStatusBar() {
|
||||
// page title, CENTRED in the tall bar and a size up (tapping the bar goes Back). The
|
||||
// chevron is tinted with the theme accent (the title text stays default).
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Round panel: the title shares row 2 with the battery readout, so it uses that
|
||||
// row's size rather than the tall bar's larger one.
|
||||
// Round panel: the title shares the one row with the clock and icons, so it uses
|
||||
// the row's size rather than the tall bar's larger one.
|
||||
lv_obj_set_style_text_font(g_statusbar.left_label, &g_font_14, LV_PART_MAIN);
|
||||
#else
|
||||
lv_obj_set_style_text_font(g_statusbar.left_label,
|
||||
@@ -55480,7 +55617,7 @@ static void updateGlobalStatusBar() {
|
||||
// was removed to reclaim height). Keep the global unread badge as a prefix so
|
||||
// you still see mail waiting in OTHER threads while reading this one. Smaller
|
||||
// font so the name + badge fit.
|
||||
#if defined(TLORA_PAGER) || CAP_ROUND_CORNERS
|
||||
#if defined(TLORA_PAGER)
|
||||
lv_obj_set_style_text_font(g_statusbar.left_label, &g_font_12, LV_PART_MAIN);
|
||||
int total_unread = g_lv.task->getUnreadTotal();
|
||||
if (total_unread > 0) {
|
||||
@@ -55597,7 +55734,9 @@ static void updateGlobalStatusBar() {
|
||||
} else {
|
||||
lv_obj_add_flag(g_statusbar.conn_icon, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
if (g_statusbar.ble_icon) {
|
||||
// The P4 has Bluetooth hardware, but wadamesh does not support it there: its glyph
|
||||
// stays hidden (from buildGlobalStatusBar on).
|
||||
if (g_statusbar.ble_icon && !CAP_ROUND_CORNERS) {
|
||||
if (ble_up) {
|
||||
#if CAP_BLE_KEYBOARD
|
||||
// Bluetooth is busy with a keyboard: say so instead of showing the radio.
|
||||
@@ -55624,12 +55763,13 @@ static void updateGlobalStatusBar() {
|
||||
|
||||
// ---- Do Not Disturb icon ---- (shown only while the configured silence
|
||||
// window is active — see dndActive()). On cramped bars (T-Display P4's
|
||||
// round-corner two-row layout, or Heltec V4 rotated to portrait, <300px wide)
|
||||
// round-corner layout, or Heltec V4 rotated to portrait, <300px wide)
|
||||
// there's no room for a dedicated slot, so DND borrows the signal-bars slot
|
||||
// instead while active; signal strength reclaims it the rest of the time.
|
||||
{
|
||||
// (The packed bars make room for the moon on their own, narrow ones included.)
|
||||
const bool cramped = CAP_ROUND_CORNERS;
|
||||
// (The round panel has room too, now that it shows no Bluetooth glyph or %.)
|
||||
const bool cramped = false;
|
||||
const bool active = dndActive();
|
||||
const bool swap_signal = cramped && active;
|
||||
|
||||
@@ -55642,14 +55782,6 @@ static void updateGlobalStatusBar() {
|
||||
else lv_obj_clear_flag(g_statusbar.sig_box, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
if (g_statusbar.dnd_icon) {
|
||||
#if CAP_ROUND_CORNERS
|
||||
if (!swap_signal) {
|
||||
// Dedicated slot — re-assert on entry/return so the moon self-heals from
|
||||
// the borrowed signal-bars position instead of staying stranded there.
|
||||
const int d = batteryIsCharging(batteryMvSmoothed()) ? 32 : 0;
|
||||
lv_obj_align(g_statusbar.dnd_icon, LV_ALIGN_RIGHT_MID, -144 + d, 0);
|
||||
}
|
||||
#endif
|
||||
if (active) lv_obj_clear_flag(g_statusbar.dnd_icon, LV_OBJ_FLAG_HIDDEN);
|
||||
else lv_obj_add_flag(g_statusbar.dnd_icon, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
@@ -55703,11 +55835,7 @@ static void updateGlobalStatusBar() {
|
||||
lv_label_set_text(g_statusbar.batt_pct, buf);
|
||||
if (charging != s_last_charging) {
|
||||
// The % column disappears while charging (bolt only). The packed bars close
|
||||
// up on their own (statusBarPackRight, run below on the change); the round
|
||||
// two-row bar slides its row-2 cluster right by the hidden column instead.
|
||||
#if CAP_ROUND_CORNERS
|
||||
statusBarLayoutTwoRow(charging ? 32 : 0);
|
||||
#endif
|
||||
// up on their own (statusBarPackRight, run below on the change).
|
||||
}
|
||||
s_last_pct = pct;
|
||||
s_last_charging = charging;
|
||||
@@ -55826,11 +55954,9 @@ static void updateGlobalStatusBar() {
|
||||
if (sig != s_clk_sig && visible(g_statusbar.clock)) {
|
||||
s_clk_sig = sig;
|
||||
#if CAP_ROUND_CORNERS
|
||||
// Round P4 two-row bar: centre the clock on ROW 1 for the home / normal screens — row-1
|
||||
// centre is free there (the node name lives on row 2). In a chat the row-1 centre is the
|
||||
// thread title, so the clock moves to row-1 right instead.
|
||||
if (chat_open) lv_obj_align(g_statusbar.clock, LV_ALIGN_TOP_RIGHT, -SB_INSET_X, SB_ROW1_Y);
|
||||
else lv_obj_align(g_statusbar.clock, LV_ALIGN_TOP_MID, 0, SB_ROW1_Y);
|
||||
// Round P4 one-row bar: the clock sits just left of the icons (statusBarLayoutOneRow);
|
||||
// in a chat chatHeaderApply places it.
|
||||
if (!chat_open) statusBarLayoutOneRow();
|
||||
#else
|
||||
// The redesign centres the clock on every board. (The block below placed it
|
||||
// beside a node-name window that the bar no longer shows; the overlap guard
|
||||
@@ -55867,7 +55993,7 @@ static void updateGlobalStatusBar() {
|
||||
for (lv_obj_t* o : sb_icons) {
|
||||
if (!visible(o)) continue;
|
||||
lv_area_t ia; lv_obj_get_coords(o, &ia);
|
||||
if (ia.y2 < ca.y1 || ia.y1 > ca.y2) continue; // another row (the round panel's two-row bar)
|
||||
if (ia.y2 < ca.y1 || ia.y1 > ca.y2) continue; // another row
|
||||
if (ia.x2 < ca.x1) continue; // left of the clock: not the right-hand cluster
|
||||
if (ia.x1 < left_edge) left_edge = ia.x1;
|
||||
if (ia.x1 < ca.x2 + kGap) overlap = true;
|
||||
@@ -55905,7 +56031,28 @@ static void updateGlobalStatusBar() {
|
||||
lv_obj_add_flag(g_statusbar.layout_label, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
#if !defined(TLORA_PAGER) && !CAP_ROUND_CORNERS
|
||||
#if CAP_ROUND_CORNERS
|
||||
{
|
||||
// The one row is packed by glyph ink and width: re-pack it when a glyph appears,
|
||||
// goes or changes shape, or the clock's text changes width. An open chat's header
|
||||
// is laid out by chatHeaderApply instead.
|
||||
static uint32_t s_row_sig = 0;
|
||||
uint32_t h = 2166136261u;
|
||||
for (lv_obj_t* o : { g_statusbar.batt_icon, g_statusbar.conn_icon, g_statusbar.dnd_icon,
|
||||
g_statusbar.sig_box, g_statusbar.clock }) {
|
||||
h ^= (o && !lv_obj_has_flag(o, LV_OBJ_FLAG_HIDDEN)) ? 1u : 0u; h *= 16777619u;
|
||||
const char* t = (o && lv_obj_check_type(o, &lv_label_class)) ? lv_label_get_text(o) : "";
|
||||
for (const char* q = t; q && *q; ++q) { h ^= (uint8_t)*q; h *= 16777619u; }
|
||||
h ^= 0xFF; h *= 16777619u;
|
||||
}
|
||||
h ^= chat_open ? 1u : 0u; h *= 16777619u;
|
||||
if (h != s_row_sig) {
|
||||
s_row_sig = h;
|
||||
if (!chat_open) statusBarLayoutOneRow();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if !defined(TLORA_PAGER)
|
||||
chatHeaderApply((s_chat_title[0] != '\0') && !s_apppage_title && (s_settings_open_cat < 0));
|
||||
#endif
|
||||
}
|
||||
@@ -57167,6 +57314,10 @@ static void themeStylesBuild() {
|
||||
lv_style_set_bg_opa(&s_ts.kb_key_pr, LV_OPA_COVER);
|
||||
lv_style_set_text_color(&s_ts.kb_key_pr, on_glow);
|
||||
lv_style_set_bg_color(&s_ts.kb_key_chk, off_tone);
|
||||
// Non-letter keys (Shift, Backspace, Enter, 1#, ...) are CHECKED items: label them
|
||||
// like the letter keys, or they fall back to the base theme's dark text.
|
||||
lv_style_set_text_color(&s_ts.kb_key_chk, text);
|
||||
lv_style_set_text_font(&s_ts.kb_key_chk, &g_font_14);
|
||||
|
||||
// Arcs and spinners.
|
||||
lv_style_set_arc_color(&s_ts.arc, border);
|
||||
@@ -62822,7 +62973,7 @@ bool luaHostMeshSendChannel(const char* chan_name, const char* text) {
|
||||
// since it is not flooded.
|
||||
// The height an AppPage's title bar ACTUALLY occupies. The Lua host had this as
|
||||
// a hardcoded 44, which is right only where STATUSBAR_H is its 22 default: it is
|
||||
// SB_TOP_PAD + SB_ROW*2 on the round-corner phone panel, and SC(22) everywhere
|
||||
// SB_TOP_PAD + SB_ROW on the round-corner phone panel, and SC(22) everywhere
|
||||
// else, so it grows with the UI scale. Wherever it was taller than 44 the app's
|
||||
// first line was drawn under the bar (#236 on the T-Display P4, and the same on
|
||||
// any board at Large or Huge scale, which the Tanmatsu uses by default).
|
||||
@@ -65956,7 +66107,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
// a resolved raw Montserrat for its chained twin, and every label that sets
|
||||
// no font of its own calls it.
|
||||
#if CAP_ROUND_CORNERS
|
||||
STATUSBAR_H = SB_TOP_PAD + SB_ROW * 2; // top safe-area + two rows (round phone panel)
|
||||
STATUSBAR_H = SB_TOP_PAD + SB_ROW; // top safe-area + one row (round phone panel)
|
||||
#else
|
||||
STATUSBAR_H = SC(22); // grow the status bar to fit bigger text at Large/Huge (no-op at 100%)
|
||||
#if defined(HELTEC_LORA_V4_R8) || defined(HAS_TDECK_GT911) || (defined(DOC_CAPTURE) && defined(DOC_VIRT_FONTSET))
|
||||
|
||||
@@ -282,9 +282,10 @@
|
||||
// Both can opt into the bounded, pre-transport saved-Wi-Fi sync from #383. The
|
||||
// T-Display P4's PCF8563 has come back asserting impossible dates, so it gets the
|
||||
// same fallback -- on the esp-hosted C6 build only: the sync drives Arduino's real
|
||||
// WiFi, which the legacy ESP-AT build must never touch.
|
||||
// WiFi, which the legacy ESP-AT build must never touch. The Wio Tracker L2 has
|
||||
// no RTC and an ESP32-S3 radio, so it gets the same opt-in.
|
||||
#if defined(HAS_TDECK_GT911) || defined(HAS_TDECK_PRO) || defined(HAS_THINKNODE_M9) || \
|
||||
(defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED)
|
||||
defined(HAS_WIO_TRACKER_L2) || (defined(HAS_TDISPLAY_P4) && TDP4_C6_HOSTED)
|
||||
#define CAP_BOOT_TIME_SYNC 1
|
||||
#else
|
||||
#define CAP_BOOT_TIME_SYNC 0
|
||||
|
||||
Reference in new issue
Block a user