- Implemented StageSurfaceColorDialog for custom color selection with live preview. - Created StageBackgroundGround class for non-walkable scenery behind the set. - Introduced StageRectSurface for shared rectangular geometry and tint preview. - Added StageSurfacePreview for rendering surface previews. - Developed comprehensive tests for surface color selection, rendering, and serialization.
506 lines
27 KiB
GDScript
506 lines
27 KiB
GDScript
extends SceneTree
|
|
|
|
const SANDBOX_STAGE: PackedScene = preload("res://scenes/stage/sandbox_stage.tscn")
|
|
const STAGE_SCENE_IO: Script = preload("res://scripts/stage/stage_scene_io.gd")
|
|
const STAGE_SCENE_ADAPTER: Script = preload("res://scripts/editors/stage/stage_scene_adapter.gd")
|
|
const STAGE_SURFACE_RENDERER: Script = preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
|
const STAGE_BACKGROUND_GROUND: Script = preload("res://scripts/stage/scenery/stage_background_ground.gd")
|
|
const STAGE_WALL_PANEL: Script = preload("res://scripts/stage/scenery/stage_wall_panel.gd")
|
|
const STAGE_SURFACE_PREVIEW: Script = preload("res://scripts/stage/scenery/stage_surface_preview.gd")
|
|
|
|
var _failures: Array[String] = []
|
|
var _stage: SandboxStage
|
|
var _initial_window_size: Vector2i = Vector2i(1280, 720)
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
_set_window_geometry(Vector2i(1280, 720))
|
|
_initial_window_size = root.size
|
|
if DisplayServer.get_name() != "headless":
|
|
print("GUI capture window: usable=%s position=%s size=%s" % [str(DisplayServer.screen_get_usable_rect(DisplayServer.window_get_current_screen())), str(root.position), str(root.size)])
|
|
_test_v15_migration()
|
|
await _test_grass_render()
|
|
_stage = SANDBOX_STAGE.instantiate() as SandboxStage
|
|
root.add_child(_stage)
|
|
await process_frame
|
|
await process_frame
|
|
var world: Node2D = _stage.get_node("World") as Node2D
|
|
var controller: StageSceneryController = _stage.get("_scenery_controller") as StageSceneryController
|
|
var selected_ground: StageBackgroundGround = _place_background_ground(world, controller)
|
|
_test_background_ground_model_and_serialization(world, selected_ground)
|
|
await _test_surface_inspector_and_color_transactions(selected_ground)
|
|
await _test_surface_erase_confirmation(world, selected_ground)
|
|
await _test_selector_setup_routes(controller)
|
|
if _stage != null and is_instance_valid(_stage):
|
|
_stage.queue_free()
|
|
await process_frame
|
|
await process_frame
|
|
if _failures.is_empty():
|
|
print("PASS: Phase 19 background ground, color, migration, and erase behavior")
|
|
else:
|
|
for failure: String in _failures:
|
|
push_error("FAIL: " + failure)
|
|
quit(0 if _failures.is_empty() else 1)
|
|
|
|
|
|
func _test_v15_migration() -> void:
|
|
var source: Dictionary = {
|
|
"version": "1.5",
|
|
"sky_id": "sky_cloudy_day",
|
|
"objects": [
|
|
{"file_id": "ground_1", "type": "terrain", "asset_id": "ground", "position": {"x": 0.0, "y": 0.0}, "finish_id": "finish_ground_sidewalk"},
|
|
{"file_id": "wall_1", "type": "wall_panel", "position": {"x": 0.0, "y": 0.0}, "size": {"x": 320.0, "y": 240.0}, "finish_id": "finish_wall_brick", "color": "#5A9D70FF"},
|
|
{"file_id": "wall_2", "type": "wall_panel", "position": {"x": 400.0, "y": 0.0}, "size": {"x": 320.0, "y": 240.0}, "finish_id": "finish_wall_wood", "color": "#4477AAFF"}
|
|
]
|
|
}
|
|
var migrated: Dictionary = STAGE_SCENE_IO._migrate_to_current(source)
|
|
_expect(String(migrated.get("version", "")) == "1.6", "v1.5 scenes migrate to v1.6")
|
|
_expect(String(migrated.get("sky_id", "")) == "sky_cloudy_day", "v1.5 migration preserves the saved Sky")
|
|
var objects: Array = migrated.get("objects", []) as Array
|
|
_expect(String((objects[0] as Dictionary).get("finish_id", "")) == "finish_ground_sidewalk", "v1.5 migration preserves walkable terrain finishes")
|
|
var migrated_brick_color: String = String((objects[1] as Dictionary).get("color", "")).to_upper()
|
|
var expected_brick_color: String = "#" + STAGE_SURFACE_RENDERER.default_tint(&"finish_wall_brick").to_html(true).to_upper()
|
|
_expect(migrated_brick_color == expected_brick_color, "old placeholder brick tint becomes the natural brick color")
|
|
_expect(String((objects[2] as Dictionary).get("color", "")) == "#4477AAFF", "intentional v1.5 wood color is preserved")
|
|
var current: Dictionary = source.duplicate(true)
|
|
current["version"] = "1.6"
|
|
var current_objects: Array = current["objects"] as Array
|
|
(current_objects[1] as Dictionary)["color"] = "#5A9D70FF"
|
|
var current_migrated: Dictionary = STAGE_SCENE_IO._migrate_to_current(current)
|
|
_expect(String(((current_migrated["objects"] as Array)[1] as Dictionary).get("color", "")) == "#5A9D70FF", "v1.6 brick colors remain authoritative")
|
|
|
|
|
|
func _test_grass_render() -> void:
|
|
if DisplayServer.get_name() == "headless":
|
|
print("SKIP: headless dummy renderer provides no visual surface capture; run this fixture in the GUI renderer")
|
|
return
|
|
var viewport := SubViewport.new()
|
|
viewport.size = Vector2i(640, 320)
|
|
viewport.transparent_bg = true
|
|
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
root.add_child(viewport)
|
|
var surface: StageBackgroundGround = STAGE_BACKGROUND_GROUND.new() as StageBackgroundGround
|
|
surface.configure(Vector2(10000.0, 6000.0), &"finish_background_grass", Color("#72A84C"))
|
|
surface.position = Vector2(320.0, 160.0)
|
|
viewport.add_child(surface)
|
|
await process_frame
|
|
await process_frame
|
|
var texture: Texture2D = viewport.get_texture()
|
|
if texture == null:
|
|
print("SKIP: headless dummy renderer provides no SubViewport texture; visual grass capture requires the GUI renderer")
|
|
viewport.queue_free()
|
|
await process_frame
|
|
return
|
|
if not texture.get_rid().is_valid():
|
|
print("SKIP: headless dummy renderer has no texture RID; visual grass capture requires the GUI renderer")
|
|
viewport.queue_free()
|
|
await process_frame
|
|
return
|
|
var image: Image = texture.get_image()
|
|
if image == null:
|
|
print("SKIP: headless dummy renderer cannot read a SubViewport image; visual capture requires the GUI renderer")
|
|
viewport.queue_free()
|
|
await process_frame
|
|
return
|
|
var distinct: Dictionary = {}
|
|
var detail_samples: int = 0
|
|
var base := Color("#72A84C")
|
|
for y: int in range(12, 308, 2):
|
|
for x: int in range(12, 628, 2):
|
|
var sample: Color = image.get_pixel(x, y)
|
|
distinct[sample.to_html(true)] = true
|
|
var color_distance: float = absf(sample.r - base.r) + absf(sample.g - base.g) + absf(sample.b - base.b)
|
|
if color_distance > 0.08:
|
|
detail_samples += 1
|
|
_expect(distinct.size() >= 5 and detail_samples >= 1100, "large grass ground renders dense, visible interior grass texture at normal Stage scale (samples=%d)" % detail_samples)
|
|
image.save_png("/private/tmp/phase19_background_grass.png")
|
|
viewport.queue_free()
|
|
await process_frame
|
|
|
|
|
|
func _place_background_ground(world: Node2D, controller: StageSceneryController) -> StageBackgroundGround:
|
|
controller.set_grid_snap(false, 15.0)
|
|
controller.begin_background_ground(&"finish_background_grass", Vector2(300.0, 100.0), Color("#72A84C"))
|
|
var press := InputEventMouseButton.new()
|
|
press.button_index = MOUSE_BUTTON_LEFT
|
|
press.pressed = true
|
|
controller.handle_mouse_button(press, Vector2(300.0, 100.0), false)
|
|
var release := InputEventMouseButton.new()
|
|
release.button_index = MOUSE_BUTTON_LEFT
|
|
release.pressed = false
|
|
controller.handle_mouse_button(release, Vector2(700.0, 340.0), false)
|
|
var placed: StageBackgroundGround
|
|
for child: Node in world.get_children():
|
|
if child.get_script() == STAGE_BACKGROUND_GROUND:
|
|
placed = child as StageBackgroundGround
|
|
_expect(placed != null, "production rectangle placement commits a background ground node")
|
|
var placed_node: Node = placed
|
|
_expect(placed != null and not (placed_node is CollisionObject2D), "background ground remains non-physical and non-walkable")
|
|
return placed
|
|
|
|
|
|
func _test_background_ground_model_and_serialization(world: Node2D, surface: StageBackgroundGround) -> void:
|
|
if surface == null:
|
|
return
|
|
print("[background ground size] %s" % str(surface.panel_size))
|
|
_expect(surface.panel_size.is_equal_approx(Vector2(400.0, 240.0)), "background ground size follows the drawn rectangle")
|
|
var sample_wall: StageWallPanel = STAGE_WALL_PANEL.new() as StageWallPanel
|
|
sample_wall.configure(Vector2(100.0, 100.0), &"finish_wall_paint", Color.WHITE)
|
|
_expect(surface.z_index < sample_wall.z_index, "background ground draws behind wall panels")
|
|
sample_wall.free()
|
|
var selection: StageSelection = _stage.get("_selection") as StageSelection
|
|
selection.set_scope(StageSelection.SelectionScope.SCENERY)
|
|
_expect(selection.hit_test(surface.global_position) == surface, "Edit Scenery hit-testing can select background ground")
|
|
var data: Dictionary = _stage.call("_collect_scene_data", false)
|
|
var validation: Dictionary = STAGE_SCENE_IO._validate_root(data)
|
|
_expect(String(data.get("version", "")) == "1.6" and bool(validation.get("ok", false)), "background ground serializes and validates in the current scene schema")
|
|
var found: Dictionary = {}
|
|
for entry_value: Variant in data.get("objects", []):
|
|
if entry_value is Dictionary and String((entry_value as Dictionary).get("type", "")) == "background_ground":
|
|
found = entry_value as Dictionary
|
|
_expect(String(found.get("finish_id", "")) == "finish_background_grass", "scene adapter saves the distinct background finish")
|
|
var restored_world := Node2D.new()
|
|
root.add_child(restored_world)
|
|
var adapter: StageSceneAdapter = _stage.get("_scene_adapter") as StageSceneAdapter
|
|
var isolated: Dictionary = data.duplicate(true)
|
|
isolated["objects"] = [found]
|
|
var result: Dictionary = adapter.apply(isolated, restored_world, _stage.get("_spawner") as StageSpawner)
|
|
var restored: StageBackgroundGround = null
|
|
for child: Node in restored_world.get_children():
|
|
if child.get_script() == STAGE_BACKGROUND_GROUND:
|
|
restored = child as StageBackgroundGround
|
|
_expect(restored != null and restored.panel_size == surface.panel_size and restored.tint.is_equal_approx(surface.tint), "background ground round-trips through the production adapter")
|
|
_expect((result.get("loaded_nodes", []) as Array).size() == 1, "background ground reload is a separate object")
|
|
restored_world.queue_free()
|
|
|
|
|
|
func _test_surface_inspector_and_color_transactions(surface: StageBackgroundGround) -> void:
|
|
if surface == null:
|
|
return
|
|
_stage.call("_enter_scenery_edit_mode")
|
|
var selection: StageSelection = _stage.get("_selection") as StageSelection
|
|
selection.select_only(surface)
|
|
var inspector: StageInspectorPanel = _stage.get("_scenery_inspector") as StageInspectorPanel
|
|
var colors: StageSurfaceColorControls = inspector.find_child("ScenerySurfaceColors", true, false) as StageSurfaceColorControls
|
|
var ground_looks: StagePanelChoiceGroup = inspector.find_child("BackgroundGroundLooks", true, false) as StagePanelChoiceGroup
|
|
_expect(inspector.visible and surface.get_parent() == _stage.get_node("World"), "selected background ground opens its normal scenery inspector")
|
|
_expect(not colors.visible and ground_looks.visible, "Grass hides Color while showing its Look choices")
|
|
_set_window_geometry(Vector2i(640, 480))
|
|
_stage.call("_refresh_scenery_inspector_layout")
|
|
inspector.set_safe_rect(_stage.call("_prop_settings_safe_rect"))
|
|
inspector.request_fit()
|
|
await process_frame
|
|
await process_frame
|
|
await process_frame
|
|
var inspector_rect: Rect2 = inspector.get_global_rect()
|
|
_expect(inspector_rect.position.x >= 0.0 and inspector_rect.position.y >= 0.0 and inspector_rect.end.x <= 640.0 and inspector_rect.end.y <= 480.0, "selected-piece inspector fits a narrow display")
|
|
var capture_bar: StageUseTargetCaptureBar = _stage.get("_use_target_capture_bar") as StageUseTargetCaptureBar
|
|
var capture_bar_rect: Rect2 = capture_bar.get_global_rect() if capture_bar != null else Rect2()
|
|
_expect(capture_bar != null and (not capture_bar.visible or capture_bar_rect.end.x <= inspector_rect.position.x + 1.0), "narrow scenery actions stay beside the selected-piece inspector")
|
|
var chrome: CanvasLayer = _stage.get("_chrome") as CanvasLayer
|
|
var mode_buttons: Dictionary = chrome.call("get_mode_buttons") as Dictionary if chrome != null else {}
|
|
var grid_check: CheckBox = chrome.call("get_grid_check") as CheckBox if chrome != null else null
|
|
var snap_check: CheckBox = chrome.call("get_snap_check") as CheckBox if chrome != null else null
|
|
var grid_size: SpinBox = chrome.call("get_grid_size_spin") as SpinBox if chrome != null else null
|
|
for mode_button_value: Variant in mode_buttons.values():
|
|
var mode_button: Button = mode_button_value as Button
|
|
for right_control: Control in [grid_check, snap_check, grid_size]:
|
|
if right_control != null and right_control.visible:
|
|
_expect(not mode_button.get_global_rect().intersects(right_control.get_global_rect()), "narrow Stage mode and Grid/Snap controls do not overlap")
|
|
_capture_root_viewport("phase19_inspector_narrow.png")
|
|
_set_window_geometry(_initial_window_size)
|
|
await process_frame
|
|
var original: Color = surface.tint
|
|
_stage.call("_set_scene_dirty", false)
|
|
_stage.call("_on_selected_surface_look_choice", &"finish_background_carpet")
|
|
_expect(surface.finish_id == &"finish_background_carpet" and surface.tint.is_equal_approx(original), "switching to Carpet preserves the selected piece's stored color")
|
|
_expect(colors.visible, "Carpet exposes the shared Color control")
|
|
_stage.call("_set_scene_dirty", false)
|
|
var dialog: StageSurfaceColorDialog = _stage.get("_surface_color_dialog") as StageSurfaceColorDialog
|
|
colors.custom_color_requested.emit()
|
|
var tentative := Color("#3F72B5")
|
|
var picker: ColorPicker = dialog.find_child("ColorPickerControl", true, false) as ColorPicker
|
|
if picker != null:
|
|
picker.color = tentative
|
|
if not surface.get_render_tint().is_equal_approx(tentative):
|
|
dialog.call("_on_picker_color_changed", tentative)
|
|
else:
|
|
dialog.preview_color_changed.emit(tentative)
|
|
_expect(not surface.tint.is_equal_approx(tentative) and surface.get_render_tint().is_equal_approx(tentative), "custom-color picker changes only the temporary render tint")
|
|
var dialog_preview: StageSurfacePreview = dialog.get("_surface_preview") as StageSurfacePreview
|
|
_expect(dialog_preview != null and dialog_preview.finish_id == surface.finish_id and dialog_preview.tint.is_equal_approx(tentative), "custom-color dialog preview follows the selected look and tentative color")
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
_capture_gui_screen("phase19_color_preview.png")
|
|
_expect(not bool((_stage.get("_document") as StageDocumentController).dirty), "tentative color does not dirty the Stage document")
|
|
var saved: Dictionary = _stage.call("_collect_scene_data", true)
|
|
var saved_surface: Dictionary = _find_object(saved, "background_ground")
|
|
_expect(String(saved_surface.get("color", "")).to_upper() == "#" + original.to_html(true).to_upper(), "autosave snapshot cannot capture a tentative color")
|
|
_stage.call("_on_surface_color_dialog_cancelled")
|
|
_expect(surface.get_render_tint().is_equal_approx(original), "Cancel restores the selected piece's prior color")
|
|
_expect(not bool((_stage.get("_document") as StageDocumentController).dirty), "Cancel leaves the Stage clean")
|
|
colors.custom_color_requested.emit()
|
|
if picker != null:
|
|
picker.color = tentative
|
|
dialog.call("_confirm_color")
|
|
await process_frame
|
|
_expect(surface.tint.is_equal_approx(tentative), "Use color commits the chosen tint to the selected piece")
|
|
_expect(bool((_stage.get("_document") as StageDocumentController).dirty), "confirmed color marks the Stage document dirty")
|
|
var recent: Array[Color] = _stage.get("_recent_surface_colors")
|
|
_expect(not recent.is_empty() and recent[0].is_equal_approx(tentative), "confirmed color enters the shared recent-color history")
|
|
for index: int in range(10):
|
|
_stage.call("_record_recent_surface_color", Color.from_hsv(float(index) / 10.0, 0.78, 0.92))
|
|
recent = _stage.get("_recent_surface_colors")
|
|
var recent_keys: Array[String] = _stage.call("_surface_color_history_strings")
|
|
_expect(recent.size() == 8 and recent_keys.size() == 8 and recent_keys[0] == _stage.call("_canonical_surface_color", Color.from_hsv(0.9, 0.78, 0.92)), "shared recent-color history deduplicates and keeps the newest eight colors")
|
|
_stage.call("_on_selected_surface_look_choice", &"finish_background_grass")
|
|
_stage.call("_on_selected_surface_look_choice", &"finish_background_carpet")
|
|
_expect(surface.tint.is_equal_approx(tentative), "Carpet keeps its color when switching to Grass and back")
|
|
|
|
|
|
func _test_surface_erase_confirmation(world: Node2D, background: StageBackgroundGround) -> void:
|
|
if background == null:
|
|
return
|
|
var wall: StageWallPanel = STAGE_WALL_PANEL.new() as StageWallPanel
|
|
wall.configure(Vector2(120.0, 120.0), &"finish_wall_wood", Color("#C87137"))
|
|
wall.position = Vector2(900.0, 400.0)
|
|
world.add_child(wall)
|
|
var selection: StageSelection = _stage.get("_selection") as StageSelection
|
|
selection.select_only(background)
|
|
_stage.call("_set_scene_dirty", false)
|
|
_stage.call("_begin_selected_wall_erase")
|
|
_expect(not (_stage.get("_scenery_controller") as StageSceneryController).is_active(), "wall-area eraser cannot start from a background ground selection")
|
|
_stage.call("_request_erase_selected_scenery")
|
|
var dialog: ConfirmationDialog = _stage.get("_scenery_erase_piece_dialog") as ConfirmationDialog
|
|
_expect(dialog.visible and dialog.dialog_text.contains("Erase this piece?"), "Erase background ground asks for confirmation")
|
|
dialog.canceled.emit()
|
|
dialog.hide()
|
|
_expect(is_instance_valid(background) and background.get_parent() == world, "Keep it leaves background ground in place")
|
|
_expect(not bool((_stage.get("_document") as StageDocumentController).dirty), "Keep it leaves the Stage clean")
|
|
selection.select_only(background)
|
|
_stage.call("_request_erase_selected_scenery")
|
|
dialog.confirmed.emit()
|
|
dialog.hide()
|
|
await process_frame
|
|
_expect(not is_instance_valid(background), "confirmed Erase removes only the captured background ground")
|
|
_expect(bool((_stage.get("_document") as StageDocumentController).dirty), "confirmed Erase dirties the Stage once")
|
|
_stage.call("_set_scene_dirty", false)
|
|
# Use a fresh background node so the Delete-key group route can verify its count.
|
|
var second_ground: StageBackgroundGround = STAGE_BACKGROUND_GROUND.new() as StageBackgroundGround
|
|
second_ground.configure(Vector2(80.0, 80.0), &"finish_background_concrete", Color("#A6AAA3"))
|
|
world.add_child(second_ground)
|
|
selection.clear_selection()
|
|
selection.select_only(wall)
|
|
selection.add_to_selection(second_ground)
|
|
var delete_event := InputEventKey.new()
|
|
delete_event.keycode = KEY_DELETE
|
|
delete_event.pressed = true
|
|
_stage.call("_unhandled_key_input", delete_event)
|
|
_expect(dialog.visible and dialog.dialog_text.contains("2 scenery pieces"), "Delete uses a counted confirmation for a scenery group")
|
|
dialog.canceled.emit()
|
|
dialog.hide()
|
|
_expect(is_instance_valid(wall) and is_instance_valid(second_ground), "Cancel leaves every group member intact")
|
|
_stage.call("_request_erase_selected_scenery")
|
|
dialog.confirmed.emit()
|
|
dialog.hide()
|
|
await process_frame
|
|
_expect(not is_instance_valid(wall) and not is_instance_valid(second_ground), "confirmed group erase removes only its captured scenery members")
|
|
|
|
|
|
func _test_selector_setup_routes(controller: StageSceneryController) -> void:
|
|
var browser: StageAssetBrowser = _stage.get("_asset_browser") as StageAssetBrowser
|
|
var selector: AssetSelector = browser.get_selector()
|
|
_expect(is_instance_valid(selector), "Scenery chooser instance is live")
|
|
if not is_instance_valid(selector):
|
|
return
|
|
var gui_render: bool = DisplayServer.get_name() != "headless"
|
|
if gui_render:
|
|
_stage.call("_open_selector", "scenery")
|
|
await process_frame
|
|
await process_frame
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
_capture_gui_screen("phase19_chooser.png")
|
|
else:
|
|
# Avoid PopupPanel.popup_centered() under the macOS dummy renderer while
|
|
# still exercising the production selector signal and StageAssetBrowser routing.
|
|
browser.set_selector_kind("scenery")
|
|
selector.kind = "scenery"
|
|
var entries: Array[Dictionary] = browser.scan_entries("scenery")
|
|
entries.append_array(browser.call("_scenery_background_entries") as Array[Dictionary])
|
|
selector.set_entries(entries)
|
|
var wall_card: AssetCard = _find_card(selector.find_child("GridContainer", true, false) as GridContainer, "wall_panel_setup")
|
|
_expect(wall_card != null, "Scenery gallery offers a Wall panel setup card")
|
|
if wall_card != null:
|
|
selector.call("_on_cell_pressed", wall_card.payload())
|
|
if gui_render:
|
|
await process_frame
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
_capture_gui_screen("phase19_wall_setup.png")
|
|
var setup: Control = selector.find_child("SurfaceSetupView", true, false) as Control
|
|
var draw: Button = selector.find_child("DrawSurfaceButton", true, false) as Button
|
|
var chooser_colors: StageSurfaceColorControls = selector.find_child("SurfaceColorControls", true, false) as StageSurfaceColorControls
|
|
_expect(setup != null and setup.visible and draw != null, "Wall panel setup shows a reachable preview and Draw action")
|
|
var wall_looks: StagePanelChoiceGroup = selector.get_meta("wall_look_group") as StagePanelChoiceGroup
|
|
if wall_looks != null:
|
|
wall_looks.choice_selected.emit(&"finish_wall_wood")
|
|
var selected_color := Color("#734E31")
|
|
if chooser_colors != null:
|
|
chooser_colors.color_selected.emit(selected_color)
|
|
if draw != null:
|
|
draw.pressed.emit()
|
|
_expect(controller.get_active_tool() == &"wall", "Draw starts wall placement from the chooser")
|
|
var selected_wall_tint: Color = controller.get("_wall_tint")
|
|
_expect(String(controller.get_active_tool()) == "wall" and String(controller.get("_wall_finish")) == "finish_wall_wood" and selected_wall_tint.is_equal_approx(selected_color), "wall placement receives the chosen material and color")
|
|
if gui_render:
|
|
Input.warp_mouse(Vector2i(640, 260))
|
|
var wall_motion := InputEventMouseMotion.new()
|
|
wall_motion.position = Vector2(640.0, 260.0)
|
|
wall_motion.global_position = wall_motion.position
|
|
_stage.get_viewport().push_input(wall_motion)
|
|
await process_frame
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
var wall_ghost: StageRectSurface = controller.get("_ghost") as StageRectSurface
|
|
_expect(wall_ghost != null and wall_ghost.panel_size.is_equal_approx(Vector2(240.0, 280.0)), "wall placement displays its patterned cursor ghost after production mouse motion")
|
|
_capture_root_viewport("phase19_wall_placement.png")
|
|
controller.exit_tool()
|
|
selector.hide()
|
|
if gui_render:
|
|
_stage.call("_open_selector", "scenery")
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
else:
|
|
browser.set_selector_kind("scenery")
|
|
selector.kind = "scenery"
|
|
var ground_card: AssetCard = _find_card(selector.find_child("GridContainer", true, false) as GridContainer, "background_ground_setup")
|
|
_expect(ground_card != null, "Scenery gallery offers a Background ground setup card")
|
|
var ground_entry_description: String = String(ground_card.payload().get("description", "")) if ground_card != null else ""
|
|
_expect(ground_entry_description.contains("Decorative") and ground_entry_description.contains("No walking"), "Background ground card explains that it is decorative and non-walkable")
|
|
if ground_card != null:
|
|
selector.call("_on_cell_pressed", ground_card.payload())
|
|
if gui_render:
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
_capture_gui_screen("phase19_ground_setup.png")
|
|
var ground_looks: StagePanelChoiceGroup = selector.get_meta("ground_look_group") as StagePanelChoiceGroup
|
|
if ground_looks != null:
|
|
ground_looks.choice_selected.emit(&"finish_background_carpet")
|
|
var carpet_color := Color("#9B5C83")
|
|
if chooser_colors != null:
|
|
chooser_colors.color_selected.emit(carpet_color)
|
|
var ground_draw: Button = selector.find_child("DrawSurfaceButton", true, false) as Button
|
|
if ground_draw != null:
|
|
ground_draw.pressed.emit()
|
|
_expect(controller.get_active_tool() == &"background_ground", "Draw starts background-ground placement from the chooser")
|
|
var chosen_ground_tint: Color = controller.get("_wall_tint")
|
|
_expect(String(controller.get("_wall_finish")) == "finish_background_carpet" and chosen_ground_tint.is_equal_approx(carpet_color), "background-ground placement receives the chosen material and color")
|
|
if gui_render:
|
|
Input.warp_mouse(Vector2i(500, 260))
|
|
var ground_motion := InputEventMouseMotion.new()
|
|
ground_motion.position = Vector2(500.0, 260.0)
|
|
ground_motion.global_position = ground_motion.position
|
|
_stage.get_viewport().push_input(ground_motion)
|
|
await process_frame
|
|
await process_frame
|
|
await create_timer(0.2).timeout
|
|
var ground_ghost: StageRectSurface = controller.get("_ghost") as StageRectSurface
|
|
_expect(ground_ghost != null and ground_ghost.panel_size.is_equal_approx(Vector2(800.0, 320.0)), "background-ground placement displays its patterned cursor ghost after production mouse motion")
|
|
_capture_root_viewport("phase19_ground_placement.png")
|
|
controller.exit_tool()
|
|
selector.hide()
|
|
|
|
|
|
func _capture_gui_screen(file_name: String) -> void:
|
|
if DisplayServer.get_name() == "headless":
|
|
return
|
|
var image: Image = DisplayServer.screen_get_image()
|
|
if image == null or image.is_empty():
|
|
print("SKIP: could not capture GUI screen for %s" % file_name)
|
|
return
|
|
var path: String = "/private/tmp/" + file_name
|
|
var error: Error = image.save_png(path)
|
|
print("CAPTURE: %s size=%s result=%s" % [path, str(image.get_size()), str(error)])
|
|
|
|
|
|
func _capture_root_viewport(file_name: String) -> void:
|
|
if DisplayServer.get_name() == "headless":
|
|
return
|
|
var texture: Texture2D = root.get_texture()
|
|
if texture == null:
|
|
print("SKIP: root viewport has no capture texture for %s" % file_name)
|
|
return
|
|
var image: Image = texture.get_image()
|
|
if image == null or image.is_empty():
|
|
print("SKIP: root viewport image is empty for %s" % file_name)
|
|
return
|
|
var path: String = "/private/tmp/" + file_name
|
|
var error: Error = image.save_png(path)
|
|
print("CAPTURE: %s size=%s result=%s" % [path, str(image.get_size()), str(error)])
|
|
|
|
|
|
func _set_window_geometry(requested_size: Vector2i) -> void:
|
|
var next_size: Vector2i = requested_size
|
|
if DisplayServer.get_name() != "headless":
|
|
var usable_rect: Rect2i = DisplayServer.screen_get_usable_rect(DisplayServer.window_get_current_screen())
|
|
if usable_rect.size.x > 0 and usable_rect.size.y > 0:
|
|
next_size = Vector2i(
|
|
mini(requested_size.x, maxi(1, usable_rect.size.x - 32)),
|
|
mini(requested_size.y, maxi(1, usable_rect.size.y - 32))
|
|
)
|
|
root.size = next_size
|
|
root.position = usable_rect.position + (usable_rect.size - next_size) / 2
|
|
return
|
|
root.size = next_size
|
|
|
|
|
|
func _click_control(control: Control) -> void:
|
|
if control == null or not control.is_visible_in_tree():
|
|
return
|
|
var viewport: Viewport = control.get_viewport()
|
|
var point: Vector2 = control.get_global_rect().get_center()
|
|
var press := InputEventMouseButton.new()
|
|
press.button_index = MOUSE_BUTTON_LEFT
|
|
press.position = point
|
|
press.global_position = point
|
|
press.pressed = true
|
|
viewport.push_input(press)
|
|
await process_frame
|
|
var release := InputEventMouseButton.new()
|
|
release.button_index = MOUSE_BUTTON_LEFT
|
|
release.position = point
|
|
release.global_position = point
|
|
release.pressed = false
|
|
viewport.push_input(release)
|
|
await process_frame
|
|
|
|
|
|
func _find_card(grid: GridContainer, id: String) -> AssetCard:
|
|
if grid == null:
|
|
return null
|
|
for child: Node in grid.get_children():
|
|
var card: AssetCard = child as AssetCard
|
|
if card != null and String(card.payload().get("placement_id", "")) == id:
|
|
return card
|
|
return null
|
|
|
|
|
|
func _find_object(data: Dictionary, object_type: String) -> Dictionary:
|
|
for entry_value: Variant in data.get("objects", []):
|
|
if entry_value is Dictionary and String((entry_value as Dictionary).get("type", "")) == object_type:
|
|
return entry_value as Dictionary
|
|
return {}
|
|
|
|
|
|
func _expect(condition: bool, message: String) -> void:
|
|
if condition:
|
|
print("PASS: " + message)
|
|
else:
|
|
_failures.append(message)
|
|
push_error("FAIL: " + message)
|