diff --git a/examples/companion_radio/ui-lvgl/HomeScreen.h b/examples/companion_radio/ui-lvgl/HomeScreen.h index 98214d71..753d24c9 100644 --- a/examples/companion_radio/ui-lvgl/HomeScreen.h +++ b/examples/companion_radio/ui-lvgl/HomeScreen.h @@ -723,6 +723,7 @@ void UITask::homeMapLayout() { 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); + bool tiled = true; for (int j = 0; j < ROWS; j++) { for (int i = 0; i < COLS; i++) { lv_obj_t* cell = s_cells[j * COLS + i]; @@ -743,7 +744,11 @@ void UITask::homeMapLayout() { show = mapview::cachedAncestor(s_z, wx, ty, k); } } - if (!show) { lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); continue; } + if (!show) { + lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); + if (px < w && py < h) tiled = false; + continue; + } if (lv_image_get_src(img) != &show->dsc) lv_image_set_src(img, &show->dsc); lv_image_set_scale(img, LV_SCALE_NONE << k); int m = (1 << k) - 1; @@ -753,6 +758,7 @@ void UITask::homeMapLayout() { shown++; } } + mapview::showTiled(s_area, s_grid, tiled); for (int i = 0; i < s_npts; i++) { double x = mapview::lonToTileX(s_pts[i].lon / 1e6, s_z) * mapview::TILE_PX - left; double y = mapview::latToTileY(s_pts[i].lat / 1e6, s_z) * mapview::TILE_PX - top; diff --git a/examples/companion_radio/ui-lvgl/MapScreen.h b/examples/companion_radio/ui-lvgl/MapScreen.h index 04912558..b25bea45 100644 --- a/examples/companion_radio/ui-lvgl/MapScreen.h +++ b/examples/companion_radio/ui-lvgl/MapScreen.h @@ -69,6 +69,7 @@ static double latToTileY(double lat, int z) { struct Grid { double left = 0, top = 0; // world px of the view's top-left double step_px = 0; // 0: none + bool tiled = false; // every visible cell holds a tile: grid and fill are under them }; static Grid s_grid; // the main map's static lv_obj_t* s_scale_bar = nullptr; @@ -96,7 +97,7 @@ static double gridStep(int z, double lat, bool imperial, int min_px, char* lbl, // user data is the Grid. static void drawGrid(lv_event_t* e) { const Grid* g = (const Grid*)lv_event_get_user_data(e); - if (!g || g->step_px < 8) return; + if (!g || g->step_px < 8 || g->tiled) return; lv_obj_t* o = (lv_obj_t*)lv_event_get_target(e); lv_area_t a; lv_obj_get_coords(o, &a); @@ -118,6 +119,38 @@ static void drawGrid(lv_event_t* e) { } } +// A map area whose visible cells all hold a tile: its fill and grid lie +// under them (~5 ms a frame on the main map), so it drops both -- and with +// tiledCovers still tells LVGL it hides what's below, so that isn't drawn +// either. +static void showTiled(lv_obj_t* area, Grid& g, bool tiled) { + g.tiled = tiled; + lv_opa_t opa = tiled ? LV_OPA_TRANSP : LV_OPA_COVER; + if (lv_obj_get_style_bg_opa(area, LV_PART_MAIN) != opa) lv_obj_set_style_bg_opa(area, opa, 0); +} +static void tiledCovers(lv_event_t* e) { // LV_EVENT_COVER_CHECK, user data the Grid + if (((const Grid*)lv_event_get_user_data(e))->tiled) coversOwnArea(e); +} + +// The screen under the main map: the map area and the status bar hide all of +// it but a strip the area uncovers as it drifts in (Anim.h screenIn). Filled +// whole it cost ~2 ms a frame; now only what shows is. User data: the area. +static void fillAroundMap(lv_event_t* e) { + lv_obj_t* scr = (lv_obj_t*)lv_event_get_target(e); + lv_area_t s, m; + lv_obj_get_coords(scr, &s); + lv_obj_get_coords((lv_obj_t*)lv_event_get_user_data(e), &m); + s.y1 = belowStatusBar(); + const lv_area_t strips[] = { { s.x1, s.y1, s.x2, m.y1 - 1 }, { s.x1, m.y2 + 1, s.x2, s.y2 }, + { s.x1, m.y1, m.x1 - 1, m.y2 }, { m.x2 + 1, m.y1, s.x2, m.y2 } }; + lv_draw_rect_dsc_t d; + lv_draw_rect_dsc_init(&d); + d.bg_color = lv_obj_get_style_bg_color(scr, LV_PART_MAIN); + lv_layer_t* layer = lv_event_get_layer(e); + for (const lv_area_t& a : strips) + if (a.x1 <= a.x2 && a.y1 <= a.y2) lv_draw_rect(layer, &d, &a); +} + // Markers, remembered so layout can reposition them. `idx` is the Nearby row // (MK_NODE), the waypoint index (MK_WAYPOINT) or the live-share slot (MK_LIVE). enum : uint8_t { MK_NODE, MK_WAYPOINT, MK_LIVE }; @@ -347,6 +380,10 @@ void UITask::buildMap() { 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_event_cb(body, mapview::tiledCovers, LV_EVENT_COVER_CHECK, &mapview::s_grid); + lv_obj_t* scr = lv_obj_get_screen(body); + lv_obj_set_style_bg_opa(scr, LV_OPA_TRANSP, 0); + lv_obj_add_event_cb(scr, mapview::fillAroundMap, LV_EVENT_DRAW_MAIN, body); lv_obj_add_flag(body, LV_OBJ_FLAG_CLICKABLE); lv_obj_add_event_cb(body, onMapPress, LV_EVENT_PRESSED, NULL); lv_obj_add_event_cb(body, onMapPress, LV_EVENT_PRESSING, NULL); @@ -479,6 +516,7 @@ void UITask::layoutMap() { int n = 1 << _map_z; bool have_provider = mapview::s_available; int shown = 0, missing = 0, over = 0; + bool tiled = true; _map_pending = false; for (int j = 0; j < mapview::GRID_ROWS; j++) { @@ -518,9 +556,11 @@ void UITask::layoutMap() { if (k > over && !stand_in) over = k; } else { lv_obj_add_flag(cell, LV_OBJ_FLAG_HIDDEN); + if (on_screen) tiled = false; } } } + mapview::showTiled(_map_area, mapview::s_grid, tiled); mapview::labels::layout(left, top, w, h, _map_z, have_provider && mapview::s_provider == &mapview::s_vector && mapview::s_vector.hasData(), _map_nav ? navmap::BAR_H : 0); diff --git a/examples/companion_radio/ui-lvgl/UITask.cpp b/examples/companion_radio/ui-lvgl/UITask.cpp index eaa797f5..5d13240c 100644 --- a/examples/companion_radio/ui-lvgl/UITask.cpp +++ b/examples/companion_radio/ui-lvgl/UITask.cpp @@ -11,7 +11,7 @@ SET_LOOP_TASK_STACK_SIZE(16 * 1024); #endif #include -#include // lv_cover_check_info_t (freeze::imgCovers) +#include // lv_cover_check_info_t (coversOwnArea) #include #include @@ -221,6 +221,24 @@ static lv_obj_t* dimOverlay(lv_obj_t* parent) { return o; } +// LV_EVENT_COVER_CHECK: an opaque thing LVGL can't tell is (an image, a box +// drawn by its children) hides all under its own area, and LVGL then starts +// drawing from it. +static void coversOwnArea(lv_event_t* e) { + lv_cover_check_info_t* info = (lv_cover_check_info_t*)lv_event_get_param(e); + lv_area_t a; + lv_obj_get_coords((lv_obj_t*)lv_event_get_current_target(e), &a); + const lv_area_t* r = info->area; + if (info->res != LV_COVER_RES_MASKED && r->x1 >= a.x1 && r->y1 >= a.y1 && r->x2 <= a.x2 && r->y2 <= a.y2) + info->res = LV_COVER_RES_COVER; +} + +static lv_obj_t* s_status_bar = nullptr; // on the top layer, opaque +// Where the screen shows below the status bar (its top when the bar is hidden). +static int32_t belowStatusBar() { + return s_status_bar && !lv_obj_has_flag(s_status_bar, LV_OBJ_FLAG_HIDDEN) ? lv_obj_get_y2(s_status_bar) + 1 : 0; +} + // The screen under an open popup, frozen: once the dim is up, the screen is // drawn once into an image, darkened as the dim layer darkens it, and shown // opaque on top of the screen with the dim layer cleared -- LVGL then draws @@ -248,16 +266,6 @@ namespace freeze { s_owner = s_strip = nullptr; if (s_img) lv_obj_delete(s_img); } - // The image hides all under it: LVGL then starts drawing from it. Its own - // check says no, an image widget having no background. - static void imgCovers(lv_event_t* e) { - lv_cover_check_info_t* info = (lv_cover_check_info_t*)lv_event_get_param(e); - lv_area_t a; - lv_obj_get_coords((lv_obj_t*)lv_event_get_current_target(e), &a); - const lv_area_t* r = info->area; - if (info->res != LV_COVER_RES_MASKED && r->x1 >= a.x1 && r->y1 >= a.y1 && r->x2 <= a.x2 && r->y2 <= a.y2) - info->res = LV_COVER_RES_COVER; - } static bool moving(lv_obj_t* scr) { // a screen still sliding / fading in if (lv_display_get_screen_prev(NULL)) return true; for (uint32_t i = 0; i < lv_obj_get_child_count(scr); i++) @@ -298,7 +306,7 @@ namespace freeze { lv_obj_remove_flag(s_img, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_pos(s_img, 0, 0); lv_obj_add_event_cb(s_img, imgDeleted, LV_EVENT_DELETE, buf); - lv_obj_add_event_cb(s_img, imgCovers, LV_EVENT_COVER_CHECK, NULL); + lv_obj_add_event_cb(s_img, coversOwnArea, LV_EVENT_COVER_CHECK, NULL); // no background: its own check says no if (!top) lv_obj_move_foreground(overlay); // the image under the popup, over the rest s_owner = overlay; lv_obj_set_style_bg_opa(overlay, LV_OPA_TRANSP, 0); @@ -1815,6 +1823,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()); + s_status_bar = bar; styleOpaque(bar, theme::BG); lv_obj_remove_flag(bar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_remove_flag(bar, LV_OBJ_FLAG_CLICKABLE);