feat(stage): complete phase 20b action plans
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
extends SceneTree
|
||||
|
||||
const ACTION_EDITOR_SCRIPT := preload("res://scripts/editors/stage/action_editor.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
const PROP_BLOCK_SCRIPT := preload("res://scripts/props/prop_block.gd")
|
||||
const PROP_ANCHOR_DATA := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const STAGE_SELECTION_SCRIPT := preload("res://scripts/stage/stage_selection.gd")
|
||||
const CAPTURE_SESSION_SCRIPT := preload("res://scripts/editors/stage/stage_target_capture_session.gd")
|
||||
const DIRECTOR_VISUALS_SCRIPT := preload("res://scripts/stage/stage_director_visuals.gd")
|
||||
const CAPTURE_DIR := "res://tests/phase20b_captures/pickup_prop_ui"
|
||||
|
||||
var _failures: int = 0
|
||||
var _long_name: bool = false
|
||||
var _resolved_action: Dictionary = {}
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
root.gui_embed_subwindows = true
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(CAPTURE_DIR))
|
||||
var camera := Camera2D.new()
|
||||
root.add_child(camera)
|
||||
camera.make_current()
|
||||
var world := Node2D.new()
|
||||
world.name = "World"
|
||||
root.add_child(world)
|
||||
var eligible: PropBlock = _make_prop("pickup-prop-1", "Brown Suitcase", Vector2(-120.0, 30.0), true)
|
||||
var ineligible: PropBlock = _make_prop("pickup-prop-2", "Garden Gnome", Vector2(190.0, 30.0), false)
|
||||
world.add_child(eligible)
|
||||
world.add_child(ineligible)
|
||||
await _wait_frames(3)
|
||||
|
||||
var visuals: StageDirectorVisuals = DIRECTOR_VISUALS_SCRIPT.new() as StageDirectorVisuals
|
||||
visuals.camera = camera
|
||||
visuals.world = world
|
||||
visuals.ui_font = ThemeDB.fallback_font
|
||||
visuals.emoji_font = ThemeDB.fallback_font
|
||||
root.add_child(visuals)
|
||||
var candidate_props: Array[PropBlock] = [eligible]
|
||||
visuals.set_pickup_candidates(candidate_props, eligible)
|
||||
await _wait_frames(3)
|
||||
if _can_capture_image():
|
||||
var candidate_texture: Texture2D = root.get_texture()
|
||||
if candidate_texture != null:
|
||||
var candidate_image: Image = candidate_texture.get_image()
|
||||
if candidate_image != null and not candidate_image.is_empty():
|
||||
candidate_image.save_png(ProjectSettings.globalize_path(CAPTURE_DIR.path_join("pickup_candidates.png")))
|
||||
_check(visuals.get("_pickup_candidate_props").size() == 1, "only eligible props are highlighted as pickup candidates")
|
||||
_check(visuals.get("_pickup_hovered_prop") == eligible, "hover focus stays on an eligible prop")
|
||||
|
||||
var selection: StageSelection = STAGE_SELECTION_SCRIPT.new(world, camera) as StageSelection
|
||||
var session: StageTargetCaptureSession = CAPTURE_SESSION_SCRIPT.new() as StageTargetCaptureSession
|
||||
session.begin(StageTargetCaptureSession.Kind.PICKUP_PROP, "Choose a prop", Callable(), Callable())
|
||||
var valid_result: Dictionary = session.resolve_target(eligible.global_position, selection, visuals, false, 16.0)
|
||||
_check(bool(valid_result.get("valid", false)) and valid_result.get("value") == eligible, "pickup capture resolves an eligible prop")
|
||||
var invalid_result: Dictionary = session.resolve_target(ineligible.global_position, selection, visuals, false, 16.0)
|
||||
_check(not bool(invalid_result.get("valid", false)) and String(invalid_result.get("reason", "")).contains("grip"), "pickup capture explains why a non-pickup prop cannot be chosen")
|
||||
session.kind = StageTargetCaptureSession.Kind.PROP
|
||||
var generic_result: Dictionary = session.resolve_target(ineligible.global_position, selection, visuals, false, 16.0)
|
||||
_check(bool(generic_result.get("valid", false)) and generic_result.get("value") == ineligible, "generic prop capture remains able to target any prop")
|
||||
var pickup_action: Dictionary = {"type": "pick_up_prop", "target_prop_id": eligible.stage_object_id}
|
||||
_check(ACTION_REGISTRY.summarize(pickup_action, Callable(self, "_resolve_prop_name")) == "Pick Up Prop · Brown Suitcase", "pickup summaries use the stage prop name")
|
||||
_check(ACTION_REGISTRY.summarize(pickup_action) == "Pick Up Prop", "pickup summaries never expose internal IDs when no name resolver is set")
|
||||
visuals.clear_pickup_candidates()
|
||||
await _wait_frames(3)
|
||||
|
||||
var action_editor: ActionEditor = ACTION_EDITOR_SCRIPT.new() as ActionEditor
|
||||
root.add_child(action_editor)
|
||||
action_editor.set_prop_target_name_resolver(Callable(self, "_resolve_prop_name"))
|
||||
action_editor.open_new(false, false, "pick_up_prop")
|
||||
action_editor.set_prop_target(eligible.stage_object_id)
|
||||
await _wait_frames(3)
|
||||
var prop_label: Label = action_editor.get("_prop_target_label") as Label
|
||||
var change_button: Button = action_editor.get("_set_prop_target_btn") as Button
|
||||
var left_button: Button = _button_with_text(action_editor, "Left")
|
||||
var auto_button: Button = _button_with_text(action_editor, "Auto")
|
||||
var right_button: Button = _button_with_text(action_editor, "Right")
|
||||
var done_button: Button = _button_with_text(action_editor, "Done")
|
||||
var cancel_button: Button = _button_with_text(action_editor, "Cancel")
|
||||
_check(prop_label != null and prop_label.text == "Brown Suitcase" and not prop_label.text.contains(eligible.stage_object_id), "pickup dialog shows the friendly stage name instead of the ID")
|
||||
_check(change_button != null and change_button.text == "Change prop" and change_button.get_global_rect().size.x >= 300.0, "Change prop is its own wide control below the name card")
|
||||
_check(left_button != null and auto_button != null and right_button != null and auto_button.custom_minimum_size.y >= 44.0 and left_button.custom_minimum_size.y >= 44.0 and right_button.custom_minimum_size.y >= 44.0, "hand choices are large, visible segmented buttons")
|
||||
_check(done_button != null and cancel_button != null and done_button.get_global_rect().end.y <= root.size.y and cancel_button.get_global_rect().end.y <= root.size.y, "Cancel and Done remain visible in the safe viewport")
|
||||
_long_name = true
|
||||
action_editor.set_prop_target(eligible.stage_object_id)
|
||||
await _wait_frames(3)
|
||||
_check(prop_label.text.length() > 50 and prop_label.get_global_rect().position.x >= 0.0 and prop_label.get_global_rect().end.x <= root.size.x, "a long prop name wraps inside the dialog without clipping its controls")
|
||||
var body_scroll: ScrollContainer = action_editor.find_child("ActionEditorBodyScroll", true, false) as ScrollContainer
|
||||
_check(body_scroll != null and auto_button.get_global_rect().position.y >= body_scroll.get_global_rect().position.y and right_button.get_global_rect().end.y <= body_scroll.get_global_rect().end.y, "all hand choices remain visible without scrolling when the prop name is long")
|
||||
if _can_capture_image():
|
||||
var dialog_texture: Texture2D = root.get_texture()
|
||||
if dialog_texture != null:
|
||||
var dialog_image: Image = dialog_texture.get_image()
|
||||
if dialog_image != null and not dialog_image.is_empty():
|
||||
dialog_image.save_png(ProjectSettings.globalize_path(CAPTURE_DIR.path_join("pickup_action_dialog_long_name.png")))
|
||||
action_editor.call("_set_pickup_hand", "left")
|
||||
_check(String(action_editor.call("_selected_pickup_hand")) == "left", "hand selection records the clicked hand choice")
|
||||
action_editor.committed.connect(func(action: Dictionary) -> void: _resolved_action = action.duplicate(true))
|
||||
action_editor.call("_on_ok_pressed")
|
||||
print("Pickup action commit: %s" % JSON.stringify(_resolved_action))
|
||||
_check(String(_resolved_action.get("target_prop_id", "")) == eligible.stage_object_id and String(_resolved_action.get("hand", "")) == "left", "Done retains the prop ID for saved data and commits the selected hand")
|
||||
|
||||
_check((visuals.get("_pickup_candidate_props") as Array).is_empty(), "pickup highlights can be cleared after capture ends")
|
||||
_print_result()
|
||||
quit(1 if _failures > 0 else 0)
|
||||
|
||||
|
||||
func _make_prop(stage_id: String, display_name: String, position: Vector2, eligible: bool) -> PropBlock:
|
||||
var prop: PropBlock = PROP_BLOCK_SCRIPT.new() as PropBlock
|
||||
prop.name = display_name.replace(" ", "")
|
||||
prop.stage_object_id = stage_id
|
||||
prop.stage_name = display_name
|
||||
prop.position = position
|
||||
prop.polygon_points = PackedVector2Array([Vector2(-44.0, -28.0), Vector2(44.0, -28.0), Vector2(44.0, 28.0), Vector2(-44.0, 28.0)])
|
||||
prop.stage_roles = {"can_pick_up": eligible}
|
||||
prop.set_interaction_anchors(PROP_ANCHOR_DATA.preset_layout("one_handed", Rect2(Vector2(-44.0, -28.0), Vector2(88.0, 56.0))))
|
||||
return prop
|
||||
|
||||
|
||||
func _resolve_prop_name(_prop_id: String) -> String:
|
||||
return "The Very Long Brown Suitcase with a Surprisingly Long Name for Layout Testing" if _long_name else "Brown Suitcase"
|
||||
|
||||
|
||||
func _button_with_text(root_node: Node, button_text: String) -> Button:
|
||||
if root_node == null:
|
||||
return null
|
||||
for candidate: Node in root_node.find_children("*", "Button", true, false):
|
||||
var button: Button = candidate as Button
|
||||
if button != null and button.text == button_text:
|
||||
return button
|
||||
return null
|
||||
|
||||
|
||||
func _wait_frames(count: int) -> void:
|
||||
for _index: int in count:
|
||||
await process_frame
|
||||
|
||||
|
||||
func _can_capture_image() -> bool:
|
||||
var display_name: String = DisplayServer.get_name().to_lower()
|
||||
return not OS.has_feature("headless") and display_name != "headless" and display_name != "dummy"
|
||||
|
||||
|
||||
func _check(condition: bool, description: String) -> void:
|
||||
if condition:
|
||||
print("PASS: %s" % description)
|
||||
else:
|
||||
_failures += 1
|
||||
push_error("FAIL: %s" % description)
|
||||
|
||||
|
||||
func _print_result() -> void:
|
||||
print("PHASE20B_PICKUP_PROP_UI_RESULT failures=%d captures=%s" % [_failures, CAPTURE_DIR])
|
||||
Reference in New Issue
Block a user