diff --git a/BuildController.gd b/BuildController.gd index b98c40d..d44eec7 100644 --- a/BuildController.gd +++ b/BuildController.gd @@ -19,6 +19,7 @@ extends Node2D @onready var world_map: Control = get_node("../UI/WorldMap") @onready var build_button: IconButton = get_node("../UI/BuildButton") @onready var action_button: IconButton = get_node("../UI/BuildActionButton") +@onready var building_action_button: IconButton = get_node("../UI/BuildingActionButton") @onready var build_menu: Control = get_node("../UI/BuildMenu") @onready var build_mothership_button: IconButton = get_node("../UI/BuildMenu/MothershipButton") @@ -209,6 +210,7 @@ var transfer_building_id: int = 0 var current_building: Node2D = null var pending_building: Node2D = null var pending_time: float = 0.0 +var context_actions_expanded: bool = false func _ready() -> void: @@ -230,7 +232,8 @@ func _ready() -> void: info_close_button.pressed.connect(_close_info) info_build_button.pressed.connect(_confirm_build) - action_button.pressed.connect(_on_action_pressed) + action_button.pressed.connect(_on_context_action_pressed) + building_action_button.pressed.connect(_on_action_pressed) craft_interceptor_button.pressed.connect(_on_craft_interceptor) craft_transporter_button.pressed.connect(_on_craft_transporter) @@ -266,6 +269,7 @@ func _ready() -> void: lab_menu.visible = false transfer_menu.visible = false action_button.visible = false + building_action_button.visible = false transfer_button.visible = false hangar_button.visible = false hangar_menu.visible = false @@ -281,6 +285,12 @@ func is_showing_building_prompt() -> bool: func _unhandled_input(event: InputEvent) -> void: + if context_actions_expanded and (event is InputEventScreenTouch and event.pressed \ + or event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT): + # Buttons consume their own touch; reaching this handler means the tap + # landed somewhere else, which dismisses an expanded action list. + _collapse_context_actions() + # The formation editor and the skill tree are driven from here rather than # via _gui_input, for the same reason as the joystick: a Control that # consumes touches through the GUI system swallows them before they get @@ -337,8 +347,10 @@ func _process(delta: float) -> void: current_building = null pending_building = null action_button.visible = false + building_action_button.visible = false transfer_button.visible = false hangar_button.visible = false + context_actions_expanded = false return # Debounced exactly like the enter-atmosphere prompt, so the action @@ -355,7 +367,7 @@ func _process(delta: float) -> void: return current_building = pending_building - action_button.visible = current_building != null + context_actions_expanded = false # The hangar is where finished vessels are delivered (#23), so it belongs # at the mothership and nowhere else. hangar_button.visible = current_building != null \ @@ -363,26 +375,68 @@ func _process(delta: float) -> void: # Every building can hold ore, so the transfer button rides alongside the # action button rather than being one of the things the action button # turns into - the two are independent choices at the same structure. - transfer_button.visible = current_building != null + transfer_button.visible = false + building_action_button.visible = false + action_button.visible = current_building != null if current_building != null: - action_button.icon = _action_icon_for(current_building.building_type) + var actions: Array = _context_action_buttons() + building_action_button.icon = _action_icon_for(current_building.building_type) + building_action_button.queue_redraw() + action_button.icon = building_action_button.icon if actions.size() == 1 else IconButton.Icon.ACTION_MENU action_button.queue_redraw() _layout_context_buttons() func _layout_context_buttons() -> void: - # Every contextual action uses a distinct, fixed berth above Build. This - # replaces the old hand-picked offsets where Hangar overlapped Action at a - # mothership (#25). - var buttons: Array = [action_button, transfer_button] - if hangar_button.visible: - buttons.append(hangar_button) + # Build is permanently under the resource counter. The contextual berth + # directly below it is compact until it has more than one real choice. + action_button.offset_top = 324 + action_button.offset_bottom = 472 + var buttons: Array = _context_action_buttons() + if not context_actions_expanded: + for button in buttons: + button.visible = false + return for i in range(buttons.size()): var button: Control = buttons[i] - button.offset_top = -720 + i * 164 + button.visible = true + button.offset_top = 488 + i * 164 button.offset_bottom = button.offset_top + 148 +func _context_action_buttons() -> Array: + if current_building == null: + return [] + var actions: Array = [transfer_button] + if current_building.building_type in [GameState.BUILDING_MOTHERSHIP, GameState.BUILDING_SHIP_CRAFTER, + GameState.BUILDING_SHIP_LAB, GameState.BUILDING_COMMAND_CENTER]: + actions.push_front(building_action_button) + if current_building.building_type == GameState.BUILDING_MOTHERSHIP: + actions.append(hangar_button) + return actions + + +func _on_context_action_pressed() -> void: + if current_building == null: + return + var actions: Array = _context_action_buttons() + if actions.size() == 1: + if actions[0] == transfer_button: + _on_transfer_pressed() + else: + _on_action_pressed() + return + context_actions_expanded = not context_actions_expanded + _layout_context_buttons() + + +func _collapse_context_actions() -> void: + if not context_actions_expanded: + return + context_actions_expanded = false + _layout_context_buttons() + + func _action_icon_for(type: String) -> int: match type: GameState.BUILDING_SHIP_CRAFTER: diff --git a/IconButton.gd b/IconButton.gd index e2f09fc..767faf8 100644 --- a/IconButton.gd +++ b/IconButton.gd @@ -25,6 +25,7 @@ enum Icon { UPGRADE_DAMAGE, UPGRADE_RATE, UPGRADE_RANGE, UPGRADE_THRUSTERS, UPGRADE_SHIELD_HITS, UPGRADE_SHIELD_RECHARGE, UPGRADE_CARGO, UPGRADE_ENGINES, UPGRADE_UNLOCK_SHIELD, + ACTION_MENU, } # Size the glyphs were originally drawn against. Every glyph dimension is @@ -176,6 +177,8 @@ func _draw() -> void: _draw_upgrade_engines(center, pulse, s) Icon.UPGRADE_UNLOCK_SHIELD: _draw_upgrade_unlock_shield(center, pulse, s) + Icon.ACTION_MENU: + _draw_action_menu(center, pulse, s) func _draw_close(center: Vector2, pulse: float, s: float) -> void: @@ -300,6 +303,17 @@ func _draw_upgrade_unlock_shield(center: Vector2, pulse: float, s: float) -> voi NeonDraw.draw_glowing_line(self, center + Vector2(0, -4) * s, center + Vector2(0, 4) * s, _active_color(), pulse, 0.45 * s, core_color) +func _draw_action_menu(center: Vector2, pulse: float, s: float) -> void: + # A small, intentionally neutral stack of choices. It marks a button that + # opens context actions rather than carrying out one particular action. + for y in [-8.0, 0.0, 8.0]: + NeonDraw.draw_glowing_line(self, center + Vector2(-10, y) * s, center + Vector2(10, y) * s, _active_color(), pulse, 0.42 * s, core_color) + NeonDraw.draw_glowing_polygon(self, PackedVector2Array([ + center + Vector2(-15, y - 2) * s, center + Vector2(-11, y) * s, + center + Vector2(-15, y + 2) * s, + ]), _active_color(), pulse, core_color) + + func _draw_build(center: Vector2, pulse: float, s: float) -> void: # A plus inside a frame - "add a structure". var r: float = 11.0 * s diff --git a/Main.tscn b/Main.tscn index 450d77f..dbd94fd 100644 --- a/Main.tscn +++ b/Main.tscn @@ -121,46 +121,47 @@ script = ExtResource("10") icon = 3 [node name="BuildButton" type="Control" parent="UI"] -anchor_top = 1.0 -anchor_bottom = 1.0 offset_left = 24 offset_right = 172 -offset_top = -890 -offset_bottom = -742 +offset_top = 160 +offset_bottom = 308 script = ExtResource("10") icon = 5 [node name="BuildActionButton" type="Control" parent="UI"] visible = false -anchor_top = 1.0 -anchor_bottom = 1.0 offset_left = 24 offset_right = 172 -offset_top = -720 -offset_bottom = -572 +offset_top = 324 +offset_bottom = 472 +script = ExtResource("10") +icon = 2 + +[node name="BuildingActionButton" type="Control" parent="UI"] +visible = false +offset_left = 24 +offset_right = 172 +offset_top = 488 +offset_bottom = 636 script = ExtResource("10") icon = 2 [node name="HangarButton" type="Control" parent="UI"] visible = false -anchor_top = 1.0 -anchor_bottom = 1.0 offset_left = 24 offset_right = 172 -offset_top = -776 -offset_bottom = -628 +offset_top = 488 +offset_bottom = 636 script = ExtResource("10") icon = 2 glow_color = Color(0.45, 0.9, 0.55, 1) [node name="TransferButton" type="Control" parent="UI"] visible = false -anchor_top = 1.0 -anchor_bottom = 1.0 offset_left = 24 offset_right = 172 -offset_top = -600 -offset_bottom = -452 +offset_top = 488 +offset_bottom = 636 script = ExtResource("10") icon = 14 glow_color = Color(1, 0.78, 0.25, 1) diff --git a/tests/test_hangar_management.gd b/tests/test_hangar_management.gd index 8ad6cd3..a842eb0 100644 --- a/tests/test_hangar_management.gd +++ b/tests/test_hangar_management.gd @@ -63,18 +63,25 @@ func _process(delta: float) -> bool: _fail("hangar allowed the last deployed ship to be stored") return true - # #25: all three contextual controls have non-overlapping vertical - # rectangles when the mothership exposes Action, Transfer and Hangar. - controller.action_button.visible = true - controller.transfer_button.visible = true - controller.hangar_button.visible = true + # Context actions are compact until expanded. A mothership has three + # choices: research, transfer cargo and hangar, which must receive + # their own non-overlapping berth only after expansion. + var mothership: Node2D = load("res://Building.tscn").instantiate() + mothership.set("building_type", GameState.BUILDING_MOTHERSHIP) + controller.current_building = mothership + controller.context_actions_expanded = true controller._layout_context_buttons() - var buttons: Array = [controller.action_button, controller.transfer_button, controller.hangar_button] + var buttons: Array = [controller.building_action_button, controller.transfer_button, controller.hangar_button] for i in range(buttons.size() - 1): if buttons[i].offset_bottom > buttons[i + 1].offset_top: _fail("context buttons %d and %d overlap" % [i, i + 1]) return true - print("hangar crews and stores typed vessels; contextual buttons have separate berths") + controller._collapse_context_actions() + if controller.building_action_button.visible or controller.transfer_button.visible or controller.hangar_button.visible: + _fail("collapsing the context button left an action visible") + return true + mothership.free() + print("hangar crews and stores typed vessels; contextual actions expand and collapse") print("TEST_HANGAR_MANAGEMENT: PASS") quit(0) return true