diff --git a/examples/companion_radio/ui-lvgl/MapScreen.h b/examples/companion_radio/ui-lvgl/MapScreen.h index 8612e5dd..744a21d0 100644 --- a/examples/companion_radio/ui-lvgl/MapScreen.h +++ b/examples/companion_radio/ui-lvgl/MapScreen.h @@ -710,20 +710,7 @@ void UITask::mapDownloadPopup() { if (_dl_zmax < _map_z) _dl_zmax = _map_z + 3; if (_dl_zmax > src_max) _dl_zmax = src_max; // the server has nothing finer - _dl_overlay = dimOverlay(screen()); - - lv_obj_t* panel = lv_obj_create(_dl_overlay); - anim::popup(_dl_overlay); - lv_obj_set_size(panel, lv_display_get_horizontal_resolution(NULL) - 16, LV_SIZE_CONTENT); - lv_obj_align(panel, LV_ALIGN_CENTER, 0, theme::STATUS_H / 2); - lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); - lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); - lv_obj_set_style_border_width(panel, 1, 0); - lv_obj_set_style_radius(panel, theme::RADIUS, 0); - lv_obj_set_style_pad_all(panel, theme::PAD, 0); - lv_obj_set_style_pad_row(panel, 4, 0); - lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_max_height(panel, lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 12, 0); // scrolls past that + lv_obj_t* panel = popupOpen(screen(), POP_FIT, _dl_overlay); lv_obj_t* hdr = lv_obj_create(panel); styleSurface(hdr, theme::BG); diff --git a/examples/companion_radio/ui-lvgl/NavMap.h b/examples/companion_radio/ui-lvgl/NavMap.h index 39ecb27f..687eb9b3 100644 --- a/examples/companion_radio/ui-lvgl/NavMap.h +++ b/examples/companion_radio/ui-lvgl/NavMap.h @@ -476,27 +476,8 @@ void UITask::navToNode(const uint8_t* key, int32_t lat, int32_t lon, const char* lv_obj_t* UITask::navPopupPanel(const char* title, bool full) { navClosePopup(); - _nav_overlay = dimOverlay(screen()); - - lv_obj_t* panel = lv_obj_create(_nav_overlay); - anim::popup(_nav_overlay); - int w = lv_display_get_horizontal_resolution(NULL) - 16; - if (full) { - lv_obj_set_size(panel, w, lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 12); - lv_obj_set_pos(panel, 8, theme::STATUS_H + 6); - } else { // as tall as its content, scrolling past the screen's height - lv_obj_set_size(panel, w, LV_SIZE_CONTENT); - lv_obj_set_style_max_height(panel, lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 12, 0); - lv_obj_align(panel, LV_ALIGN_CENTER, 0, theme::STATUS_H / 2); - } - lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); - lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); - lv_obj_set_style_border_width(panel, 1, 0); - lv_obj_set_style_radius(panel, theme::RADIUS, 0); - lv_obj_set_style_pad_all(panel, theme::PAD, 0); + lv_obj_t* panel = popupOpen(screen(), full ? POP_FULL : POP_FIT, _nav_overlay); lv_obj_set_style_pad_row(panel, 6, 0); - lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); - if (full) lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); // its own list scrolls lv_obj_t* hdr = lv_obj_create(panel); styleSurface(hdr, theme::BG); diff --git a/examples/companion_radio/ui-lvgl/RadioScreen.h b/examples/companion_radio/ui-lvgl/RadioScreen.h index a83ca0fb..845da4be 100644 --- a/examples/companion_radio/ui-lvgl/RadioScreen.h +++ b/examples/companion_radio/ui-lvgl/RadioScreen.h @@ -141,19 +141,8 @@ void UITask::radioFreqPopup(bool repeater) { using namespace radioview; if (s_overlay) return; s_freq_rpt = repeater; - s_overlay = dimOverlay(screen()); - lv_obj_t* panel = lv_obj_create(s_overlay); - anim::popup(s_overlay); - lv_obj_set_size(panel, lv_display_get_horizontal_resolution(NULL) - 16, LV_SIZE_CONTENT); - lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, theme::STATUS_H + 4); - lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); - lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); - lv_obj_set_style_border_width(panel, 1, 0); - lv_obj_set_style_radius(panel, theme::RADIUS, 0); - lv_obj_set_style_pad_all(panel, theme::PAD, 0); + lv_obj_t* panel = popupOpen(screen(), POP_TOP, s_overlay); lv_obj_set_style_pad_row(panel, 6, 0); - lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); - lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); float lo, hi; radio_driver.getFreqBounds(lo, hi); char t[48]; diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index 91eef312..18ad1a39 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -150,6 +150,41 @@ static lv_obj_t* dimOverlay(lv_obj_t* parent) { return o; } +// A popup: the dimmed layer (into `overlay`, to close it by) and its panel -- +// the page colour, the accent hairline, a flex column -- rising into view. +// POP_FIT: as tall as its content, centred below the status bar, scrolling +// past that height. POP_FULL: all of that height; its own list scrolls. +// POP_TOP: as tall as its content, under the status bar, clear of a keyboard. +enum PopFit : uint8_t { POP_FIT, POP_FULL, POP_TOP }; +static lv_obj_t* popupOpen(lv_obj_t* parent, PopFit fit, lv_obj_t*& overlay, int32_t inset = 8) { + overlay = dimOverlay(parent); + 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); + if (fit == POP_FULL) { + lv_obj_set_height(panel, h - theme::STATUS_H - 12); + lv_obj_set_pos(panel, inset, theme::STATUS_H + 6); + } else if (fit == POP_TOP) { + lv_obj_set_height(panel, LV_SIZE_CONTENT); + lv_obj_align(panel, LV_ALIGN_TOP_MID, 0, theme::STATUS_H + 4); + } else { + lv_obj_set_height(panel, LV_SIZE_CONTENT); + lv_obj_set_style_max_height(panel, h - theme::STATUS_H - 12, 0); + lv_obj_align(panel, LV_ALIGN_CENTER, 0, theme::STATUS_H / 2); + lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_ACTIVE); + } + if (fit != POP_FIT) lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); + lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); + lv_obj_set_style_border_width(panel, 1, 0); + lv_obj_set_style_radius(panel, theme::RADIUS, 0); + lv_obj_set_style_pad_all(panel, theme::PAD, 0); + lv_obj_set_style_pad_row(panel, 4, 0); + lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); + anim::popup(overlay); + return panel; +} + // ── Info cards ── // Label / value rows on a card (Diagnostics, GPS, node detail, About): the // label small and muted on the left, the value on the right -- wrapping, @@ -429,23 +464,11 @@ static void pickerOpen(lv_obj_t* c) { Choice* ch = choiceOf(c); pickerClose(); s_pick_target = c; - s_pick_overlay = dimOverlay(lv_layer_top()); + lv_obj_t* panel = popupOpen(lv_layer_top(), POP_FIT, s_pick_overlay, 24); lv_obj_add_event_cb(s_pick_overlay, [](lv_event_t* e) { // a tap beside the list: nothing changes if (lv_event_get_target(e) == lv_event_get_current_target(e)) pickerClose(); }, LV_EVENT_CLICKED, NULL); - lv_obj_t* panel = lv_obj_create(s_pick_overlay); - styleSurface(panel, theme::BG); - lv_obj_set_width(panel, lv_display_get_horizontal_resolution(NULL) - 48); - lv_obj_set_height(panel, LV_SIZE_CONTENT); - lv_obj_set_style_max_height(panel, lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 16, 0); - lv_obj_align(panel, LV_ALIGN_CENTER, 0, theme::STATUS_H / 2); - lv_obj_set_style_radius(panel, theme::RADIUS, 0); - lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); - lv_obj_set_style_border_width(panel, 1, 0); lv_obj_set_style_pad_all(panel, 6, 0); - lv_obj_set_style_pad_row(panel, 4, 0); - lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); - lv_obj_set_scrollbar_mode(panel, LV_SCROLLBAR_MODE_ACTIVE); lv_obj_t* t = label(panel, ch->title, THEME_FONT_SMALL, theme::TEXT_MUTED); lv_obj_set_style_pad_hor(t, 4, 0); lv_obj_t* card = group(panel, nullptr); @@ -459,7 +482,6 @@ 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); } - anim::popup(s_pick_overlay); if (sel_row) { lv_obj_update_layout(panel); lv_obj_scroll_to_view_recursive(sel_row, LV_ANIM_OFF); } } @@ -1812,21 +1834,7 @@ void UITask::startNearbyScan() { // Dimmed full-screen overlay (swallows taps) holding a panel with the results. // A child of the current screen, so it goes away with it. void UITask::showScanPopup() { - _scan_overlay = dimOverlay(screen()); - - lv_obj_t* panel = lv_obj_create(_scan_overlay); - anim::popup(_scan_overlay); - lv_obj_set_size(panel, lv_display_get_horizontal_resolution(NULL) - 16, - lv_display_get_vertical_resolution(NULL) - theme::STATUS_H - 12); - lv_obj_set_pos(panel, 8, theme::STATUS_H + 6); - lv_obj_set_style_bg_color(panel, lv_color_hex(theme::BG), 0); - lv_obj_set_style_border_color(panel, lv_color_hex(theme::ACCENT), 0); - lv_obj_set_style_border_width(panel, 1, 0); - lv_obj_set_style_radius(panel, theme::RADIUS, 0); - lv_obj_set_style_pad_all(panel, theme::PAD, 0); - lv_obj_set_style_pad_row(panel, 4, 0); - lv_obj_set_flex_flow(panel, LV_FLEX_FLOW_COLUMN); - lv_obj_remove_flag(panel, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_t* panel = popupOpen(screen(), POP_FULL, _scan_overlay); lv_obj_t* hdr = lv_obj_create(panel); styleSurface(hdr, theme::BG); diff --git a/examples/companion_radio/ui-lvgl/lvgl_patches.py b/examples/companion_radio/ui-lvgl/lvgl_patches.py new file mode 100644 index 00000000..86a9740f --- /dev/null +++ b/examples/companion_radio/ui-lvgl/lvgl_patches.py @@ -0,0 +1,60 @@ +# Fixes to LVGL 9.2.2 (lib_deps) that ui-lvgl needs, applied in place to the +# downloaded library -- idempotent, so every build can run it. +# +# PlatformIO: extra_scripts = pre:examples/companion_radio/ui-lvgl/lvgl_patches.py +# (patches as LVGL is compiled, i.e. after lib_deps are fetched) +# The sim: python3 examples/companion_radio/ui-lvgl/lvgl_patches.py +# +# Content height counted as scrolled (lv_obj_pos.c, calc_content_height): a +# SIZE_CONTENT box capped by max_height measures its children where the scroll +# has moved them, so an elastic pull past its end shrinks it by the overshoot, +# again on every pull (popups with a list). The function zeroes scroll.y for +# the count, but the children's coords already carry it; it's added back. +import os +import sys + +PATCHES = [ + ("src/core/lv_obj_pos.c", [ + (" /*Normal top aligns. */\n" + " child_res_tmp = child->coords.y2 - obj->coords.y1 + 1;\n", + " /*Normal top aligns. */\n" + " child_res_tmp = child->coords.y2 - obj->coords.y1 + 1 + scroll_y_tmp;\n"), + (" else {\n" + " child_res_tmp = child->coords.y2 - obj->coords.y1 + 1;\n" + " }\n", + " else {\n" + " child_res_tmp = child->coords.y2 - obj->coords.y1 + 1 + scroll_y_tmp;\n" + " }\n"), + ]), +] + + +def apply(lvgl_dir): + for rel, subs in PATCHES: + path = os.path.join(lvgl_dir, rel) + with open(path) as f: + src = f.read() + out = src + for old, new in subs: + if new in out: + continue + if out.count(old) != 1: + raise RuntimeError("lvgl_patches: %s changed upstream, re-check the patch" % rel) + out = out.replace(old, new) + if out != src: + with open(path, "w") as f: + f.write(out) + print("lvgl_patches: patched %s" % rel) + + +if __name__ == "__main__": + apply(sys.argv[1]) +else: + Import("env") # noqa: F821 (SCons) + + def _patch(env, node): + path = node.srcnode().get_abspath() + apply(path[: path.rindex(os.sep + "src" + os.sep)]) + return node + + env.AddBuildMiddleware(_patch, "*/lvgl/src/core/lv_obj_pos.c") # noqa: F821 diff --git a/solo/wio-tracker-l2/platformio.ini b/solo/wio-tracker-l2/platformio.ini index 6c684390..720956ee 100644 --- a/solo/wio-tracker-l2/platformio.ini +++ b/solo/wio-tracker-l2/platformio.ini @@ -70,6 +70,9 @@ build_flags = -D NEARBY_MAX=64 -D HIST_ARCHIVE ; message history also on the SD card (ui-lvgl/HistoryStore.h) -D GPS_SKYVIEW ; satellites / signal / DOPs for the GPS screen (helpers/sensors/GpsSky.h) +extra_scripts = + ${esp32_base.extra_scripts} + pre:examples/companion_radio/ui-lvgl/lvgl_patches.py ; fixes to LVGL itself build_src_filter = ${Wio_Tracker_L2.build_src_filter} + + diff --git a/variants/sim/build_wasm.sh b/variants/sim/build_wasm.sh index dfe440b0..3db9bba9 100755 --- a/variants/sim/build_wasm.sh +++ b/variants/sim/build_wasm.sh @@ -246,6 +246,7 @@ if [ "$SIM_UI" = "lvgl" ]; then # would shadow the libc the C headers include. C_FLAGS=(-std=gnu99 "${OPT_FLAGS[@]}" "${DEFINES[@]}" "-I$LVGL_DIR" -Iexamples/companion_radio/ui-lvgl) LV_OBJ_DIR="$OUT_DIR/obj_lvgl" + python3 examples/companion_radio/ui-lvgl/lvgl_patches.py "$LVGL_DIR" # as the firmware build does C_SRCS=() while IFS= read -r f; do C_SRCS+=("$f"); done < <( find "$LVGL_DIR/src" -name '*.c' | sed "s|^$REPO_ROOT/||" | sort