from __future__ import annotations from typing import Any import dpg_map as dpgm from dpg_map.commands import CommandKind, MapCommand from dpg_map.providers import TileProvider from dpg_map.renderer import MapRenderer, drain_renderer_commands from dpg_map.state import DirtyFlags, create_map_state from dpg_map.tiles import TileID, TileResult, TileStatus class FakeDpg: def __init__(self) -> None: self.items: set[str | int] = set() self.deleted: list[tuple[str | int, bool]] = [] self.drawn: list[tuple[str, str | int]] = [] def does_item_exist(self, tag: str | int) -> bool: return tag in self.items def add_draw_layer(self, *, parent: str | int, tag: str | int) -> None: _ = parent self.items.add(tag) def delete_item(self, tag: str | int, *, children_only: bool = False) -> None: self.deleted.append((tag, children_only)) def draw_rectangle(self, *args: Any, parent: str | int, **kwargs: Any) -> None: _ = (args, kwargs) self.drawn.append(("rectangle", parent)) def draw_image(self, *args: Any, parent: str | int, **kwargs: Any) -> None: _ = (args, kwargs) self.drawn.append(("image", parent)) def draw_text(self, *args: Any, parent: str | int, **kwargs: Any) -> None: _ = (args, kwargs) self.drawn.append(("text", parent)) def draw_circle(self, *args: Any, parent: str | int, **kwargs: Any) -> None: _ = (args, kwargs) self.drawn.append(("circle", parent)) def draw_polyline(self, *args: Any, parent: str | int, **kwargs: Any) -> None: _ = (args, kwargs) self.drawn.append(("polyline", parent)) def test_renderer_command_drain_preserves_structural_order_and_coalesces() -> None: state = create_map_state(tag="renderer-drain") state.dirty = DirtyFlags.NONE state.command_queue.put(MapCommand(CommandKind.ADD_OVERLAY, state.tag, {"tag": "a"})) state.command_queue.put(MapCommand(CommandKind.SET_VIEW, state.tag, {"zoom": 3})) state.command_queue.put(MapCommand(CommandKind.SET_VIEW, state.tag, {"zoom": 4})) state.command_queue.put(MapCommand(CommandKind.UPDATE_OVERLAY, state.tag, {"tag": "a", "v": 1})) state.command_queue.put(MapCommand(CommandKind.UPDATE_OVERLAY, state.tag, {"tag": "a", "v": 2})) state.command_queue.put(MapCommand(CommandKind.DELETE_OVERLAY, state.tag, {"tag": "a"})) commands = drain_renderer_commands(state) assert [command.kind for command in commands] == [ CommandKind.ADD_OVERLAY, CommandKind.SET_VIEW, CommandKind.UPDATE_OVERLAY, CommandKind.DELETE_OVERLAY, ] assert commands[1].payload == {"zoom": 4} assert commands[2].payload == {"tag": "a", "v": 2} assert state.dirty & DirtyFlags.VIEW assert state.dirty & DirtyFlags.OVERLAYS def test_overlay_draw_clears_only_overlay_layer() -> None: state = create_map_state(tag="overlay-draw", center=(47.0, 2.0), zoom=8) dpgm.add_marker( "vehicle", lat=47.0, lon=2.0, show_label=True, label="Vehicle", map_tag="overlay-draw", ) fake = FakeDpg() fake.items.add(state.drawlist_tag) renderer = MapRenderer(state, fake) renderer._draw_tile_layer( visible_tiles=[], width=400, height=300, attribution="Tiles", tile_size=256 ) fake.deleted.clear() with state.lock: overlays = tuple(state.overlays.values()) layers = {name: (layer.show, layer.z_index) for name, layer in state.layers.items()} renderer._draw_overlay_layer( overlays=overlays, layers=layers, center=state.center, zoom=state.zoom, width=400, height=300, tile_size=256, ) assert fake.deleted == [("overlay-draw##layer-overlays", True)] assert ("circle", "overlay-draw##layer-overlays") in fake.drawn assert ("text", "overlay-draw##layer-overlays") in fake.drawn def test_overlay_update_drain_sets_only_overlay_dirty() -> None: state = create_map_state(tag="overlay-dirty") state.dirty = DirtyFlags.NONE state.command_queue.put(MapCommand(CommandKind.UPDATE_OVERLAY, state.tag, {"tag": "a"})) drain_renderer_commands(state) assert state.dirty == DirtyFlags.OVERLAYS def test_provider_switch_keeps_overlays_and_invalidates_tiles() -> None: provider = TileProvider( name="renderer-switch-provider", url_template="https://tiles.example.test/{z}/{x}/{y}.png", min_zoom=3, max_zoom=4, attribution="Example", ) dpgm.register_provider(provider) try: state = create_map_state(tag="provider-switch", center=(47.0, 2.0), zoom=8) dpgm.add_marker("vehicle", lat=47.0, lon=2.0, map_tag="provider-switch") state.command_queue.drain() state.dirty = DirtyFlags.NONE tile_id = TileID("osm", 3, 1, 2) state.tile_manager._result_queue.put( TileResult( tile_id, generation=state.generation, status=TileStatus.READY, width=1, height=1, pixels=(1.0, 1.0, 1.0, 1.0), source="disk", ) ) state.tile_manager.drain_results(generation=state.generation, provider_name="osm") state.tile_manager.set_texture_tag(tile_id, "old-texture") dpgm.set_provider("renderer-switch-provider", map_tag="provider-switch") drain_renderer_commands(state) assert "vehicle" in state.overlays assert state.center == (47.0, 2.0) assert state.zoom == 4 assert state.generation == 1 assert state.provider.name == "renderer-switch-provider" assert state.dirty & DirtyFlags.PROVIDER assert state.dirty & DirtyFlags.TILES assert state.dirty & DirtyFlags.OVERLAYS assert state.tile_manager.get_ready_tile(tile_id) is None assert state.tile_manager.take_texture_deletions() == ["old-texture"] finally: dpgm.unregister_provider("renderer-switch-provider") def test_map_scoped_clear_disk_cache_command_keeps_dearpygui_out_of_caller_thread() -> None: state = create_map_state(tag="clear-disk-command") state.dirty = DirtyFlags.NONE dpgm.clear_disk_cache(map_tag="clear-disk-command") commands = state.command_queue.drain() assert [command.kind for command in commands] == [CommandKind.CLEAR_DISK_CACHE] assert state.dirty == DirtyFlags.TILES