// Copyright (c) 2026 Pyxis contributors // SPDX-License-Identifier: MIT #ifndef UI_LXMF_MAP_SCREEN_H #define UI_LXMF_MAP_SCREEN_H #include "MapScreenPresenter.h" #include "DecodedTileCache.h" #include "MapStyleSelector.h" #ifdef ARDUINO #include #include #include #include #include #include #include #include "Hardware/TDeck/MapTileStoreSD.h" #include "Hardware/TDeck/MapTilePack.h" #include "Hardware/TDeck/MapStyleCatalog.h" namespace UI { namespace LXMF { class MapScreen { public: using BackCallback = std::function; enum : std::size_t { TILE_COUNT = Pyxis::MapScreenPresenter::TILE_SLOT_COUNT, MARKER_COUNT = 33, MAX_COMPLETIONS_PER_TICK = 1, READ_CHUNK_BYTES = 4096 }; enum : std::uint32_t { MAX_COMPRESSED_TILE_BYTES = 384U * 1024U }; explicit MapScreen(lv_obj_t* parent = nullptr); ~MapScreen(); void set_back_callback(BackCallback callback) { back_callback_ = callback; } void show(); void hide(); // These methods never call LVGL and are invoked before LVGL_LOCK. void serviceIo(); void updateModel(const Pyxis::MapView::Request& request); // These methods only mutate the pre-created object pool and are invoked // while UIManager owns LVGL_LOCK. void applyFrame(); bool applyOneCompletion(); private: struct StyleActivationCommitContext { MapScreen* screen; std::uint32_t token; std::uint32_t lifecycle_epoch; }; static bool beginStyleActivationCommit(void* context); lv_obj_t* screen_; lv_obj_t* toolbar_; lv_obj_t* viewport_; lv_obj_t* status_label_; lv_obj_t* attribution_label_; lv_obj_t* zoom_label_; lv_obj_t* back_button_; lv_obj_t* zoom_out_button_; lv_obj_t* zoom_in_button_; lv_obj_t* recenter_button_; lv_obj_t* style_button_; lv_obj_t* style_label_; lv_obj_t* pan_buttons_[4]; lv_obj_t* tile_images_[TILE_COUNT]; lv_img_dsc_t tile_descriptors_[TILE_COUNT]; lv_color_t* tile_pixels_[TILE_COUNT]; Pyxis::DecodedTileCache decoded_tile_cache_; std::uint16_t* decoded_cache_pixels_[Pyxis::DecodedTileCache::CAPACITY]; lv_obj_t* approximation_halos_[MARKER_COUNT]; lv_obj_t* markers_[MARKER_COUNT]; lv_obj_t* marker_labels_[MARKER_COUNT]; Pyxis::MapScreenPresenter presenter_; Pyxis::MapStyleSelector style_selector_; Hardware::TDeck::MapTileStoreSD storage_; Hardware::TDeck::MapTilePack pack_; Hardware::TDeck::MapStyleCatalog style_catalog_; Pyxis::MapStyleRequest style_request_; bool style_request_pending_; std::uint32_t style_request_lifecycle_epoch_; char pack_attribution_[Pyxis::MapPackManifest::ATTRIBUTION_CAPACITY]; std::atomic screen_visible_; std::uint32_t style_lifecycle_epoch_; bool style_lifecycle_exhausted_; std::atomic pack_refresh_epoch_; std::uint8_t* compressed_staging_; SemaphoreHandle_t state_mutex_; TaskHandle_t worker_task_; std::atomic stop_requested_; std::atomic worker_exited_; bool worker_started_; bool requests_released_; bool has_location_fix_; bool center_initialized_; Telemetry::LocationTelemetry current_location_; bool dragging_; lv_point_t last_drag_point_; BackCallback back_callback_; static void workerEntry(void* context); void workerLoop(); void publishPackAttribution(); void publishStyleCatalog(std::uint32_t activation_token = 0U, std::uint32_t request_lifecycle_epoch = 0U, bool activation_failed = false); Pyxis::MapTileLoadResult loadTile(const Pyxis::MapTileRequest& request); Pyxis::MapTileLoadResult readTile(const Pyxis::MapTileRequest& request); Pyxis::MapTileLoadResult readCompressedTile(const Pyxis::MapTileRequest& request); bool startWorker(); void stopWorker(); bool lockState(TickType_t ticks = portMAX_DELAY); void unlockState(); void setPlaceholder(std::size_t index); void setStatusFor(Pyxis::MapTileLoadResult result); void refreshVisibleStatus(); void pan(double dx, double dy); static MapScreen* fromEvent(lv_event_t* event); static void onBack(lv_event_t* event); static void onZoomIn(lv_event_t* event); static void onZoomOut(lv_event_t* event); static void onRecenter(lv_event_t* event); static void onStyle(lv_event_t* event); static void onPan(lv_event_t* event); static void onMapPressed(lv_event_t* event); static void onMapPressing(lv_event_t* event); static void onMapReleased(lv_event_t* event); }; static_assert(MapScreen::TILE_COUNT == 6, "MapScreen owns exactly six permanent tile image objects"); static_assert(MapScreen::MARKER_COUNT == static_cast(Pyxis::MapView::MAX_MAP_MARKERS), "MapScreen marker pool must cover local plus all peer snapshots"); static_assert(MapScreen::MARKER_COUNT <= 33, "MapScreen marker object pools must remain bounded"); static_assert(MapScreen::MAX_COMPRESSED_TILE_BYTES == 384U * 1024U, "compressed tile staging cap is part of the memory contract"); } // namespace LXMF } // namespace UI #endif // ARDUINO #endif