perf(l2): screens lay out in up to half the time: row widths known before the first layout

A group row's text column (and an info row's key) grows into what the
controls on its right leave, and LVGL lays out children before their
parent, so its labels were first measured 1 px wide; LVGL also works out a
new label's height at its old width (0) before the real one. Each height
that changed then took another pass up the screen: 4-6 passes per screen.

rowsFit(), run before a frame's layout (LV_EVENT_REFR_START) and by
layoutNow() in place of every explicit lv_obj_update_layout(), gives each
new row's growing child the width the flex will give it, worked out from
the other children's styles and text, and labelFit() measures wrapping
labels at their real width up front. The grow stays, so a control that
can't be sized ahead only falls back to the old passes.

Layout on the device: Display page 34 -> 24 ms, Bot 33 -> 18, Quick
37 -> 22, Radio 26 -> 18, Diagnostics 20 -> 12. Sim screenshots of 26
screens unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-01 10:30:27 +02:00
co-authored by Claude Opus 5.5
parent 634294ee3a
commit f77c7ea901
12 changed files with 154 additions and 33 deletions
@@ -270,7 +270,7 @@ void UITask::channelEditField(lv_obj_t* ta) {
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));
}
lv_obj_update_layout(screen());
layoutNow(screen());
lv_obj_scroll_to_view(ta, LV_ANIM_OFF);
}
@@ -392,7 +392,7 @@ void UITask::pinRowRefresh() {
if (_screen != SCR_SETTINGS_NAV) return;
buildSchemaSettings();
fillFlush();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
}
// ── Accent colour ─────────────────────────────────────────────────────────────
@@ -616,7 +616,7 @@ void UITask::buildHomeMap(lv_obj_t* box) {
lv_obj_align(s_caption, LV_ALIGN_BOTTOM_LEFT, 6, -6);
mapview::s_available = lvport::mountStorage() && mapview::s_provider->available();
mapview::s_trails_on = lvport::trailsOn();
lv_obj_update_layout(s_area);
layoutNow(s_area);
homeMapFit();
homeMapLayout();
}
+1 -1
View File
@@ -418,7 +418,7 @@ void UITask::mapAreaPopup(int idx) {
s_del_lbl = actButton(r, LV_SYMBOL_TRASH, "Delete", A_DELETE);
// Framed in the map above the sheet: the view is kept to go back to.
lv_obj_update_layout(panel);
layoutNow(panel);
int w = _map_area ? lv_obj_get_width(_map_area) : 320, h = _map_area ? lv_obj_get_height(_map_area) : 218;
int sheet = lv_obj_get_height(panel) + 8; // its inset
int above = h - sheet;
+2 -2
View File
@@ -493,7 +493,7 @@ void UITask::buildMap() {
lv_obj_add_flag(_map_hint, LV_OBJ_FLAG_HIDDEN);
if (_map_nav) buildNavControls(body);
lv_obj_update_layout(body);
layoutNow(body);
mapview::s_mark_count = 0; // the previous markers went with the previous screen
rebuildMapMarkers();
layoutMap();
@@ -691,7 +691,7 @@ void UITask::rebuildMapMarkers() {
else rebuildNodeMarkers();
for (int k = mapview::s_mark_count; k < mapview::s_mark_prev; k++) lv_obj_delete(mapview::s_marks[k].obj);
mapview::s_mark_prev = 0;
lv_obj_update_layout(_map_marks); // so layoutMap() knows where each dot sits in its marker
layoutNow(_map_marks); // so layoutMap() knows where each dot sits in its marker
}
void UITask::rebuildNodeMarkers() {
@@ -185,7 +185,7 @@ void UITask::quickEditDone(bool ok) {
lv_obj_t* body = _body;
int32_t y = body ? lv_obj_get_scroll_y(body) : 0;
buildQuickMsgs();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
}
@@ -139,7 +139,7 @@ void UITask::rebuildRadio() {
int32_t y = _body ? lv_obj_get_scroll_y(_body) : 0;
buildRadio();
fillFlush();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
void UITask::radioFreqPopup(bool repeater) {
@@ -114,7 +114,7 @@ void UITask::rebuildRepeater() {
int32_t y = _body ? lv_obj_get_scroll_y(_body) : 0;
buildRepeater();
fillFlush();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
void UITask::repeaterSet(int which, int v) {
@@ -107,7 +107,7 @@ static void rebuildStrip() {
cellText(i);
styleCell(i, i == s_sel);
}
lv_obj_update_layout(s_strip);
layoutNow(s_strip);
if (s_sel < s_mel.len) lv_obj_scroll_to_view(s_cells[s_sel], LV_ANIM_OFF);
}
@@ -196,7 +196,7 @@ static lv_obj_t* catRow(lv_obj_t* g, int c) {
// Segment widths: each category's share of the card, the rest of `used` grey.
static void showSegments(uint64_t total, uint64_t used) {
if (!s_sd_bar || !total) return;
lv_obj_update_layout(s_sd_bar);
layoutNow(s_sd_bar);
int w = lv_obj_get_width(s_sd_bar), x = 0;
uint64_t counted = 0;
for (int c = 0; c <= C_COUNT; c++) {
+140 -19
View File
@@ -138,12 +138,18 @@ static void styleOpaque(lv_obj_t* o, uint32_t bg) {
lv_obj_set_style_bg_opa(o, LV_OPA_COVER, 0);
}
static bool growsWide(lv_obj_t* o) { // a flex grow along a row: the width is the layout's
lv_obj_t* p = lv_obj_get_parent(o);
if (!p || !lv_obj_get_style_flex_grow(o, LV_PART_MAIN) || lv_obj_get_style_layout(p, LV_PART_MAIN) != LV_LAYOUT_FLEX) return false;
lv_flex_flow_t f = lv_obj_get_style_flex_flow(p, LV_PART_MAIN);
return !(f & LV_FLEX_COLUMN);
}
// The width of `o`'s content, worked out from the styles -- or -1 where it
// hangs on its content or a flex grow. A new box's own size is only known
// hangs on its content or a row's flex grow. A new box's own size is only known
// after a layout pass, and LVGL lays out children before their parent, so
// each box sized in % of the one above adds a whole pass over the screen.
static int32_t innerW(lv_obj_t* o) {
if (!o || lv_obj_get_style_flex_grow(o, LV_PART_MAIN)) return -1;
if (!o || growsWide(o)) return -1;
int32_t w = lv_obj_get_style_width(o, LV_PART_MAIN);
if (LV_COORD_IS_PCT(w)) {
int32_t pw = innerW(lv_obj_get_parent(o));
@@ -159,10 +165,27 @@ static int32_t innerW(lv_obj_t* o) {
if (side & LV_BORDER_SIDE_RIGHT) w -= bw;
return w;
}
extern "C" bool lv_obj_refr_size(lv_obj_t* obj); // lv_obj_pos.c, in no header
// A wrapping label's size settled now, `w` wide. LVGL's first layout of a
// label works out its height at the width it had before -- 0 for a new one,
// a line per letter -- and only then measures it again at the new width.
static void labelFit(lv_obj_t* l, int32_t w) {
l->coords.x2 = l->coords.x1 + w - 1;
lv_obj_refr_size(l);
}
// A flex row whose one growing child gets its width before the first layout
// (rowsFit(), further down).
static bool s_fit_pending;
static lv_obj_t* s_fit_screen; // newScreen()'s, maybe still fading in: not lv_screen_active() yet
static void fitLater(lv_obj_t* row) {
lv_obj_add_flag(row, LV_OBJ_FLAG_USER_2);
s_fit_pending = true;
}
// The parent's full width, in pixels when that's known now (see innerW()).
static void fillWidth(lv_obj_t* o) {
int32_t w = innerW(lv_obj_get_parent(o));
lv_obj_set_width(o, w >= 0 ? w : LV_PCT(100));
if (w > 0 && lv_obj_check_type(o, &lv_label_class)) labelFit(o, w);
}
static lv_obj_t* label(lv_obj_t* parent, const char* text, const lv_font_t* font, uint32_t color) {
@@ -361,6 +384,7 @@ namespace freeze {
enum PopFit : uint8_t { POP_FIT, POP_FULL, POP_TOP, POP_BOTTOM };
static lv_obj_t* popupOpen(lv_obj_t* parent, PopFit fit, lv_obj_t*& overlay, int32_t inset = 8) {
overlay = dimOverlay(parent);
s_fit_pending = true; // rowsFit()
lv_obj_t* panel = lv_obj_create(overlay);
int32_t w = lv_display_get_horizontal_resolution(NULL), h = lv_display_get_vertical_resolution(NULL);
lv_obj_set_width(panel, w - 2 * inset);
@@ -468,6 +492,7 @@ static lv_obj_t* infoRow(lv_obj_t* card, const char* key, const char* value, uin
lv_obj_set_width(v, LV_SIZE_CONTENT);
lv_obj_set_style_max_width(v, LV_PCT(66), 0);
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_RIGHT, 0);
fitLater(r);
return v;
}
@@ -569,9 +594,102 @@ static lv_obj_t* groupText(lv_obj_t* row, const char* text, const char* hint, ui
noteLabel(t, hint);
}
lv_obj_set_user_data(row, l); // rowTitle()
fitLater(row);
return l;
}
// A group row's text column grows into what the controls on its right leave,
// and LVGL lays out children before their parent: its labels were first
// measured 1 px wide (a line per letter), then at the real width, and each
// height that changed took another pass up the screen -- four in all. Before
// a frame's layout, rowsFit() gives each new column the width the flex will:
// its row's minus the controls', worked out from their styles and text. The
// grow stays, so a control this can't size only costs the old passes.
static constexpr lv_obj_flag_t NOT_LAID_OUT =
(lv_obj_flag_t)(LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING | LV_OBJ_FLAG_IGNORE_LAYOUT);
// `o`'s outer width as the layout will make it; -1 when that's unclear.
static int32_t layoutW(lv_obj_t* o) {
int32_t w = lv_obj_get_style_width(o, LV_PART_MAIN);
if (LV_COORD_IS_PX(w)) {
// as it is
} else if (w != LV_SIZE_CONTENT) {
return -1;
} else if (lv_obj_check_type(o, &lv_label_class)) {
lv_obj_refr_size(o); // the label's own measure, cached for the layout
w = lv_obj_get_width(o);
} else if (lv_obj_get_style_layout(o, LV_PART_MAIN) == LV_LAYOUT_FLEX &&
lv_obj_get_style_flex_flow(o, LV_PART_MAIN) == LV_FLEX_FLOW_ROW) {
int32_t gap = lv_obj_get_style_pad_column(o, LV_PART_MAIN), n = 0;
w = lv_obj_get_style_pad_left(o, LV_PART_MAIN) + lv_obj_get_style_pad_right(o, LV_PART_MAIN);
for (uint32_t i = 0; i < lv_obj_get_child_count(o); i++) {
lv_obj_t* c = lv_obj_get_child(o, i);
if (lv_obj_has_flag_any(c, NOT_LAID_OUT)) continue;
int32_t cw = layoutW(c);
if (cw < 0) return -1;
w += cw + (n++ ? gap : 0);
}
w = LV_CLAMP(lv_obj_get_style_min_width(o, LV_PART_MAIN), w, lv_obj_get_style_max_width(o, LV_PART_MAIN));
} else {
return -1;
}
return w + lv_obj_get_style_margin_left(o, LV_PART_MAIN) + lv_obj_get_style_margin_right(o, LV_PART_MAIN);
}
static void rowFit(lv_obj_t* row) {
lv_obj_refr_size(row); // its own width now: a child's % size is of it
int32_t w = innerW(row), gap = lv_obj_get_style_pad_column(row, LV_PART_MAIN);
if (w < 0) return;
lv_obj_t* grow = nullptr;
for (uint32_t i = 0; i < lv_obj_get_child_count(row); i++) {
lv_obj_t* c = lv_obj_get_child(row, i);
if (lv_obj_has_flag_any(c, NOT_LAID_OUT)) continue;
if (lv_obj_get_style_flex_grow(c, LV_PART_MAIN)) {
if (grow) return; // one grows, or the share isn't worth working out
grow = c;
continue;
}
int32_t cw = layoutW(c);
if (cw < 0) return;
w -= cw + gap;
}
if (!grow || w < 1) return;
lv_obj_set_width(grow, w);
if (lv_obj_check_type(grow, &lv_label_class)) { labelFit(grow, w); return; }
lv_obj_refr_size(grow);
for (uint32_t i = 0; i < lv_obj_get_child_count(grow); i++) { // its labels, 100 % of it
lv_obj_t* l = lv_obj_get_child(grow, i);
if (lv_obj_check_type(l, &lv_label_class)) labelFit(l, w);
}
}
// ... and every label given a width in pixels, which labelFit() then measures
// at it (the screen titles, a list row's cut-off name ...).
static void rowsFit(lv_obj_t* root) {
lv_obj_tree_walk(root, [](lv_obj_t* o, void*) {
if (lv_obj_has_flag(o, LV_OBJ_FLAG_USER_2)) {
lv_obj_remove_flag(o, LV_OBJ_FLAG_USER_2);
rowFit(o);
} else if (lv_obj_check_type(o, &lv_label_class)) {
int32_t w = lv_obj_get_style_width(o, LV_PART_MAIN);
if (LV_COORD_IS_PX(w) && w > 0 && lv_obj_get_width(o) != w) labelFit(o, w);
}
return LV_OBJ_TREE_WALK_NEXT;
}, nullptr);
}
static void rowsFitPending() {
if (!s_fit_pending) return;
s_fit_pending = false;
if (s_fit_screen && s_fit_screen != lv_screen_active()) rowsFit(s_fit_screen);
rowsFit(lv_screen_active());
rowsFit(lv_layer_top());
}
// lv_obj_update_layout() for a screen just built: its rows fitted first.
static void layoutNow(lv_obj_t* o) {
rowsFitPending();
lv_obj_update_layout(o);
}
// The title label of a settingRow() / listRow(), to recolour or rename it.
static lv_obj_t* rowTitle(lv_obj_t* row) { return (lv_obj_t*)lv_obj_get_user_data(row); }
@@ -719,7 +837,7 @@ static void pickerOpen(lv_obj_t* c) {
if (cur) { label(r, LV_SYMBOL_OK, THEME_FONT_BODY, theme::ACCENT); sel_row = r; }
lv_obj_add_event_cb(r, onPickOption, LV_EVENT_CLICKED, (void*)(uintptr_t)i);
}
if (sel_row) { lv_obj_update_layout(panel); lv_obj_scroll_to_view_recursive(sel_row, LV_ANIM_OFF); }
if (sel_row) { layoutNow(panel); lv_obj_scroll_to_view_recursive(sel_row, LV_ANIM_OFF); }
}
// A choice: in a group row the value is bare text (accent) and a chevron;
@@ -1128,6 +1246,7 @@ void UITask::begin(DisplayDriver* display_drv, SensorManager* sensors, NodePrefs
}
theme::setAccent(lvport::loadAccent());
theme::install(lv_display_get_default());
lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t*) { rowsFitPending(); }, LV_EVENT_REFR_START, NULL);
buildStatusBar();
applyDisplayPrefs(); // a slider percentage overrides the level main.cpp set
@@ -1417,7 +1536,7 @@ void UITask::loop() {
uint32_t b = micros() - t;
TM_PRINT("build"); TM_RESET();
uint32_t tl = micros();
lv_obj_update_layout(lv_screen_active()); // layout apart from drawing
layoutNow(lv_screen_active()); // layout apart from drawing
tl = micros() - tl;
TM_PRINT("layout"); TM_RESET();
uint32_t fl0 = lvport::s_flush_us, td = micros();
@@ -1777,7 +1896,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_update_layout(s_banner);
layoutNow(s_banner);
anim::run(s_banner, anim::setTy, -(lv_obj_get_height(s_banner) + theme::STATUS_H), 0, anim::SCREEN_MS);
} else {
lv_obj_set_style_translate_y(s_banner, 0, 0);
@@ -1891,7 +2010,7 @@ void UITask::buildStatusBar() {
// 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);
layoutNow(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)
@@ -2104,7 +2223,7 @@ void UITask::showToast(const char* text, uint32_t ms) {
int32_t y = theme::STATUS_H + 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);
lv_obj_update_layout(_toast);
layoutNow(_toast);
if (!shown) anim::run(_toast, anim::setTy, -(y + lv_obj_get_height(_toast)), 0, anim::POP_MS); // a replaced text just changes
else lv_obj_set_style_translate_y(_toast, 0, 0);
if (!_toast_timer) _toast_timer = lv_timer_create(toastTimerCb, ms, _toast);
@@ -2204,6 +2323,8 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
for (lv_obj_t*& t : _map_tiles) t = nullptr;
lv_obj_t* prev = _scr;
lv_obj_t* scr = lv_obj_create(NULL);
s_fit_screen = scr; // rowsFit()
s_fit_pending = true;
styleOpaque(scr, theme::BG);
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
@@ -2299,13 +2420,13 @@ void UITask::back() {
case SCR_QUICK: // back to Messages & contacts' bottom, where the row is
if (_nav_overlay) { navClosePopup(); break; }
showSchemaSettings(settings::PG_MESSAGES);
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
break;
case SCR_MELODY: // back to the Sound page's bottom, where the melodies are
melodySave();
stopMelody();
showSchemaSettings(settings::PG_SOUND);
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
break;
case SCR_CLOCK: showHome(); break;
case SCR_RADIO:
@@ -2321,7 +2442,7 @@ void UITask::back() {
case SCR_SCOPES: // back to the Radio screen's bottom, where Scopes is
if (_nav_overlay) { navClosePopup(); break; }
showRadio();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_by(_body, 0, -lv_obj_get_scroll_bottom(_body), LV_ANIM_OFF); }
break;
case SCR_CHATS: if (_nav_overlay) navClosePopup(); else showHome(); break;
case SCR_NEARBY:
@@ -2518,7 +2639,7 @@ void UITask::buildChats() {
stylePrimary(nb);
if (unreadTotal() > 0) {
lv_obj_t* rb = headerButton(_header, LV_SYMBOL_OK " Read all", onMarkAllRead, 0, NULL);
lv_obj_update_layout(_header);
layoutNow(_header);
lv_obj_align_to(rb, nb, LV_ALIGN_OUT_LEFT_MID, -6, 0);
}
}
@@ -2627,7 +2748,7 @@ void UITask::chatFold(int which) {
lvport::saveChatFold(s_chat_fold);
int y = _body ? lv_obj_get_scroll_y(_body) : 0;
buildChats();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
// ── Contact picker (start a DM) ───────────────────────────────────────────────
@@ -2931,7 +3052,7 @@ void UITask::refreshNearbyList() {
lv_obj_set_style_pad_top(l, 12, 0);
}
if (scroll > 0) {
lv_obj_update_layout(_nearby_list);
layoutNow(_nearby_list);
lv_obj_scroll_to_y(_nearby_list, scroll, LV_ANIM_OFF);
}
}
@@ -3186,7 +3307,7 @@ void UITask::setKeyboardVisible(bool show) {
lv_obj_set_height(_body, lv_display_get_vertical_resolution(NULL) - top);
}
if (_thread_list) { // keep the newest message in view
lv_obj_update_layout(_thread_list);
layoutNow(_thread_list);
lv_obj_scroll_to_y(_thread_list, LV_COORD_MAX, LV_ANIM_OFF);
}
}
@@ -3273,7 +3394,7 @@ void UITask::buildThread() {
if (_compose_ta && _share_text[0]) { // shareToMessage(): the text waits in the field
lv_textarea_set_text(_compose_ta, _share_text);
_share_text[0] = '\0';
lv_obj_update_layout(screen());
layoutNow(screen());
setKeyboardVisible(true);
}
}
@@ -3694,7 +3815,7 @@ void UITask::refreshThread() {
if (s_older_lbl) { char t[40]; olderText(t, sizeof(t)); setText(s_older_lbl, t); }
s_th_rows_n = s_msg_meta_n = s_msg_loc_n = n;
if (n > kept) { // new messages: down to them
lv_obj_update_layout(_thread_list);
layoutNow(_thread_list);
lv_obj_scroll_to_y(_thread_list, LV_COORD_MAX, LV_ANIM_OFF);
}
return;
@@ -3714,7 +3835,7 @@ void UITask::refreshThread() {
s_th_rows_newest = _thread_skip == 0; // a page back is built whole each time
if (_thread_skip > 0) pageButton(_thread_list, LV_SYMBOL_DOWN " Newer messages", -1);
if (n == 0) label(_thread_list, "No messages yet", THEME_FONT_BODY, theme::TEXT_MUTED);
lv_obj_update_layout(_thread_list);
layoutNow(_thread_list);
lv_obj_scroll_to_y(_thread_list, _thread_scroll_top ? 0 : LV_COORD_MAX, LV_ANIM_OFF);
_thread_scroll_top = false;
}
@@ -4025,7 +4146,7 @@ void UITask::setSchemaValue(int idx, int v) {
int32_t y = body ? lv_obj_get_scroll_y(body) : 0;
buildSchemaSettings();
fillFlush();
if (_body) { lv_obj_update_layout(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
if (_body) { layoutNow(_body); lv_obj_scroll_to_y(_body, y, LV_ANIM_OFF); }
}
}
@@ -4045,7 +4166,7 @@ void UITask::buildSettings() {
// A group at a time (fillStart): the first shows at once. Coming back to
// where it was scrolled needs them all first.
fillStart(SETTINGS_GROUPS, 1, &UITask::settingsGroup);
if (restore) { fillFlush(); lv_obj_update_layout(body); lv_obj_scroll_to_y(body, s_settings_y, LV_ANIM_OFF); }
if (restore) { fillFlush(); layoutNow(body); lv_obj_scroll_to_y(body, s_settings_y, LV_ANIM_OFF); }
}
void UITask::settingsGroup(int i) {
@@ -147,7 +147,7 @@ void UITask::buildWifi() {
static void wifiRebuild(UITask* ui, lv_obj_t*& body) {
int32_t y = body ? lv_obj_get_scroll_y(body) : 0;
ui->showWifi();
if (body) { lv_obj_update_layout(body); lv_obj_scroll_to_y(body, y, LV_ANIM_OFF); }
if (body) { layoutNow(body); lv_obj_scroll_to_y(body, y, LV_ANIM_OFF); }
}
void UITask::wifiEdit(lv_obj_t* ta) {
@@ -160,7 +160,7 @@ void UITask::wifiEdit(lv_obj_t* ta) {
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));
}
lv_obj_update_layout(screen());
layoutNow(screen());
lv_obj_scroll_to_view(ta, LV_ANIM_OFF);
}
@@ -241,7 +241,7 @@ void UITask::wifiTestShow(const char* msg, const char* ssid, const char* pass) {
lv_textarea_set_text(_wifi_ssid, ssid);
lv_textarea_set_text(_wifi_pass, pass);
}
lv_obj_update_layout(_body);
layoutNow(_body);
lv_obj_scroll_to_view(_wifi_status, LV_ANIM_ON);
}