From 1ff58d4a81b7e0756fc310ae0308ae53dad022c8 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:22:31 +0200 Subject: [PATCH] perf(l2): no redraws while nothing changes Found by counting flushes on idle screens in the sim: - The compose field's cursor blinked (a redraw every 400 ms) as soon as a conversation opened, the keyboard hidden: lv_keyboard_set_textarea() focuses the field, and LVGL only stops a blink when the cursor moves. fieldFocus() sets the state and nudges the blink; also used for the WiFi and channel fields, which left theirs running. - lv_obj_align_to() restyles on every call, so the status bar's icons redrew every second; placeLeftOf() moves them only when they move. - Text colours set every second (nav bar, GPS status, Diag errors) go through setTextColor(), which skips an unchanged colour. - A LV_LABEL_LONG_DOT label writes its "..." into its own text, so setText() never matched and rewrote it every refresh (the nav bar's hint); labelShows() reads past the dots. - The map area, the area-outline, trail and vector-label layers were invalidated on every map refresh (every 3 s, a full-map redraw); now only when the view or what they draw changed. Idle map: 17 -> 1 flush in 5 s; list and settings screens: 0. Co-Authored-By: Claude Opus 5.5 --- .../companion_radio/ui-lvgl/ChannelScreen.h | 10 ++-- examples/companion_radio/ui-lvgl/DiagScreen.h | 2 +- examples/companion_radio/ui-lvgl/GpsScreen.h | 10 ++-- examples/companion_radio/ui-lvgl/HomeScreen.h | 3 +- examples/companion_radio/ui-lvgl/MapAreas.h | 6 ++- examples/companion_radio/ui-lvgl/MapScreen.h | 16 +++--- examples/companion_radio/ui-lvgl/NavMap.h | 29 ++++++---- examples/companion_radio/ui-lvgl/UITask.cpp | 53 ++++++++++++++++--- examples/companion_radio/ui-lvgl/WifiScreen.h | 10 ++-- .../ui-lvgl/map/VectorLabels.h | 6 ++- 10 files changed, 102 insertions(+), 43 deletions(-) diff --git a/examples/companion_radio/ui-lvgl/ChannelScreen.h b/examples/companion_radio/ui-lvgl/ChannelScreen.h index 76d7a190..73dcda0c 100644 --- a/examples/companion_radio/ui-lvgl/ChannelScreen.h +++ b/examples/companion_radio/ui-lvgl/ChannelScreen.h @@ -262,10 +262,10 @@ void UITask::channelEditHex(bool hex) { void UITask::channelEditField(lv_obj_t* ta) { using namespace chanview; if (!s_kb) return; - lv_obj_remove_state(s_name, LV_STATE_FOCUSED); - lv_obj_remove_state(s_secret, LV_STATE_FOCUSED); - lv_obj_add_state(ta, LV_STATE_FOCUSED); // draws the cursor + fieldFocus(s_name, false); + fieldFocus(s_secret, false); lv_keyboard_set_textarea(s_kb, ta); + fieldFocus(ta, true); if (lv_obj_has_flag(s_kb, LV_OBJ_FLAG_HIDDEN)) { lv_obj_remove_flag(s_kb, LV_OBJ_FLAG_HIDDEN); if (_body) lv_obj_set_height(_body, lv_obj_get_height(_body) - lv_obj_get_height(s_kb)); @@ -278,8 +278,8 @@ void UITask::channelEditKbHide() { using namespace chanview; if (!s_kb || lv_obj_has_flag(s_kb, LV_OBJ_FLAG_HIDDEN)) return; lv_obj_add_flag(s_kb, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(s_name, LV_STATE_FOCUSED); - lv_obj_remove_state(s_secret, LV_STATE_FOCUSED); + fieldFocus(s_name, false); + fieldFocus(s_secret, false); if (_body) lv_obj_set_height(_body, lv_obj_get_height(_body) + lv_obj_get_height(s_kb)); } diff --git a/examples/companion_radio/ui-lvgl/DiagScreen.h b/examples/companion_radio/ui-lvgl/DiagScreen.h index c3bf5b6a..ba6cc903 100644 --- a/examples/companion_radio/ui-lvgl/DiagScreen.h +++ b/examples/companion_radio/ui-lvgl/DiagScreen.h @@ -99,7 +99,7 @@ static void setLive(int i, const diag::Row& r) { char v[40]; showValue(r.label, r.value, v, sizeof(v)); setText(lv.val, v); - if (!strcmp(r.label, "Errors")) lv_obj_set_style_text_color(lv.val, lv_color_hex(strcmp(r.value, "OK") ? theme::FAIL : theme::OK), 0); + if (!strcmp(r.label, "Errors")) setTextColor(lv.val, strcmp(r.value, "OK") ? theme::FAIL : theme::OK); } // A packets row: the name, then received and sent in fixed columns. diff --git a/examples/companion_radio/ui-lvgl/GpsScreen.h b/examples/companion_radio/ui-lvgl/GpsScreen.h index 07b2b574..dd46a97f 100644 --- a/examples/companion_radio/ui-lvgl/GpsScreen.h +++ b/examples/companion_radio/ui-lvgl/GpsScreen.h @@ -325,13 +325,13 @@ void UITask::refreshGps() { bool fix = false; if (!g) { setText(s_status, "Not available"); - lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0); + setTextColor(s_status, theme::TEXT_MUTED); } else if (off) { setText(s_status, "GPS is off"); - lv_obj_set_style_text_color(s_status, lv_color_hex(theme::TEXT_MUTED), 0); + setTextColor(s_status, theme::TEXT_MUTED); } else if (!data) { setText(s_status, "No data"); - lv_obj_set_style_text_color(s_status, lv_color_hex(theme::FAIL), 0); + setTextColor(s_status, theme::FAIL); #if defined(SEEED_WIO_TRACKER_L2) snprintf(used, sizeof(used), "Nothing from the receiver (%lu B)", (unsigned long)gps.rxChars()); #else @@ -343,10 +343,10 @@ void UITask::refreshGps() { fix = g->hasFix(); if (fix) { setText(s_status, g->fix_mode == 2 ? "2D fix" : "3D fix"); - lv_obj_set_style_text_color(s_status, lv_color_hex(theme::OK), 0); + setTextColor(s_status, theme::OK); } else { setText(s_status, "Searching"); - lv_obj_set_style_text_color(s_status, lv_color_hex(theme::ACCENT), 0); + setTextColor(s_status, theme::ACCENT); } snprintf(used, sizeof(used), "%d of %d used", fix ? (used_n ? used_n : g->sats_used) : 0, s_n); snprintf(inview, sizeof(inview), "%d heard", tracked); diff --git a/examples/companion_radio/ui-lvgl/HomeScreen.h b/examples/companion_radio/ui-lvgl/HomeScreen.h index 753d24c9..0c4b6a54 100644 --- a/examples/companion_radio/ui-lvgl/HomeScreen.h +++ b/examples/companion_radio/ui-lvgl/HomeScreen.h @@ -721,8 +721,7 @@ void UITask::homeMapLayout() { int shown = 0; char sl[16]; double lat = atan(sinh(M_PI * (1 - 2 * s_cy / (double)n))) * 180.0 / M_PI; - s_grid = { left, top, mapview::gridStep(s_z, lat, _prefs && _prefs->units_imperial, 48, sl, sizeof(sl)) }; - lv_obj_invalidate(s_area); + mapview::setGrid(s_area, s_grid, left, top, mapview::gridStep(s_z, lat, _prefs && _prefs->units_imperial, 48, sl, sizeof(sl))); bool tiled = true; for (int j = 0; j < ROWS; j++) { for (int i = 0; i < COLS; i++) { diff --git a/examples/companion_radio/ui-lvgl/MapAreas.h b/examples/companion_radio/ui-lvgl/MapAreas.h index 0a598c57..c60a1cec 100644 --- a/examples/companion_radio/ui-lvgl/MapAreas.h +++ b/examples/companion_radio/ui-lvgl/MapAreas.h @@ -28,6 +28,7 @@ static lv_obj_t* s_info = nullptr; static lv_obj_t* s_zoom = nullptr; static lv_obj_t* s_trails_btn = nullptr; static int s_show = -1; // area outlined while its sheet is open +static bool s_drawn = false; // the layer's last draw put something on the map static bool s_preview = false; // the map is framing it; the view before: static double s_prev_cx, s_prev_cy; static int s_prev_z; @@ -91,7 +92,8 @@ static void onRenameKb(lv_event_t* e) { s_ui->mapAreaRenameDone(lv_event_get_cod // LV_EVENT_DRAW_MAIN of the layer: outlines, the frame and the dim outside it. static void drawLayer(lv_event_t* e) { - if (!s_sel && s_show < 0) return; + s_drawn = s_sel || s_show >= 0; + if (!s_drawn) return; lv_obj_t* o = (lv_obj_t*)lv_event_get_target(e); lv_layer_t* layer = lv_event_get_layer(e); lv_area_t a; @@ -291,7 +293,7 @@ void UITask::areaHandleDrag(int corner, int dx, int dy) { void UITask::areaLayout() { using namespace areas; if (!s_layer || !_map_area) return; - lv_obj_invalidate(s_layer); + if (s_sel || s_show >= 0 || s_drawn) lv_obj_invalidate(s_layer); // not a full-map redraw each refresh for nothing if (!s_sel) return; double scale = (double)(1 << _map_z) * mapview::TILE_PX; double xs[2] = { s_fx0 * scale - mapview::s_left, s_fx1 * scale - mapview::s_left }; diff --git a/examples/companion_radio/ui-lvgl/MapScreen.h b/examples/companion_radio/ui-lvgl/MapScreen.h index b25bea45..1b125995 100644 --- a/examples/companion_radio/ui-lvgl/MapScreen.h +++ b/examples/companion_radio/ui-lvgl/MapScreen.h @@ -72,6 +72,14 @@ struct Grid { bool tiled = false; // every visible cell holds a tile: grid and fill are under them }; static Grid s_grid; // the main map's + +// The view moved (or the step changed): the grid with it, and the area +// redrawn -- not when a refresh finds it where it was. +static void setGrid(lv_obj_t* area, Grid& g, double left, double top, double step_px) { + if (g.left == left && g.top == top && g.step_px == step_px) return; + g.left = left; g.top = top; g.step_px = step_px; + lv_obj_invalidate(area); +} static lv_obj_t* s_scale_bar = nullptr; static lv_obj_t* s_scale_lbl = nullptr; @@ -280,10 +288,7 @@ static void onMapMarker(lv_event_t* e) { // The centre-on-me button in the accent colour while the map follows you. static void followShown(lv_obj_t* btn, bool on) { - if (!btn) return; - lv_obj_t* l = lv_obj_get_child(btn, 0); - lv_color_t c = lv_color_hex(on ? theme::ACCENT : theme::TEXT); - if (!lv_color_eq(lv_obj_get_style_text_color(l, LV_PART_MAIN), c)) lv_obj_set_style_text_color(l, c, 0); + if (btn) setTextColor(lv_obj_get_child(btn, 0), on ? theme::ACCENT : theme::TEXT); } static lv_obj_t* mapButton(lv_obj_t* parent, const char* text, lv_event_cb_t cb) { @@ -505,12 +510,11 @@ void UITask::layoutMap() { { // the grid under the tiles, the scale bar char sl[16]; double lat = atan(sinh(M_PI * (1 - 2 * _map_cy / (double)(1 << _map_z)))) * 180.0 / M_PI; - mapview::s_grid = { left, top, mapview::gridStep(_map_z, lat, _prefs && _prefs->units_imperial, 56, sl, sizeof(sl)) }; + mapview::setGrid(_map_area, mapview::s_grid, left, top, mapview::gridStep(_map_z, lat, _prefs && _prefs->units_imperial, 56, sl, sizeof(sl))); if (mapview::s_scale_lbl) { setText(mapview::s_scale_lbl, sl); lv_obj_set_width(mapview::s_scale_bar, (int)lround(mapview::s_grid.step_px)); } - lv_obj_invalidate(_map_area); } int tx0 = (int)floor(left / mapview::TILE_PX), ty0 = (int)floor(top / mapview::TILE_PX); int n = 1 << _map_z; diff --git a/examples/companion_radio/ui-lvgl/NavMap.h b/examples/companion_radio/ui-lvgl/NavMap.h index b4a9c69e..f4449855 100644 --- a/examples/companion_radio/ui-lvgl/NavMap.h +++ b/examples/companion_radio/ui-lvgl/NavMap.h @@ -280,7 +280,16 @@ static void layoutNavTrail(double scale) { using namespace navmap; s_pts_n = 0; if (!s_trail) return; - lv_obj_invalidate(s_trail); + { // redrawn when the view or the trail changed, not on every map refresh + static double v_left = NAN, v_top, v_scale; + static int v_n; + static uint32_t v_gen, v_seq; + if (v_left != mapview::s_left || v_top != mapview::s_top || v_scale != scale || v_n != s_sync_n || v_gen != s_sync_gen || v_seq != s_sync_seq) { + v_left = mapview::s_left; v_top = mapview::s_top; v_scale = scale; + v_n = s_sync_n; v_gen = s_sync_gen; v_seq = s_sync_seq; + lv_obj_invalidate(s_trail); + } + } int n = s_sync_n; if (!s_pts || n < 2) return; lv_area_t a; @@ -393,12 +402,12 @@ void UITask::refreshNavBar() { bool pos = navCurrentTarget(tlat, tlon, name, sizeof(name), person, set); if (!set) { setText(_nav_title, "No target"); - lv_obj_set_style_text_color(_nav_title, lv_color_hex(theme::TEXT_MUTED), 0); + setTextColor(_nav_title, theme::TEXT_MUTED); setText(_nav_info, "Tap here to pick a target, or hold the map on a spot"); lv_obj_add_flag(_nav_clear, LV_OBJ_FLAG_HIDDEN); return; } - lv_obj_set_style_text_color(_nav_title, lv_color_hex(theme::ACCENT), 0); + setTextColor(_nav_title, theme::ACCENT); lv_obj_remove_flag(_nav_clear, LV_OBJ_FLAG_HIDDEN); setTextFmt(_nav_title, "%s %s", navmap::s_tb.active() ? LV_SYMBOL_LOOP : person ? UI_SYMBOL_USERS : UI_SYMBOL_FLAG, name); @@ -1039,19 +1048,19 @@ void UITask::refreshNavTools() { navmap::fmtDuration(dur, sizeof(dur), ts.elapsedSeconds()); const char* state = !ts.isActive() ? (ts.empty() ? "Not recording" : "Stopped") : ts.isPaused() ? "Paused (standing still)" : "Recording"; - if (ts.empty()) lv_label_set_text(_nav_trail_lbl, state); - else lv_label_set_text_fmt(_nav_trail_lbl, "%s - %s, %s, %d points", state, dist, dur, ts.count()); - lv_label_set_text(_nav_trail_btn, ts.isActive() ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Record"); - lv_label_set_text(_nav_tb_btn, navmap::s_tb.active() ? LV_SYMBOL_STOP " Stop back" : LV_SYMBOL_LOOP " Track back"); + if (ts.empty()) setText(_nav_trail_lbl, state); + else setTextFmt(_nav_trail_lbl, "%s - %s, %s, %d points", state, dist, dur, ts.count()); + setText(_nav_trail_btn, ts.isActive() ? LV_SYMBOL_STOP " Stop" : LV_SYMBOL_PLAY " Record"); + setText(_nav_tb_btn, navmap::s_tb.active() ? LV_SYMBOL_STOP " Stop back" : LV_SYMBOL_LOOP " Track back"); if (_prefs->loc_share_enabled) { char left[12]; navmap::fmtDuration(left, sizeof(left), _core->live_share.remainingSecs()); - lv_label_set_text_fmt(_nav_share_lbl, "Sharing your position - %s left", left); + setTextFmt(_nav_share_lbl, "Sharing your position - %s left", left); } else { - lv_label_set_text(_nav_share_lbl, "Off. Sends your position while you move, then stops by itself."); + setText(_nav_share_lbl, "Off. Sends your position while you move, then stops by itself."); } - lv_label_set_text(_nav_share_btn, _prefs->loc_share_enabled ? LV_SYMBOL_STOP " Stop sharing" : LV_SYMBOL_PLAY " Share live"); + setText(_nav_share_btn, _prefs->loc_share_enabled ? LV_SYMBOL_STOP " Stop sharing" : LV_SYMBOL_PLAY " Share live"); } void UITask::navSetShareTarget(int sel) { diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 33350232..895c69ff 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -12,6 +12,7 @@ #endif #include #include // lv_cover_check_info_t (coversOwnArea) +#include // lv_label_t (labelShows) #include #include @@ -170,14 +171,32 @@ static lv_obj_t* label(lv_obj_t* parent, const char* text, const lv_font_t* font lv_obj_set_style_text_color(l, lv_color_hex(color), 0); return l; } +// Whether label `l` holds `text`. A LV_LABEL_LONG_DOT one that's cut short +// has the "..." written into its own copy of the text, the bytes they cover +// kept aside (lv_label_set_dots): it's read back past them. +static bool labelShows(lv_obj_t* l, const char* text) { + const lv_label_t* lb = (const lv_label_t*)l; + const char* cur = lb->text; + if (!cur) return false; + uint32_t d = lb->dot_begin; + if (d == 0xFFFFFFFF || lb->static_txt) return strcmp(cur, text) == 0; // no dots (lv_label.c LV_LABEL_DOT_BEGIN_INV) + if (strncmp(cur, text, d) != 0) return false; + size_t k = strnlen(lb->dot, sizeof(lb->dot)); // the bytes under the dots + if (strncmp(lb->dot, text + d, k) != 0) return false; + return k < sizeof(lb->dot) ? text[d + k] == '\0' : strcmp(cur + d + k, text + d + k) == 0; +} // A label's text, set only when it differs: LVGL redraws a label on every // set_text, the same text or not, and the refreshes that run every second // mostly write back what's already there. static void setText(lv_obj_t* l, const char* text) { - const char* cur = lv_label_get_text(l); - if (cur && strcmp(cur, text) == 0) return; + if (labelShows(l, text)) return; lv_label_set_text(l, text); } +// The same for a text colour: LVGL restyles, and redraws, on every set. +static void setTextColor(lv_obj_t* l, uint32_t color) { + lv_color_t c = lv_color_hex(color); + if (!lv_color_eq(lv_obj_get_style_text_color(l, LV_PART_MAIN), c)) lv_obj_set_style_text_color(l, c, 0); +} static void setTextFmt(lv_obj_t* l, const char* fmt, ...) __attribute__((format(printf, 2, 3))); static void setTextFmt(lv_obj_t* l, const char* fmt, ...) { char b[160]; @@ -842,6 +861,16 @@ static lv_obj_t* textField(lv_obj_t* parent, const char* placeholder = NULL) { lv_obj_set_style_border_width(ta, 2, LV_PART_CURSOR | LV_STATE_FOCUSED); return ta; } +// A field's cursor shown (FOCUSED, blinking) or not. LVGL starts and stops +// the blink only as the cursor moves, so it's nudged here: left running on a +// field without focus, it redraws the field every 400 ms for nothing. +// (lv_keyboard_set_textarea() focuses the field it's given.) +static void fieldFocus(lv_obj_t* ta, bool on) { + if (!ta) return; + if (on) lv_obj_add_state(ta, LV_STATE_FOCUSED); + else lv_obj_remove_state(ta, LV_STATE_FOCUSED); + lv_textarea_set_cursor_pos(ta, lv_textarea_get_cursor_pos(ta)); +} // The primary action of a screen or popup: full amber, dark text (Theme.h); // call after its label is made. @@ -1857,6 +1886,17 @@ void UITask::buildStatusBar() { refreshStatusBar(); } +// lv_obj_align_to(o, base, LV_ALIGN_OUT_LEFT_MID, dx, 0), `base` a sibling, +// for what's refreshed every second: LVGL restyles the box on every call, so +// it redraws moved or not -- here it's only moved. +static void placeLeftOf(lv_obj_t* o, lv_obj_t* base, int32_t dx) { + lv_obj_update_layout(o); + int32_t x = lv_obj_get_x(base) - lv_obj_get_width(o) + dx; + int32_t y = lv_obj_get_y(base) + (lv_obj_get_height(base) - lv_obj_get_height(o)) / 2; + if (lv_obj_get_style_align(o, LV_PART_MAIN) != LV_ALIGN_TOP_LEFT || lv_obj_get_x(o) != x || lv_obj_get_y(o) != y) + lv_obj_align_to(o, base, LV_ALIGN_OUT_LEFT_MID, dx, 0); +} + void UITask::refreshStatusBar() { if (!_status_time) return; struct tm ti; @@ -1884,7 +1924,7 @@ void UITask::refreshStatusBar() { lv_obj_t* left_of = _status_batt; // the icons pack up to this if (_board->isExternalPowered()) { lv_obj_remove_flag(_status_chg, LV_OBJ_FLAG_HIDDEN); - lv_obj_align_to(_status_chg, _status_batt, LV_ALIGN_OUT_LEFT_MID, -2, 0); + placeLeftOf(_status_chg, _status_batt, -2); left_of = _status_chg; } else lv_obj_add_flag(_status_chg, LV_OBJ_FLAG_HIDDEN); @@ -1921,7 +1961,7 @@ void UITask::refreshStatusBar() { for (int i = n - 1; i >= 0; i--) // the icon font gives each the same size and cell label(_status_icons, icons[i].sym, THEME_FONT_ICONS, icons[i].col); } - lv_obj_align_to(_status_icons, left_of, LV_ALIGN_OUT_LEFT_MID, -5, 0); + placeLeftOf(_status_icons, left_of, -5); } void UITask::setGps(bool on) { @@ -3131,10 +3171,10 @@ void UITask::setKeyboardVisible(bool show) { if (show == !lv_obj_has_flag(_keyboard, LV_OBJ_FLAG_HIDDEN)) return; if (show) { lv_obj_remove_flag(_keyboard, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_state(_compose_ta, LV_STATE_FOCUSED); + fieldFocus(_compose_ta, true); } else { lv_obj_add_flag(_keyboard, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(_compose_ta, LV_STATE_FOCUSED); + fieldFocus(_compose_ta, false); } if (_header && _body) { int top = theme::STATUS_H + (show ? 0 : lv_obj_get_height(_header)); @@ -3221,6 +3261,7 @@ void UITask::buildThread() { _keyboard = kb::create(body, _prefs); // phone-style, scripts from prefs, hold for accents (Keyboard.h) lv_obj_set_size(_keyboard, LV_PCT(100), 124); lv_keyboard_set_textarea(_keyboard, _compose_ta); + fieldFocus(_compose_ta, false); // until the keyboard is up lv_obj_add_event_cb(_keyboard, onKeyboard, LV_EVENT_READY, _keyboard); lv_obj_add_event_cb(_keyboard, onKeyboard, LV_EVENT_CANCEL, _keyboard); lv_obj_add_flag(_keyboard, LV_OBJ_FLAG_HIDDEN); diff --git a/examples/companion_radio/ui-lvgl/WifiScreen.h b/examples/companion_radio/ui-lvgl/WifiScreen.h index e3833a9e..aa72a8ce 100644 --- a/examples/companion_radio/ui-lvgl/WifiScreen.h +++ b/examples/companion_radio/ui-lvgl/WifiScreen.h @@ -152,10 +152,10 @@ static void wifiRebuild(UITask* ui, lv_obj_t*& body) { void UITask::wifiEdit(lv_obj_t* ta) { if (!_wifi_kb) return; - lv_obj_remove_state(_wifi_ssid, LV_STATE_FOCUSED); - lv_obj_remove_state(_wifi_pass, LV_STATE_FOCUSED); - lv_obj_add_state(ta, LV_STATE_FOCUSED); // draws the cursor + fieldFocus(_wifi_ssid, false); + fieldFocus(_wifi_pass, false); lv_keyboard_set_textarea(_wifi_kb, ta); + fieldFocus(ta, true); if (lv_obj_has_flag(_wifi_kb, LV_OBJ_FLAG_HIDDEN)) { lv_obj_remove_flag(_wifi_kb, LV_OBJ_FLAG_HIDDEN); if (_body) lv_obj_set_height(_body, lv_obj_get_height(_body) - lv_obj_get_height(_wifi_kb)); @@ -167,8 +167,8 @@ void UITask::wifiEdit(lv_obj_t* ta) { void UITask::wifiKeyboardHide() { if (!_wifi_kb || lv_obj_has_flag(_wifi_kb, LV_OBJ_FLAG_HIDDEN)) return; lv_obj_add_flag(_wifi_kb, LV_OBJ_FLAG_HIDDEN); - lv_obj_remove_state(_wifi_ssid, LV_STATE_FOCUSED); - lv_obj_remove_state(_wifi_pass, LV_STATE_FOCUSED); + fieldFocus(_wifi_ssid, false); + fieldFocus(_wifi_pass, false); if (_body) lv_obj_set_height(_body, lv_obj_get_height(_body) + lv_obj_get_height(_wifi_kb)); } diff --git a/examples/companion_radio/ui-lvgl/map/VectorLabels.h b/examples/companion_radio/ui-lvgl/map/VectorLabels.h index ce80eecb..c538aa84 100644 --- a/examples/companion_radio/ui-lvgl/map/VectorLabels.h +++ b/examples/companion_radio/ui-lvgl/map/VectorLabels.h @@ -118,8 +118,12 @@ static void clear() { // Places the labels for a view (world px of its top-left at zoom z, size; // `bottom` px taken by a bar along the bottom). static void layout(double left, double top, int w, int h, int z, bool on, int bottom) { + int had = s_n; clear(); - if (!on || z < 10 || !s_layer || !s_placed || !s_cand) { if (s_layer) lv_obj_invalidate(s_layer); return; } + if (!on || z < 10 || !s_layer || !s_placed || !s_cand) { // none: those shown go (none shown, nothing to redraw) + if (s_layer && had) lv_obj_invalidate(s_layer); + return; + } int dz = z >= 14 ? 14 : z >= 12 ? 12 : 10, k = z - dz; double span = (double)(TILE_PX << k); // a data tile, in px at zoom z int x0 = (int)floor(left / span), x1 = (int)floor((left + w) / span);