Add StageBackgroundView and related tests for plain background functionality
- Implemented StageBackgroundView class for managing sky and plain backgrounds in the stage. - Added functionality to set background type and color, bind camera, and apply themes. - Created test_phase19_plain_background.gd to validate plain background behavior and migration. - Developed test_phase19_starter_chooser_layout.gd to ensure proper layout and functionality of the starter chooser. - Included visual capture tests for UI elements and interactions.
This commit is contained in:
@@ -3,23 +3,29 @@ extends Control
|
||||
## Reuses the Stage's Sky and wall-finish drawing rules in gallery cards.
|
||||
|
||||
const SKY_VIEW: Script = preload("res://scripts/stage/scenery/stage_sky_view.gd")
|
||||
const BACKGROUND_VIEW: Script = preload("res://scripts/stage/scenery/stage_background_view.gd")
|
||||
const SURFACE_RENDERER: Script = preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
||||
|
||||
var _background_kind: String = ""
|
||||
var _preview_id: StringName = &""
|
||||
var _sky_view: StageSkyView
|
||||
var _background_view: StageBackgroundView
|
||||
var _plain_color: Color = Color("#F5EFE0")
|
||||
|
||||
|
||||
func configure(entry: Dictionary) -> void:
|
||||
_background_kind = String(entry.get("background_kind", ""))
|
||||
_preview_id = StringName(String(entry.get("id", ""))) if _background_kind == "sky" else StringName(String(entry.get("finish_id", "")))
|
||||
var raw_plain_color: Variant = entry.get("plain_color", Color("#F5EFE0"))
|
||||
_plain_color = raw_plain_color if raw_plain_color is Color else Color.from_string(String(raw_plain_color), Color("#F5EFE0"))
|
||||
clip_contents = true
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
if _background_kind == "sky":
|
||||
_sky_view = SKY_VIEW.new() as StageSkyView
|
||||
_sky_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_sky_view.set_sky(_preview_id)
|
||||
add_child(_sky_view)
|
||||
if _background_kind in ["sky", "plain"]:
|
||||
_background_view = BACKGROUND_VIEW.new() as StageBackgroundView
|
||||
_background_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_background_view.set_background(StringName(_background_kind), _preview_id, _plain_color)
|
||||
add_child(_background_view)
|
||||
_sky_view = _background_view.get_sky_view()
|
||||
var frame: Panel = Panel.new()
|
||||
frame.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
frame.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
@@ -35,6 +41,8 @@ func configure(entry: Dictionary) -> void:
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if _background_kind == "plain":
|
||||
return
|
||||
if _background_kind == "wall" and SURFACE_RENDERER.is_wall_finish(_preview_id):
|
||||
var wall_tint: Color = Color("#7FA36C") if _preview_id == &"finish_wall_paint" else Color.WHITE
|
||||
# The preview rect is already small and clipped, so draw its full pattern directly.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
class_name StageBackgroundView
|
||||
extends Control
|
||||
## Shared full-rect backdrop for the live Stage and still card previews.
|
||||
|
||||
const SKY_VIEW_SCRIPT := preload("res://scripts/stage/scenery/stage_sky_view.gd")
|
||||
|
||||
var _sky_view: StageSkyView
|
||||
var _plain_fill: ColorRect
|
||||
var _bound_camera: Camera2D
|
||||
var _background_kind: StringName = &"sky"
|
||||
var _sky_id: StringName = &"sky_neutral"
|
||||
var _plain_color: Color = Color("#F5EFE0")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
if _sky_view == null:
|
||||
_sky_view = SKY_VIEW_SCRIPT.new() as StageSkyView
|
||||
_sky_view.name = "StageSkyView"
|
||||
_sky_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_sky_view.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_sky_view)
|
||||
if _plain_fill == null:
|
||||
_plain_fill = ColorRect.new()
|
||||
_plain_fill.name = "PlainFill"
|
||||
_plain_fill.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_plain_fill.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_plain_fill.color = _opaque(_plain_color)
|
||||
add_child(_plain_fill)
|
||||
_refresh_children()
|
||||
|
||||
|
||||
func set_background(kind: StringName, sky_id: StringName, plain_color: Color) -> void:
|
||||
_background_kind = &"plain" if kind == &"plain" else &"sky"
|
||||
_sky_id = sky_id
|
||||
_plain_color = _opaque(plain_color)
|
||||
_refresh_children()
|
||||
|
||||
|
||||
func bind_camera(camera: Camera2D) -> void:
|
||||
_bound_camera = camera
|
||||
if _sky_view != null:
|
||||
_sky_view.bind_camera(_bound_camera if _background_kind == &"sky" else null)
|
||||
|
||||
|
||||
func apply_theme(stage_theme: SandboxTheme) -> void:
|
||||
if _sky_view != null:
|
||||
_sky_view.apply_theme(stage_theme)
|
||||
|
||||
|
||||
func get_sky_view() -> StageSkyView:
|
||||
return _sky_view
|
||||
|
||||
|
||||
func get_background_kind() -> StringName:
|
||||
return _background_kind
|
||||
|
||||
|
||||
func get_plain_color() -> Color:
|
||||
return _plain_color
|
||||
|
||||
|
||||
func _refresh_children() -> void:
|
||||
if _sky_view == null or _plain_fill == null:
|
||||
return
|
||||
_sky_view.set_sky(_sky_id)
|
||||
_sky_view.visible = _background_kind == &"sky"
|
||||
_sky_view.bind_camera(_bound_camera if _background_kind == &"sky" else null)
|
||||
_plain_fill.color = _plain_color
|
||||
_plain_fill.visible = _background_kind == &"plain"
|
||||
|
||||
|
||||
func _opaque(color: Color) -> Color:
|
||||
return Color(color.r, color.g, color.b, 1.0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://dc72udajou30y
|
||||
@@ -6,6 +6,7 @@ const STAGE_SCENE_IO := preload("res://scripts/stage/stage_scene_io.gd")
|
||||
const STAGE_SCENE_ADAPTER := preload("res://scripts/editors/stage/stage_scene_adapter.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
|
||||
const STAGE_SKY_VIEW := preload("res://scripts/stage/scenery/stage_sky_view.gd")
|
||||
const STAGE_BACKGROUND_VIEW := preload("res://scripts/stage/scenery/stage_background_view.gd")
|
||||
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
||||
|
||||
const PREVIEW_SIZE := Vector2i(256, 256)
|
||||
@@ -14,14 +15,16 @@ const REFERENCE_VIEWPORT_SIZE := Vector2(1280.0, 720.0)
|
||||
|
||||
var _viewport: SubViewport
|
||||
var _sky_view: StageSkyView
|
||||
var _background_view: StageBackgroundView
|
||||
var _world: Node2D
|
||||
var _loaded_nodes: Array[Node2D] = []
|
||||
var _scene_valid: bool = false
|
||||
|
||||
|
||||
func configure(entry: Dictionary) -> void:
|
||||
func configure(entry: Dictionary, requested_preview_size: Vector2 = Vector2.ZERO) -> void:
|
||||
name = "StarterScenePreview"
|
||||
custom_minimum_size = Vector2(128.0, 128.0)
|
||||
var resolved_preview_size: Vector2 = requested_preview_size if requested_preview_size.x > 0.0 and requested_preview_size.y > 0.0 else Vector2(PREVIEW_SIZE)
|
||||
custom_minimum_size = resolved_preview_size
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
stretch = true
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
@@ -31,36 +34,40 @@ func configure(entry: Dictionary) -> void:
|
||||
return
|
||||
var data: Dictionary = result.get("data", {}) as Dictionary
|
||||
_scene_valid = true
|
||||
_build_preview_viewport(data)
|
||||
_build_preview_viewport(data, resolved_preview_size)
|
||||
|
||||
|
||||
func has_preview_scene() -> bool:
|
||||
return _scene_valid and _viewport != null and _sky_view != null and _world != null
|
||||
return _scene_valid and _viewport != null and _background_view != null and _world != null
|
||||
|
||||
|
||||
func get_loaded_object_count() -> int:
|
||||
return _loaded_nodes.size()
|
||||
|
||||
|
||||
func _build_preview_viewport(data: Dictionary) -> void:
|
||||
func _build_preview_viewport(data: Dictionary, requested_preview_size: Vector2) -> void:
|
||||
_viewport = SubViewport.new()
|
||||
_viewport.name = "PreviewViewport"
|
||||
_viewport.size = PREVIEW_SIZE
|
||||
_viewport.size = Vector2i(maxi(1, roundi(requested_preview_size.x)), maxi(1, roundi(requested_preview_size.y)))
|
||||
_viewport.world_2d = World2D.new()
|
||||
_viewport.transparent_bg = false
|
||||
_viewport.render_target_update_mode = SubViewport.UPDATE_DISABLED
|
||||
add_child(_viewport)
|
||||
|
||||
var sky_layer := CanvasLayer.new()
|
||||
sky_layer.name = "PreviewSky"
|
||||
sky_layer.layer = -1
|
||||
_viewport.add_child(sky_layer)
|
||||
_sky_view = STAGE_SKY_VIEW.new() as StageSkyView
|
||||
_sky_view.name = "SkyView"
|
||||
_sky_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_sky_view.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_sky_view.set_sky(StringName(String(data.get("sky_id", "sky_neutral"))))
|
||||
sky_layer.add_child(_sky_view)
|
||||
var background_layer := CanvasLayer.new()
|
||||
background_layer.name = "PreviewBackgroundLayer"
|
||||
background_layer.layer = -1
|
||||
_viewport.add_child(background_layer)
|
||||
_background_view = STAGE_BACKGROUND_VIEW.new() as StageBackgroundView
|
||||
_background_view.name = "StageBackgroundView"
|
||||
_background_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_background_view.set_background(
|
||||
StringName(String(data.get("background_kind", "sky"))),
|
||||
StringName(String(data.get("sky_id", "sky_neutral"))),
|
||||
STAGE_SCENE_IO.color_from_data(data.get("plain_color", STAGE_SCENE_IO.DEFAULT_PLAIN_COLOR), Color("#F5EFE0"))
|
||||
)
|
||||
background_layer.add_child(_background_view)
|
||||
_sky_view = _background_view.get_sky_view()
|
||||
|
||||
_world = Node2D.new()
|
||||
_world.name = "PreviewWorld"
|
||||
@@ -85,27 +92,30 @@ func _build_preview_viewport(data: Dictionary) -> void:
|
||||
func _fit_scene_to_preview(camera_state: Dictionary) -> void:
|
||||
if _world == null or not is_instance_valid(_world):
|
||||
return
|
||||
var preview_frame: Dictionary = _preview_transform_for_camera(camera_state)
|
||||
var preview_frame: Dictionary = _preview_transform_for_camera(camera_state, Vector2(_viewport.size))
|
||||
var preview_scale: float = float(preview_frame["scale"])
|
||||
_world.scale = Vector2.ONE * preview_scale
|
||||
_world.position = preview_frame["position"] as Vector2
|
||||
_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
|
||||
|
||||
|
||||
static func _preview_transform_for_camera(camera_state: Dictionary) -> Dictionary:
|
||||
static func _preview_transform_for_camera(camera_state: Dictionary, preview_size: Vector2 = Vector2(PREVIEW_SIZE)) -> Dictionary:
|
||||
var camera_position: Vector2 = STAGE_SCENE_IO.data_to_vector(camera_state.get("position"), Vector2.ZERO)
|
||||
var camera_zoom: float = float(camera_state.get("zoom", 0.5))
|
||||
if not is_finite(camera_zoom) or camera_zoom <= 0.0:
|
||||
camera_zoom = 0.5
|
||||
var safe_preview_size: Vector2 = preview_size if preview_size.x > 0.0 and preview_size.y > 0.0 else Vector2(PREVIEW_SIZE)
|
||||
var preview_aspect: float = safe_preview_size.x / safe_preview_size.y
|
||||
var square_world_extent: float = REFERENCE_VIEWPORT_SIZE.y / camera_zoom
|
||||
var focus_size := Vector2(square_world_extent * preview_aspect, square_world_extent)
|
||||
var focus_rect := Rect2(
|
||||
camera_position - Vector2.ONE * square_world_extent * 0.5,
|
||||
Vector2.ONE * square_world_extent
|
||||
camera_position - focus_size * 0.5,
|
||||
focus_size
|
||||
)
|
||||
var usable_size: Vector2 = Vector2(PREVIEW_SIZE) - Vector2.ONE * PREVIEW_MARGIN * 2.0
|
||||
var usable_size: Vector2 = safe_preview_size - Vector2(PREVIEW_MARGIN * 2.0, PREVIEW_MARGIN * 2.0)
|
||||
var preview_scale: float = minf(usable_size.x / focus_rect.size.x, usable_size.y / focus_rect.size.y)
|
||||
return {
|
||||
"focus_rect": focus_rect,
|
||||
"scale": preview_scale,
|
||||
"position": Vector2(PREVIEW_SIZE) * 0.5 - camera_position * preview_scale,
|
||||
"position": safe_preview_size * 0.5 - camera_position * preview_scale,
|
||||
}
|
||||
|
||||
@@ -29,14 +29,16 @@ func _draw() -> void:
|
||||
var rect: Rect2 = Rect2(Vector2(3.0, 3.0), size - Vector2(6.0, 6.0))
|
||||
if rect.size.x <= 0.0 or rect.size.y <= 0.0:
|
||||
return
|
||||
if surface_kind == &"background_ground":
|
||||
if surface_kind == &"plain_background":
|
||||
draw_rect(rect, Color(tint.r, tint.g, tint.b, 1.0), true)
|
||||
elif surface_kind == &"background_ground":
|
||||
SURFACE_RENDERER.draw_background(self, rect, finish_id, tint, Vector2.ZERO, Vector2.ONE, false)
|
||||
else:
|
||||
SURFACE_RENDERER.draw_wall(self, rect, finish_id, tint, Vector2.ZERO, Vector2.ONE, false)
|
||||
var radius: float = minf(corner_radius, minf(rect.size.x, rect.size.y) * 0.5)
|
||||
if radius > 0.0:
|
||||
var wall: bool = surface_kind != &"background_ground"
|
||||
var base_color: Color = SURFACE_RENDERER.surface_base_color(finish_id, tint, wall)
|
||||
var base_color: Color = Color(tint.r, tint.g, tint.b, 1.0) if surface_kind == &"plain_background" else SURFACE_RENDERER.surface_base_color(finish_id, tint, wall)
|
||||
_draw_rounded_corner(rect, radius, base_color, outside_color, 0)
|
||||
_draw_rounded_corner(rect, radius, base_color, outside_color, 1)
|
||||
_draw_rounded_corner(rect, radius, base_color, outside_color, 2)
|
||||
|
||||
Reference in New Issue
Block a user