diff --git a/client/tests/test_atlas_overlays.gd b/client/tests/test_atlas_overlays.gd index 0fca3c4eb..8b3f408fc 100644 --- a/client/tests/test_atlas_overlays.gd +++ b/client/tests/test_atlas_overlays.gd @@ -510,3 +510,32 @@ func test_legend_panel_shows_active_overlay_and_hides_when_toggled_off() -> void ).is_false() v.queue_free() + + +## PR #186 regression: the overlay bar spans the full header-adjacent width +## so rows can wrap (ALIGNMENT_END right-aligns the chips), which leaves a +## wide EMPTY strip inside the container rect on wide screens. That strip +## must never eat map input: the container is MOUSE_FILTER_IGNORE (chip +## Buttons STOP their own events) and _is_over_ui() must not treat the bar +## rect as UI — both are pinned here because no other test exercises them. +func test_overlay_bar_empty_area_does_not_block_map_input() -> void: + var v: AtlasViewer = AtlasViewer.new() + add_child(v) + v.size = Vector2(1920.0, 1080.0) + v._position_overlay_bar() + await get_tree().process_frame + + assert_int(v._overlay_bar.mouse_filter).override_failure_message( + "overlay bar container must be MOUSE_FILTER_IGNORE — STOP turns the" + + " empty flow area into a dead strip that swallows map clicks" + ).is_equal(Control.MOUSE_FILTER_IGNORE) + + # A point just inside the bar's top-left is empty flow area (chips are + # right-aligned and occupy well under the full width at 1920px). + var strip_point: Vector2 = v._overlay_bar.global_position + Vector2(8.0, 8.0) + assert_bool(v._is_over_ui(strip_point)).override_failure_message( + "_is_over_ui must not claim the overlay bar's empty strip — map" + + " pan/click near the top edge would silently die there" + ).is_false() + + v.queue_free() diff --git a/client/ui/implant/apps/atlas/atlas_overlay_bar.gd b/client/ui/implant/apps/atlas/atlas_overlay_bar.gd index fe7418701..8d14a0537 100644 --- a/client/ui/implant/apps/atlas/atlas_overlay_bar.gd +++ b/client/ui/implant/apps/atlas/atlas_overlay_bar.gd @@ -34,7 +34,14 @@ func _init(viewer_ref = null) -> void: func _ready() -> void: - mouse_filter = Control.MOUSE_FILTER_STOP + # IGNORE, not STOP (PR #186 review): the container spans the full + # header-adjacent width so rows can wrap, and with ALIGNMENT_END most of + # that rect is empty space left of the chips — STOP made it a dead strip + # that swallowed map clicks/drags near the top edge. The chip Buttons + # STOP their own input (Godot Button default), so toggling and tooltips + # are unaffected; empty flow area passes through to the map. Same idiom + # as the legend panel / screen header / empty notice. + mouse_filter = Control.MOUSE_FILTER_IGNORE if _viewer == null: return for def: Dictionary in _viewer.get_overlay_defs(): diff --git a/client/ui/implant/apps/atlas/atlas_viewer.gd b/client/ui/implant/apps/atlas/atlas_viewer.gd index de09f16e4..44cdfc668 100644 --- a/client/ui/implant/apps/atlas/atlas_viewer.gd +++ b/client/ui/implant/apps/atlas/atlas_viewer.gd @@ -707,8 +707,12 @@ static func _dict_str(d: Dictionary, key: String, fallback: String) -> String: func _is_over_ui(pos: Vector2) -> bool: - if _overlay_bar and _overlay_bar.get_global_rect().has_point(pos): - return true + # The overlay bar is deliberately NOT checked here (PR #186 review): its + # container spans the full header-adjacent width for row wrapping but is + # MOUSE_FILTER_IGNORE — chip Buttons STOP their own events (they never + # reach this viewer), and the empty flow area left of the right-aligned + # chips must stay interactive map surface. Checking the wide bar rect + # would re-create the ~700px dead strip the review caught. if _city_panel and _city_panel.visible and _city_panel.get_global_rect().has_point(pos): return true return false