feat: Enhance surface setup chooser with compact mode and cancel functionality

This commit is contained in:
2026-10-19 09:46:51 -04:00
parent 26dd9c64e1
commit 80ba83b703
5 changed files with 158 additions and 24 deletions
+9 -1
View File
@@ -6,7 +6,15 @@ Phase 19 is in progress on the local branch `feature/phase-19-scenery`. This is
The integrated feature lets children choose a Sky, place and edit inert Scenery artwork and wall panels, paint Ground finishes, use editable starter scenes, and save/reopen the result. Scenery artwork can be placed Far, Near, or On the floor and resized independently. The Prop Editor has a stickman-relative size guide and reversible size presets for authoring art at a useful scale. Missing artwork remains visible and repairable. The integrated feature lets children choose a Sky, place and edit inert Scenery artwork and wall panels, paint Ground finishes, use editable starter scenes, and save/reopen the result. Scenery artwork can be placed Far, Near, or On the floor and resized independently. The Prop Editor has a stickman-relative size guide and reversible size presets for authoring art at a useful scale. Missing artwork remains visible and repairable.
Recent reported editor issues were addressed during Phase 19 iteration, including scene loading, grid snapping/placement previews, starter-scene thumbnails, artwork cursor alignment, wall-panel erasing and tile rendering, and scenery size/depth controls. Treat those fixes as needing a focused regression pass on the next machine; the user has not completed overall Phase 19 testing. The selected-artwork depth inspector was the latest specifically approved piece. Recent reported editor issues were addressed during Phase 19 iteration, including scene loading, grid snapping/placement previews, starter-scene thumbnails, artwork cursor alignment, wall-panel erasing and tile rendering, scenery size/depth controls, and the scenery chooser's surface-setup flow. Treat those fixes as needing a focused regression pass on the next machine; the user has not completed overall Phase 19 testing. The selected-artwork depth inspector was the latest specifically approved piece.
### Latest bug fix: surface setup chooser visibility (2026-10-19)
The Paint/Brick/Wood wall setup and Carpet background-ground setup previously put the Draw action after the color controls, where it could fall below the visible part of the dialog. In setup mode, the chooser now moves its action row (Cancel + Draw) above the scrollable setup content, compacts the setup layout, limits the popup to the usable screen area, and re-centers after size changes. Look choices and color controls remain scrollable. Outside clicks are blocked from reaching the Stage, and an unexpected popup hide restores the chooser instead of cancelling setup.
The latest focused Godot GUI run of `tests/test_phase19_surface_followup.gd` passed after this change. Its capture showed the Draw action visible for wall and background-ground setup. The test also passed its placement, color, erase, and outside-click scenarios. Captures and logs are in `/private/tmp` on the current Mac and are not repository artifacts.
The chooser fix is currently uncommitted in `scripts/shared/ui/asset_selector.gd`, `scripts/shared/stage_ui/stage_surface_color_controls.gd`, and `scripts/editors/stage/stage_asset_browser.gd`, with related assertions in `tests/test_phase19_surface_followup.gd`. Include these changes and this handoff update in the commit before pushing the branch for the other computer.
Stage-wide Undo/Redo is deliberately out of scope and recorded as technical debt for a separate phase. No new scene save workflow was requested; the current ordinary scene and Prop save flows remain authoritative. Stage-wide Undo/Redo is deliberately out of scope and recorded as technical debt for a separate phase. No new scene save workflow was requested; the current ordinary scene and Prop save flows remain authoritative.
+16 -2
View File
@@ -47,7 +47,8 @@ func build_ui(ui_layer: CanvasLayer, ui_font: Font, emoji_font: Font, sandbox_th
selector_dim = ColorRect.new() selector_dim = ColorRect.new()
selector_dim.name = "SelectorDim" selector_dim.name = "SelectorDim"
selector_dim.color = Color(0.0, 0.0, 0.0, SELECTOR_DIM_ALPHA) selector_dim.color = Color(0.0, 0.0, 0.0, SELECTOR_DIM_ALPHA)
selector_dim.mouse_filter = Control.MOUSE_FILTER_IGNORE # Keep clicks outside the modal from reaching the Stage canvas beneath it.
selector_dim.mouse_filter = Control.MOUSE_FILTER_STOP
selector_dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) selector_dim.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
selector_dim.visible = false selector_dim.visible = false
ui_layer.add_child(selector_dim) ui_layer.add_child(selector_dim)
@@ -55,7 +56,7 @@ func build_ui(ui_layer: CanvasLayer, ui_font: Font, emoji_font: Font, sandbox_th
selector = ASSET_SELECTOR_SCENE.instantiate() as AssetSelector selector = ASSET_SELECTOR_SCENE.instantiate() as AssetSelector
selector.item_selected.connect(func(entry: Dictionary) -> void: asset_selected_requested.emit(entry)) selector.item_selected.connect(func(entry: Dictionary) -> void: asset_selected_requested.emit(entry))
selector.cancelled.connect(_on_selector_cancelled) selector.cancelled.connect(_on_selector_cancelled)
selector.popup_hide.connect(_on_selector_cancelled) selector.popup_hide.connect(_on_selector_popup_hide)
selector.browse_requested.connect(show_browse_dialog) selector.browse_requested.connect(show_browse_dialog)
selector.refresh_requested.connect(func() -> void: refresh_requested.emit()) selector.refresh_requested.connect(func() -> void: refresh_requested.emit())
selector.make_artwork_requested.connect(func() -> void: make_artwork_requested.emit()) selector.make_artwork_requested.connect(func() -> void: make_artwork_requested.emit())
@@ -281,6 +282,19 @@ func _on_selector_cancelled() -> void:
selector_cancel_requested.emit() selector_cancel_requested.emit()
func _on_selector_popup_hide() -> void:
if not selector_open:
return
# PopupPanel can hide itself when it loses focus. Keep the chooser alive so an
# outside click cannot silently cancel the user's scenery setup.
call_deferred("_restore_selector_after_unexpected_hide")
func _restore_selector_after_unexpected_hide() -> void:
if selector_open and selector != null and not selector.visible:
selector.popup_centered()
func get_selector() -> AssetSelector: func get_selector() -> AssetSelector:
return selector return selector
@@ -18,6 +18,13 @@ var _recent_empty_label: Label
var _recent_swatches: ColorSwatchSelector var _recent_swatches: ColorSwatchSelector
var _preset_swatches: ColorSwatchSelector var _preset_swatches: ColorSwatchSelector
var _more_button: Button var _more_button: Button
var _compact_mode: bool = false
func set_compact_mode(enabled: bool) -> void:
_compact_mode = enabled
if _built:
_apply_compact_layout()
func configure(selected: Color, recents: Array[Color], presets: Array[Color], stage_theme: SandboxTheme) -> void: func configure(selected: Color, recents: Array[Color], presets: Array[Color], stage_theme: SandboxTheme) -> void:
@@ -53,14 +60,16 @@ func focus_custom_button() -> void:
func _build() -> void: func _build() -> void:
_built = true _built = true
add_theme_constant_override("separation", _integer("stage_surface_colors.section_gap", 8, 0, 28)) _apply_compact_layout()
var current_heading: Label = _make_heading("Color") var current_heading: Label = _make_heading("Color")
add_child(current_heading) add_child(current_heading)
var current_row := HBoxContainer.new() var current_row := HBoxContainer.new()
current_row.add_theme_constant_override("separation", _integer("stage_surface_colors.section_gap", 8, 0, 28)) current_row.add_theme_constant_override("separation", _compact_gap())
add_child(current_row) add_child(current_row)
_current_chip = Button.new() _current_chip = Button.new()
var swatch_size: float = float(_integer("stage_surface_colors.swatch_size", 48, 44, 96)) var swatch_size: float = float(_integer("stage_surface_colors.swatch_size", 48, 44, 96))
if _compact_mode:
swatch_size = minf(swatch_size, 44.0)
_current_chip.custom_minimum_size = Vector2(swatch_size, swatch_size) _current_chip.custom_minimum_size = Vector2(swatch_size, swatch_size)
_current_chip.focus_mode = Control.FOCUS_NONE _current_chip.focus_mode = Control.FOCUS_NONE
_current_chip.mouse_filter = Control.MOUSE_FILTER_IGNORE _current_chip.mouse_filter = Control.MOUSE_FILTER_IGNORE
@@ -84,7 +93,8 @@ func _build() -> void:
add_child(_preset_swatches) add_child(_preset_swatches)
_more_button = Button.new() _more_button = Button.new()
_more_button.text = "More colors…" _more_button.text = "More colors…"
_more_button.custom_minimum_size.y = float(_integer("stage_panels.action.min_target_size", 48, 44, 96)) var action_height: float = float(_integer("stage_panels.action.min_target_size", 48, 44, 96))
_more_button.custom_minimum_size.y = minf(action_height, 44.0) if _compact_mode else action_height
_more_button.focus_mode = Control.FOCUS_ALL _more_button.focus_mode = Control.FOCUS_ALL
_more_button.pressed.connect(func() -> void: custom_color_requested.emit()) _more_button.pressed.connect(func() -> void: custom_color_requested.emit())
add_child(_more_button) add_child(_more_button)
@@ -132,13 +142,26 @@ func _on_swatch_selected(color: Color) -> void:
func _make_heading(text_value: String) -> Label: func _make_heading(text_value: String) -> Label:
var heading := Label.new() var heading := Label.new()
heading.text = text_value heading.text = text_value
heading.add_theme_font_size_override("font_size", _integer("stage_panels.type.section_size", 20, 12, 40)) var heading_size: int = _integer("stage_panels.type.section_size", 20, 12, 40)
if _compact_mode:
heading_size = mini(heading_size, 18)
heading.add_theme_font_size_override("font_size", heading_size)
heading.add_theme_color_override("font_color", _color("stage_panels.type.text_color", Color("#F5F7FA"))) heading.add_theme_color_override("font_color", _color("stage_panels.type.text_color", Color("#F5F7FA")))
if _stage_theme != null and _stage_theme.font() != null: if _stage_theme != null and _stage_theme.font() != null:
heading.add_theme_font_override("font", _stage_theme.font()) heading.add_theme_font_override("font", _stage_theme.font())
return heading return heading
func _apply_compact_layout() -> void:
var section_gap: int = _integer("stage_surface_colors.section_gap", 8, 0, 28)
add_theme_constant_override("separation", mini(section_gap, 4) if _compact_mode else section_gap)
func _compact_gap() -> int:
var section_gap: int = _integer("stage_surface_colors.section_gap", 8, 0, 28)
return mini(section_gap, 4) if _compact_mode else section_gap
func _integer(path: String, fallback: int, minimum: int, maximum: int) -> int: func _integer(path: String, fallback: int, minimum: int, maximum: int) -> int:
return fallback if _stage_theme == null else _stage_theme.integer(path, fallback, minimum, maximum) return fallback if _stage_theme == null else _stage_theme.integer(path, fallback, minimum, maximum)
+69 -17
View File
@@ -56,6 +56,8 @@ var _setup_color_chosen: bool = false
var _setup_preview: StageSurfacePreview var _setup_preview: StageSurfacePreview
var _setup_color_controls: StageSurfaceColorControls var _setup_color_controls: StageSurfaceColorControls
var _setup_color_dialog: StageSurfaceColorDialog var _setup_color_dialog: StageSurfaceColorDialog
var _setup_cancel_button: Button
var _setup_draw_button: Button
@onready var _title_label: Label = %TitleLabel @onready var _title_label: Label = %TitleLabel
@onready var _grid: GridContainer = %GridContainer @onready var _grid: GridContainer = %GridContainer
@@ -101,7 +103,6 @@ func _on_root_size_changed() -> void:
if visible: if visible:
_update_popup_layout() _update_popup_layout()
_rebuild() _rebuild()
popup_centered()
func open(p_kind: String, entries: Array[Dictionary], current_sky_id: String = "") -> void: func open(p_kind: String, entries: Array[Dictionary], current_sky_id: String = "") -> void:
@@ -198,7 +199,7 @@ func _build_surface_setup_view() -> void:
_setup_view = VBoxContainer.new() _setup_view = VBoxContainer.new()
_setup_view.name = "SurfaceSetupView" _setup_view.name = "SurfaceSetupView"
_setup_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL _setup_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_setup_view.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.setup_gap", 10, 0, 32)) _setup_view.add_theme_constant_override("separation", mini(_theme_integer("popups.asset_selector.setup_gap", 10, 0, 32), 6))
_setup_view.visible = false _setup_view.visible = false
_content_stack.add_child(_setup_view) _content_stack.add_child(_setup_view)
var heading_row := HBoxContainer.new() var heading_row := HBoxContainer.new()
@@ -226,6 +227,7 @@ func _build_surface_setup_view() -> void:
wall_choices.add_choice(&"finish_wall_brick", "Brick", _make_look_cue(&"wall", &"finish_wall_brick")) wall_choices.add_choice(&"finish_wall_brick", "Brick", _make_look_cue(&"wall", &"finish_wall_brick"))
wall_choices.add_choice(&"finish_wall_wood", "Wood", _make_look_cue(&"wall", &"finish_wall_wood")) wall_choices.add_choice(&"finish_wall_wood", "Wood", _make_look_cue(&"wall", &"finish_wall_wood"))
wall_choices.choice_selected.connect(_on_setup_look_choice) wall_choices.choice_selected.connect(_on_setup_look_choice)
_compact_look_choices(wall_choices)
_setup_view.add_child(wall_choices) _setup_view.add_child(wall_choices)
var ground_choices: StagePanelChoiceGroup = STAGE_PANEL_CHOICE_GROUP.new() as StagePanelChoiceGroup var ground_choices: StagePanelChoiceGroup = STAGE_PANEL_CHOICE_GROUP.new() as StagePanelChoiceGroup
ground_choices.name = "BackgroundGroundLookChoices" ground_choices.name = "BackgroundGroundLookChoices"
@@ -233,12 +235,14 @@ func _build_surface_setup_view() -> void:
ground_choices.add_choice(&"finish_background_concrete", "Concrete", _make_look_cue(&"background_ground", &"finish_background_concrete")) ground_choices.add_choice(&"finish_background_concrete", "Concrete", _make_look_cue(&"background_ground", &"finish_background_concrete"))
ground_choices.add_choice(&"finish_background_carpet", "Carpet", _make_look_cue(&"background_ground", &"finish_background_carpet")) ground_choices.add_choice(&"finish_background_carpet", "Carpet", _make_look_cue(&"background_ground", &"finish_background_carpet"))
ground_choices.choice_selected.connect(_on_setup_look_choice) ground_choices.choice_selected.connect(_on_setup_look_choice)
_compact_look_choices(ground_choices)
_setup_view.add_child(ground_choices) _setup_view.add_child(ground_choices)
set_meta("wall_look_group", wall_choices) set_meta("wall_look_group", wall_choices)
set_meta("ground_look_group", ground_choices) set_meta("ground_look_group", ground_choices)
var preview_frame := PanelContainer.new() var preview_frame := PanelContainer.new()
preview_frame.name = "SurfacePreviewFrame" preview_frame.name = "SurfacePreviewFrame"
var preview_size: Vector2 = _theme_vector2("popups.asset_selector.surface_preview_size", Vector2(420.0, 132.0)) var preview_size: Vector2 = _theme_vector2("popups.asset_selector.surface_preview_size", Vector2(420.0, 132.0))
preview_size.y = minf(preview_size.y, 96.0)
preview_frame.custom_minimum_size.y = preview_size.y + 10.0 preview_frame.custom_minimum_size.y = preview_size.y + 10.0
preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_setup_preview = SURFACE_PREVIEW.new() as StageSurfacePreview _setup_preview = SURFACE_PREVIEW.new() as StageSurfacePreview
@@ -248,6 +252,7 @@ func _build_surface_setup_view() -> void:
_setup_view.add_child(preview_frame) _setup_view.add_child(preview_frame)
_setup_color_controls = SURFACE_COLOR_CONTROLS.new() as StageSurfaceColorControls _setup_color_controls = SURFACE_COLOR_CONTROLS.new() as StageSurfaceColorControls
_setup_color_controls.name = "SurfaceColorControls" _setup_color_controls.name = "SurfaceColorControls"
_setup_color_controls.set_compact_mode(true)
_setup_color_controls.color_selected.connect(_on_setup_color_selected) _setup_color_controls.color_selected.connect(_on_setup_color_selected)
_setup_color_controls.custom_color_requested.connect(_open_setup_color_dialog) _setup_color_controls.custom_color_requested.connect(_open_setup_color_dialog)
_setup_view.add_child(_setup_color_controls) _setup_view.add_child(_setup_color_controls)
@@ -256,25 +261,35 @@ func _build_surface_setup_view() -> void:
helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
helper.add_theme_color_override("font_color", _theme_color("global.muted_text_color", Color("#AAB2C0"))) helper.add_theme_color_override("font_color", _theme_color("global.muted_text_color", Color("#AAB2C0")))
_setup_view.add_child(helper) _setup_view.add_child(helper)
var draw_button := Button.new() _setup_cancel_button = Button.new()
draw_button.name = "DrawSurfaceButton" _setup_cancel_button.name = "CancelSurfaceSetupButton"
draw_button.custom_minimum_size.y = float(_theme_integer("stage_panels.choice.minimum_height", 56, 44, 112)) _setup_cancel_button.text = "Cancel"
draw_button.focus_mode = Control.FOCUS_ALL _setup_cancel_button.custom_minimum_size = Vector2(104.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
draw_button.pressed.connect(_draw_selected_surface) _setup_cancel_button.focus_mode = Control.FOCUS_ALL
_setup_view.add_child(draw_button) _setup_cancel_button.pressed.connect(_cancel_surface_setup)
_footer.add_child(_setup_cancel_button)
_setup_draw_button = Button.new()
_setup_draw_button.name = "DrawSurfaceButton"
_setup_draw_button.custom_minimum_size = Vector2(220.0, float(_theme_integer("stage_panels.choice.minimum_height", 56, 44, 112)))
_setup_draw_button.focus_mode = Control.FOCUS_ALL
_setup_draw_button.pressed.connect(_draw_selected_surface)
_footer.add_child(_setup_draw_button)
set_meta("setup_heading", heading) set_meta("setup_heading", heading)
set_meta("setup_helper", helper) set_meta("setup_helper", helper)
set_meta("setup_draw_button", draw_button)
_apply_font_to(back_button) _apply_font_to(back_button)
_apply_font_to(heading) _apply_font_to(heading)
_apply_font_to(look_heading) _apply_font_to(look_heading)
_apply_font_to(helper) _apply_font_to(helper)
_apply_font_to(draw_button) _apply_font_to(_setup_cancel_button)
_apply_font_to(_setup_draw_button)
if _sandbox_theme != null: if _sandbox_theme != null:
draw_button.add_theme_stylebox_override("normal", _sandbox_theme.stylebox("stage_panels.action")) _setup_cancel_button.add_theme_stylebox_override("normal", _sandbox_theme.stylebox("stage_panels.choice"))
draw_button.add_theme_stylebox_override("hover", _sandbox_theme.stylebox("stage_panels.action")) _setup_cancel_button.add_theme_stylebox_override("hover", _sandbox_theme.stylebox("stage_panels.choice"))
draw_button.add_theme_color_override("font_color", _theme_color("stage_panels.type.text_color", Color("#F5F7FA"))) _setup_cancel_button.add_theme_font_size_override("font_size", _theme_integer("stage_panels.type.body_size", 20, 14, 40))
draw_button.add_theme_font_size_override("font_size", _theme_integer("stage_panels.type.body_size", 20, 14, 40)) _setup_draw_button.add_theme_stylebox_override("normal", _sandbox_theme.stylebox("stage_panels.action"))
_setup_draw_button.add_theme_stylebox_override("hover", _sandbox_theme.stylebox("stage_panels.action"))
_setup_draw_button.add_theme_color_override("font_color", _theme_color("stage_panels.type.text_color", Color("#F5F7FA")))
_setup_draw_button.add_theme_font_size_override("font_size", _theme_integer("stage_panels.type.body_size", 20, 14, 40))
for choice: StagePanelChoiceGroup in [wall_choices, ground_choices]: for choice: StagePanelChoiceGroup in [wall_choices, ground_choices]:
choice.apply_theme(_sandbox_theme as SandboxTheme) choice.apply_theme(_sandbox_theme as SandboxTheme)
_apply_font_to(choice) _apply_font_to(choice)
@@ -282,11 +297,21 @@ func _build_surface_setup_view() -> void:
func _make_look_cue(kind: StringName, finish: StringName) -> Control: func _make_look_cue(kind: StringName, finish: StringName) -> Control:
var preview: StageSurfacePreview = SURFACE_PREVIEW.new() as StageSurfacePreview var preview: StageSurfacePreview = SURFACE_PREVIEW.new() as StageSurfacePreview
preview.custom_minimum_size = _theme_vector2("popups.asset_selector.look_cue_size", Vector2(80.0, 42.0)) var cue_size: Vector2 = _theme_vector2("popups.asset_selector.look_cue_size", Vector2(80.0, 42.0))
cue_size.x = minf(cue_size.x, 68.0)
cue_size.y = minf(cue_size.y, 34.0)
preview.custom_minimum_size = cue_size
preview.configure(kind, finish, SURFACE_RENDERER.default_tint(finish)) preview.configure(kind, finish, SURFACE_RENDERER.default_tint(finish))
return preview return preview
func _compact_look_choices(group: StagePanelChoiceGroup) -> void:
for child: Node in group.get_children():
if child is Button:
var button := child as Button
button.custom_minimum_size.y = minf(button.custom_minimum_size.y, 50.0)
func _open_surface_setup(kind: StringName) -> void: func _open_surface_setup(kind: StringName) -> void:
_setup_kind = kind _setup_kind = kind
_setup_color_chosen = false _setup_color_chosen = false
@@ -301,7 +326,7 @@ func _open_surface_setup(kind: StringName) -> void:
ground_choices.select_no_signal(_setup_finish) ground_choices.select_no_signal(_setup_finish)
(get_meta("setup_heading") as Label).text = "Wall panel" if kind == &"wall" else "Background ground" (get_meta("setup_heading") as Label).text = "Wall panel" if kind == &"wall" else "Background ground"
(get_meta("setup_helper") as Label).text = "Looks like a wall · Stickmen can pass through." if kind == &"wall" else "Decorative only · Stickmen don’t walk here." (get_meta("setup_helper") as Label).text = "Looks like a wall · Stickmen can pass through." if kind == &"wall" else "Decorative only · Stickmen don’t walk here."
(get_meta("setup_draw_button") as Button).text = "Draw wall panel" if kind == &"wall" else "Draw background ground" _setup_draw_button.text = "Draw wall panel" if kind == &"wall" else "Draw background ground"
_setup_color_controls.visible = _supports_setup_color() _setup_color_controls.visible = _supports_setup_color()
_setup_color_controls.configure(_setup_color, _surface_color_history, _preset_surface_colors(), _sandbox_theme as SandboxTheme) _setup_color_controls.configure(_setup_color, _surface_color_history, _preset_surface_colors(), _sandbox_theme as SandboxTheme)
_update_setup_preview() _update_setup_preview()
@@ -400,6 +425,12 @@ func _draw_selected_surface() -> void:
var background_kind: String = "wall" if _setup_kind == &"wall" else "background_ground" var background_kind: String = "wall" if _setup_kind == &"wall" else "background_ground"
item_selected.emit({"background_kind": background_kind, "finish_id": String(_setup_finish), "color": _setup_color}) item_selected.emit({"background_kind": background_kind, "finish_id": String(_setup_finish), "color": _setup_color})
func _cancel_surface_setup() -> void:
_setup_kind = &""
_setup_view.visible = false
cancelled.emit()
var _cell_min_size: Vector2 = CELL_MIN_SIZE var _cell_min_size: Vector2 = CELL_MIN_SIZE
var _thumbnail_size: Vector2 = THUMB_SIZE var _thumbnail_size: Vector2 = THUMB_SIZE
@@ -500,9 +531,15 @@ func _usable_screen_size() -> Vector2:
func _update_popup_layout() -> void: func _update_popup_layout() -> void:
var in_setup: bool = not _setup_kind.is_empty()
_position_footer(in_setup)
_setup_cancel_button.visible = in_setup
_setup_draw_button.visible = in_setup
_edit_scenery_button.visible = kind == "scenery" and not in_setup
_footer.alignment = BoxContainer.ALIGNMENT_END if in_setup else BoxContainer.ALIGNMENT_CENTER
var available: Vector2 = _usable_screen_size() var available: Vector2 = _usable_screen_size()
var max_width: float = maxf(1.0, minf(available.x * 0.94, available.x - POPUP_SAFE_SCREEN_MARGIN)) var max_width: float = maxf(1.0, minf(available.x * 0.94, available.x - POPUP_SAFE_SCREEN_MARGIN))
var max_height: float = maxf(1.0, minf(available.y * 0.90, available.y - POPUP_SAFE_SCREEN_MARGIN)) var max_height: float = maxf(1.0, minf(available.y * 0.86, available.y - POPUP_SAFE_SCREEN_MARGIN * 2.0))
var preferred_width: float = CELL_MIN_SIZE.x * COLUMNS + GRID_H_SEPARATION * (COLUMNS - 1) + POPUP_SIDE_PADDING var preferred_width: float = CELL_MIN_SIZE.x * COLUMNS + GRID_H_SEPARATION * (COLUMNS - 1) + POPUP_SIDE_PADDING
var popup_width: float = minf(preferred_width, max_width) var popup_width: float = minf(preferred_width, max_width)
var grid_width: float = maxf(1.0, popup_width - POPUP_SIDE_PADDING) var grid_width: float = maxf(1.0, popup_width - POPUP_SIDE_PADDING)
@@ -534,6 +571,8 @@ func _update_popup_layout() -> void:
fixed_height += float(visible_sections - 1) * 8.0 fixed_height += float(visible_sections - 1) * 8.0
var popup_height: float = minf(fixed_height + body_height, max_height) var popup_height: float = minf(fixed_height + body_height, max_height)
size = Vector2i(maxi(1, roundi(popup_width)), maxi(1, roundi(popup_height))) size = Vector2i(maxi(1, roundi(popup_width)), maxi(1, roundi(popup_height)))
if visible:
popup_centered()
func _rebuild() -> void: func _rebuild() -> void:
@@ -543,7 +582,11 @@ func _rebuild() -> void:
_cell_texrects.clear() _cell_texrects.clear()
var filtered: Array[Dictionary] = _filtered_entries() var filtered: Array[Dictionary] = _filtered_entries()
var in_setup: bool = not _setup_kind.is_empty() var in_setup: bool = not _setup_kind.is_empty()
_position_footer(in_setup)
_setup_view.visible = in_setup _setup_view.visible = in_setup
_setup_cancel_button.visible = in_setup
_setup_draw_button.visible = in_setup
_edit_scenery_button.visible = kind == "scenery" and not in_setup
_empty_label.visible = filtered.is_empty() and not in_setup _empty_label.visible = filtered.is_empty() and not in_setup
_grid.visible = not filtered.is_empty() and not in_setup _grid.visible = not filtered.is_empty() and not in_setup
_grid_scroll.visible = not filtered.is_empty() or in_setup _grid_scroll.visible = not filtered.is_empty() or in_setup
@@ -570,6 +613,15 @@ func _rebuild() -> void:
_grid.add_child(_build_cell(filtered[i])) _grid.add_child(_build_cell(filtered[i]))
func _position_footer(in_setup: bool) -> void:
var parent := _footer.get_parent() as VBoxContainer
if parent == null:
return
var target_index: int = _tabs.get_index() + 1 if in_setup else parent.get_child_count() - 1
if _footer.get_index() != target_index:
parent.move_child(_footer, target_index)
func _build_cell(entry: Dictionary) -> Control: func _build_cell(entry: Dictionary) -> Control:
var key := _entry_key(entry) var key := _entry_key(entry)
var cell: Variant = ASSET_CARD.new() var cell: Variant = ASSET_CARD.new()
+37
View File
@@ -344,8 +344,45 @@ func _test_selector_setup_routes(controller: StageSceneryController) -> void:
_capture_gui_screen("phase19_wall_setup.png") _capture_gui_screen("phase19_wall_setup.png")
var setup: Control = selector.find_child("SurfaceSetupView", true, false) as Control var setup: Control = selector.find_child("SurfaceSetupView", true, false) as Control
var draw: Button = selector.find_child("DrawSurfaceButton", true, false) as Button var draw: Button = selector.find_child("DrawSurfaceButton", true, false) as Button
var cancel_setup: Button = selector.find_child("CancelSurfaceSetupButton", true, false) as Button
var chooser_colors: StageSurfaceColorControls = selector.find_child("SurfaceColorControls", true, false) as StageSurfaceColorControls 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") _expect(setup != null and setup.visible and draw != null, "Wall panel setup shows a reachable preview and Draw action")
_expect(draw != null and draw.get_parent() == selector.find_child("Footer", true, false), "Wall panel Draw action stays fixed in the chooser footer")
_expect(cancel_setup != null and cancel_setup.visible and cancel_setup.get_parent() == selector.find_child("Footer", true, false), "Wall panel setup keeps an explicit Cancel action in the chooser footer")
_expect(browser.selector_dim != null and browser.selector_dim.mouse_filter == Control.MOUSE_FILTER_STOP, "Clicks outside the scenery chooser are blocked from the Stage canvas")
if gui_render:
selector.hide()
await process_frame
await process_frame
_expect(browser.selector_open and selector.visible, "Unexpected popup dismissal restores the chooser without cancelling setup")
if not gui_render:
browser.selector_open = true
if cancel_setup != null:
cancel_setup.pressed.emit()
_expect(not browser.selector_open and String(controller.get_active_tool()).is_empty(), "Cancel exits the scenery chooser without starting placement")
if gui_render:
_stage.call("_open_selector", "scenery")
await process_frame
await process_frame
await create_timer(0.2).timeout
else:
browser.selector_open = true
browser.set_selector_kind("scenery")
selector.kind = "scenery"
var reopened_entries: Array[Dictionary] = browser.scan_entries("scenery")
reopened_entries.append_array(browser.call("_scenery_background_entries") as Array[Dictionary])
selector.set_entries(reopened_entries)
wall_card = _find_card(selector.find_child("GridContainer", true, false) as GridContainer, "wall_panel_setup")
_expect(wall_card != null, "Cancel returns to a fresh scenery gallery")
if wall_card != null:
selector.call("_on_cell_pressed", wall_card.payload())
if gui_render:
await process_frame
await process_frame
setup = selector.find_child("SurfaceSetupView", true, false) as Control
draw = selector.find_child("DrawSurfaceButton", true, false) as Button
chooser_colors = selector.find_child("SurfaceColorControls", true, false) as StageSurfaceColorControls
_expect(setup != null and setup.visible and draw != null and draw.visible, "Wall setup reopens after Cancel with Draw available")
var wall_looks: StagePanelChoiceGroup = selector.get_meta("wall_look_group") as StagePanelChoiceGroup var wall_looks: StagePanelChoiceGroup = selector.get_meta("wall_look_group") as StagePanelChoiceGroup
if wall_looks != null: if wall_looks != null:
wall_looks.choice_selected.emit(&"finish_wall_wood") wall_looks.choice_selected.emit(&"finish_wall_wood")