mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
perf(l2): cheaper frames -- less overdraw and blending
Measured on the L2 (steady full frames): Settings page 37.7 -> 31.4 ms, Display 41.5 -> 37.9, Contacts 34.6 -> 32.9, Home 37.3 -> 32.4. - styleSurface() leaves boxes in the page colour unpainted; styleOpaque() for the screen and the status bar, which things slide under. - group() cards clip their corners only while a row is held (rowPressable(), shared by the tappable rows). - rowSlider(): the track an opaque tint (theme::mix(), also behind ACCENT_DIM) instead of 20 % accent blended every frame. - Map buttons, pills, scale, nav bar and the Home minimap labels opaque. - Screens drift in without the fading cover: its full-screen blend was in every entrance frame (page 39 -> 29 ms). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,19 +47,13 @@ namespace anim {
|
||||
if (panel) rise(panel);
|
||||
}
|
||||
|
||||
// A new screen emerges: a cover in the background colour fades away over
|
||||
// it while its content drifts a few pixels into place (up going deeper,
|
||||
// down coming back). A plain fill blended on top -- far cheaper than
|
||||
// fading the screen itself (a full-screen layer) or sliding two screens.
|
||||
// A fading cover's end: it goes.
|
||||
static void coverDone(lv_anim_t* a) { lv_obj_delete((lv_obj_t*)a->var); }
|
||||
static void screenIn(lv_obj_t* scr, lv_obj_t* body, bool back) {
|
||||
lv_obj_t* cover = lv_obj_create(scr);
|
||||
lv_obj_remove_style_all(cover);
|
||||
lv_obj_remove_flag(cover, LV_OBJ_FLAG_CLICKABLE); // taps go through to the screen
|
||||
lv_obj_add_flag(cover, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_size(cover, LV_PCT(100), LV_PCT(100));
|
||||
lv_obj_set_style_bg_color(cover, lv_obj_get_style_bg_color(scr, LV_PART_MAIN), 0);
|
||||
run(cover, setBgOpa, LV_OPA_COVER, LV_OPA_TRANSP, SCREEN_MS, coverDone);
|
||||
|
||||
// A new screen emerges: its content drifts a few pixels into place (up
|
||||
// going deeper, down coming back). No fade: a cover fading over the screen
|
||||
// blended a full-screen fill into every frame of it (~7 ms each on the L2).
|
||||
static void screenIn(lv_obj_t* body, bool back) {
|
||||
if (body) run(body, setTy, back ? -6 : 6, 0, SCREEN_MS);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +124,7 @@ void UITask::buildBot() {
|
||||
lv_obj_set_width(v, botcfg::isHour(r.kind) ? 60 : 150); // fixed: LONG_DOT needs a width
|
||||
lv_obj_set_style_text_align(v, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
label(row, LV_SYMBOL_RIGHT, THEME_FONT_SMALL, theme::TEXT_MUTED);
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_bg_color(row, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_opa(row, LV_OPA_COVER, LV_STATE_PRESSED);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, onBotRow, LV_EVENT_CLICKED, (void*)(uintptr_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,7 +600,7 @@ void UITask::buildHomeMap(lv_obj_t* box) {
|
||||
s_fitted = false;
|
||||
s_hint = label(s_area, "", THEME_FONT_SMALL, theme::TEXT);
|
||||
lv_obj_set_style_bg_color(s_hint, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(s_hint, LV_OPA_80, 0);
|
||||
lv_obj_set_style_bg_opa(s_hint, LV_OPA_COVER, 0); // opaque over the tiles, as the map's pills
|
||||
lv_obj_set_style_radius(s_hint, theme::RADIUS_SM, 0);
|
||||
lv_obj_set_style_pad_hor(s_hint, 8, 0);
|
||||
lv_obj_set_style_pad_ver(s_hint, 4, 0);
|
||||
@@ -609,7 +609,7 @@ void UITask::buildHomeMap(lv_obj_t* box) {
|
||||
lv_obj_add_flag(s_hint, LV_OBJ_FLAG_HIDDEN);
|
||||
s_caption = label(s_area, "", THEME_FONT_SMALL, theme::TEXT); // who's on it
|
||||
lv_obj_set_style_bg_color(s_caption, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(s_caption, LV_OPA_80, 0);
|
||||
lv_obj_set_style_bg_opa(s_caption, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(s_caption, theme::RADIUS_SM, 0);
|
||||
lv_obj_set_style_pad_hor(s_caption, 6, 0);
|
||||
lv_obj_set_style_pad_ver(s_caption, 2, 0);
|
||||
|
||||
@@ -259,7 +259,7 @@ static lv_obj_t* mapButton(lv_obj_t* parent, const char* text, lv_event_cb_t cb)
|
||||
lv_obj_set_style_radius(b, theme::RADIUS, 0);
|
||||
lv_obj_set_style_shadow_width(b, 0, 0);
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(b, LV_OPA_80, 0);
|
||||
lv_obj_set_style_bg_opa(b, LV_OPA_COVER, 0); // opaque: blended over the tiles, each cost ~1.6 ms a frame
|
||||
lv_obj_set_style_bg_color(b, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_border_color(b, lv_color_hex(theme::SURFACE_2), 0);
|
||||
lv_obj_set_style_border_width(b, 1, 0);
|
||||
@@ -271,7 +271,7 @@ static lv_obj_t* mapButton(lv_obj_t* parent, const char* text, lv_event_cb_t cb)
|
||||
static lv_obj_t* mapPill(lv_obj_t* parent, const char* text) {
|
||||
lv_obj_t* l = label(parent, text, THEME_FONT_SMALL, theme::TEXT);
|
||||
lv_obj_set_style_bg_color(l, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(l, LV_OPA_70, 0);
|
||||
lv_obj_set_style_bg_opa(l, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_pad_hor(l, 5, 0);
|
||||
lv_obj_set_style_pad_ver(l, 1, 0);
|
||||
lv_obj_set_style_radius(l, 4, 0);
|
||||
@@ -344,6 +344,7 @@ void UITask::buildMap() {
|
||||
lv_obj_set_style_pad_all(body, 0, 0);
|
||||
lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_style_bg_color(body, lv_color_hex(0x1A1A1E), 0); // unloaded / missing tiles
|
||||
lv_obj_set_style_bg_opa(body, LV_OPA_COVER, 0);
|
||||
lv_obj_add_event_cb(body, mapview::drawGrid, LV_EVENT_DRAW_MAIN_END, &mapview::s_grid);
|
||||
lv_obj_add_flag(body, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_event_cb(body, onMapPress, LV_EVENT_PRESSED, NULL);
|
||||
@@ -426,7 +427,7 @@ void UITask::buildMap() {
|
||||
lv_obj_remove_flag(sc, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_size(sc, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_bg_color(sc, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(sc, LV_OPA_70, 0);
|
||||
lv_obj_set_style_bg_opa(sc, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_radius(sc, 4, 0);
|
||||
lv_obj_set_style_pad_hor(sc, 5, 0);
|
||||
lv_obj_set_style_pad_ver(sc, 3, 0);
|
||||
|
||||
@@ -167,7 +167,7 @@ void UITask::buildNavControls(lv_obj_t* body) {
|
||||
lv_obj_set_style_radius(_nav_bar, 0, 0);
|
||||
lv_obj_set_style_shadow_width(_nav_bar, 0, 0);
|
||||
lv_obj_set_style_bg_color(_nav_bar, lv_color_hex(theme::BG), 0);
|
||||
lv_obj_set_style_bg_opa(_nav_bar, LV_OPA_90, 0);
|
||||
lv_obj_set_style_bg_opa(_nav_bar, LV_OPA_COVER, 0); // opaque, as the map buttons
|
||||
lv_obj_set_style_bg_color(_nav_bar, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_border_side(_nav_bar, LV_BORDER_SIDE_TOP, 0);
|
||||
lv_obj_set_style_border_color(_nav_bar, lv_color_hex(theme::SURFACE_2), 0);
|
||||
|
||||
@@ -66,16 +66,21 @@ namespace theme {
|
||||
};
|
||||
static const int ACCENT_COUNT = sizeof(ACCENTS) / sizeof(ACCENTS[0]);
|
||||
static int s_accent = 0;
|
||||
// `fg` at `pct` % over `bg`, worked out once: an opaque fill in the colour
|
||||
// a translucent one would blend to, without blending it every frame.
|
||||
static uint32_t mix(uint32_t fg, uint32_t bg, uint32_t pct) {
|
||||
uint32_t out = 0;
|
||||
for (int sh = 0; sh <= 16; sh += 8) {
|
||||
uint32_t c = (fg >> sh) & 0xFF, b = (bg >> sh) & 0xFF;
|
||||
out |= ((c * pct + b * (100 - pct)) / 100) << sh;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
static void setAccent(int idx) {
|
||||
if (idx < 0 || idx >= ACCENT_COUNT) idx = 0;
|
||||
s_accent = idx;
|
||||
ACCENT = ACCENTS[idx].col;
|
||||
uint32_t dim = 0;
|
||||
for (int sh = 0; sh <= 16; sh += 8) {
|
||||
uint32_t c = (ACCENT >> sh) & 0xFF, b = (BG >> sh) & 0xFF;
|
||||
dim |= ((c * 42 + b * 58) / 100) << sh;
|
||||
}
|
||||
ACCENT_DIM = dim;
|
||||
ACCENT_DIM = mix(ACCENT, BG, 42);
|
||||
}
|
||||
|
||||
// Over LVGL's default theme, for every widget: a checked button is
|
||||
|
||||
@@ -119,13 +119,21 @@ extern "C" EMSCRIPTEN_KEEPALIVE int sim_buzzer_get_volume() { return s_ui ? (int
|
||||
|
||||
// ── Small helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
// A plain box in `bg`. One in the page colour is left unpainted: the screen
|
||||
// under it (or the popup panel) already is that colour, and filling it again
|
||||
// costs a full-area blend per frame. The screen and the status bar, which
|
||||
// something may scroll or slide under, are made opaque with styleOpaque().
|
||||
static void styleSurface(lv_obj_t* o, uint32_t bg) {
|
||||
lv_obj_set_style_bg_color(o, lv_color_hex(bg), 0);
|
||||
lv_obj_set_style_bg_opa(o, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_bg_opa(o, bg == theme::BG ? LV_OPA_TRANSP : LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(o, 0, 0);
|
||||
lv_obj_set_style_radius(o, 0, 0);
|
||||
lv_obj_set_style_pad_all(o, 0, 0);
|
||||
}
|
||||
static void styleOpaque(lv_obj_t* o, uint32_t bg) {
|
||||
styleSurface(o, bg);
|
||||
lv_obj_set_style_bg_opa(o, LV_OPA_COVER, 0);
|
||||
}
|
||||
|
||||
static lv_obj_t* label(lv_obj_t* parent, const char* text, const lv_font_t* font, uint32_t color) {
|
||||
lv_obj_t* l = lv_label_create(parent);
|
||||
@@ -341,11 +349,26 @@ static lv_obj_t* group(lv_obj_t* parent, const char* title) {
|
||||
if (title) sectionTitle(parent, title);
|
||||
lv_obj_t* c = infoCard(parent);
|
||||
lv_obj_set_style_pad_hor(c, 0, 0); // rows pad themselves: a pressed row lights edge to edge
|
||||
lv_obj_set_style_clip_corner(c, true, 0);
|
||||
lv_obj_add_flag(c, LV_OBJ_FLAG_USER_1);
|
||||
return c;
|
||||
}
|
||||
|
||||
// A row that lights up under the finger. In a group() the card clips its
|
||||
// corners while a row is held, so the first / last row's light is rounded
|
||||
// with it -- only then: clipping a card's corners costs a few ms every frame.
|
||||
static void groupPressClip(lv_event_t* e) {
|
||||
lv_obj_t* card = lv_obj_get_parent((lv_obj_t*)lv_event_get_current_target(e));
|
||||
if (isGroup(card)) lv_obj_set_style_clip_corner(card, lv_event_get_code(e) == LV_EVENT_PRESSED, 0);
|
||||
}
|
||||
static void rowPressable(lv_obj_t* row) {
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_bg_color(row, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_opa(row, LV_OPA_COVER, LV_STATE_PRESSED);
|
||||
lv_obj_add_event_cb(row, groupPressClip, LV_EVENT_PRESSED, NULL);
|
||||
lv_obj_add_event_cb(row, groupPressClip, LV_EVENT_RELEASED, NULL);
|
||||
lv_obj_add_event_cb(row, groupPressClip, LV_EVENT_PRESS_LOST, NULL);
|
||||
}
|
||||
|
||||
static lv_obj_t* groupNote(lv_obj_t* parent, const char* text) {
|
||||
lv_obj_t* l = noteLabel(parent, text);
|
||||
lv_obj_set_style_pad_hor(l, theme::PAD, 0);
|
||||
@@ -362,11 +385,7 @@ static lv_obj_t* groupLine(lv_obj_t* card, bool tappable) {
|
||||
lv_obj_set_flex_flow(r, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(r, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_column(r, 8, 0);
|
||||
if (tappable) {
|
||||
lv_obj_add_flag(r, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_bg_color(r, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_opa(r, LV_OPA_COVER, LV_STATE_PRESSED);
|
||||
}
|
||||
if (tappable) rowPressable(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -423,6 +442,20 @@ static lv_obj_t* settingRow(lv_obj_t* parent, const char* text, const char* hint
|
||||
return row;
|
||||
}
|
||||
|
||||
// A slider on the right of a settingRow(). Its track is the theme's 20 %
|
||||
// accent tint worked out over the card, opaque: blended, it cost ~3 ms a frame.
|
||||
static lv_obj_t* rowSlider(lv_obj_t* row, int32_t min, int32_t max, int32_t v) {
|
||||
lv_obj_t* sl = lv_slider_create(row);
|
||||
lv_slider_set_range(sl, min, max);
|
||||
lv_slider_set_value(sl, v, LV_ANIM_OFF);
|
||||
lv_obj_set_size(sl, 150, 8);
|
||||
lv_obj_set_style_margin_right(sl, 8, 0);
|
||||
lv_obj_set_ext_click_area(sl, 14);
|
||||
lv_obj_set_style_bg_color(sl, lv_color_hex(theme::mix(theme::ACCENT, theme::SURFACE, 20)), LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_opa(sl, LV_OPA_COVER, LV_PART_MAIN);
|
||||
return sl;
|
||||
}
|
||||
|
||||
// Does something at once (Reboot, Delete ...): its text in `col`, no chevron.
|
||||
// Returns the label, for a confirm-by-second-tap text.
|
||||
static lv_obj_t* actionRow(lv_obj_t* card, const char* text, lv_event_cb_t cb, void* user,
|
||||
@@ -1458,7 +1491,7 @@ void UITask::buildStatusBar() {
|
||||
lv_obj_set_style_text_align(tl, LV_TEXT_ALIGN_CENTER, 0);
|
||||
lv_obj_add_flag(_toast, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_t* bar = lv_obj_create(lv_layer_top());
|
||||
styleSurface(bar, theme::BG);
|
||||
styleOpaque(bar, theme::BG);
|
||||
lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_remove_flag(bar, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_size(bar, LV_PCT(100), theme::STATUS_H);
|
||||
@@ -1788,7 +1821,7 @@ 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);
|
||||
styleSurface(scr, theme::BG);
|
||||
styleOpaque(scr, theme::BG);
|
||||
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
int top = theme::STATUS_H;
|
||||
@@ -1839,7 +1872,7 @@ lv_obj_t* UITask::newScreen(const char* title, bool with_back) {
|
||||
} else {
|
||||
lv_screen_load(scr);
|
||||
if (prev) lv_obj_delete_async(prev); // this usually runs from one of its own widgets
|
||||
if (prev && !same && !_asleep) anim::screenIn(scr, body, backward);
|
||||
if (prev && !same && !_asleep) anim::screenIn(body, backward);
|
||||
}
|
||||
_fade_next = false;
|
||||
return body;
|
||||
@@ -3568,26 +3601,14 @@ void UITask::schemaRow(lv_obj_t* card, int i) {
|
||||
const settings::Setting& st = settings::ALL[i];
|
||||
uint8_t v = settings::get(*_prefs, st);
|
||||
if (st.offset == offsetof(NodePrefs, buzzer_volume)) { // a five-step slider, heard on release
|
||||
lv_obj_t* row = settingRow(card, st.label, NULL);
|
||||
lv_obj_t* sl = lv_slider_create(row);
|
||||
lv_slider_set_range(sl, 0, 4);
|
||||
lv_slider_set_value(sl, v, LV_ANIM_OFF);
|
||||
lv_obj_set_size(sl, 150, 8);
|
||||
lv_obj_set_style_margin_right(sl, 8, 0);
|
||||
lv_obj_set_ext_click_area(sl, 14);
|
||||
lv_obj_t* sl = rowSlider(settingRow(card, st.label, NULL), 0, 4, v);
|
||||
lv_obj_add_event_cb(sl, onVolumeSlider, LV_EVENT_RELEASED, NULL);
|
||||
return;
|
||||
}
|
||||
if (st.offset == offsetof(NodePrefs, display_brightness)) { // a slider here instead of five steps
|
||||
lv_obj_t* row = settingRow(card, st.label, NULL);
|
||||
lv_obj_t* sl = lv_slider_create(row);
|
||||
lv_slider_set_range(sl, 5, 100);
|
||||
uint8_t pct = _prefs->display_brightness_pct ? _prefs->display_brightness_pct
|
||||
: (uint8_t)(_prefs->display_brightness * 25 > 5 ? _prefs->display_brightness * 25 : 5);
|
||||
lv_slider_set_value(sl, pct, LV_ANIM_OFF);
|
||||
lv_obj_set_size(sl, 150, 8);
|
||||
lv_obj_set_style_margin_right(sl, 8, 0);
|
||||
lv_obj_set_ext_click_area(sl, 14);
|
||||
lv_obj_t* sl = rowSlider(settingRow(card, st.label, NULL), 5, 100, pct);
|
||||
lv_obj_add_event_cb(sl, onBrightnessSlider, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
lv_obj_add_event_cb(sl, onBrightnessSlider, LV_EVENT_RELEASED, NULL);
|
||||
return;
|
||||
@@ -3692,9 +3713,7 @@ void UITask::settingsGroup(int i) {
|
||||
if (_core->gpsEnabled()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onGpsSwitch, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
lv_obj_t* row = lv_obj_get_parent(sw);
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_bg_color(row, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_opa(row, LV_OPA_COVER, LV_STATE_PRESSED);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, [](lv_event_t* e) {
|
||||
if (lv_event_get_target(e) == lv_event_get_current_target(e)) onOpenGpsFromSettings(e); // not the switch
|
||||
}, LV_EVENT_CLICKED, NULL);
|
||||
|
||||
@@ -181,9 +181,7 @@ static void wifiRow(lv_obj_t* body) {
|
||||
if (lvport::wifiAllowed()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, onWifiAllowed, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
lv_obj_t* row = lv_obj_get_parent(sw);
|
||||
lv_obj_add_flag(row, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_set_style_bg_color(row, lv_color_hex(theme::SURFACE_2), LV_STATE_PRESSED);
|
||||
lv_obj_set_style_bg_opa(row, LV_OPA_COVER, LV_STATE_PRESSED);
|
||||
rowPressable(row);
|
||||
lv_obj_add_event_cb(row, [](lv_event_t* e) {
|
||||
if (lv_event_get_target(e) == lv_event_get_current_target(e)) s_ui->showWifi(); // not the switch
|
||||
}, LV_EVENT_CLICKED, NULL);
|
||||
|
||||
Reference in New Issue
Block a user