diff --git a/PlanetSetupEditor.gd b/PlanetSetupEditor.gd index 0f34b17..7d0f9fd 100644 --- a/PlanetSetupEditor.gd +++ b/PlanetSetupEditor.gd @@ -25,6 +25,10 @@ const MIN_ZOOM: float = 0.3 const MAX_ZOOM: float = 4.0 const WHEEL_ZOOM_STEP: float = 1.1 const HIT_RADIUS: float = 50.0 +# #65: how far a single-finger press has to move before it counts as a pan +# rather than a tap-to-place, once it's confirmed not to be starting on an +# existing marker. +const PAN_DEADZONE: float = 24.0 const ORIGIN_COLOR: Color = Color(1.0, 0.9, 0.4) # Duplicated from PaletteEntryButton.RING_COLOR rather than referenced # cross-class - a global class_name lookup at parse time needs the class @@ -45,14 +49,22 @@ const PALETTE_STEP: float = 92.0 @onready var load_button: IconButton = $UI/LoadButton @onready var delete_button: IconButton = $UI/DeleteButton @onready var status_label: Label = $UI/StatusLabel +@onready var selected_label: Label = $UI/SelectedLabel @onready var palette_row: Control = $UI/PaletteRow +@onready var load_panel: Control = $UI/LoadPanel +@onready var load_list: ItemList = $UI/LoadPanel/List +@onready var load_close_button: IconButton = $UI/LoadPanel/CloseButton var current_setup: Resource var selected_palette: String = "capture_point" var selected_item: Dictionary = {} var _profile_ids: Array = [] var _profile_cursor: int = 0 -var _load_cursor: int = -1 +# #64: replaced the old "Load cycles to the next saved setup" behaviour, +# which gave no way to see what existed or jump straight to one by name - +# kept only as the id list _load_list_setup_ids indexes into when the +# panel's own selection fires. +var _load_list_setup_ids: PackedStringArray = PackedStringArray() var _palette_buttons: Array = [] var _origin_marker: NeonPiece @@ -76,6 +88,8 @@ func _ready() -> void: load_button.pressed.connect(_on_load_pressed) delete_button.pressed.connect(_on_delete_pressed) name_input.text_changed.connect(_on_name_changed) + load_close_button.pressed.connect(func(): load_panel.visible = false) + load_list.item_selected.connect(_on_load_list_item_selected) _origin_marker = NeonPiece.create(item_host, Vector2.ZERO, ORIGIN_COLOR) var cross_size: float = 26.0 @@ -100,6 +114,7 @@ func new_setup(setup_id: String, display_name: String, profile_id: String) -> vo # fails at runtime, since the compiler here only sees current_setup as # a plain Resource and can't confirm the target is Array[Resource]. selected_item = {} + _refresh_selected_label() _profile_cursor = maxi(_profile_ids.find(profile_id), 0) if name_input != null: name_input.text = display_name @@ -116,6 +131,7 @@ func load_setup(setup_id: String) -> bool: # explicit save. current_setup = setup.duplicate(true) selected_item = {} + _refresh_selected_label() _profile_cursor = maxi(_profile_ids.find(current_setup.surface_profile_id), 0) if name_input != null: name_input.text = current_setup.display_name @@ -301,8 +317,19 @@ func _on_load_pressed() -> void: if setups.is_empty(): _set_status("No saved setups yet.") return - _load_cursor = (_load_cursor + 1) % setups.size() - load_setup(setups[_load_cursor].setup_id) + load_list.clear() + _load_list_setup_ids = PackedStringArray() + for setup in setups: + load_list.add_item("%s (%s)" % [setup.display_name, setup.surface_profile_id]) + _load_list_setup_ids.append(setup.setup_id) + load_panel.visible = true + + +func _on_load_list_item_selected(index: int) -> void: + if index < 0 or index >= _load_list_setup_ids.size(): + return + load_setup(_load_list_setup_ids[index]) + load_panel.visible = false func _on_delete_pressed() -> void: @@ -314,6 +341,7 @@ func _on_delete_pressed() -> void: else: remove_enemy(selected_item.index) selected_item = {} + _refresh_selected_label() func _on_name_changed(new_text: String) -> void: @@ -336,6 +364,24 @@ func _set_status(message: String) -> void: status_label.text = message +# #66: names the currently-selected marker so a tap actually confirms what +# got picked, instead of the player having to infer it from which dot on +# screen turned white. +func _refresh_selected_label() -> void: + if selected_label == null: + return + if selected_item.is_empty() or current_setup == null: + selected_label.text = "" + return + var index: int = int(selected_item.index) + if selected_item.kind == "capture": + selected_label.text = "Selected: Capture Point #%d" % (index + 1) + else: + var spawn: Resource = current_setup.enemy_spawns[index] + var definition: Resource = ENEMY_DEFINITIONS.get_definition(spawn.enemy_id) + selected_label.text = "Selected: %s #%d" % [definition.display_name, index + 1] + + # --- placement/selection helpers ---------------------------------------- func _place_selected(world_pos: Vector2) -> void: @@ -405,26 +451,33 @@ func _handle_touch(event: InputEventScreenTouch) -> void: var world_pos: Vector2 = _screen_to_world(event.position) var hit: Dictionary = _find_marker_near(world_pos) drag_touch_index = event.index + pan_start_screen = event.position + pan_start_camera = camera.position if not hit.is_empty(): selected_item = hit dragging_item = hit drag_mode = "item" _rebuild_markers() - elif selected_palette != "": - _place_selected(world_pos) - drag_mode = "" + _refresh_selected_label() else: - drag_mode = "pan" - pan_start_screen = event.position - pan_start_camera = camera.position + # #65: a press over empty canvas no longer commits to placing + # an item immediately - it might be the start of a pan. Which + # one it turns out to be is only known on release (still + # under PAN_DEADZONE = a tap = place) or once a drag actually + # crosses that deadzone (= a pan), same tap-vs-drag idiom + # GasBombController uses for its own AIM_DEADZONE. + dragging_item = {} + drag_mode = "pending" else: - touches.erase(event.index) - if touches.size() < 2: - pinch_active = false if event.index == drag_touch_index: + if drag_mode == "pending": + _place_selected(_screen_to_world(event.position)) drag_touch_index = -1 drag_mode = "" dragging_item = {} + touches.erase(event.index) + if touches.size() < 2: + pinch_active = false func _handle_drag(event: InputEventScreenDrag) -> void: @@ -440,6 +493,8 @@ func _handle_drag(event: InputEventScreenDrag) -> void: if event.index != drag_touch_index: return + if drag_mode == "pending" and event.position.distance_to(pan_start_screen) >= PAN_DEADZONE: + drag_mode = "pan" match drag_mode: "item": _move_selected(_screen_to_world(event.position)) diff --git a/PlanetSetupEditor.tscn b/PlanetSetupEditor.tscn index 99e033d..0d41a64 100644 --- a/PlanetSetupEditor.tscn +++ b/PlanetSetupEditor.tscn @@ -170,6 +170,17 @@ theme_override_font_sizes/font_size = 20 autowrap_mode = 2 text = "" +[node name="SelectedLabel" type="Label" parent="UI"] +mouse_filter = 2 +offset_left = 20 +offset_top = 250 +offset_right = 696 +offset_bottom = 286 +theme_override_font_sizes/font_size = 22 +theme_override_colors/font_color = Color(1, 0.95, 0.9, 1) +autowrap_mode = 2 +text = "" + [node name="PaletteRow" type="Control" parent="UI"] anchor_top = 1.0 anchor_bottom = 1.0 @@ -177,3 +188,42 @@ offset_left = 20 offset_top = -110 offset_right = 696 offset_bottom = -20 + +[node name="LoadPanel" type="Control" parent="UI"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="Backdrop" type="ColorRect" parent="UI/LoadPanel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +color = Color(0.03, 0.03, 0.08, 0.94) + +[node name="TitleLabel" type="Label" parent="UI/LoadPanel"] +mouse_filter = 2 +anchor_right = 1.0 +offset_left = 24 +offset_top = 36 +offset_right = -24 +offset_bottom = 90 +theme_override_font_sizes/font_size = 34 +horizontal_alignment = 1 +text = "Load a setup" + +[node name="List" type="ItemList" parent="UI/LoadPanel"] +offset_left = 24 +offset_top = 110 +offset_right = 696 +offset_bottom = 1180 +theme_override_font_sizes/font_size = 24 + +[node name="CloseButton" type="Control" parent="UI/LoadPanel"] +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -92 +offset_top = 20 +offset_right = -20 +offset_bottom = 92 +script = ExtResource("4") +icon = 1 +glow_color = Color(0.9, 0.35, 0.35, 1) diff --git a/tests/_setup_editor_load_panel_preview.gd b/tests/_setup_editor_load_panel_preview.gd new file mode 100644 index 0000000..798d1b0 --- /dev/null +++ b/tests/_setup_editor_load_panel_preview.gd @@ -0,0 +1,12 @@ +extends Node2D + +# Preview, not a test - #64's Load panel: a real selectable list of saved +# setups instead of blind cycling. +# +# bash tests/screenshot.sh res://tests/_setup_editor_load_panel_preview.tscn /tmp/load.png 20 720x1280 + + +func _ready() -> void: + var editor: Node = load("res://PlanetSetupEditor.tscn").instantiate() + add_child(editor) + editor._on_load_pressed() diff --git a/tests/_setup_editor_load_panel_preview.tscn b/tests/_setup_editor_load_panel_preview.tscn new file mode 100644 index 0000000..bc5137f --- /dev/null +++ b/tests/_setup_editor_load_panel_preview.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/_setup_editor_load_panel_preview.gd" id="1"] + +[node name="SetupEditorLoadPanelPreview" type="Node2D"] +script = ExtResource("1") diff --git a/tests/_setup_editor_preview.gd b/tests/_setup_editor_preview.gd index 2cc48dd..4087ba8 100644 --- a/tests/_setup_editor_preview.gd +++ b/tests/_setup_editor_preview.gd @@ -12,3 +12,4 @@ func _ready() -> void: editor.add_enemy("artillery", Vector2(0, 350)) editor.selected_item = {"kind": "capture", "index": 0} editor._rebuild_markers() + editor._refresh_selected_label()