219 lines
11 KiB
GDScript
219 lines
11 KiB
GDScript
extends SceneTree
|
|
|
|
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
|
|
const STAGE_DIALOG_STYLER := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
|
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
|
|
|
var _failures: int = 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run_preview")
|
|
|
|
|
|
func _run_preview() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
root.gui_embed_subwindows = true
|
|
var stage: SandboxStage = SANDBOX_STAGE.instantiate() as SandboxStage
|
|
root.add_child(stage)
|
|
await _wait_frames(10)
|
|
|
|
# Keep any existing user recovery copy intact while removing its modal from
|
|
# this visual-only preview. No recovery action or document edit is performed.
|
|
var recovery_dialog: ConfirmationDialog = stage.find_child("AutosaveRecoveryDialog", true, false) as ConfirmationDialog
|
|
if recovery_dialog != null:
|
|
STAGE_DIALOG_STYLER.hide(recovery_dialog)
|
|
await _wait_frames(2)
|
|
|
|
var spawner: StageSpawner = stage.get("_spawner") as StageSpawner
|
|
var first: StickmanRig = spawner.spawn_stickman_asset("res://content/stickmen/break.stk", Vector2(-18.0, 0.0))
|
|
var second: StickmanRig = spawner.spawn_stickman_asset("res://content/stickmen/break.stk", Vector2(18.0, 0.0))
|
|
_check(first != null and second != null, "overlapping character fixtures are available")
|
|
if first == null or second == null:
|
|
quit(1)
|
|
return
|
|
first.stage_name = "Jimmy"
|
|
second.stage_name = "Timmy"
|
|
var crate: PropBlock = spawner.spawn_prop_asset("crate", Vector2(170.0, 190.0))
|
|
_check(crate != null, "prop inspector fixture is available")
|
|
if crate == null:
|
|
quit(1)
|
|
return
|
|
crate.stage_name = "Blue Door"
|
|
var selection: StageSelection = stage.get("_selection") as StageSelection
|
|
selection.select_only(crate)
|
|
await _wait_frames(4)
|
|
var views: StageModalViews = stage.get("_modal_views") as StageModalViews
|
|
var ui_layer: CanvasLayer = stage.get_node("UI") as CanvasLayer
|
|
_check(views != null and ui_layer != null, "stage popup UI is available")
|
|
if views == null or ui_layer == null:
|
|
quit(1)
|
|
return
|
|
var avoid_rects: Array[Rect2] = [
|
|
Rect2(Vector2.ZERO, Vector2(1280.0, 72.0)),
|
|
Rect2(Vector2(0.0, 682.0), Vector2(1280.0, 38.0)),
|
|
Rect2(Vector2.ZERO, Vector2(120.0, 720.0)),
|
|
]
|
|
var prop_view: StagePropSettingsView = stage.get("_prop_settings_view") as StagePropSettingsView
|
|
if prop_view != null and prop_view.visible:
|
|
var prop_card: Control = prop_view.get_card()
|
|
if prop_card != null:
|
|
avoid_rects.append(prop_card.get_global_rect().grow(8.0))
|
|
for character: StickmanRig in [first, second]:
|
|
var world_bounds: Rect2 = STAGE_SELECTION.get_world_aabb(character)
|
|
if world_bounds.has_area():
|
|
avoid_rects.append(stage.call("_world_rect_to_viewport_rect", world_bounds).grow(8.0))
|
|
var click_position: Vector2 = stage.get_viewport().get_canvas_transform() * Vector2.ZERO
|
|
views.show_objects_here(ui_layer, [first, second], selection.get_selected().duplicate(), Vector2(1280.0, 720.0), click_position, avoid_rects)
|
|
await _wait_frames(8)
|
|
var popup: PopupPanel = views.get_objects_here_popup()
|
|
_check(popup != null and popup.visible, "anchored chooser is visible")
|
|
if popup != null:
|
|
var candidate_button: Button = popup.find_child("ObjectCandidate_0", true, false) as Button
|
|
if candidate_button != null:
|
|
candidate_button.grab_focus()
|
|
var gizmos: StageGizmos = stage.get("_gizmos") as StageGizmos
|
|
avoid_rects.append(views.get_objects_here_screen_rect().grow(8.0))
|
|
if gizmos != null:
|
|
gizmos.set_candidate_preview_obstacles(avoid_rects)
|
|
gizmos.set_enabled(true)
|
|
gizmos.set_candidate_preview(first)
|
|
await _wait_frames(4)
|
|
if gizmos != null:
|
|
var name_pill_style: StyleBoxFlat = gizmos.get("_candidate_preview_name_style") as StyleBoxFlat
|
|
_check(name_pill_style != null and name_pill_style.corner_radius_top_left >= 8, "candidate name label uses rounded pill corners")
|
|
_check(name_pill_style != null and name_pill_style.border_width_left >= 3, "candidate name pill keeps a clear cyan outline")
|
|
var shell: StyleBoxFlat = popup.get_theme_stylebox("panel") as StyleBoxFlat
|
|
_check(shell != null and shell.corner_radius_top_left >= 12, "chooser shell has rounded corners")
|
|
_check(shell != null and shell.border_width_left >= 2 and shell.shadow_size > 0, "chooser shell has a border and local shadow")
|
|
var cancel: Button = popup.find_child("CancelObjectsHere", true, false) as Button
|
|
var cancel_style: StyleBoxFlat = cancel.get_theme_stylebox("normal") as StyleBoxFlat if cancel != null else null
|
|
_check(cancel != null and cancel.custom_minimum_size.y >= 48, "Cancel has a large target")
|
|
_check(cancel_style != null and cancel_style.corner_radius_top_left >= 8, "Cancel has rounded secondary styling")
|
|
|
|
await RenderingServer.frame_post_draw
|
|
var capture: Image = root.get_texture().get_image()
|
|
var capture_names: Array[String] = [
|
|
"overlap_chooser_popup_style",
|
|
"character_overlap_preview",
|
|
"overlap_hover_preview",
|
|
"overlap_candidate_preview",
|
|
]
|
|
for capture_name: String in capture_names:
|
|
var capture_path: String = "res://tests/phase20a_captures/%s.png" % capture_name
|
|
var capture_error: Error = capture.save_png(ProjectSettings.globalize_path(capture_path))
|
|
print("PHASE20A_CAPTURE path=%s size=%s error=%d" % [capture_path, capture.get_size(), capture_error])
|
|
_check(capture_error == OK, "capture %s is saved" % capture_name)
|
|
await _verify_overlap_gestures(stage, crate, views, selection)
|
|
await _wait_frames(2)
|
|
stage.queue_free()
|
|
await _wait_frames(2)
|
|
quit(1 if _failures > 0 else 0)
|
|
|
|
|
|
func _verify_overlap_gestures(stage: SandboxStage, selected_prop: PropBlock, views: StageModalViews, selection: StageSelection) -> void:
|
|
var spawner: StageSpawner = stage.get("_spawner") as StageSpawner
|
|
var overlapping_prop: PropBlock = spawner.spawn_prop_asset("crate", selected_prop.global_position) as PropBlock
|
|
_check(overlapping_prop != null, "overlap gesture fixture is available")
|
|
if overlapping_prop == null:
|
|
return
|
|
stage.call("_finalize_placed_node", overlapping_prop)
|
|
await _wait_frames(4)
|
|
var gizmos: StageGizmos = stage.get("_gizmos") as StageGizmos
|
|
var canvas: Transform2D = stage.get_viewport().get_canvas_transform()
|
|
var click_world: Vector2 = selected_prop.global_position
|
|
var click_screen: Vector2 = canvas * click_world
|
|
var candidates: Array[Node2D] = selection.hit_test_all(click_world)
|
|
_check(candidates.has(selected_prop) and candidates.has(overlapping_prop), "gesture fixture has two visible objects at the click point")
|
|
selection.select_only(selected_prop)
|
|
await _wait_frames(2)
|
|
if gizmos != null:
|
|
gizmos.set_snap_size(0.0)
|
|
|
|
await _send_mouse_motion(click_screen)
|
|
await _send_mouse_button(click_screen, true)
|
|
_check(stage.get("_objects_here_popup") == null and not gizmos.is_dragging(), "overlap press waits without opening chooser or moving")
|
|
await _send_mouse_motion(click_screen + Vector2(4.0, 0.0), MOUSE_BUTTON_MASK_LEFT)
|
|
_check(stage.get("_objects_here_popup") == null and not gizmos.is_dragging(), "sub-threshold movement remains a click candidate")
|
|
await _send_mouse_button(click_screen + Vector2(4.0, 0.0), false)
|
|
var click_popup: PopupPanel = stage.get("_objects_here_popup") as PopupPanel
|
|
_check(click_popup != null and click_popup.visible, "release before the threshold opens the overlap chooser")
|
|
_check(selection.is_selected(selected_prop), "opening the chooser preserves the prior selection")
|
|
views.dismiss_objects_here()
|
|
await _wait_frames(2)
|
|
|
|
var start_position: Vector2 = selected_prop.global_position
|
|
var start_screen: Vector2 = canvas * start_position
|
|
var end_screen: Vector2 = start_screen + Vector2(24.0, 12.0)
|
|
var expected_position: Vector2 = canvas.affine_inverse() * end_screen
|
|
await _send_mouse_motion(start_screen)
|
|
await _send_mouse_button(start_screen, true)
|
|
await _send_mouse_motion(end_screen, MOUSE_BUTTON_MASK_LEFT)
|
|
_check(stage.get("_objects_here_popup") == null, "drag past the threshold never opens the overlap chooser")
|
|
_check(gizmos.is_dragging(), "drag past the threshold begins moving the selected overlap")
|
|
_check(selected_prop.global_position.is_equal_approx(expected_position), "selected object follows the overlap drag")
|
|
_check(overlapping_prop.global_position.is_equal_approx(start_position), "unselected overlapping object stays in place")
|
|
await _send_mouse_button(end_screen, false)
|
|
_check(stage.get("_objects_here_popup") == null, "drag release does not reopen the chooser")
|
|
_check(not gizmos.is_dragging(), "drag release finishes the selected-object move")
|
|
|
|
# Verify rotate and resize handles remain ahead of overlap selection.
|
|
selected_prop.global_position = start_position
|
|
overlapping_prop.global_position = start_position
|
|
selection.select_only(selected_prop)
|
|
await _wait_frames(2)
|
|
var prop_bounds: Rect2 = STAGE_SELECTION.get_world_aabb(selected_prop)
|
|
var scale_handle: Vector2 = Vector2(prop_bounds.end.x, prop_bounds.position.y)
|
|
var scale_screen: Vector2 = canvas * scale_handle
|
|
await _send_mouse_motion(scale_screen)
|
|
await _send_mouse_button(scale_screen, true)
|
|
_check(gizmos.is_dragging() and gizmos._dragging == StageGizmos.Handle.SCALE, "overlapped resize handle starts scaling immediately")
|
|
_check(stage.get("_objects_here_popup") == null, "resize handle does not open the overlap chooser")
|
|
await _send_mouse_button(scale_screen, false)
|
|
_check(not gizmos.is_dragging(), "resize handle release completes without opening chooser")
|
|
|
|
selected_prop.global_position = start_position
|
|
await _wait_frames(2)
|
|
prop_bounds = STAGE_SELECTION.get_world_aabb(selected_prop)
|
|
var rotate_handle: Vector2 = gizmos._rotate_handle_position(prop_bounds)
|
|
var rotate_screen: Vector2 = canvas * rotate_handle
|
|
await _send_mouse_motion(rotate_screen)
|
|
await _send_mouse_button(rotate_screen, true)
|
|
_check(gizmos.is_dragging() and gizmos._dragging == StageGizmos.Handle.ROTATE, "overlapped rotate handle starts rotation immediately")
|
|
_check(stage.get("_objects_here_popup") == null, "rotate handle does not open the overlap chooser")
|
|
await _send_mouse_button(rotate_screen, false)
|
|
_check(not gizmos.is_dragging(), "rotate handle release completes without opening chooser")
|
|
|
|
|
|
func _send_mouse_button(position: Vector2, pressed: bool) -> void:
|
|
var event: InputEventMouseButton = InputEventMouseButton.new()
|
|
event.button_index = MOUSE_BUTTON_LEFT
|
|
event.position = position
|
|
event.global_position = position
|
|
event.pressed = pressed
|
|
Input.parse_input_event(event)
|
|
await process_frame
|
|
|
|
|
|
func _send_mouse_motion(position: Vector2, button_mask: int = 0) -> void:
|
|
var event: InputEventMouseMotion = InputEventMouseMotion.new()
|
|
event.position = position
|
|
event.global_position = position
|
|
event.button_mask = button_mask
|
|
Input.parse_input_event(event)
|
|
await process_frame
|
|
|
|
|
|
func _wait_frames(count: int) -> void:
|
|
for _index: int in count:
|
|
await process_frame
|
|
await RenderingServer.frame_post_draw
|
|
|
|
|
|
func _check(condition: bool, label: String) -> void:
|
|
if condition:
|
|
print("PHASE20A_CHECK PASS: %s" % label)
|
|
else:
|
|
_failures += 1
|
|
push_error("PHASE20A_CHECK FAIL: %s" % label)
|