mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 19:02:00 +00:00
feat(ui-lvgl): title bar with the clock, tidier controls, contextual nav bar
Density from less chrome instead of smaller rows (ROW_H stays 44): - titled screens drop the status bar; the title bar (APPBAR_H) holds back, the title, the screen's actions and the time/battery on a pill; the status bar returns for Home, the map, typing, the quick panel and the lock - one radius (RADIUS) for everything tapped: buttons, tabs, choices, chips - tabs are all segmented at TAB_H in the body (GPS, Clock converted) - Nodes: chips row + sort + status line become one toolbar row with Show / Order choices; Messages' All/Favourites is the same choice - nav map: the target bar only while there is a target; the pin opens one "Places" popup; back from a map options page restores the map and Map tools as left, without a jump - node detail actions at the top of the scroll; Home tiles fill the page - rowsFit() leaves growing labels to the flex (a screen title cut to one letter after Messages' filter changed) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -560,6 +560,7 @@ static void bannerShow(const char* icon, const char* title, const char* text) {
|
||||
lv_anim_delete(s_banner, NULL);
|
||||
lv_obj_remove_flag(s_banner, LV_OBJ_FLAG_HIDDEN);
|
||||
if (!shown) { // a new one while it's up just changes the text
|
||||
lv_obj_align(s_banner, LV_ALIGN_TOP_MID, 0, belowTopBar() + 2);
|
||||
layoutNow(s_banner);
|
||||
anim::run(s_banner, anim::setTy, -(lv_obj_get_height(s_banner) + theme::STATUS_H), 0, anim::SCREEN_MS);
|
||||
} else {
|
||||
@@ -715,8 +716,20 @@ void UITask::refreshStatusBar() {
|
||||
default: break;
|
||||
}
|
||||
setTextFmt(_status_batt, "%s%s", batt, level);
|
||||
const bool chg = _board->isExternalPowered();
|
||||
if (_hdr_clock) { // the title bar's: the time over the battery, green on the charger
|
||||
setText(_hdr_time, lv_label_get_text(_status_time));
|
||||
setTextFmt(_hdr_batt, "%s%s", batt, level);
|
||||
setTextColor(_hdr_batt, chg ? theme::OK : theme::TEXT_MUTED);
|
||||
layoutNow(_hdr_clock);
|
||||
const int32_t w = lv_obj_get_width(_hdr_clock); // the row stops short of it
|
||||
if (lv_obj_get_style_pad_right(_header, LV_PART_MAIN) != w) {
|
||||
lv_obj_set_style_pad_right(_header, w, 0);
|
||||
lv_obj_align(_hdr_clock, LV_ALIGN_RIGHT_MID, w, 0); // aligned inside the padding: out into it
|
||||
}
|
||||
}
|
||||
lv_obj_t* left_of = _status_batt; // the icons pack up to this
|
||||
if (_board->isExternalPowered()) {
|
||||
if (chg) {
|
||||
lv_obj_remove_flag(_status_chg, LV_OBJ_FLAG_HIDDEN);
|
||||
placeLeftOf(_status_chg, _status_batt, -2);
|
||||
left_of = _status_chg;
|
||||
@@ -760,6 +773,16 @@ void UITask::refreshStatusBar() {
|
||||
placeLeftOf(_status_icons, left_of, -5);
|
||||
}
|
||||
|
||||
// The status bar is up over a screen without a title bar (Home, the map, a
|
||||
// conversation typing), and over the quick panel and the lock whatever the
|
||||
// screen; a title bar carries the time and battery itself.
|
||||
void UITask::statusBarSync() {
|
||||
if (!s_status_bar) return;
|
||||
const bool titled = _header && !lv_obj_has_flag(_header, LV_OBJ_FLAG_HIDDEN);
|
||||
const bool hide = titled && !s_sb_panel && !s_sb_lock;
|
||||
if (hide != lv_obj_has_flag(s_status_bar, LV_OBJ_FLAG_HIDDEN)) lv_obj_set_flag(s_status_bar, LV_OBJ_FLAG_HIDDEN, hide);
|
||||
}
|
||||
|
||||
void UITask::setGps(bool on) {
|
||||
if (!_core->setGpsEnabled(on, false)) { showToast("No GPS on this device"); return; }
|
||||
prefsSave(); // written on leaving the screen, as the other switches
|
||||
@@ -895,8 +918,8 @@ void UITask::showToast(const char* text, uint32_t ms) {
|
||||
bool shown = !lv_obj_has_flag(_toast, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_anim_delete(_toast, NULL); // on its way out
|
||||
lv_obj_remove_flag(_toast, LV_OBJ_FLAG_HIDDEN);
|
||||
// Under the message banner while that's up, else right under the status bar.
|
||||
int32_t y = theme::STATUS_H + 4;
|
||||
// Under the message banner while that's up, else right under the top bar.
|
||||
int32_t y = belowTopBar() + 4;
|
||||
if (s_banner && !lv_obj_has_flag(s_banner, LV_OBJ_FLAG_HIDDEN)) y = lv_obj_get_y(s_banner) + lv_obj_get_height(s_banner) + 4;
|
||||
lv_obj_align(_toast, LV_ALIGN_TOP_MID, 0, y);
|
||||
layoutNow(_toast);
|
||||
@@ -990,7 +1013,7 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
|
||||
if (_shown_screen == SCR_SETTINGS && _body) s_settings_y = lv_obj_get_scroll_y(_body);
|
||||
pickerClose();
|
||||
_header = _body = nullptr;
|
||||
_nearby_list = _nearby_status = _nearby_sort_lbl = _nearby_chips = nullptr;
|
||||
_nearby_list = _nearby_status = nullptr;
|
||||
_node_info = _node_ping = _node_delete_lbl = nullptr;
|
||||
nodeviz::s_card = nullptr;
|
||||
_scan_overlay = _scan_list = _scan_status = nullptr; // the popup went with the old screen
|
||||
@@ -1012,18 +1035,30 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
|
||||
styleOpaque(scr, theme::BG);
|
||||
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
// The title bar, at the very top in place of the status bar: back, the
|
||||
// title, the screen's actions (headerButton(), after the title in a row),
|
||||
// and at the right end the time and battery. A drag down on it brings the
|
||||
// quick panel, as on the status bar.
|
||||
int top = theme::STATUS_H;
|
||||
_hdr_clock = _hdr_time = _hdr_batt = nullptr;
|
||||
if (title) {
|
||||
lv_obj_t* hdr = lv_obj_create(scr);
|
||||
styleSurface(hdr, theme::BG);
|
||||
lv_obj_remove_flag(hdr, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_size(hdr, LV_PCT(100), 38);
|
||||
lv_obj_set_pos(hdr, 0, top);
|
||||
lv_obj_set_size(hdr, LV_PCT(100), theme::APPBAR_H);
|
||||
lv_obj_set_pos(hdr, 0, 0);
|
||||
lv_obj_set_flex_flow(hdr, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(hdr, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_left(hdr, with_back ? 4 : theme::PAD, 0);
|
||||
lv_obj_set_style_pad_column(hdr, theme::GAP, 0);
|
||||
lv_obj_add_flag(hdr, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(hdr, LV_OBJ_FLAG_PRESS_LOCK);
|
||||
lv_obj_remove_flag(hdr, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||
lv_obj_add_event_cb(hdr, onStatusBarTouch, LV_EVENT_ALL, (void*)1); // a drag only: a tap on the title is nothing
|
||||
_header = hdr;
|
||||
if (with_back) {
|
||||
lv_obj_t* b = lv_button_create(hdr);
|
||||
lv_obj_set_size(b, 40, 28);
|
||||
lv_obj_align(b, LV_ALIGN_LEFT_MID, 4, 0);
|
||||
lv_obj_set_size(b, 40, theme::BAR_BTN_H);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(theme::SURFACE), 0);
|
||||
lv_obj_set_style_shadow_width(b, 0, 0);
|
||||
lv_obj_add_event_cb(b, onBack, LV_EVENT_CLICKED, NULL);
|
||||
@@ -1031,9 +1066,38 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
|
||||
}
|
||||
lv_obj_t* t = label(hdr, title, THEME_FONT_TITLE, theme::TEXT);
|
||||
lv_label_set_long_mode(t, LV_LABEL_LONG_DOT);
|
||||
lv_obj_set_width(t, 230);
|
||||
lv_obj_align(t, LV_ALIGN_LEFT_MID, with_back ? 52 : theme::PAD, 0);
|
||||
top += 38;
|
||||
lv_obj_set_width(t, 1);
|
||||
lv_obj_set_flex_grow(t, 1); // what the actions leave
|
||||
// The time and battery on a pill as the bar's buttons are, out of the
|
||||
// row: the row stops short of it (the bar's right padding, refreshStatusBar()).
|
||||
lv_obj_t* c = lv_obj_create(hdr);
|
||||
lv_obj_remove_style_all(c);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_PRESS_LOCK);
|
||||
lv_obj_remove_flag(c, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||
lv_obj_add_event_cb(c, onStatusBarTouch, LV_EVENT_ALL, NULL); // a tap opens the panel, as the status bar's
|
||||
lv_obj_set_size(c, LV_SIZE_CONTENT, LV_PCT(100));
|
||||
lv_obj_set_style_pad_left(c, theme::GAP, 0);
|
||||
lv_obj_set_style_pad_right(c, 4, 0);
|
||||
lv_obj_set_flex_flow(c, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(c, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_align(c, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
_hdr_clock = c;
|
||||
lv_obj_t* p = lv_obj_create(c);
|
||||
lv_obj_remove_style_all(p);
|
||||
lv_obj_remove_flag(p, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_size(p, LV_SIZE_CONTENT, theme::BAR_BTN_H);
|
||||
lv_obj_set_flex_flow(p, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(p, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(p, 6, 0);
|
||||
lv_obj_set_style_bg_color(p, lv_color_hex(theme::SURFACE), 0);
|
||||
lv_obj_set_style_bg_opa(p, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(p, theme::RADIUS, 0);
|
||||
lv_obj_set_style_pad_hor(p, 8, 0);
|
||||
_hdr_time = label(p, "", THEME_FONT_BODY, theme::TEXT);
|
||||
_hdr_batt = label(p, "", THEME_FONT_ICONS, theme::TEXT_MUTED);
|
||||
top = theme::APPBAR_H;
|
||||
}
|
||||
|
||||
lv_obj_t* body = lv_obj_create(scr);
|
||||
@@ -1063,6 +1127,8 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
|
||||
if (prev && !same && !_asleep) anim::screenIn(body, backward);
|
||||
}
|
||||
_fade_next = false;
|
||||
statusBarSync();
|
||||
refreshStatusBar(); // the title bar's time and battery
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -1103,8 +1169,12 @@ void UITask::back() {
|
||||
case SCR_SETTINGS: if (_nav_overlay) navClosePopup(); else showHome(); break;
|
||||
case SCR_SETTINGS_NAV: // the map's options go back to the map
|
||||
if (_settings_page == settings::PG_NAV && s_opts_from_map) {
|
||||
openMap(true);
|
||||
if (s_nav_section >= 0) navToolsPopup(); // one section's options: back into Map tools
|
||||
// The map as it was left -- its mode and view, Map tools over it,
|
||||
// scrolled where it was -- dissolving in over the page.
|
||||
const bool tools = s_nav_section >= 0;
|
||||
_fade_next = true;
|
||||
showMap(false);
|
||||
if (tools) navToolsPopup(true);
|
||||
} else if (_settings_page == settings::PG_RADIO) {
|
||||
showRadio();
|
||||
} else {
|
||||
@@ -1145,7 +1215,7 @@ void UITask::back() {
|
||||
else showHome();
|
||||
break;
|
||||
case SCR_NODE: // back to where the node was picked: map, the list, or the scan popup over it
|
||||
if (_node_from_map) { openMap(false); break; }
|
||||
if (_node_from_map) { _map_nodes = true; showMap(false); break; } // the nodes map as it was left
|
||||
_screen = SCR_NEARBY;
|
||||
buildNearby();
|
||||
if (_node_from_scan) showScanPopup();
|
||||
@@ -1355,3 +1425,36 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_scroll(int dy) {
|
||||
return left;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SIM_PLATFORM) && defined(__EMSCRIPTEN__)
|
||||
// The simulator page opens a screen by name (screenshots of the layout):
|
||||
// home, chats, contacts, settings, settings:<page>, nearby, map, navmap, places, clock, radio,
|
||||
// gps, diag, compass, storage, repeater, wifi, bot, quick, node, channel.
|
||||
extern "C" EMSCRIPTEN_KEEPALIVE int sim_ui_show(const char* name) {
|
||||
if (!s_ui || !name) return 0;
|
||||
UITask& u = *s_ui;
|
||||
if (!strcmp(name, "home")) u.showHome();
|
||||
else if (!strcmp(name, "chats")) u.showChats();
|
||||
else if (!strcmp(name, "contacts")) u.showContacts();
|
||||
else if (!strcmp(name, "settings")) u.showSettings();
|
||||
else if (!strncmp(name, "settings:", 9)) u.showSchemaSettings(atoi(name + 9));
|
||||
else if (!strcmp(name, "nearby")) u.showNearby();
|
||||
else if (!strcmp(name, "map")) u.openMap(false);
|
||||
else if (!strcmp(name, "navmap")) u.openMap(true);
|
||||
else if (!strcmp(name, "places")) { u.openMap(true); u.navTargetsPopup(); }
|
||||
else if (!strcmp(name, "clock")) u.showClock();
|
||||
else if (!strcmp(name, "radio")) u.showRadio();
|
||||
else if (!strcmp(name, "gps")) u.showGps(false);
|
||||
else if (!strcmp(name, "diag")) u.showDiag();
|
||||
else if (!strcmp(name, "compass")) u.showCompass();
|
||||
else if (!strcmp(name, "storage")) u.showStorage();
|
||||
else if (!strcmp(name, "repeater")) u.showRepeater();
|
||||
else if (!strcmp(name, "wifi")) u.showWifi();
|
||||
else if (!strcmp(name, "bot")) u.showBot();
|
||||
else if (!strcmp(name, "quick")) u.showQuickMsgs();
|
||||
else if (!strcmp(name, "node")) { u.showNearby(); u.openNode(0); }
|
||||
else if (!strcmp(name, "channel")) u.openChannel(0);
|
||||
else return 0;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user