Files
stickman/tests/phase20a_popup_style_preview.gd
T

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)