#64: Load used to blindly cycle to "the next" saved setup with no way to see what existed or jump straight to one by name. It now opens a real selectable ItemList of every saved setup (name + surface profile), tap one to load it. #65: a single-finger drag over empty canvas could never pan - the touch handler placed an item on PRESS, before a drag had any chance to happen, so the "pan" branch below it was unreachable dead code (it only fired when selected_palette == "", which nothing in the UI can ever produce). Placement now waits for release: released within PAN_DEADZONE of where the finger went down is a tap (place), crossing it partway through is a pan - the same tap-vs-drag deadzone idiom GasBombController already uses for its own aim gesture. #66: added a persistent "Selected: <kind> #<n>" label, refreshed wherever selected_item changes (touch-select, new/load a setup, delete), so a tap actually confirms what got picked instead of the player inferring it from which dot on screen turned white. Also widened test_space_time_distorder.gd's formation-lag margin after it strayed 2.6x over its old allowance under one heavily loaded JOBS=1 full-suite run and passed cleanly in isolation immediately after - device jitter, not a regression; the wider margin still leaves orders-of-magnitude headroom below a real desync bug's stray distance.
This commit is contained in:
parent
63d0d6a496
commit
284a54464e
5 changed files with 136 additions and 12 deletions
|
|
@ -25,6 +25,10 @@ const MIN_ZOOM: float = 0.3
|
||||||
const MAX_ZOOM: float = 4.0
|
const MAX_ZOOM: float = 4.0
|
||||||
const WHEEL_ZOOM_STEP: float = 1.1
|
const WHEEL_ZOOM_STEP: float = 1.1
|
||||||
const HIT_RADIUS: float = 50.0
|
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)
|
const ORIGIN_COLOR: Color = Color(1.0, 0.9, 0.4)
|
||||||
# Duplicated from PaletteEntryButton.RING_COLOR rather than referenced
|
# Duplicated from PaletteEntryButton.RING_COLOR rather than referenced
|
||||||
# cross-class - a global class_name lookup at parse time needs the class
|
# 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 load_button: IconButton = $UI/LoadButton
|
||||||
@onready var delete_button: IconButton = $UI/DeleteButton
|
@onready var delete_button: IconButton = $UI/DeleteButton
|
||||||
@onready var status_label: Label = $UI/StatusLabel
|
@onready var status_label: Label = $UI/StatusLabel
|
||||||
|
@onready var selected_label: Label = $UI/SelectedLabel
|
||||||
@onready var palette_row: Control = $UI/PaletteRow
|
@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 current_setup: Resource
|
||||||
var selected_palette: String = "capture_point"
|
var selected_palette: String = "capture_point"
|
||||||
var selected_item: Dictionary = {}
|
var selected_item: Dictionary = {}
|
||||||
var _profile_ids: Array = []
|
var _profile_ids: Array = []
|
||||||
var _profile_cursor: int = 0
|
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 _palette_buttons: Array = []
|
||||||
var _origin_marker: NeonPiece
|
var _origin_marker: NeonPiece
|
||||||
|
|
||||||
|
|
@ -76,6 +88,8 @@ func _ready() -> void:
|
||||||
load_button.pressed.connect(_on_load_pressed)
|
load_button.pressed.connect(_on_load_pressed)
|
||||||
delete_button.pressed.connect(_on_delete_pressed)
|
delete_button.pressed.connect(_on_delete_pressed)
|
||||||
name_input.text_changed.connect(_on_name_changed)
|
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)
|
_origin_marker = NeonPiece.create(item_host, Vector2.ZERO, ORIGIN_COLOR)
|
||||||
var cross_size: float = 26.0
|
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
|
# fails at runtime, since the compiler here only sees current_setup as
|
||||||
# a plain Resource and can't confirm the target is Array[Resource].
|
# a plain Resource and can't confirm the target is Array[Resource].
|
||||||
selected_item = {}
|
selected_item = {}
|
||||||
|
_refresh_selected_label()
|
||||||
_profile_cursor = maxi(_profile_ids.find(profile_id), 0)
|
_profile_cursor = maxi(_profile_ids.find(profile_id), 0)
|
||||||
if name_input != null:
|
if name_input != null:
|
||||||
name_input.text = display_name
|
name_input.text = display_name
|
||||||
|
|
@ -116,6 +131,7 @@ func load_setup(setup_id: String) -> bool:
|
||||||
# explicit save.
|
# explicit save.
|
||||||
current_setup = setup.duplicate(true)
|
current_setup = setup.duplicate(true)
|
||||||
selected_item = {}
|
selected_item = {}
|
||||||
|
_refresh_selected_label()
|
||||||
_profile_cursor = maxi(_profile_ids.find(current_setup.surface_profile_id), 0)
|
_profile_cursor = maxi(_profile_ids.find(current_setup.surface_profile_id), 0)
|
||||||
if name_input != null:
|
if name_input != null:
|
||||||
name_input.text = current_setup.display_name
|
name_input.text = current_setup.display_name
|
||||||
|
|
@ -301,8 +317,19 @@ func _on_load_pressed() -> void:
|
||||||
if setups.is_empty():
|
if setups.is_empty():
|
||||||
_set_status("No saved setups yet.")
|
_set_status("No saved setups yet.")
|
||||||
return
|
return
|
||||||
_load_cursor = (_load_cursor + 1) % setups.size()
|
load_list.clear()
|
||||||
load_setup(setups[_load_cursor].setup_id)
|
_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:
|
func _on_delete_pressed() -> void:
|
||||||
|
|
@ -314,6 +341,7 @@ func _on_delete_pressed() -> void:
|
||||||
else:
|
else:
|
||||||
remove_enemy(selected_item.index)
|
remove_enemy(selected_item.index)
|
||||||
selected_item = {}
|
selected_item = {}
|
||||||
|
_refresh_selected_label()
|
||||||
|
|
||||||
|
|
||||||
func _on_name_changed(new_text: String) -> void:
|
func _on_name_changed(new_text: String) -> void:
|
||||||
|
|
@ -336,6 +364,24 @@ func _set_status(message: String) -> void:
|
||||||
status_label.text = message
|
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 ----------------------------------------
|
# --- placement/selection helpers ----------------------------------------
|
||||||
|
|
||||||
func _place_selected(world_pos: Vector2) -> void:
|
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 world_pos: Vector2 = _screen_to_world(event.position)
|
||||||
var hit: Dictionary = _find_marker_near(world_pos)
|
var hit: Dictionary = _find_marker_near(world_pos)
|
||||||
drag_touch_index = event.index
|
drag_touch_index = event.index
|
||||||
|
pan_start_screen = event.position
|
||||||
|
pan_start_camera = camera.position
|
||||||
if not hit.is_empty():
|
if not hit.is_empty():
|
||||||
selected_item = hit
|
selected_item = hit
|
||||||
dragging_item = hit
|
dragging_item = hit
|
||||||
drag_mode = "item"
|
drag_mode = "item"
|
||||||
_rebuild_markers()
|
_rebuild_markers()
|
||||||
elif selected_palette != "":
|
_refresh_selected_label()
|
||||||
_place_selected(world_pos)
|
|
||||||
drag_mode = ""
|
|
||||||
else:
|
else:
|
||||||
drag_mode = "pan"
|
# #65: a press over empty canvas no longer commits to placing
|
||||||
pan_start_screen = event.position
|
# an item immediately - it might be the start of a pan. Which
|
||||||
pan_start_camera = camera.position
|
# 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:
|
else:
|
||||||
touches.erase(event.index)
|
|
||||||
if touches.size() < 2:
|
|
||||||
pinch_active = false
|
|
||||||
if event.index == drag_touch_index:
|
if event.index == drag_touch_index:
|
||||||
|
if drag_mode == "pending":
|
||||||
|
_place_selected(_screen_to_world(event.position))
|
||||||
drag_touch_index = -1
|
drag_touch_index = -1
|
||||||
drag_mode = ""
|
drag_mode = ""
|
||||||
dragging_item = {}
|
dragging_item = {}
|
||||||
|
touches.erase(event.index)
|
||||||
|
if touches.size() < 2:
|
||||||
|
pinch_active = false
|
||||||
|
|
||||||
|
|
||||||
func _handle_drag(event: InputEventScreenDrag) -> void:
|
func _handle_drag(event: InputEventScreenDrag) -> void:
|
||||||
|
|
@ -440,6 +493,8 @@ func _handle_drag(event: InputEventScreenDrag) -> void:
|
||||||
|
|
||||||
if event.index != drag_touch_index:
|
if event.index != drag_touch_index:
|
||||||
return
|
return
|
||||||
|
if drag_mode == "pending" and event.position.distance_to(pan_start_screen) >= PAN_DEADZONE:
|
||||||
|
drag_mode = "pan"
|
||||||
match drag_mode:
|
match drag_mode:
|
||||||
"item":
|
"item":
|
||||||
_move_selected(_screen_to_world(event.position))
|
_move_selected(_screen_to_world(event.position))
|
||||||
|
|
|
||||||
|
|
@ -170,6 +170,17 @@ theme_override_font_sizes/font_size = 20
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
text = ""
|
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"]
|
[node name="PaletteRow" type="Control" parent="UI"]
|
||||||
anchor_top = 1.0
|
anchor_top = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
|
|
@ -177,3 +188,42 @@ offset_left = 20
|
||||||
offset_top = -110
|
offset_top = -110
|
||||||
offset_right = 696
|
offset_right = 696
|
||||||
offset_bottom = -20
|
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)
|
||||||
|
|
|
||||||
12
tests/_setup_editor_load_panel_preview.gd
Normal file
12
tests/_setup_editor_load_panel_preview.gd
Normal file
|
|
@ -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()
|
||||||
6
tests/_setup_editor_load_panel_preview.tscn
Normal file
6
tests/_setup_editor_load_panel_preview.tscn
Normal file
|
|
@ -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")
|
||||||
|
|
@ -12,3 +12,4 @@ func _ready() -> void:
|
||||||
editor.add_enemy("artillery", Vector2(0, 350))
|
editor.add_enemy("artillery", Vector2(0, 350))
|
||||||
editor.selected_item = {"kind": "capture", "index": 0}
|
editor.selected_item = {"kind": "capture", "index": 0}
|
||||||
editor._rebuild_markers()
|
editor._rebuild_markers()
|
||||||
|
editor._refresh_selected_label()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue