From 902df6554c66680fb257a741b5147174ef9f2ec3 Mon Sep 17 00:00:00 2001 From: Pike Date: Wed, 2 Sep 2026 21:20:24 +0000 Subject: [PATCH] ui(map): contrast pin labels against active basemap Marker (pin) labels previously inherited the app's default text color, which reads white and disappears on light basemaps. Set each label's text color in applyFrame() against the active style: black by default (light basemaps osm-bright/positron/toner) and white only on the one dark basemap (dark-matter). Re-evaluated every frame so a style switch re-colors visible labels on the next applied frame. Add a contract test pinning the dark-basemap detection and the black-by-default / white-on-dark ternary. --- lib/tdeck_ui/UI/LXMF/MapScreen.cpp | 15 ++++++++++++++ .../build_scripts/test_map_screen_contract.py | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/tdeck_ui/UI/LXMF/MapScreen.cpp b/lib/tdeck_ui/UI/LXMF/MapScreen.cpp index 09312603..af7e5c59 100644 --- a/lib/tdeck_ui/UI/LXMF/MapScreen.cpp +++ b/lib/tdeck_ui/UI/LXMF/MapScreen.cpp @@ -100,6 +100,13 @@ const char* styleLabel(const char* style_id) { return "Style"; } +// The allowlist is otherwise light basemaps (osm-bright, positron, toner). +// Marker labels sit directly on those tiles with no backing plate, so black +// reads on light backgrounds and white only on the one dark basemap. +bool isDarkBasemap(const char* style_id) { + return style_id != nullptr && std::strcmp(style_id, "dark-matter") == 0; +} + } // namespace static_assert(sizeof(lv_color_t) == 2U, @@ -728,6 +735,13 @@ void MapScreen::applyFrame() { setPlaceholder(index); } } + // Marker labels float directly over the basemap tiles (no backing plate), + // so their ink must contrast the active style: black on light basemaps, + // white on the one dark basemap. Re-evaluated every frame so a style + // switch re-colors any visible labels on the next applied frame. + const lv_color_t marker_label_color = + isDarkBasemap(style_selector_.activeId()) + ? lv_color_white() : lv_color_black(); for (std::size_t index = 0; index < MARKER_COUNT; ++index) { if (index >= frame.marker_count) { lv_obj_add_flag(approximation_halos_[index], LV_OBJ_FLAG_HIDDEN); @@ -764,6 +778,7 @@ void MapScreen::applyFrame() { marker.peer.bytes[Telemetry::PEER_ID_SIZE - 1U]); } lv_label_set_text(marker_labels_[index], label); + lv_obj_set_style_text_color(marker_labels_[index], marker_label_color, 0); lv_obj_set_pos(marker_labels_[index], x + 6, y - 7); lv_obj_clear_flag(marker_labels_[index], LV_OBJ_FLAG_HIDDEN); } diff --git a/tests/build_scripts/test_map_screen_contract.py b/tests/build_scripts/test_map_screen_contract.py index 59c9b125..62097423 100644 --- a/tests/build_scripts/test_map_screen_contract.py +++ b/tests/build_scripts/test_map_screen_contract.py @@ -262,6 +262,26 @@ def test_style_switch_is_bounded_and_worker_owned(): assert "style_selector_.clearError()" in hide +def test_marker_labels_contrast_active_basemap_style(): + source = text(UI / "MapScreen.cpp") + # The allowlist is light basemaps except one dark style, so pin labels are + # black by default and white only over the dark basemap. + assert "bool isDarkBasemap(const char* style_id)" in source + dark = function_body(source, "bool isDarkBasemap(const char* style_id)") + assert "std::strcmp(style_id, \"dark-matter\") == 0" in dark + + frame = function_body(source, "void MapScreen::applyFrame()") + assert "isDarkBasemap(style_selector_.activeId())" in frame + # The ternary text encodes the contract: dark basemap -> white, + # otherwise -> black (so black is the default for light basemaps). + assert "lv_color_white() : lv_color_black()" in frame + assert "lv_obj_set_style_text_color(marker_labels_[index], marker_label_color, 0)" in frame + # The color is derived once per frame and applied inside the marker loop. + derive = frame.index("marker_label_color") + apply = frame.index("lv_obj_set_style_text_color(marker_labels_[index], marker_label_color") + assert derive < apply + + def test_ui_manager_services_before_lock_and_hides_map_everywhere(): source = text(UI / "UIManager.cpp") update = function_body(source, "void UIManager::update()")