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:
@@ -50,7 +50,7 @@ const STAGE_MODAL_VIEWS_SCRIPT := preload("res://scripts/editors/stage/stage_mod
|
||||
const STAGE_THEME_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_theme_controller.gd")
|
||||
const STAGE_TARGET_CAPTURE_SCRIPT := preload("res://scripts/editors/stage/stage_target_capture_session.gd")
|
||||
const STAGE_SCENERY_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/stage_scenery_controller.gd")
|
||||
const STAGE_SKY_VIEW_SCRIPT := preload("res://scripts/stage/scenery/stage_sky_view.gd")
|
||||
const STAGE_BACKGROUND_VIEW_SCRIPT := preload("res://scripts/stage/scenery/stage_background_view.gd")
|
||||
const STAGE_DRAW_ORDER_SCRIPT := preload("res://scripts/stage/stage_draw_order.gd")
|
||||
const STAGE_WALL_PANEL_SCRIPT := preload("res://scripts/stage/scenery/stage_wall_panel.gd")
|
||||
const STAGE_BACKGROUND_GROUND_SCRIPT := preload("res://scripts/stage/scenery/stage_background_ground.gd")
|
||||
@@ -258,13 +258,21 @@ var _spawner: StageSpawner
|
||||
var _selection: StageSelection
|
||||
var _gizmos: StageGizmos
|
||||
var _stage_input_ready: bool = false
|
||||
var _startup_scene_applied: bool = false
|
||||
var _startup_starter_chooser_pending: bool = false
|
||||
var _startup_starter_chooser_shown: bool = false
|
||||
var _grid: StageGrid
|
||||
var _spatial_index: StageSpatialIndex
|
||||
var _terrain_painter: StageTerrainPainter
|
||||
var _scenery_controller: StageSceneryController
|
||||
var _sky_layer: CanvasLayer
|
||||
var _sky_view: StageSkyView
|
||||
var _background_view: StageBackgroundView
|
||||
var _sky_id: StringName = &"sky_neutral"
|
||||
var _background_kind: StringName = &"sky"
|
||||
var _plain_color: Color = Color("#F5EFE0")
|
||||
var _plain_color_draft_active: bool = false
|
||||
var _plain_color_draft: Color = Color("#F5EFE0")
|
||||
var _new_ground_finish: StringName = &"finish_ground_grass"
|
||||
var _hosted_prop_editor: PropEditor
|
||||
var _hosted_artwork_path: String = ""
|
||||
@@ -1850,6 +1858,8 @@ func set_mode(mode: StageMode) -> void:
|
||||
_show_toast("Finish or cancel speech editing first.")
|
||||
_sync_mode_buttons()
|
||||
return
|
||||
if _plain_color_draft_active:
|
||||
_close_selector()
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_controller != null:
|
||||
if _scenery_controller.is_group_preview_pending():
|
||||
@@ -2513,6 +2523,74 @@ func _mark_scene_dirty() -> void:
|
||||
_paused_edit_autosave_remaining = 1.0
|
||||
|
||||
|
||||
func _refresh_background_view() -> void:
|
||||
if _background_view != null:
|
||||
var displayed_color: Color = _plain_color_draft if _plain_color_draft_active else _plain_color
|
||||
_background_view.set_background(_background_kind, _sky_id, displayed_color)
|
||||
_sky_view = _background_view.get_sky_view()
|
||||
_update_grid_background_style()
|
||||
|
||||
|
||||
func _commit_background_kind(kind: StringName, sky_id: StringName = &"") -> void:
|
||||
var next_kind: StringName = &"plain" if kind == &"plain" else &"sky"
|
||||
var next_sky: StringName = sky_id if not sky_id.is_empty() else _sky_id
|
||||
var changed: bool = _background_kind != next_kind or _sky_id != next_sky
|
||||
_background_kind = next_kind
|
||||
_sky_id = next_sky
|
||||
_refresh_background_view()
|
||||
if _asset_browser != null:
|
||||
_asset_browser.set_background_state(_background_kind, _sky_id, _plain_color)
|
||||
if changed:
|
||||
_mark_scene_dirty()
|
||||
|
||||
|
||||
func _preview_plain_color(color: Color) -> void:
|
||||
_plain_color_draft = _opaque_stage_color(color)
|
||||
_plain_color_draft_active = true
|
||||
_refresh_background_view()
|
||||
|
||||
|
||||
func _cancel_plain_color_preview() -> void:
|
||||
if not _plain_color_draft_active:
|
||||
return
|
||||
_plain_color_draft_active = false
|
||||
_plain_color_draft = _plain_color
|
||||
_refresh_background_view()
|
||||
|
||||
|
||||
func _commit_plain_color(color: Color) -> void:
|
||||
var next_color: Color = _opaque_stage_color(color)
|
||||
var changed: bool = _canonical_surface_color(_plain_color) != _canonical_surface_color(next_color)
|
||||
_plain_color = next_color
|
||||
_plain_color_draft = next_color
|
||||
_plain_color_draft_active = false
|
||||
_refresh_background_view()
|
||||
_record_recent_surface_color(next_color)
|
||||
if _asset_browser != null:
|
||||
_asset_browser.set_background_state(_background_kind, _sky_id, _plain_color)
|
||||
if changed:
|
||||
_mark_scene_dirty()
|
||||
|
||||
|
||||
func _opaque_stage_color(color: Color) -> Color:
|
||||
return Color(color.r, color.g, color.b, 1.0)
|
||||
|
||||
|
||||
func _update_grid_background_style() -> void:
|
||||
if _grid == null:
|
||||
return
|
||||
var displayed_color: Color = _plain_color_draft if _plain_color_draft_active else _plain_color
|
||||
var plain_active: bool = _background_kind == &"plain"
|
||||
_grid.set_background_treatment(
|
||||
plain_active,
|
||||
displayed_color,
|
||||
_sandbox_theme.color("grid.plain_light_minor", Color("#11182120")) if _sandbox_theme != null else Color("#11182120"),
|
||||
_sandbox_theme.color("grid.plain_light_major", Color("#11182148")) if _sandbox_theme != null else Color("#11182148"),
|
||||
_sandbox_theme.color("grid.plain_dark_minor", Color("#FFFFFF25")) if _sandbox_theme != null else Color("#FFFFFF25"),
|
||||
_sandbox_theme.color("grid.plain_dark_major", Color("#FFFFFF58")) if _sandbox_theme != null else Color("#FFFFFF58")
|
||||
)
|
||||
|
||||
|
||||
func _set_scene_dirty(value: bool) -> void:
|
||||
if _document != null:
|
||||
_document.set_dirty(value)
|
||||
@@ -2533,6 +2611,8 @@ func _collect_scene_data(is_autosave: bool = false) -> Dictionary:
|
||||
var document_metadata: Dictionary = {
|
||||
"scene_name": _scene_name,
|
||||
"sky_id": String(_sky_id),
|
||||
"background_kind": String(_background_kind),
|
||||
"plain_color": _canonical_surface_color(_plain_color),
|
||||
"metadata": _document.metadata_for_snapshot(is_autosave),
|
||||
}
|
||||
return _scene_adapter.collect(_world, _authored, _event_rules, camera_state, document_metadata)
|
||||
@@ -2613,6 +2693,7 @@ func _validate_queues_for_save() -> String:
|
||||
|
||||
func _reset_scene_content() -> void:
|
||||
set_mode(StageMode.EDIT)
|
||||
_cancel_plain_color_preview()
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_erase_piece_dialog != null:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
@@ -2622,8 +2703,9 @@ func _reset_scene_content() -> void:
|
||||
_cancel_scenery_repair_preview()
|
||||
_scenery_repair_target = null
|
||||
_sky_id = &"sky_neutral"
|
||||
if _sky_view != null:
|
||||
_sky_view.set_sky(_sky_id)
|
||||
_background_kind = &"sky"
|
||||
_plain_color = Color("#F5EFE0")
|
||||
_refresh_background_view()
|
||||
if _scenery_controller != null:
|
||||
_scenery_controller.exit_tool()
|
||||
_scenery_controller.cancel_group()
|
||||
@@ -2655,11 +2737,19 @@ func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool)
|
||||
source_path = CONTENT_PATHS.canonicalize(source_path)
|
||||
if _scene_adapter == null:
|
||||
return false
|
||||
if _plain_color_draft_active:
|
||||
_close_selector()
|
||||
_cancel_plain_color_preview()
|
||||
var was_suppressed: bool = _suppress_dirty
|
||||
_suppress_dirty = true
|
||||
_reset_scene_content()
|
||||
var adapter_result: Dictionary = _scene_adapter.apply(data, _world, _spawner)
|
||||
_sky_id = StringName(String(adapter_result.get("sky_id", "sky_neutral")))
|
||||
_background_kind = StringName(String(adapter_result.get("background_kind", "sky")))
|
||||
_plain_color = STAGE_SCENE_IO.color_from_data(
|
||||
adapter_result.get("plain_color", STAGE_SCENE_IO.DEFAULT_PLAIN_COLOR),
|
||||
Color("#F5EFE0")
|
||||
)
|
||||
var loaded_nodes: Array[Node2D] = adapter_result.get("loaded_nodes", [])
|
||||
for node: Node2D in loaded_nodes:
|
||||
_finalize_placed_node(node)
|
||||
@@ -2683,8 +2773,7 @@ func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool)
|
||||
var camera_data: Dictionary = raw_camera as Dictionary if raw_camera is Dictionary else {}
|
||||
_camera.global_position = STAGE_SCENE_IO.data_to_vector(camera_data.get("position"), DEFAULT_CAMERA_POSITION)
|
||||
_camera.zoom = Vector2.ONE * clampf(float(camera_data.get("zoom", DEFAULT_CAMERA_ZOOM)), min_zoom, max_zoom)
|
||||
if _sky_view != null:
|
||||
_sky_view.set_sky(_sky_id)
|
||||
_refresh_background_view()
|
||||
var raw_metadata: Variant = data.get("metadata", {})
|
||||
var metadata: Dictionary = raw_metadata as Dictionary if raw_metadata is Dictionary else {}
|
||||
if _document != null:
|
||||
@@ -2712,6 +2801,7 @@ func _apply_scene_data(data: Dictionary, source_path: String, is_recovery: bool)
|
||||
_add_recent_scene(source_path)
|
||||
if _document != null:
|
||||
_document.remove_autosave()
|
||||
_startup_scene_applied = true
|
||||
return true
|
||||
|
||||
func _show_scene_load_warnings(warnings: Array[String]) -> void:
|
||||
@@ -2782,20 +2872,79 @@ func _update_scene_chrome() -> void:
|
||||
_refresh_status()
|
||||
|
||||
|
||||
func _check_autosave_recovery() -> void:
|
||||
func _check_autosave_recovery(show_starter_chooser_after_discard: bool = false) -> void:
|
||||
if _document == null:
|
||||
if show_starter_chooser_after_discard:
|
||||
_queue_startup_starter_chooser()
|
||||
return
|
||||
var inspection: Dictionary = _document.inspect_autosave()
|
||||
if not bool(inspection.get("available", false)):
|
||||
if show_starter_chooser_after_discard:
|
||||
_queue_startup_starter_chooser()
|
||||
return
|
||||
var autosave_data: Dictionary = inspection.get("data", {}) as Dictionary
|
||||
var dialog := ConfirmationDialog.new()
|
||||
dialog.name = "AutosaveRecoveryDialog"; dialog.title = "Recover Sandbox Scene?"; dialog.dialog_text = "A previous Play session was not closed cleanly. Recover it?"
|
||||
dialog.confirmed.connect(func() -> void: _apply_scene_data(autosave_data, AUTOSAVE_PATH, true); dialog.queue_free())
|
||||
dialog.canceled.connect(func() -> void: _document.remove_autosave(); dialog.queue_free())
|
||||
dialog.canceled.connect(func() -> void:
|
||||
_document.remove_autosave()
|
||||
dialog.queue_free()
|
||||
if show_starter_chooser_after_discard:
|
||||
_queue_startup_starter_chooser()
|
||||
)
|
||||
get_node(NodePath("UI")).add_child(dialog); _apply_dialog_theme(dialog); dialog.popup_centered()
|
||||
|
||||
|
||||
func _queue_startup_starter_chooser() -> void:
|
||||
if _startup_starter_chooser_pending or _startup_starter_chooser_shown:
|
||||
return
|
||||
_startup_starter_chooser_pending = true
|
||||
call_deferred("_present_startup_starter_chooser")
|
||||
|
||||
|
||||
func _present_startup_starter_chooser() -> void:
|
||||
while _startup_starter_chooser_pending and is_inside_tree():
|
||||
if not _stage_input_ready or _startup_modal_is_visible():
|
||||
await get_tree().process_frame
|
||||
continue
|
||||
if get_tree().current_scene != self or _startup_scene_applied or current_mode != StageMode.EDIT or not _current_scene_path.is_empty() or _hosted_prop_editor != null:
|
||||
_startup_starter_chooser_pending = false
|
||||
return
|
||||
_startup_starter_chooser_pending = false
|
||||
_startup_starter_chooser_shown = true
|
||||
_open_selector("starters")
|
||||
return
|
||||
_startup_starter_chooser_pending = false
|
||||
|
||||
|
||||
func _startup_modal_is_visible() -> bool:
|
||||
if _selector_open or _speech_composer_is_open():
|
||||
return true
|
||||
var ui_layer: CanvasLayer = _chrome.get_canvas_layer() if _chrome != null else null
|
||||
if ui_layer != null:
|
||||
for child: Node in ui_layer.get_children():
|
||||
var window := child as Window
|
||||
if window != null and window.visible:
|
||||
return true
|
||||
for dialog: Window in [
|
||||
_cli_error_dialog,
|
||||
_speech_recovery_dialog,
|
||||
_speech_save_gate_dialog,
|
||||
_scene_open_dialog,
|
||||
_scene_save_dialog,
|
||||
_unsaved_changes_dialog,
|
||||
_rename_scene_dialog,
|
||||
_missing_assets_dialog,
|
||||
_missing_scenery_dialog,
|
||||
_scenery_repair_confirm_dialog,
|
||||
_group_preview_save_dialog,
|
||||
_confirm_dialog,
|
||||
]:
|
||||
if dialog != null and is_instance_valid(dialog) and dialog.visible:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _handle_cli_startup(args: Array) -> void:
|
||||
if OS.get_environment("STICKMAN_WALK_DEBUG") == "1":
|
||||
print("[WalkDebug] CLI_STARTUP args=%s" % [args])
|
||||
@@ -2806,7 +2955,7 @@ func _handle_cli_startup(args: Array) -> void:
|
||||
return
|
||||
var scene_path := String(result.get("scene_path", ""))
|
||||
if scene_path.is_empty():
|
||||
_check_autosave_recovery()
|
||||
_check_autosave_recovery(true)
|
||||
return
|
||||
if _document != null and _document.has_autosave():
|
||||
_document.remove_autosave()
|
||||
@@ -3085,6 +3234,9 @@ func _build_ui() -> void:
|
||||
|
||||
_asset_browser.asset_selected_requested.connect(_on_asset_selected)
|
||||
_asset_browser.selector_cancel_requested.connect(_on_selector_cancelled)
|
||||
_asset_browser.plain_color_preview_requested.connect(_preview_plain_color)
|
||||
_asset_browser.plain_color_commit_requested.connect(_commit_plain_color)
|
||||
_asset_browser.plain_color_preview_cancel_requested.connect(_cancel_plain_color_preview)
|
||||
_asset_browser.asset_load_failed.connect(func(kind: String, path: String) -> void:
|
||||
if _scenery_repair_target != null and is_instance_valid(_scenery_repair_target):
|
||||
_show_toast("Could not open that artwork. Try another file.")
|
||||
@@ -3097,6 +3249,7 @@ func _build_ui() -> void:
|
||||
_asset_browser.edit_scenery_requested.connect(_enter_scenery_edit_mode)
|
||||
_asset_browser.surface_color_confirmed.connect(_record_recent_surface_color)
|
||||
_asset_browser.set_surface_color_history(_recent_surface_colors)
|
||||
_asset_browser.set_background_state(_background_kind, _sky_id, _plain_color)
|
||||
_build_ground_finish_popup(ui)
|
||||
|
||||
_modal_views.build_file_dialogs(ui)
|
||||
@@ -4510,6 +4663,8 @@ func _build_gizmo_layer() -> void:
|
||||
_gizmos = STAGE_GIZMOS.new()
|
||||
_gizmos.name = "GizmoLayer"
|
||||
_gizmos.camera = _camera
|
||||
if _sandbox_theme != null:
|
||||
_gizmos.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
_gizmos.transform_committed.connect(_on_transform_committed)
|
||||
_gizmos.transform_started.connect(_on_gizmo_transform_started)
|
||||
_gizmos.transform_updated.connect(_on_gizmo_transform_updated)
|
||||
@@ -4522,6 +4677,7 @@ func _build_grid_layer() -> void:
|
||||
_grid.camera = _camera
|
||||
add_child(_grid)
|
||||
move_child(_grid, 0)
|
||||
_update_grid_background_style()
|
||||
|
||||
|
||||
func _build_ghost_holder() -> void:
|
||||
@@ -4542,15 +4698,19 @@ func _build_scenery_controller() -> void:
|
||||
|
||||
func _build_sky_layer() -> void:
|
||||
_sky_layer = CanvasLayer.new()
|
||||
_sky_layer.name = "StageSkyLayer"
|
||||
_sky_layer.name = "StageBackgroundLayer"
|
||||
_sky_layer.layer = -1
|
||||
_sky_view = STAGE_SKY_VIEW_SCRIPT.new() as StageSkyView
|
||||
_sky_view.name = "StageSkyView"
|
||||
_sky_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_sky_view.set_sky(_sky_id)
|
||||
_sky_layer.add_child(_sky_view)
|
||||
_background_view = STAGE_BACKGROUND_VIEW_SCRIPT.new() as StageBackgroundView
|
||||
_background_view.name = "StageBackgroundView"
|
||||
_background_view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_background_view.set_background(_background_kind, _sky_id, _plain_color)
|
||||
_sky_layer.add_child(_background_view)
|
||||
add_child(_sky_layer)
|
||||
_sky_view.bind_camera(_camera)
|
||||
_background_view.bind_camera(_camera)
|
||||
_sky_view = _background_view.get_sky_view()
|
||||
if _sandbox_theme != null:
|
||||
_background_view.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
_update_grid_background_style()
|
||||
|
||||
|
||||
func _build_placement_overlay() -> void:
|
||||
@@ -4912,7 +5072,13 @@ func _open_selector(kind: String) -> void:
|
||||
(_palette_buttons[pid] as Button).set_pressed_no_signal(pid == kind)
|
||||
if kind == "scenery":
|
||||
_asset_browser.set_surface_color_history(_recent_surface_colors)
|
||||
var selector_state: Dictionary = _asset_browser.open_selector(kind, _spawner.get_selected_stickman_path(), _sky_id)
|
||||
var selector_state: Dictionary = _asset_browser.open_selector(
|
||||
kind,
|
||||
_spawner.get_selected_stickman_path(),
|
||||
_sky_id,
|
||||
_background_kind,
|
||||
_plain_color
|
||||
)
|
||||
if kind == "stickman":
|
||||
var selected_stickman_path: String = String(selector_state.get("selected_stickman_path", _spawner.get_selected_stickman_path()))
|
||||
if selected_stickman_path != _spawner.get_selected_stickman_path():
|
||||
@@ -4940,12 +5106,14 @@ func _on_asset_selected(entry: Dictionary) -> void:
|
||||
return
|
||||
var background_kind: String = String(entry.get("background_kind", ""))
|
||||
if background_kind == "sky":
|
||||
_sky_id = StringName(String(entry.get("id", "sky_neutral")))
|
||||
_sky_view.set_sky(_sky_id)
|
||||
var scenery_selector: AssetSelector = _asset_browser.get_selector()
|
||||
if scenery_selector != null:
|
||||
scenery_selector.set_current_sky(_sky_id)
|
||||
_mark_scene_dirty()
|
||||
_commit_background_kind(&"sky", StringName(String(entry.get("id", "sky_neutral"))))
|
||||
_refresh_scenery_capture_bar()
|
||||
return
|
||||
if background_kind == "plain":
|
||||
_commit_background_kind(&"plain")
|
||||
var plain_selector: AssetSelector = _asset_browser.get_selector()
|
||||
if plain_selector != null:
|
||||
plain_selector.open_plain_background_choices(_plain_color)
|
||||
_refresh_scenery_capture_bar()
|
||||
return
|
||||
if background_kind == "wall":
|
||||
@@ -5774,8 +5942,15 @@ func _handle_scenery_edit_motion(world_position: Vector2) -> void:
|
||||
|
||||
|
||||
func _close_selector() -> void:
|
||||
var closing_kind: String = _selector_kind
|
||||
_cancel_plain_color_preview()
|
||||
if _asset_browser != null:
|
||||
_asset_browser.close()
|
||||
if closing_kind == "scenery":
|
||||
# Scenery is a chooser entry; the capture bar communicates active drawing tools.
|
||||
var scenery_button: Button = _palette_buttons.get("scenery") as Button
|
||||
if scenery_button != null:
|
||||
scenery_button.set_pressed_no_signal(false)
|
||||
|
||||
|
||||
func _on_selector_cancelled() -> void:
|
||||
@@ -5783,7 +5958,7 @@ func _on_selector_cancelled() -> void:
|
||||
return
|
||||
var kind: String = _selector_kind
|
||||
_close_selector()
|
||||
if kind != "scenery" and kind != "starters":
|
||||
if kind != "starters":
|
||||
set_placement_mode("")
|
||||
|
||||
|
||||
@@ -7900,8 +8075,12 @@ func _load_font(path: String) -> Font:
|
||||
return _theme_controller.load_font(path)
|
||||
|
||||
func _apply_theme_to_visuals() -> void:
|
||||
if _sky_view != null and _sandbox_theme != null:
|
||||
_sky_view.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
if _background_view != null and _sandbox_theme != null:
|
||||
_background_view.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
if _grid != null:
|
||||
_update_grid_background_style()
|
||||
if _gizmos != null and _sandbox_theme != null:
|
||||
_gizmos.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
if _director_visuals != null:
|
||||
_director_visuals.set_style(_theme)
|
||||
_director_visuals.completed_path_alpha = _sandbox_theme.number("overlays.completed_path_alpha", 0.20, 0.0, 1.0) if _sandbox_theme != null else 0.20
|
||||
|
||||
@@ -9,6 +9,9 @@ signal refresh_requested
|
||||
signal make_artwork_requested
|
||||
signal edit_scenery_requested
|
||||
signal surface_color_confirmed(color: Color)
|
||||
signal plain_color_preview_requested(color: Color)
|
||||
signal plain_color_commit_requested(color: Color)
|
||||
signal plain_color_preview_cancel_requested()
|
||||
|
||||
const ASSET_SELECTOR_SCENE := preload("res://scenes/stage/asset_selector.tscn")
|
||||
const STICKMAN_LIBRARY_SCRIPT := preload("res://scripts/characters/stickman/stickman_library.gd")
|
||||
@@ -32,6 +35,9 @@ var browse_dialog: FileDialog
|
||||
var thumbnail_queue: Array[Dictionary] = []
|
||||
var thumbnail_busy: bool = false
|
||||
var thumbnail_generation: int = 0
|
||||
var _current_background_kind: StringName = &"sky"
|
||||
var _current_sky_id: StringName = &"sky_neutral"
|
||||
var _current_plain_color: Color = Color("#F5EFE0")
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -62,6 +68,10 @@ func build_ui(ui_layer: CanvasLayer, ui_font: Font, emoji_font: Font, sandbox_th
|
||||
selector.make_artwork_requested.connect(func() -> void: make_artwork_requested.emit())
|
||||
selector.edit_scenery_requested.connect(func() -> void: edit_scenery_requested.emit())
|
||||
selector.surface_color_confirmed.connect(func(color: Color) -> void: surface_color_confirmed.emit(color))
|
||||
selector.plain_color_preview_requested.connect(func(color: Color) -> void: plain_color_preview_requested.emit(color))
|
||||
selector.plain_color_commit_requested.connect(func(color: Color) -> void: plain_color_commit_requested.emit(color))
|
||||
selector.plain_color_preview_cancel_requested.connect(func() -> void: plain_color_preview_cancel_requested.emit())
|
||||
selector.stage_preview_dimming_changed.connect(_on_selector_stage_preview_dimming_changed)
|
||||
ui_layer.add_child(selector)
|
||||
# apply_font AFTER add_child so the selector's @onready nodes resolve first.
|
||||
selector.apply_font(ui_font, emoji_font)
|
||||
@@ -92,12 +102,28 @@ func set_surface_color_history(colors: Array[Color]) -> void:
|
||||
selector.set_surface_color_history(colors)
|
||||
|
||||
|
||||
func set_background_state(kind: StringName, sky_id: StringName, plain_color: Color) -> void:
|
||||
_current_background_kind = &"plain" if kind == &"plain" else &"sky"
|
||||
_current_sky_id = sky_id
|
||||
_current_plain_color = Color(plain_color.r, plain_color.g, plain_color.b, 1.0)
|
||||
if selector != null:
|
||||
selector.set_background_state(_current_background_kind, _current_sky_id, _current_plain_color)
|
||||
|
||||
|
||||
func _starter_scene_entries() -> Array[Dictionary]:
|
||||
return [
|
||||
{
|
||||
"id": "starter_plain_background",
|
||||
"name": "Plain Background",
|
||||
"description": "An empty stage with one color. Choose a new color whenever you like.",
|
||||
"category": "Make your own",
|
||||
"path": "res://content/stages/starters/plain_background.scene",
|
||||
"icon": "🎨",
|
||||
},
|
||||
{
|
||||
"id": "starter_blank_scene",
|
||||
"name": "Blank Scene",
|
||||
"description": "Start fresh with an open stage and a clear sky.",
|
||||
"name": "Open Sky",
|
||||
"description": "An empty stage with a soft sky. Sun and clouds, ready for your story.",
|
||||
"category": "Make your own",
|
||||
"path": "res://content/stages/starters/blank.scene",
|
||||
"icon": "✨",
|
||||
@@ -129,7 +155,16 @@ func make_browse_entry(kind: String, path: String) -> Dictionary:
|
||||
return stickman_library.make_entry(path)
|
||||
|
||||
|
||||
func open_selector(kind: String, current_stickman_path: String, current_sky_id: StringName = &"") -> Dictionary:
|
||||
func open_selector(
|
||||
kind: String,
|
||||
current_stickman_path: String,
|
||||
current_sky_id: StringName = &"",
|
||||
background_kind: StringName = &"sky",
|
||||
plain_color: Color = Color("#F5EFE0")
|
||||
) -> Dictionary:
|
||||
_current_background_kind = &"plain" if background_kind == &"plain" else &"sky"
|
||||
_current_sky_id = current_sky_id if not current_sky_id.is_empty() else &"sky_neutral"
|
||||
_current_plain_color = Color(plain_color.r, plain_color.g, plain_color.b, 1.0)
|
||||
set_selector_kind(kind)
|
||||
var entries: Array[Dictionary] = scan_entries(kind)
|
||||
if kind == "starters":
|
||||
@@ -142,7 +177,7 @@ func open_selector(kind: String, current_stickman_path: String, current_sky_id:
|
||||
selected_stickman_path = String(entries[0].get("path", ""))
|
||||
var auto_selection: Dictionary = entries[0] if kind == "stickman" and entries.size() == 1 else {}
|
||||
if auto_selection.is_empty():
|
||||
open(kind, entries, current_sky_id)
|
||||
open(kind, entries, _current_sky_id)
|
||||
return {
|
||||
"entries": entries,
|
||||
"selected_stickman_path": selected_stickman_path,
|
||||
@@ -152,6 +187,7 @@ func open_selector(kind: String, current_stickman_path: String, current_sky_id:
|
||||
|
||||
func _scenery_background_entries() -> Array[Dictionary]:
|
||||
return [
|
||||
{"id": "plain_background", "name": "Plain Background", "category": "Scene background", "gallery_tab": "backgrounds", "background_kind": "plain", "plain_color": _current_plain_color, "icon": "🎨"},
|
||||
{"id": "sky_clear_day", "name": "Clear Day", "category": "Sky", "gallery_tab": "backgrounds", "background_kind": "sky", "icon": "☀️"},
|
||||
{"id": "sky_cloudy_day", "name": "Cloudy Day", "category": "Sky", "gallery_tab": "backgrounds", "background_kind": "sky", "icon": "☁️"},
|
||||
{"id": "sky_sunset", "name": "Sunset", "category": "Sky", "gallery_tab": "backgrounds", "background_kind": "sky", "icon": "🌅"},
|
||||
@@ -170,8 +206,9 @@ func open(kind: String, entries: Array[Dictionary], current_sky_id: StringName =
|
||||
selector_open = true
|
||||
if selector_dim != null:
|
||||
selector_dim.visible = true
|
||||
selector_dim.color = Color(0.0, 0.0, 0.0, SELECTOR_DIM_ALPHA)
|
||||
if selector != null:
|
||||
selector.open(kind, entries, String(current_sky_id))
|
||||
selector.open(kind, entries, String(current_sky_id), String(_current_background_kind), _current_plain_color)
|
||||
load_or_enqueue_thumbnails(kind, entries)
|
||||
|
||||
|
||||
@@ -206,6 +243,8 @@ func set_entries(entries: Array[Dictionary]) -> void:
|
||||
|
||||
func refresh_selector_entries() -> Array[Dictionary]:
|
||||
var entries: Array[Dictionary] = scan_entries(selector_kind)
|
||||
if selector_kind == "scenery":
|
||||
entries.append_array(_scenery_background_entries())
|
||||
set_entries(entries)
|
||||
return entries
|
||||
|
||||
@@ -282,6 +321,13 @@ func _on_selector_cancelled() -> void:
|
||||
selector_cancel_requested.emit()
|
||||
|
||||
|
||||
func _on_selector_stage_preview_dimming_changed(should_dim: bool) -> void:
|
||||
if selector_dim == null:
|
||||
return
|
||||
selector_dim.color = Color(0.0, 0.0, 0.0, SELECTOR_DIM_ALPHA if should_dim else 0.0)
|
||||
selector_dim.visible = selector_open
|
||||
|
||||
|
||||
func _on_selector_popup_hide() -> void:
|
||||
if not selector_open:
|
||||
return
|
||||
|
||||
@@ -130,6 +130,8 @@ func collect(
|
||||
"version": STAGE_SCENE_IO.FILE_VERSION,
|
||||
"scene_name": scene_name,
|
||||
"sky_id": String(document_metadata.get("sky_id", "sky_neutral")),
|
||||
"background_kind": String(document_metadata.get("background_kind", "sky")),
|
||||
"plain_color": String(document_metadata.get("plain_color", STAGE_SCENE_IO.DEFAULT_PLAIN_COLOR)),
|
||||
"camera": {"position": STAGE_SCENE_IO.vector_to_data(camera_position), "zoom": camera_zoom},
|
||||
"objects": objects,
|
||||
"event_rules": rules_result["rules"],
|
||||
@@ -299,6 +301,8 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
"missing_paths": missing_paths,
|
||||
"decoded_rules": decoded_rules,
|
||||
"sky_id": String(data.get("sky_id", "sky_neutral")),
|
||||
"background_kind": String(data.get("background_kind", "sky")),
|
||||
"plain_color": String(data.get("plain_color", STAGE_SCENE_IO.DEFAULT_PLAIN_COLOR)),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,14 +39,18 @@ func configure(selected: Color, recents: Array[Color], presets: Array[Color], st
|
||||
func set_selected_no_signal(color: Color) -> void:
|
||||
_selected = color
|
||||
_refresh_current_color()
|
||||
_recent_swatches.set_selected(color)
|
||||
_preset_swatches.set_selected(color)
|
||||
if _recent_swatches != null:
|
||||
_recent_swatches.set_selected(color)
|
||||
if _preset_swatches != null:
|
||||
_preset_swatches.set_selected(color)
|
||||
|
||||
|
||||
func set_recent_colors(colors: Array[Color]) -> void:
|
||||
if _recents == colors:
|
||||
return
|
||||
_recents = colors.duplicate()
|
||||
if _recent_empty_label == null or _recent_swatches == null:
|
||||
return
|
||||
_recent_empty_label.visible = _recents.is_empty()
|
||||
_recent_swatches.visible = not _recents.is_empty()
|
||||
_recent_swatches.configure(_recents, _selected, _stage_theme)
|
||||
@@ -99,6 +103,7 @@ func _build_current_color_section(parent: Control) -> void:
|
||||
current_row.add_theme_constant_override("separation", _compact_gap())
|
||||
parent.add_child(current_row)
|
||||
_current_chip = Button.new()
|
||||
_current_chip.name = "CurrentColorChip"
|
||||
var swatch_size: float = float(_integer("stage_surface_colors.swatch_size", 48, 44, 96))
|
||||
if _compact_mode:
|
||||
swatch_size = maxf(swatch_size, 52.0)
|
||||
@@ -116,7 +121,8 @@ func _build_current_color_section(parent: Control) -> void:
|
||||
func _build_recent_color_section(parent: Control) -> void:
|
||||
parent.add_child(_make_heading("Recently used"))
|
||||
_recent_empty_label = Label.new()
|
||||
_recent_empty_label.text = "No colors yet" if _compact_mode else "Colors you use will appear here"
|
||||
_recent_empty_label.name = "RecentColorsEmptyLabel"
|
||||
_recent_empty_label.text = "No colors yet" if _compact_mode else "Colors you use will appear here."
|
||||
_recent_empty_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_recent_empty_label.add_theme_color_override("font_color", _color("global.muted_text_color", Color("#AAB2C0")))
|
||||
if _compact_mode:
|
||||
@@ -125,7 +131,7 @@ func _build_recent_color_section(parent: Control) -> void:
|
||||
_recent_swatches = COLOR_SWATCH_SELECTOR.new() as ColorSwatchSelector
|
||||
_recent_swatches.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if _compact_mode:
|
||||
_recent_swatches.set_swatch_layout_override(_integer("popups.asset_selector.compact_swatch_size", 42, 28, 52), 8)
|
||||
_recent_swatches.set_swatch_layout_override(_integer("popups.asset_selector.compact_swatch_size", 44, 44, 52), 8)
|
||||
_recent_swatches.color_selected.connect(_on_swatch_selected)
|
||||
parent.add_child(_recent_swatches)
|
||||
|
||||
@@ -135,7 +141,7 @@ func _build_palette_section(parent: Control) -> void:
|
||||
_preset_swatches = COLOR_SWATCH_SELECTOR.new() as ColorSwatchSelector
|
||||
_preset_swatches.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
if _compact_mode:
|
||||
_preset_swatches.set_swatch_layout_override(_integer("popups.asset_selector.compact_swatch_size", 42, 28, 52), 8)
|
||||
_preset_swatches.set_swatch_layout_override(_integer("popups.asset_selector.compact_swatch_size", 44, 44, 52), 8)
|
||||
_preset_swatches.color_selected.connect(_on_swatch_selected)
|
||||
parent.add_child(_preset_swatches)
|
||||
|
||||
|
||||
@@ -22,9 +22,11 @@ var _preferred_preview_height: float = 140.0
|
||||
var _preferred_picker_min_size: Vector2 = Vector2(320.0, 340.0)
|
||||
var _built: bool = false
|
||||
var _suppress_color_signal: bool = false
|
||||
var _plain_background_mode: bool = false
|
||||
|
||||
|
||||
func configure(surface_kind: StringName, finish: StringName, color: Color, stage_theme: SandboxTheme) -> void:
|
||||
_plain_background_mode = false
|
||||
_surface_kind = surface_kind
|
||||
_finish_id = finish
|
||||
_stage_theme = stage_theme
|
||||
@@ -35,6 +37,19 @@ func configure(surface_kind: StringName, finish: StringName, color: Color, stage
|
||||
_apply_preview_heading_theme()
|
||||
|
||||
|
||||
func configure_plain_background(color: Color, stage_theme: SandboxTheme) -> void:
|
||||
_plain_background_mode = true
|
||||
_surface_kind = &"plain_background"
|
||||
_finish_id = &""
|
||||
_stage_theme = stage_theme
|
||||
if not _built:
|
||||
_build()
|
||||
set_color_no_signal(Color(color.r, color.g, color.b, 1.0))
|
||||
_color_picker.edit_alpha = false
|
||||
theme = stage_theme.control_theme() if stage_theme != null else null
|
||||
_apply_preview_heading_theme()
|
||||
|
||||
|
||||
func get_color() -> Color:
|
||||
return _color_picker.color if _color_picker != null else Color.WHITE
|
||||
|
||||
|
||||
@@ -15,6 +15,26 @@ var _label: Label = null
|
||||
var _description: Label = null
|
||||
var _status_label: Label = null
|
||||
|
||||
static var _rounded_preview_shader: Shader
|
||||
|
||||
const ROUNDED_PREVIEW_SHADER_CODE := """
|
||||
shader_type canvas_item;
|
||||
|
||||
uniform float corner_radius_px = 12.0;
|
||||
|
||||
void fragment() {
|
||||
vec4 preview_color = texture(TEXTURE, UV);
|
||||
vec2 preview_size = vec2(1.0) / TEXTURE_PIXEL_SIZE;
|
||||
vec2 half_size = preview_size * 0.5;
|
||||
vec2 local_position = UV * preview_size;
|
||||
vec2 rounded_box = abs(local_position - half_size) - (half_size - vec2(corner_radius_px));
|
||||
float edge_distance = length(max(rounded_box, vec2(0.0))) + min(max(rounded_box.x, rounded_box.y), 0.0) - corner_radius_px;
|
||||
float antialias_width = max(fwidth(edge_distance), 1.0);
|
||||
preview_color.a *= 1.0 - smoothstep(0.0, antialias_width, edge_distance);
|
||||
COLOR = preview_color;
|
||||
}
|
||||
"""
|
||||
|
||||
func configure(payload: Dictionary, icon_texture: Texture2D = null, theme: Variant = null) -> void:
|
||||
_payload = payload.duplicate(true)
|
||||
_drag_threshold = theme.number("palette.drag_threshold", 8.0, 1.0, 128.0) if theme != null else 8.0
|
||||
@@ -32,7 +52,9 @@ func configure(payload: Dictionary, icon_texture: Texture2D = null, theme: Varia
|
||||
_box.alignment = BoxContainer.ALIGNMENT_CENTER; _box.add_theme_constant_override("separation", 2); add_child(_box)
|
||||
_icon = TextureRect.new(); _icon.custom_minimum_size = thumbnail_size; _icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE; _icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED; _icon.texture = icon_texture; _icon.visible = icon_texture != null; _icon.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.add_child(_icon)
|
||||
_glyph = Label.new(); _glyph.text = String(_payload.get("icon", "")); _glyph.visible = icon_texture == null; _glyph.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _glyph.mouse_filter = Control.MOUSE_FILTER_IGNORE; _glyph.add_theme_font_size_override("font_size", 30); _box.add_child(_glyph)
|
||||
_label = Label.new(); _label.text = String(_payload.get("label", "")); _label.custom_minimum_size = Vector2(0, 28); _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; _label.clip_text = true; _label.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.add_child(_label)
|
||||
_label = Label.new(); _label.text = String(_payload.get("label", "")); _label.custom_minimum_size = Vector2(0, float(_payload.get("label_min_height", 28.0))); _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; _label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; _label.clip_text = true; _label.mouse_filter = Control.MOUSE_FILTER_IGNORE; _box.add_child(_label)
|
||||
if _payload.has("label_font_size"):
|
||||
_label.add_theme_font_size_override("font_size", int(_payload["label_font_size"]))
|
||||
var description: String = String(_payload.get("description", ""))
|
||||
var status: String = String(_payload.get("status", ""))
|
||||
if not status.is_empty():
|
||||
@@ -56,6 +78,8 @@ func configure(payload: Dictionary, icon_texture: Texture2D = null, theme: Varia
|
||||
_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_description.max_lines_visible = 2
|
||||
_description.add_theme_font_size_override("font_size", 14)
|
||||
if _payload.has("description_font_size"):
|
||||
_description.add_theme_font_size_override("font_size", int(_payload["description_font_size"]))
|
||||
_description.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_box.add_child(_description)
|
||||
|
||||
@@ -75,8 +99,34 @@ func set_preview_control(preview: Control) -> void:
|
||||
preview.custom_minimum_size = preview_size
|
||||
preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_box.add_child(preview)
|
||||
_box.move_child(preview, 0)
|
||||
var preview_corner_radius: float = float(_payload.get("preview_corner_radius", 0.0))
|
||||
if preview_corner_radius > 0.0:
|
||||
var rounded_material := ShaderMaterial.new()
|
||||
rounded_material.shader = _get_rounded_preview_shader()
|
||||
rounded_material.set_shader_parameter("corner_radius_px", preview_corner_radius)
|
||||
preview.material = rounded_material
|
||||
var horizontal_inset: int = int(_payload.get("preview_horizontal_inset", 0))
|
||||
var top_inset: int = int(_payload.get("preview_top_inset", 0))
|
||||
if horizontal_inset > 0 or top_inset > 0:
|
||||
var preview_frame := MarginContainer.new()
|
||||
preview_frame.name = "PreviewInsetFrame"
|
||||
preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
preview_frame.add_theme_constant_override("margin_left", horizontal_inset)
|
||||
preview_frame.add_theme_constant_override("margin_right", horizontal_inset)
|
||||
preview_frame.add_theme_constant_override("margin_top", top_inset)
|
||||
preview_frame.add_child(preview)
|
||||
_box.add_child(preview_frame)
|
||||
_box.move_child(preview_frame, 0)
|
||||
else:
|
||||
_box.add_child(preview)
|
||||
_box.move_child(preview, 0)
|
||||
|
||||
|
||||
func _get_rounded_preview_shader() -> Shader:
|
||||
if _rounded_preview_shader == null:
|
||||
_rounded_preview_shader = Shader.new()
|
||||
_rounded_preview_shader.code = ROUNDED_PREVIEW_SHADER_CODE
|
||||
return _rounded_preview_shader
|
||||
|
||||
func set_thumbnail(texture: Texture2D) -> void:
|
||||
if _icon != null: _icon.texture = texture; _icon.visible = texture != null
|
||||
|
||||
@@ -8,6 +8,10 @@ signal refresh_requested()
|
||||
signal make_artwork_requested()
|
||||
signal edit_scenery_requested()
|
||||
signal surface_color_confirmed(color: Color)
|
||||
signal plain_color_preview_requested(color: Color)
|
||||
signal plain_color_commit_requested(color: Color)
|
||||
signal plain_color_preview_cancel_requested()
|
||||
signal stage_preview_dimming_changed(should_dim: bool)
|
||||
|
||||
const COLUMNS := 4
|
||||
const ROWS := 3
|
||||
@@ -49,11 +53,14 @@ var _filter_chip_group: ButtonGroup
|
||||
var _artwork_filter_buttons: Array[Button] = []
|
||||
var _tag_id: String = "all"
|
||||
var _current_sky_id: String = ""
|
||||
var _current_background_kind: StringName = &"sky"
|
||||
var _current_plain_color: Color = Color("#F5EFE0")
|
||||
var _sky_card_group: ButtonGroup
|
||||
var _active_columns: int = COLUMNS
|
||||
var _page_capacity: int = PAGE_SIZE
|
||||
var _effective_cell_size: Vector2 = CELL_MIN_SIZE
|
||||
var _surface_cell_size: Vector2 = CELL_MIN_SIZE
|
||||
var _surface_row_heading: Label
|
||||
var _surface_color_history: Array[Color] = []
|
||||
var _setup_view: VBoxContainer
|
||||
var _color_picker_view: StageSurfaceColorPickerView
|
||||
@@ -69,6 +76,18 @@ var _setup_cancel_button: Button
|
||||
var _setup_draw_button: Button
|
||||
var _color_back_button: Button
|
||||
var _color_use_button: Button
|
||||
var _plain_color_view: VBoxContainer
|
||||
var _plain_color_preview: StageSurfacePreview
|
||||
var _plain_color_controls: StageSurfaceColorControls
|
||||
var _plain_color_helper: Label
|
||||
var _plain_color_choices_open: bool = false
|
||||
var _plain_color_original: Color = Color("#F5EFE0")
|
||||
var _plain_color_draft: Color = Color("#F5EFE0")
|
||||
var _color_picker_owner: StringName = &"surface"
|
||||
var _backgrounds_back_button: Button
|
||||
var _plain_footer_row: HBoxContainer
|
||||
var _plain_footer_column: VBoxContainer
|
||||
var _stage_preview_is_dimmed: bool = true
|
||||
|
||||
@onready var _title_label: Label = %TitleLabel
|
||||
@onready var _grid: GridContainer = %GridContainer
|
||||
@@ -103,6 +122,8 @@ func _ready() -> void:
|
||||
tab.toggle_mode = true
|
||||
tab.button_group = _gallery_tab_group
|
||||
_build_surface_setup_view()
|
||||
_build_plain_background_view()
|
||||
_build_surface_row_heading()
|
||||
_rebuild_artwork_filter_chips()
|
||||
_prev_button.pressed.connect(_on_prev)
|
||||
_next_button.pressed.connect(_on_next)
|
||||
@@ -113,6 +134,7 @@ func _ready() -> void:
|
||||
_artwork_tab.pressed.connect(func() -> void: _set_gallery_tab("artwork"))
|
||||
_make_artwork_button.pressed.connect(func() -> void: make_artwork_requested.emit())
|
||||
_edit_scenery_button.pressed.connect(func() -> void: edit_scenery_requested.emit())
|
||||
popup_hide.connect(_on_popup_hide)
|
||||
get_tree().root.size_changed.connect(_on_root_size_changed)
|
||||
_apply_chooser_visual_style()
|
||||
|
||||
@@ -123,15 +145,27 @@ func _on_root_size_changed() -> void:
|
||||
_rebuild()
|
||||
|
||||
|
||||
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 = "",
|
||||
current_background_kind: String = "sky",
|
||||
plain_color: Color = Color("#F5EFE0")
|
||||
) -> void:
|
||||
_cancel_plain_picker_preview(false)
|
||||
kind = p_kind
|
||||
_setup_kind = &""
|
||||
_color_picker_open = false
|
||||
_plain_color_choices_open = false
|
||||
_setup_view.visible = false
|
||||
_plain_color_view.visible = false
|
||||
if _color_picker_view != null:
|
||||
_color_picker_view.visible = false
|
||||
_current_sky_id = current_sky_id if kind == "scenery" else ""
|
||||
_current_background_kind = &"plain" if current_background_kind == "plain" else &"sky"
|
||||
_current_plain_color = _opaque_color(plain_color)
|
||||
_title_label.text = "Choose Your Stickman" if kind == "stickman" else ("Choose Scenery" if kind == "scenery" else ("Choose a starter scene" if kind == "starters" else "Choose a Prop"))
|
||||
_backgrounds_back_button.visible = false
|
||||
_apply_chooser_visual_style()
|
||||
_tabs.visible = kind == "scenery"
|
||||
_filter_chips.visible = false
|
||||
@@ -165,6 +199,47 @@ func set_surface_color_history(colors: Array[Color]) -> void:
|
||||
_surface_color_history = colors.duplicate()
|
||||
if _setup_color_controls != null and is_instance_valid(_setup_color_controls):
|
||||
_setup_color_controls.set_recent_colors(_surface_color_history)
|
||||
if _plain_color_controls != null and is_instance_valid(_plain_color_controls):
|
||||
_plain_color_controls.set_recent_colors(_surface_color_history)
|
||||
|
||||
|
||||
func set_background_state(background_kind: StringName, sky_id: StringName, plain_color: Color) -> void:
|
||||
_current_background_kind = &"plain" if background_kind == &"plain" else &"sky"
|
||||
_current_sky_id = String(sky_id)
|
||||
_current_plain_color = _opaque_color(plain_color)
|
||||
for entry: Dictionary in _entries:
|
||||
if String(entry.get("background_kind", "")) == "plain":
|
||||
entry["plain_color"] = _current_plain_color
|
||||
if not _plain_color_choices_open and not (_color_picker_open and _color_picker_owner == &"plain"):
|
||||
_plain_color_draft = _current_plain_color
|
||||
if _plain_color_preview != null:
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
if _plain_color_controls != null:
|
||||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||||
if _color_picker_open and _color_picker_owner == &"plain" and _color_picker_view != null:
|
||||
_color_picker_view.configure_plain_background(_current_plain_color, _sandbox_theme as SandboxTheme)
|
||||
if kind == "scenery" and _gallery_tab == "backgrounds":
|
||||
_rebuild()
|
||||
|
||||
|
||||
func open_plain_background_choices(color: Color) -> void:
|
||||
_current_plain_color = _opaque_color(color)
|
||||
_plain_color_draft = _current_plain_color
|
||||
_plain_color_choices_open = true
|
||||
_color_picker_open = false
|
||||
_color_picker_owner = &"plain"
|
||||
_title_label.text = "Choose background color"
|
||||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||||
_backgrounds_back_button.visible = true
|
||||
_plain_color_view.visible = true
|
||||
_setup_view.visible = false
|
||||
_color_picker_view.visible = false
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
_plain_color_controls.configure(_current_plain_color, _surface_color_history, _preset_surface_colors(), _sandbox_theme as SandboxTheme)
|
||||
_update_popup_layout(true)
|
||||
_rebuild()
|
||||
call_deferred("_update_popup_layout_after_layout")
|
||||
call_deferred("_focus_plain_color_controls")
|
||||
|
||||
|
||||
func set_thumbnail(entry: Dictionary, tex: Texture2D) -> void:
|
||||
@@ -198,6 +273,7 @@ func apply_font(ui_font: Font, emoji_font: Font) -> void:
|
||||
_prev_button, _next_button, _browse_button, _refresh_button, _close_button,
|
||||
_edit_scenery_button, _make_artwork_button,
|
||||
_backgrounds_tab, _artwork_tab,
|
||||
_backgrounds_back_button, _plain_color_helper,
|
||||
_setup_view.get_node_or_null("SetupHeading") if _setup_view != null else null,
|
||||
get_meta("setup_helper", null) as Control,
|
||||
_setup_cancel_button, _setup_draw_button,
|
||||
@@ -226,8 +302,13 @@ func apply_theme(theme: Variant) -> void:
|
||||
ground_choices.apply_theme(theme as SandboxTheme)
|
||||
if _setup_color_controls != null:
|
||||
_setup_color_controls.configure(_setup_color, _surface_color_history, _preset_surface_colors(), theme as SandboxTheme)
|
||||
if _plain_color_controls != null:
|
||||
_plain_color_controls.configure(_current_plain_color, _surface_color_history, _preset_surface_colors(), theme as SandboxTheme)
|
||||
if _color_picker_view != null:
|
||||
_color_picker_view.configure(_setup_kind, _setup_finish, _setup_color, theme as SandboxTheme)
|
||||
if _color_picker_owner == &"plain":
|
||||
_color_picker_view.configure_plain_background(_plain_color_draft, theme as SandboxTheme)
|
||||
else:
|
||||
_color_picker_view.configure(_setup_kind, _setup_finish, _setup_color, theme as SandboxTheme)
|
||||
_apply_chooser_visual_style()
|
||||
|
||||
|
||||
@@ -305,7 +386,7 @@ func _build_surface_setup_view() -> void:
|
||||
_color_picker_view = SURFACE_COLOR_PICKER_VIEW.new() as StageSurfaceColorPickerView
|
||||
_color_picker_view.name = "SurfaceColorPickerView"
|
||||
_color_picker_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_color_picker_view.preview_color_changed.connect(_on_setup_color_preview)
|
||||
_color_picker_view.preview_color_changed.connect(_on_color_picker_preview)
|
||||
_color_picker_view.visible = false
|
||||
_content_stack.add_child(_color_picker_view)
|
||||
_setup_cancel_button = Button.new()
|
||||
@@ -349,6 +430,77 @@ func _build_surface_setup_view() -> void:
|
||||
_apply_font_to(choice)
|
||||
|
||||
|
||||
func _build_plain_background_view() -> void:
|
||||
_plain_color_view = VBoxContainer.new()
|
||||
_plain_color_view.name = "PlainBackgroundColorChoices"
|
||||
_plain_color_view.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_plain_color_view.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.setup_gap", 8, 4, 24))
|
||||
_plain_color_view.visible = false
|
||||
_content_stack.add_child(_plain_color_view)
|
||||
_plain_color_helper = Label.new()
|
||||
_plain_color_helper.name = "PlainBackgroundHelper"
|
||||
_plain_color_helper.text = "Tap a color to use it on the Stage."
|
||||
_plain_color_helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_plain_color_helper.add_theme_color_override("font_color", _theme_color("global.muted_text_color", Color("#AAB2C0")))
|
||||
_plain_color_view.add_child(_plain_color_helper)
|
||||
var preview_frame := PanelContainer.new()
|
||||
preview_frame.name = "PlainBackgroundPreviewFrame"
|
||||
var preview_size: Vector2 = _theme_vector2("popups.asset_selector.surface_preview_size", Vector2(420.0, 132.0))
|
||||
preview_size.y = minf(preview_size.y, 132.0)
|
||||
preview_frame.custom_minimum_size.y = preview_size.y + 6.0
|
||||
preview_frame.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_plain_color_preview = SURFACE_PREVIEW.new() as StageSurfacePreview
|
||||
_plain_color_preview.name = "PlainBackgroundPreview"
|
||||
_plain_color_preview.set_rounded_frame(
|
||||
float(_theme_integer("popups.asset_selector.surface_corner_radius", 14, 4, 32)),
|
||||
_theme_color("popups.style_defaults.bg_color", Color("#202633FA"))
|
||||
)
|
||||
_plain_color_preview.custom_minimum_size = preview_size
|
||||
_plain_color_preview.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
preview_frame.add_child(_plain_color_preview)
|
||||
_plain_color_view.add_child(preview_frame)
|
||||
_plain_color_controls = SURFACE_COLOR_CONTROLS.new() as StageSurfaceColorControls
|
||||
_plain_color_controls.name = "PlainBackgroundColorControls"
|
||||
_plain_color_controls.color_selected.connect(_on_plain_color_selected)
|
||||
_plain_color_controls.custom_color_requested.connect(_open_plain_color_picker)
|
||||
_plain_color_view.add_child(_plain_color_controls)
|
||||
_backgrounds_back_button = Button.new()
|
||||
_backgrounds_back_button.name = "BackgroundsBackButton"
|
||||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||||
_backgrounds_back_button.custom_minimum_size = Vector2(148.0, float(_theme_integer("stage_panels.action.min_target_size", 48, 44, 96)))
|
||||
_backgrounds_back_button.focus_mode = Control.FOCUS_ALL
|
||||
_backgrounds_back_button.visible = false
|
||||
_backgrounds_back_button.pressed.connect(_back_to_backgrounds)
|
||||
_footer.add_child(_backgrounds_back_button)
|
||||
_plain_footer_row = HBoxContainer.new()
|
||||
_plain_footer_row.name = "PlainBackgroundFooterRow"
|
||||
_plain_footer_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_plain_footer_row.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
_plain_footer_row.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.footer_gap", 16, 8, 32))
|
||||
_plain_footer_row.visible = false
|
||||
_footer.add_child(_plain_footer_row)
|
||||
_plain_footer_column = VBoxContainer.new()
|
||||
_plain_footer_column.name = "PlainBackgroundFooterColumn"
|
||||
_plain_footer_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_plain_footer_column.add_theme_constant_override("separation", _theme_integer("popups.asset_selector.footer_gap", 16, 8, 32))
|
||||
_plain_footer_column.visible = false
|
||||
_footer.add_child(_plain_footer_column)
|
||||
_apply_font_to(_plain_color_helper)
|
||||
_apply_font_to(_backgrounds_back_button)
|
||||
_apply_chooser_visual_style()
|
||||
|
||||
|
||||
func _build_surface_row_heading() -> void:
|
||||
_surface_row_heading = Label.new()
|
||||
_surface_row_heading.name = "MoreBackgroundsHeading"
|
||||
_surface_row_heading.text = "More backgrounds"
|
||||
_surface_row_heading.visible = false
|
||||
_surface_row_heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
||||
_content_stack.add_child(_surface_row_heading)
|
||||
_content_stack.move_child(_surface_row_heading, _surface_cards_row.get_index())
|
||||
_apply_font_to(_surface_row_heading)
|
||||
|
||||
|
||||
func _make_look_cue(kind: StringName, finish: StringName) -> Control:
|
||||
var preview: StageSurfacePreview = SURFACE_PREVIEW.new() as StageSurfacePreview
|
||||
var cue_size: Vector2 = _theme_vector2("popups.asset_selector.look_cue_size", Vector2(80.0, 42.0))
|
||||
@@ -418,6 +570,14 @@ func _on_setup_color_selected(color: Color) -> void:
|
||||
surface_color_confirmed.emit(_setup_color)
|
||||
|
||||
|
||||
func _on_plain_color_selected(color: Color) -> void:
|
||||
_current_plain_color = _opaque_color(color)
|
||||
_plain_color_draft = _current_plain_color
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||||
plain_color_commit_requested.emit(_current_plain_color)
|
||||
|
||||
|
||||
func _update_setup_preview() -> void:
|
||||
if _setup_preview == null:
|
||||
return
|
||||
@@ -437,6 +597,7 @@ func _open_setup_color_view() -> void:
|
||||
_setup_original_color = _setup_color
|
||||
_color_picker_view.configure(_setup_kind, _setup_finish, _setup_original_color, _sandbox_theme as SandboxTheme)
|
||||
_color_picker_open = true
|
||||
_color_picker_owner = &"surface"
|
||||
_title_label.text = "Choose color"
|
||||
_rebuild()
|
||||
_update_popup_layout()
|
||||
@@ -444,12 +605,45 @@ func _open_setup_color_view() -> void:
|
||||
_color_picker_view.focus_picker()
|
||||
|
||||
|
||||
func _open_plain_color_picker() -> void:
|
||||
if not _plain_color_choices_open:
|
||||
return
|
||||
_plain_color_original = _current_plain_color
|
||||
_plain_color_draft = _current_plain_color
|
||||
_color_picker_owner = &"plain"
|
||||
_color_picker_view.configure_plain_background(_plain_color_draft, _sandbox_theme as SandboxTheme)
|
||||
_color_back_button.text = "‹ Plain Background"
|
||||
_plain_color_choices_open = false
|
||||
_color_picker_open = true
|
||||
_title_label.text = "Choose background color"
|
||||
_backgrounds_back_button.visible = false
|
||||
_update_popup_layout(true)
|
||||
_rebuild()
|
||||
call_deferred("_update_popup_layout_after_layout")
|
||||
_color_picker_view.focus_picker()
|
||||
|
||||
|
||||
func _on_setup_color_preview(color: Color) -> void:
|
||||
_setup_color = _opaque_color(color)
|
||||
_update_setup_preview()
|
||||
|
||||
|
||||
func _on_color_picker_preview(color: Color) -> void:
|
||||
if _color_picker_owner == &"plain":
|
||||
_plain_color_draft = _opaque_color(color)
|
||||
plain_color_preview_requested.emit(_plain_color_draft)
|
||||
return
|
||||
_on_setup_color_preview(color)
|
||||
|
||||
|
||||
func _use_setup_color() -> void:
|
||||
if _color_picker_owner == &"plain":
|
||||
_current_plain_color = _opaque_color(_color_picker_view.get_color())
|
||||
_plain_color_draft = _current_plain_color
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
plain_color_commit_requested.emit(_current_plain_color)
|
||||
_close_plain_color_picker()
|
||||
return
|
||||
_setup_color = _opaque_color(_color_picker_view.get_color())
|
||||
_setup_color_chosen = true
|
||||
_update_setup_preview()
|
||||
@@ -459,6 +653,9 @@ func _use_setup_color() -> void:
|
||||
|
||||
|
||||
func _back_from_setup_color_view() -> void:
|
||||
if _color_picker_owner == &"plain":
|
||||
_cancel_plain_picker_preview(true)
|
||||
return
|
||||
_setup_color = _setup_original_color
|
||||
_setup_color_controls.set_selected_no_signal(_setup_color)
|
||||
_update_setup_preview()
|
||||
@@ -475,6 +672,74 @@ func _close_setup_color_view() -> void:
|
||||
call_deferred("_update_popup_layout_after_layout")
|
||||
|
||||
|
||||
func _close_plain_color_picker() -> void:
|
||||
_color_picker_open = false
|
||||
_plain_color_choices_open = true
|
||||
_title_label.text = "Choose background color"
|
||||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||||
_backgrounds_back_button.visible = true
|
||||
_plain_color_view.visible = true
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||||
_update_popup_layout(true)
|
||||
_rebuild()
|
||||
call_deferred("_update_popup_layout_after_layout")
|
||||
call_deferred("_focus_plain_color_controls")
|
||||
|
||||
|
||||
func _cancel_plain_picker_preview(return_to_choices: bool = true) -> void:
|
||||
if not _color_picker_open or _color_picker_owner != &"plain":
|
||||
return
|
||||
_plain_color_draft = _current_plain_color
|
||||
_color_picker_open = false
|
||||
plain_color_preview_cancel_requested.emit()
|
||||
if _color_picker_view != null:
|
||||
_color_picker_view.configure_plain_background(_current_plain_color, _sandbox_theme as SandboxTheme)
|
||||
if return_to_choices and kind == "scenery":
|
||||
_plain_color_choices_open = true
|
||||
_title_label.text = "Choose background color"
|
||||
_backgrounds_back_button.text = "‹ Backgrounds"
|
||||
_backgrounds_back_button.visible = true
|
||||
_plain_color_view.visible = true
|
||||
_plain_color_preview.configure(&"plain_background", &"", _current_plain_color)
|
||||
_plain_color_controls.set_selected_no_signal(_current_plain_color)
|
||||
_update_popup_layout(true)
|
||||
_rebuild()
|
||||
call_deferred("_update_popup_layout_after_layout")
|
||||
call_deferred("_focus_plain_color_controls")
|
||||
|
||||
|
||||
func _back_to_backgrounds() -> void:
|
||||
if _color_picker_open and _color_picker_owner == &"plain":
|
||||
_cancel_plain_picker_preview(true)
|
||||
return
|
||||
_plain_color_choices_open = false
|
||||
_plain_color_view.visible = false
|
||||
_backgrounds_back_button.visible = false
|
||||
_title_label.text = "Choose the scene background"
|
||||
_update_popup_layout(true)
|
||||
_rebuild()
|
||||
call_deferred("_focus_plain_background_card")
|
||||
|
||||
|
||||
func _focus_plain_color_controls() -> void:
|
||||
if _plain_color_controls != null and _plain_color_controls.visible:
|
||||
_plain_color_controls.focus_custom_button()
|
||||
|
||||
|
||||
func _focus_plain_background_card() -> void:
|
||||
for node: Node in _grid.get_children():
|
||||
var card: AssetCard = node as AssetCard
|
||||
if card != null and String(card.get_meta("entry_id", "")) == "plain_background":
|
||||
card.grab_focus()
|
||||
return
|
||||
|
||||
|
||||
func _on_popup_hide() -> void:
|
||||
if _color_picker_open and _color_picker_owner == &"plain":
|
||||
_cancel_plain_picker_preview(true)
|
||||
|
||||
|
||||
func _opaque_color(color: Color) -> Color:
|
||||
return Color(color.r, color.g, color.b, 1.0)
|
||||
|
||||
@@ -600,11 +865,17 @@ func _has_unsorted_artwork() -> bool:
|
||||
|
||||
|
||||
func _set_gallery_tab(tab: String) -> void:
|
||||
if _color_picker_open and _color_picker_owner == &"plain":
|
||||
_cancel_plain_picker_preview(false)
|
||||
_color_picker_open = false
|
||||
_plain_color_choices_open = false
|
||||
_setup_kind = &""
|
||||
_setup_view.visible = false
|
||||
_plain_color_view.visible = false
|
||||
_color_picker_view.visible = false
|
||||
_backgrounds_back_button.visible = false
|
||||
_gallery_tab = tab
|
||||
_title_label.text = "Choose the scene background" if tab == "backgrounds" else "Choose Scenery"
|
||||
_backgrounds_tab.set_pressed_no_signal(tab == "backgrounds")
|
||||
_artwork_tab.set_pressed_no_signal(tab == "artwork")
|
||||
_filter_chips.visible = kind == "scenery" and tab == "artwork"
|
||||
@@ -677,49 +948,70 @@ func _usable_screen_size() -> Vector2:
|
||||
func _update_popup_layout(keep_visible_position: bool = false) -> void:
|
||||
var previous_position: Vector2i = position
|
||||
var in_color_picker: bool = _color_picker_open
|
||||
var in_plain_choices: bool = _plain_color_choices_open and not in_color_picker
|
||||
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
||||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_setup and not in_color_picker
|
||||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_setup and not in_color_picker
|
||||
var in_subview: bool = in_setup or in_color_picker or in_plain_choices
|
||||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_subview
|
||||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_subview
|
||||
var filtered: Array[Dictionary] = _filtered_entries()
|
||||
_tabs.visible = kind == "scenery" and not in_color_picker
|
||||
_tabs.visible = kind == "scenery" and not in_subview
|
||||
_filter_chips.visible = scenery_artwork_gallery
|
||||
_position_footer()
|
||||
_close_button.visible = not in_setup and not in_color_picker
|
||||
_setup_cancel_button.visible = in_setup
|
||||
_setup_draw_button.visible = in_setup
|
||||
_setup_view.visible = in_setup
|
||||
_plain_color_view.visible = in_plain_choices
|
||||
_backgrounds_back_button.visible = in_plain_choices
|
||||
_color_picker_view.visible = in_color_picker
|
||||
_surface_cards_row.visible = scenery_background_gallery and _has_surface_entries(filtered)
|
||||
_surface_row_heading.visible = _surface_cards_row.visible
|
||||
_color_back_button.visible = in_color_picker
|
||||
_color_use_button.visible = in_color_picker
|
||||
_edit_scenery_button.visible = kind == "scenery" and not in_setup and not in_color_picker
|
||||
_footer_spacer.visible = scenery_artwork_gallery
|
||||
_empty_label.visible = filtered.is_empty() and not in_setup and not in_color_picker
|
||||
_grid.visible = not filtered.is_empty() and not in_setup and not in_color_picker
|
||||
_grid_scroll.visible = not filtered.is_empty() or in_setup or in_color_picker
|
||||
_footer.alignment = BoxContainer.ALIGNMENT_BEGIN if scenery_artwork_gallery else (BoxContainer.ALIGNMENT_END if in_setup or in_color_picker else BoxContainer.ALIGNMENT_CENTER)
|
||||
_empty_label.visible = filtered.is_empty() and not in_subview
|
||||
_grid.visible = not filtered.is_empty() and not in_subview
|
||||
_grid_scroll.visible = not filtered.is_empty() or in_subview
|
||||
_footer.alignment = BoxContainer.ALIGNMENT_BEGIN if scenery_artwork_gallery or in_subview else BoxContainer.ALIGNMENT_CENTER
|
||||
_footer.add_theme_constant_override(
|
||||
"separation",
|
||||
_theme_integer("popups.asset_selector.footer_gap", 16, 8, 32) if in_setup or in_color_picker or kind == "scenery" else 8
|
||||
_theme_integer("popups.asset_selector.footer_gap", 16, 8, 32) if in_subview or kind == "scenery" else 8
|
||||
)
|
||||
var available: Vector2 = _usable_screen_size()
|
||||
var width_ratio: float = 0.98 if in_setup else 0.94
|
||||
var width_ratio: float = 0.98 if in_subview else (0.96 if kind == "starters" else 0.94)
|
||||
var max_width: float = maxf(1.0, minf(available.x * width_ratio, available.x - 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 height_ratio: float = _theme_number("popups.asset_selector.starter_viewport_height_ratio", 0.96, 0.7, 0.98) if kind == "starters" else 0.86
|
||||
var max_height: float = maxf(1.0, minf(available.y * height_ratio, available.y - POPUP_SAFE_SCREEN_MARGIN * 2.0))
|
||||
var preferred_width: float = CELL_MIN_SIZE.x * (2 if kind == "starters" else COLUMNS) + GRID_H_SEPARATION * ((2 if kind == "starters" else COLUMNS) - 1) + POPUP_SIDE_PADDING
|
||||
var preferred_height: float = max_height
|
||||
if in_color_picker:
|
||||
if kind == "starters":
|
||||
preferred_width = _theme_number("popups.asset_selector.starter_dialog_width", 1160.0, 440.0, 2400.0)
|
||||
if in_color_picker or in_plain_choices:
|
||||
var color_dialog_size: Vector2 = _theme_vector2("popups.dialog.surface_color_size", Vector2(560.0, 640.0))
|
||||
preferred_width = color_dialog_size.x
|
||||
preferred_height = color_dialog_size.y
|
||||
var popup_width: float = minf(preferred_width, max_width)
|
||||
_position_footer(popup_width)
|
||||
var grid_width: float = maxf(1.0, popup_width - POPUP_SIDE_PADDING)
|
||||
_active_columns = clampi(floori((grid_width + GRID_H_SEPARATION) / (CELL_MIN_SIZE.x + GRID_H_SEPARATION)), 1, COLUMNS)
|
||||
_page_capacity = maxi(1, filtered.size()) if scenery_background_gallery or scenery_artwork_gallery else _active_columns * ROWS
|
||||
var minimum_card_width: float = CELL_MIN_SIZE.x
|
||||
if kind == "starters":
|
||||
minimum_card_width = _theme_number("popups.asset_selector.starter_card_min_width", 340.0, 220.0, 800.0)
|
||||
_active_columns = clampi(floori((grid_width + GRID_H_SEPARATION) / (minimum_card_width + GRID_H_SEPARATION)), 1, 2 if kind == "starters" else COLUMNS)
|
||||
_page_capacity = maxi(1, filtered.size()) if scenery_background_gallery or scenery_artwork_gallery or kind == "starters" else _active_columns * ROWS
|
||||
_grid.columns = _active_columns
|
||||
var card_width: float = minf(CELL_MIN_SIZE.x, (grid_width - GRID_H_SEPARATION * (_active_columns - 1)) / float(_active_columns))
|
||||
var card_height_limit: float = 248.0 if kind == "starters" else CELL_MIN_SIZE.y
|
||||
var card_height: float = minf(card_height_limit, card_width + (58.0 if kind == "starters" else 30.0))
|
||||
var available_card_width: float = (grid_width - GRID_H_SEPARATION * (_active_columns - 1)) / float(_active_columns)
|
||||
var card_width: float = available_card_width if kind == "starters" else minf(CELL_MIN_SIZE.x, available_card_width)
|
||||
var card_height_limit: float = 248.0 if kind != "starters" else INF
|
||||
var card_height: float
|
||||
if kind == "starters":
|
||||
var preview_horizontal_inset: float = _theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0)
|
||||
var preview_aspect: float = _theme_number("popups.asset_selector.starter_preview_aspect", 2.0, 1.2, 2.8)
|
||||
var preview_top_inset: float = _theme_number("popups.asset_selector.starter_preview_top_inset", 6.0, 0.0, 24.0)
|
||||
var preview_height: float = maxf(1.0, card_width - preview_horizontal_inset * 2.0) / preview_aspect
|
||||
var text_and_spacing_height: float = _theme_number("popups.asset_selector.starter_card_text_height", 92.0, 72.0, 144.0)
|
||||
card_height = preview_height + preview_top_inset + text_and_spacing_height
|
||||
else:
|
||||
card_height = minf(card_height_limit, card_width + 30.0)
|
||||
_effective_cell_size = Vector2(maxf(96.0, card_width), maxf(180.0, card_height))
|
||||
var surface_width: float = minf(_effective_cell_size.x, maxf(96.0, (grid_width - GRID_H_SEPARATION) * 0.5))
|
||||
var surface_height: float = maxf(180.0, minf(card_height_limit, surface_width + 30.0))
|
||||
@@ -729,18 +1021,20 @@ func _update_popup_layout(keep_visible_position: bool = false) -> void:
|
||||
_page = clampi(_page, 0, page_count - 1)
|
||||
var page_start: int = _page * _page_capacity
|
||||
var page_items: int = clampi(filtered_count - page_start, 0, _page_capacity)
|
||||
var grid_item_count: int = _count_sky_entries(filtered) if scenery_background_gallery else page_items
|
||||
var grid_item_count: int = _count_scene_background_entries(filtered) if scenery_background_gallery else page_items
|
||||
var grid_rows: int = int(ceil(float(grid_item_count) / float(_active_columns))) if grid_item_count > 0 else 0
|
||||
var body_height: float
|
||||
if in_color_picker:
|
||||
_color_picker_view.set_available_size(Vector2(popup_width, minf(preferred_height, max_height)))
|
||||
body_height = maxf(220.0, _color_picker_view.get_combined_minimum_size().y)
|
||||
elif in_plain_choices:
|
||||
body_height = maxf(220.0, _visible_content_minimum_height(_plain_color_view))
|
||||
elif in_setup:
|
||||
body_height = maxf(180.0, _visible_content_minimum_height(_setup_view))
|
||||
else:
|
||||
body_height = maxf(120.0, float(grid_rows) * _effective_cell_size.y + float(maxi(0, grid_rows - 1)) * GRID_V_SEPARATION)
|
||||
if scenery_background_gallery and _has_surface_entries(filtered):
|
||||
body_height += _surface_cell_size.y + GRID_V_SEPARATION
|
||||
body_height += _surface_cell_size.y + _surface_row_heading.get_combined_minimum_size().y + GRID_V_SEPARATION * 2.0
|
||||
if page_items == 0:
|
||||
body_height = maxf(body_height, _empty_label.get_combined_minimum_size().y)
|
||||
_tabs.custom_minimum_size.x = grid_width
|
||||
@@ -798,6 +1092,8 @@ func _update_popup_layout(keep_visible_position: bool = false) -> void:
|
||||
_color_back_button.custom_minimum_size.x = action_width
|
||||
_color_use_button.custom_minimum_size.x = action_width
|
||||
size = Vector2i(maxi(1, roundi(popup_width)), maxi(1, roundi(popup_height)))
|
||||
_position_footer(float(size.x))
|
||||
_sync_stage_preview_dimming()
|
||||
if visible:
|
||||
if keep_visible_position:
|
||||
var max_x: int = maxi(0, floori(available.x - float(size.x) - POPUP_SAFE_SCREEN_MARGIN))
|
||||
@@ -815,7 +1111,7 @@ func _update_popup_layout(keep_visible_position: bool = false) -> void:
|
||||
func _update_popup_layout_after_layout() -> void:
|
||||
await get_tree().process_frame
|
||||
if is_inside_tree():
|
||||
_update_popup_layout()
|
||||
_update_popup_layout(visible)
|
||||
|
||||
|
||||
func _rebuild() -> void:
|
||||
@@ -827,27 +1123,33 @@ func _rebuild() -> void:
|
||||
_cell_texrects.clear()
|
||||
var filtered: Array[Dictionary] = _filtered_entries()
|
||||
var in_color_picker: bool = _color_picker_open
|
||||
var in_plain_choices: bool = _plain_color_choices_open and not in_color_picker
|
||||
var in_setup: bool = not _setup_kind.is_empty() and not in_color_picker
|
||||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_setup and not in_color_picker
|
||||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_setup and not in_color_picker
|
||||
_position_footer()
|
||||
var in_subview: bool = in_setup or in_color_picker or in_plain_choices
|
||||
var scenery_background_gallery: bool = _is_scenery_background_gallery() and not in_subview
|
||||
var scenery_artwork_gallery: bool = _is_scenery_artwork_gallery() and not in_subview
|
||||
_position_footer(float(size.x))
|
||||
_sync_stage_preview_dimming()
|
||||
_close_button.visible = not in_setup and not in_color_picker
|
||||
_setup_view.visible = in_setup
|
||||
_plain_color_view.visible = in_plain_choices
|
||||
_backgrounds_back_button.visible = in_plain_choices
|
||||
_color_picker_view.visible = in_color_picker
|
||||
_filter_chips.visible = scenery_artwork_gallery
|
||||
_surface_cards_row.visible = scenery_background_gallery and _has_surface_entries(filtered)
|
||||
_surface_row_heading.visible = _surface_cards_row.visible
|
||||
_setup_cancel_button.visible = in_setup
|
||||
_setup_draw_button.visible = in_setup
|
||||
_color_back_button.visible = in_color_picker
|
||||
_color_use_button.visible = in_color_picker
|
||||
_edit_scenery_button.visible = kind == "scenery" and not in_setup and not in_color_picker
|
||||
_edit_scenery_button.visible = kind == "scenery" and not in_subview
|
||||
_make_artwork_button.visible = scenery_artwork_gallery
|
||||
_browse_button.visible = (kind == "stickman" or kind == "prop") and not in_setup and not in_color_picker
|
||||
_refresh_button.visible = (kind == "stickman" or kind == "prop") and not in_setup and not in_color_picker
|
||||
_footer_spacer.visible = scenery_artwork_gallery
|
||||
_empty_label.visible = filtered.is_empty() and not in_setup and not in_color_picker
|
||||
_grid.visible = not filtered.is_empty() and not in_setup and not in_color_picker
|
||||
_grid_scroll.visible = not filtered.is_empty() or in_setup or in_color_picker
|
||||
_empty_label.visible = filtered.is_empty() and not in_subview
|
||||
_grid.visible = not filtered.is_empty() and not in_subview
|
||||
_grid_scroll.visible = not filtered.is_empty() or in_subview
|
||||
var total := _page_count()
|
||||
var single := total <= 1
|
||||
_prev_button.visible = not single
|
||||
@@ -856,7 +1158,7 @@ func _rebuild() -> void:
|
||||
_prev_button.disabled = _page <= 0
|
||||
_next_button.disabled = (_page + 1) * _page_capacity >= filtered.size()
|
||||
_page_label.text = "Page %d/%d" % [_page + 1, total]
|
||||
if in_setup or in_color_picker:
|
||||
if in_subview:
|
||||
_prev_button.visible = false
|
||||
_next_button.visible = false
|
||||
_page_label.visible = false
|
||||
@@ -869,7 +1171,7 @@ func _rebuild() -> void:
|
||||
if scenery_background_gallery:
|
||||
for entry: Dictionary in filtered:
|
||||
var background_kind: String = String(entry.get("background_kind", ""))
|
||||
if background_kind == "sky":
|
||||
if background_kind == "sky" or background_kind == "plain":
|
||||
_grid.add_child(_build_cell(entry))
|
||||
elif background_kind == "wall_setup" or background_kind == "background_ground_setup":
|
||||
_surface_cards_row.add_child(_build_cell(entry, _surface_cell_size))
|
||||
@@ -879,15 +1181,70 @@ func _rebuild() -> void:
|
||||
_grid.add_child(_build_cell(filtered[i]))
|
||||
|
||||
|
||||
func _position_footer() -> void:
|
||||
func _position_footer(target_width: float = -1.0) -> void:
|
||||
var parent := _footer.get_parent() as VBoxContainer
|
||||
if parent == null:
|
||||
return
|
||||
var target_index: int = parent.get_child_count() - 1
|
||||
if _footer.get_index() != target_index:
|
||||
parent.move_child(_footer, target_index)
|
||||
if _close_button.get_parent() == _footer and _close_button.get_index() != 0:
|
||||
_footer.move_child(_close_button, 0)
|
||||
var in_plain_choices: bool = _plain_color_choices_open and not _color_picker_open
|
||||
if in_plain_choices:
|
||||
var available_width: float = target_width if target_width >= 0.0 else float(size.x)
|
||||
var stack_actions: bool = available_width < 380.0
|
||||
var action_container: Control = _plain_footer_column if stack_actions else _plain_footer_row
|
||||
_plain_footer_row.visible = not stack_actions
|
||||
_plain_footer_column.visible = stack_actions
|
||||
_move_footer_control(_backgrounds_back_button, action_container, 0)
|
||||
if stack_actions:
|
||||
_move_footer_control(_close_button, action_container, 1)
|
||||
_move_footer_control(_footer_spacer, _footer, 1)
|
||||
_footer_spacer.visible = false
|
||||
_backgrounds_back_button.custom_minimum_size.x = 0.0
|
||||
_close_button.custom_minimum_size.x = 0.0
|
||||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_close_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
else:
|
||||
_move_footer_control(_footer_spacer, action_container, 1)
|
||||
_move_footer_control(_close_button, action_container, 2)
|
||||
_footer_spacer.visible = true
|
||||
_backgrounds_back_button.custom_minimum_size.x = 148.0
|
||||
_close_button.custom_minimum_size.x = 136.0
|
||||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
_close_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
return
|
||||
_plain_footer_row.visible = false
|
||||
_plain_footer_column.visible = false
|
||||
_move_footer_control(_backgrounds_back_button, _footer, 1)
|
||||
_move_footer_control(_footer_spacer, _footer, 1)
|
||||
_move_footer_control(_close_button, _footer, 0)
|
||||
_backgrounds_back_button.custom_minimum_size.x = 148.0
|
||||
_close_button.custom_minimum_size.x = 136.0
|
||||
_backgrounds_back_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
_close_button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
|
||||
func _move_footer_control(control: Control, target: Control, index: int) -> void:
|
||||
if control == null or target == null:
|
||||
return
|
||||
if control.get_parent() != target:
|
||||
var old_parent: Node = control.get_parent()
|
||||
if old_parent != null:
|
||||
old_parent.remove_child(control)
|
||||
target.add_child(control)
|
||||
var safe_index: int = clampi(index, 0, maxi(0, target.get_child_count() - 1))
|
||||
if control.get_index() != safe_index:
|
||||
target.move_child(control, safe_index)
|
||||
|
||||
|
||||
func _sync_stage_preview_dimming() -> void:
|
||||
var should_dim: bool = not (_plain_color_choices_open and kind == "scenery")
|
||||
if _color_picker_open and _color_picker_owner == &"plain":
|
||||
should_dim = false
|
||||
if should_dim == _stage_preview_is_dimmed:
|
||||
return
|
||||
_stage_preview_is_dimmed = should_dim
|
||||
stage_preview_dimming_changed.emit(should_dim)
|
||||
|
||||
|
||||
func _is_scenery_background_gallery() -> bool:
|
||||
@@ -898,10 +1255,10 @@ func _is_scenery_artwork_gallery() -> bool:
|
||||
return kind == "scenery" and _gallery_tab == "artwork"
|
||||
|
||||
|
||||
func _count_sky_entries(entries: Array[Dictionary]) -> int:
|
||||
func _count_scene_background_entries(entries: Array[Dictionary]) -> int:
|
||||
var count: int = 0
|
||||
for entry: Dictionary in entries:
|
||||
if String(entry.get("background_kind", "")) == "sky":
|
||||
if String(entry.get("background_kind", "")) in ["sky", "plain"]:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@@ -930,8 +1287,8 @@ func _visible_content_minimum_height(container: VBoxContainer) -> float:
|
||||
func _build_cell(entry: Dictionary, requested_size: Vector2 = Vector2.ZERO) -> Control:
|
||||
var key := _entry_key(entry)
|
||||
var cell: Variant = ASSET_CARD.new()
|
||||
# Give scenery cards rounded resting and interaction states. The selected sky
|
||||
# state is applied separately below, so opening the gallery stays neutral.
|
||||
# Scene cards have clear rounded resting and interaction states. Selected
|
||||
# background state is applied separately below.
|
||||
var categories: Array = entry.get("tags", []) as Array
|
||||
var category: String = ", ".join(PackedStringArray(categories)) if not categories.is_empty() else String(entry.get("category", entry.get("material_label", "")))
|
||||
var card_size: Vector2 = _effective_cell_size if requested_size == Vector2.ZERO else requested_size
|
||||
@@ -940,56 +1297,88 @@ func _build_cell(entry: Dictionary, requested_size: Vector2 = Vector2.ZERO) -> C
|
||||
var thumb_edge: float = minf(_thumbnail_size.x, minf(card_size.x - 20.0, thumb_height_budget))
|
||||
var thumb_size := Vector2.ONE * maxf(72.0, thumb_edge)
|
||||
if kind == "starters":
|
||||
thumb_size = Vector2.ONE * minf(128.0, maxf(72.0, thumb_edge))
|
||||
var preview_horizontal_inset: float = _theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0)
|
||||
var preview_aspect: float = _theme_number("popups.asset_selector.starter_preview_aspect", 2.0, 1.2, 2.8)
|
||||
var preview_width: float = maxf(72.0, card_size.x - preview_horizontal_inset * 2.0)
|
||||
thumb_size = Vector2(preview_width, preview_width / preview_aspect)
|
||||
var description: String = "" if is_background_ground_setup else String(entry.get("description", ""))
|
||||
var card_payload: Dictionary = {"placement_id": _entry_key(entry), "label": String(entry.get("name", entry.get("id", ""))), "icon": String(entry.get("icon", "")), "category": category, "description": description, "thumbnail_size": thumb_size}
|
||||
if kind == "starters":
|
||||
card_payload["label_font_size"] = _theme_integer("popups.asset_selector.starter_label_size", 22, 18, 32)
|
||||
card_payload["label_min_height"] = _theme_integer("popups.asset_selector.starter_label_height", 34, 28, 52)
|
||||
card_payload["description_font_size"] = _theme_integer("popups.asset_selector.starter_description_size", 20, 16, 30)
|
||||
card_payload["description_min_height"] = _theme_integer("popups.asset_selector.starter_description_height", 48, 36, 80)
|
||||
card_payload["preview_corner_radius"] = _theme_integer("popups.asset_selector.starter_preview_corner_radius", 12, 6, 24)
|
||||
card_payload["preview_horizontal_inset"] = roundi(_theme_number("popups.asset_selector.starter_preview_horizontal_inset", 10.0, 0.0, 32.0))
|
||||
card_payload["preview_top_inset"] = _theme_integer("popups.asset_selector.starter_preview_top_inset", 6, 0, 24)
|
||||
if entry.has("background_kind"):
|
||||
card_payload["background_kind"] = entry["background_kind"]
|
||||
card_payload["finish_id"] = entry.get("finish_id", "")
|
||||
card_payload["id"] = entry.get("id", "")
|
||||
var is_sky_choice: bool = String(entry.get("background_kind", "")) == "sky"
|
||||
var is_current_sky: bool = is_sky_choice and String(entry.get("id", "")) == _current_sky_id
|
||||
if is_current_sky:
|
||||
card_payload["status"] = "Current Sky"
|
||||
var background_kind: String = String(entry.get("background_kind", ""))
|
||||
var is_scene_background_choice: bool = background_kind == "sky" or background_kind == "plain"
|
||||
var is_current_background: bool = background_kind == String(_current_background_kind) and (
|
||||
background_kind == "plain" or String(entry.get("id", "")) == _current_sky_id
|
||||
)
|
||||
if is_current_background:
|
||||
card_payload["status"] = "Current background"
|
||||
card_payload["status_color"] = _theme_color("mode_colors.edit", Color("#22C6FF"))
|
||||
cell.configure(card_payload, _thumbnails.get(key, null), null)
|
||||
if kind == "scenery":
|
||||
if kind == "scenery" or kind == "starters":
|
||||
var card_button: Button = cell as Button
|
||||
var card_border_width: int = _theme_integer("popups.asset_selector.button_border_width", 2, 1, 5)
|
||||
var card_corner_radius: int = _theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||||
var card_neutral_surface: Color = _theme_color("stage_panels.choice.neutral_surface", Color("#171B24"))
|
||||
var card_neutral_border: Color = _theme_color("stage_panels.choice.border_color", Color("#FFFFFF33"))
|
||||
var card_hover_surface: Color = _theme_color("stage_panels.choice.hover_surface", Color("#303A4B"))
|
||||
var card_selected_surface: Color = _theme_color("stage_panels.choice.selected_surface", Color("#12394A"))
|
||||
var card_hover_border_width: int = card_border_width + 1
|
||||
card_button.add_theme_stylebox_override("normal", _make_rounded_style(
|
||||
_theme_color("stage_panels.choice.neutral_surface", Color("#171B24")),
|
||||
_theme_color("stage_panels.choice.border_color", Color("#FFFFFF33")),
|
||||
card_neutral_surface,
|
||||
card_neutral_border,
|
||||
card_border_width,
|
||||
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||||
card_corner_radius
|
||||
))
|
||||
card_button.add_theme_stylebox_override("hover", _make_rounded_style(
|
||||
_theme_color("stage_panels.choice.hover_surface", Color("#303A4B")),
|
||||
card_hover_surface,
|
||||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||||
card_border_width + 1,
|
||||
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||||
card_hover_border_width,
|
||||
card_corner_radius
|
||||
))
|
||||
card_button.add_theme_stylebox_override("pressed", _make_rounded_style(
|
||||
_theme_color("stage_panels.choice.selected_surface", Color("#12394A")),
|
||||
card_selected_surface,
|
||||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||||
card_border_width + 1,
|
||||
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||||
card_hover_border_width,
|
||||
card_corner_radius
|
||||
))
|
||||
card_button.add_theme_stylebox_override("focus", _make_rounded_style(
|
||||
if kind == "starters":
|
||||
card_button.add_theme_stylebox_override("hover_pressed", _make_rounded_style(
|
||||
card_selected_surface.lightened(0.05),
|
||||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||||
card_hover_border_width,
|
||||
card_corner_radius
|
||||
))
|
||||
var focus_color: Color = _theme_color("stage_panels.type.text_color", Color("#F5F7FA")) if kind == "starters" else _theme_color("mode_colors.edit", Color("#22C6FF"))
|
||||
var focus_style: StyleBoxFlat = _make_rounded_style(
|
||||
Color(0, 0, 0, 0),
|
||||
_theme_color("mode_colors.edit", Color("#22C6FF")),
|
||||
card_border_width + 1,
|
||||
_theme_integer("popups.asset_selector.corner_radius", 16, 8, 32)
|
||||
))
|
||||
focus_color,
|
||||
card_hover_border_width,
|
||||
card_corner_radius
|
||||
)
|
||||
if kind == "starters":
|
||||
focus_style.set_expand_margin_all(float(card_border_width))
|
||||
card_button.add_theme_stylebox_override("focus", focus_style)
|
||||
cell.custom_minimum_size = card_size
|
||||
cell.focus_mode = Control.FOCUS_ALL
|
||||
cell.set_meta("entry_id", String(entry.get("id", "")))
|
||||
cell.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
|
||||
if is_sky_choice:
|
||||
if is_scene_background_choice:
|
||||
cell.button_group = _sky_card_group
|
||||
cell.set_selected(is_current_sky, _theme_color("stage_panels.choice.selected_surface", Color("#12394A")), _theme_color("mode_colors.edit", Color("#22C6FF")))
|
||||
cell.set_selected(is_current_background, _theme_color("stage_panels.choice.selected_surface", Color("#12394A")), _theme_color("mode_colors.edit", Color("#22C6FF")))
|
||||
var tex: Texture2D = _thumbnails.get(key, null)
|
||||
if kind == "starters":
|
||||
var preview: Control = STARTER_CARD_PREVIEW.new() as Control
|
||||
preview.call("configure", entry)
|
||||
preview.call("configure", entry, thumb_size)
|
||||
cell.set_preview_control(preview)
|
||||
elif entry.has("background_kind"):
|
||||
var preview: StageBackgroundCardPreview = BACKGROUND_CARD_PREVIEW.new() as StageBackgroundCardPreview
|
||||
@@ -1012,6 +1401,10 @@ func _theme_integer(path: String, fallback: int, minimum: int, maximum: int) ->
|
||||
return fallback if _sandbox_theme == null else _sandbox_theme.integer(path, fallback, minimum, maximum)
|
||||
|
||||
|
||||
func _theme_number(path: String, fallback: float, minimum: float, maximum: float) -> float:
|
||||
return fallback if _sandbox_theme == null else _sandbox_theme.number(path, fallback, minimum, maximum)
|
||||
|
||||
|
||||
func _theme_vector2(path: String, fallback: Vector2) -> Vector2:
|
||||
if _sandbox_theme == null:
|
||||
return fallback
|
||||
@@ -1054,7 +1447,8 @@ func _apply_chooser_visual_style() -> void:
|
||||
frame_style.set_corner_radius_all(_theme_integer("popups.asset_selector.surface_corner_radius", 14, 4, 32))
|
||||
preview_frame.add_theme_stylebox_override("panel", frame_style)
|
||||
if _title_label != null:
|
||||
_title_label.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_title_size", 30, 22, 48) if kind == "scenery" else 24)
|
||||
var title_size: int = _theme_integer("popups.asset_selector.setup_title_size", 30, 22, 48) if kind == "scenery" else (_theme_integer("popups.asset_selector.starter_title_size", 30, 22, 48) if kind == "starters" else 24)
|
||||
_title_label.add_theme_font_size_override("font_size", title_size)
|
||||
_title_label.add_theme_color_override("font_color", text_color)
|
||||
_apply_emphasis_font(_title_label)
|
||||
for tab: Button in [_backgrounds_tab, _artwork_tab]:
|
||||
@@ -1096,6 +1490,15 @@ func _apply_chooser_visual_style() -> void:
|
||||
heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
||||
heading.add_theme_color_override("font_color", text_color)
|
||||
_apply_emphasis_font(heading)
|
||||
if _surface_row_heading != null:
|
||||
_surface_row_heading.add_theme_font_size_override("font_size", _theme_integer("popups.asset_selector.setup_heading_size", 26, 18, 48))
|
||||
_surface_row_heading.add_theme_color_override("font_color", text_color)
|
||||
_apply_emphasis_font(_surface_row_heading)
|
||||
if _plain_color_helper != null:
|
||||
_plain_color_helper.add_theme_font_size_override("font_size", _theme_integer("stage_panels.type.body_size", 20, 14, 32))
|
||||
_plain_color_helper.add_theme_color_override("font_color", _theme_color("stage_panels.type.muted_text_color", Color("#AAB2C0")))
|
||||
_apply_font_to(_plain_color_helper)
|
||||
_apply_rounded_button(_backgrounds_back_button, false, accent, text_color, neutral, hover, border, radius)
|
||||
_apply_rounded_button(_setup_cancel_button, false, accent, text_color, neutral, hover, border, radius)
|
||||
_apply_rounded_button(_setup_draw_button, true, accent, text_color, neutral, hover, border, radius)
|
||||
_apply_emphasis_font(_setup_draw_button, true)
|
||||
|
||||
@@ -44,7 +44,7 @@ func configure(colors: Array[Color], selected: Color, theme: Variant = null) ->
|
||||
continue
|
||||
seen[color_key] = true
|
||||
var button := Button.new()
|
||||
var swatch_size: float = _swatch_size_override if _swatch_size_override > 0.0 else float(_integer("stage_surface_colors.swatch_size", 48))
|
||||
var swatch_size: float = maxf(44.0, _swatch_size_override if _swatch_size_override > 0.0 else float(_integer("stage_surface_colors.swatch_size", 48)))
|
||||
button.custom_minimum_size = Vector2(swatch_size, swatch_size)
|
||||
button.toggle_mode = true
|
||||
button.button_group = _group
|
||||
|
||||
@@ -61,6 +61,7 @@ var _warned: Dictionary = {}
|
||||
|
||||
func _init() -> void:
|
||||
_apply_confirmation_defaults()
|
||||
_apply_phase19_defaults()
|
||||
|
||||
func load_from(path: String = DEFAULT_PATH) -> bool:
|
||||
invalidate_caches()
|
||||
@@ -80,8 +81,59 @@ func load_from(path: String = DEFAULT_PATH) -> bool:
|
||||
return false
|
||||
_apply_legacy_aliases(parsed as Dictionary)
|
||||
_config = _deep_merge(_config, parsed as Dictionary)
|
||||
_apply_phase19_defaults()
|
||||
return true
|
||||
|
||||
|
||||
func _apply_phase19_defaults() -> void:
|
||||
var grid: Dictionary = _config.get("grid", {}).duplicate(true)
|
||||
for key: String in ["plain_light_minor", "plain_light_major", "plain_dark_minor", "plain_dark_major"]:
|
||||
if not grid.has(key):
|
||||
grid[key] = {
|
||||
"plain_light_minor": "#11182120",
|
||||
"plain_light_major": "#11182148",
|
||||
"plain_dark_minor": "#FFFFFF25",
|
||||
"plain_dark_major": "#FFFFFF58",
|
||||
}[key]
|
||||
_config["grid"] = grid
|
||||
var popups: Dictionary = _config.get("popups", {}).duplicate(true)
|
||||
var asset_selector: Dictionary = popups.get("asset_selector", {}).duplicate(true)
|
||||
asset_selector["compact_swatch_size"] = maxi(44, int(asset_selector.get("compact_swatch_size", 44)))
|
||||
var starter_defaults: Dictionary = {
|
||||
"starter_dialog_width": 1160.0,
|
||||
"starter_viewport_height_ratio": 0.96,
|
||||
"starter_card_min_width": 340.0,
|
||||
"starter_preview_aspect": 2.0,
|
||||
"starter_preview_corner_radius": 12,
|
||||
"starter_preview_horizontal_inset": 10.0,
|
||||
"starter_preview_top_inset": 6,
|
||||
"starter_card_text_height": 108.0,
|
||||
"starter_title_size": 30,
|
||||
"starter_label_size": 22,
|
||||
"starter_label_height": 34,
|
||||
"starter_description_size": 20,
|
||||
"starter_description_height": 48,
|
||||
}
|
||||
for key: String in starter_defaults:
|
||||
if not asset_selector.has(key):
|
||||
asset_selector[key] = starter_defaults[key]
|
||||
popups["asset_selector"] = asset_selector
|
||||
_config["popups"] = popups
|
||||
var overlays: Dictionary = _config.get("overlays", {}).duplicate(true)
|
||||
var gizmos: Dictionary = overlays.get("gizmos", {}).duplicate(true)
|
||||
var defaults: Dictionary = {
|
||||
"backing_color": "#111821E8",
|
||||
"selection_color": "#F5F7FAE6",
|
||||
"hover_color": "#FFE66DCC",
|
||||
"rotate_color": "#4DB9FFE6",
|
||||
"backing_extra_width": 3.0,
|
||||
}
|
||||
for key: String in defaults:
|
||||
if not gizmos.has(key):
|
||||
gizmos[key] = defaults[key]
|
||||
overlays["gizmos"] = gizmos
|
||||
_config["overlays"] = overlays
|
||||
|
||||
func _apply_confirmation_defaults() -> void:
|
||||
var popups: Dictionary = _config.get("popups", {}).duplicate(true)
|
||||
popups["confirmation"] = CONFIRMATION_DEFAULTS.duplicate(true)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -54,6 +54,13 @@ const MAX_SCALE: float = 20.0
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
var camera: Camera2D = null
|
||||
var _selection_backing_color: Color = Color("#111821E8")
|
||||
var _selection_core_color: Color = Color(1.0, 1.0, 1.0, 0.9)
|
||||
var _hover_backing_color: Color = Color("#111821E8")
|
||||
var _hover_core_color: Color = Color(1.0, 0.9, 0.2, 0.5)
|
||||
var _rotate_backing_color: Color = Color("#111821E8")
|
||||
var _rotate_core_color: Color = Color(0.3, 0.6, 1.0, 0.9)
|
||||
var _outline_extra_width: float = 3.0
|
||||
|
||||
## When > 0, translation drags snap to this grid size (0 = off).
|
||||
var snap_size: float = 0.0
|
||||
@@ -105,6 +112,19 @@ func set_snap_size(size: float) -> void:
|
||||
snap_size = maxf(size, 0.0)
|
||||
|
||||
|
||||
func apply_theme(stage_theme: SandboxTheme) -> void:
|
||||
if stage_theme == null:
|
||||
return
|
||||
_selection_backing_color = stage_theme.color("overlays.gizmos.backing_color", Color("#111821E8"))
|
||||
_selection_core_color = stage_theme.color("overlays.gizmos.selection_color", SELECTION_COLOR)
|
||||
_hover_backing_color = stage_theme.color("overlays.gizmos.backing_color", Color("#111821E8"))
|
||||
_hover_core_color = stage_theme.color("overlays.gizmos.hover_color", HOVER_COLOR)
|
||||
_rotate_backing_color = stage_theme.color("overlays.gizmos.backing_color", Color("#111821E8"))
|
||||
_rotate_core_color = stage_theme.color("overlays.gizmos.rotate_color", ROTATE_COLOR)
|
||||
_outline_extra_width = stage_theme.number("overlays.gizmos.backing_extra_width", 3.0, 1.0, 8.0)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func set_box_rect(rect: Rect2) -> void:
|
||||
_box_rect = rect
|
||||
queue_redraw()
|
||||
@@ -253,16 +273,18 @@ func _draw() -> void:
|
||||
return
|
||||
var zoom := _zoom()
|
||||
if _hovered != null and is_instance_valid(_hovered) and not _hovered.is_queued_for_deletion() and not _targets.has(_hovered):
|
||||
draw_rect(_target_bounds(_hovered), HOVER_COLOR, false, HOVER_WIDTH / zoom)
|
||||
_draw_halo_rect(_target_bounds(_hovered), _hover_core_color, HOVER_WIDTH / zoom, _hover_backing_color)
|
||||
# A selection outline for every selected object.
|
||||
for node: Node2D in _targets:
|
||||
if node != null and is_instance_valid(node):
|
||||
draw_rect(_target_bounds(node), SELECTION_COLOR, false, SELECTION_WIDTH / zoom)
|
||||
_draw_halo_rect(_target_bounds(node), _selection_core_color, SELECTION_WIDTH / zoom, _selection_backing_color)
|
||||
# Rotate ring only for a single selection.
|
||||
if _transform_handles_visible and _targets.size() == 1 and is_instance_valid(_targets[0]):
|
||||
var rect: Rect2 = _target_bounds(_targets[0])
|
||||
if not _is_scenery_target(_targets[0]):
|
||||
draw_arc(rect.get_center(), _rotate_ring_radius(rect), 0.0, TAU, 64, ROTATE_COLOR, ROTATE_WIDTH / zoom, true)
|
||||
var ring_radius: float = _rotate_ring_radius(rect)
|
||||
draw_arc(rect.get_center(), ring_radius, 0.0, TAU, 64, _rotate_backing_color, ROTATE_WIDTH / zoom + _outline_extra_width / zoom, true)
|
||||
draw_arc(rect.get_center(), ring_radius, 0.0, TAU, 64, _rotate_core_color, ROTATE_WIDTH / zoom, true)
|
||||
if _is_scenery_target(_targets[0]):
|
||||
for corner: Vector2 in _corners(rect):
|
||||
draw_circle(corner, 11.0 / zoom, Color("#111821E8"))
|
||||
@@ -275,7 +297,7 @@ func _draw() -> void:
|
||||
draw_line(corner + Vector2(-half, 0.0), corner + Vector2(half, 0.0), SELECTION_COLOR, 1.5 / zoom)
|
||||
draw_line(corner + Vector2(0.0, -half), corner + Vector2(0.0, half), SELECTION_COLOR, 1.5 / zoom)
|
||||
if _box_rect.size != Vector2.ZERO:
|
||||
draw_rect(_box_rect.abs(), HOVER_COLOR, false, HOVER_WIDTH / zoom)
|
||||
_draw_halo_rect(_box_rect.abs(), _hover_core_color, HOVER_WIDTH / zoom, _hover_backing_color)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
@@ -287,6 +309,11 @@ func _zoom() -> float:
|
||||
return 1.0
|
||||
|
||||
|
||||
func _draw_halo_rect(rect: Rect2, core_color: Color, core_width: float, backing_color: Color) -> void:
|
||||
draw_rect(rect, backing_color, false, core_width + _outline_extra_width / _zoom())
|
||||
draw_rect(rect, core_color, false, core_width)
|
||||
|
||||
|
||||
func _rotate_ring_radius(rect: Rect2) -> float:
|
||||
return rect.size.length() * 0.5 + ROTATE_HANDLE_MARGIN_PX / _zoom()
|
||||
|
||||
|
||||
@@ -9,6 +9,12 @@ extends Node2D
|
||||
var camera: Camera2D = null
|
||||
var grid_size: float = 15.0
|
||||
var enabled: bool = true
|
||||
var _plain_background_active: bool = false
|
||||
var _plain_background_color: Color = Color("#F5EFE0")
|
||||
var _plain_light_minor_color: Color = Color("#11182120")
|
||||
var _plain_light_major_color: Color = Color("#11182148")
|
||||
var _plain_dark_minor_color: Color = Color("#FFFFFF25")
|
||||
var _plain_dark_major_color: Color = Color("#FFFFFF58")
|
||||
|
||||
const GRID_COLOR := Color(1.0, 1.0, 1.0, 0.08)
|
||||
const MAJOR_COLOR := Color(1.0, 1.0, 1.0, 0.16)
|
||||
@@ -19,6 +25,23 @@ func _process(_delta: float) -> void:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func set_background_treatment(
|
||||
plain_active: bool,
|
||||
plain_color: Color,
|
||||
light_minor: Color,
|
||||
light_major: Color,
|
||||
dark_minor: Color,
|
||||
dark_major: Color
|
||||
) -> void:
|
||||
_plain_background_active = plain_active
|
||||
_plain_background_color = Color(plain_color.r, plain_color.g, plain_color.b, 1.0)
|
||||
_plain_light_minor_color = light_minor
|
||||
_plain_light_major_color = light_major
|
||||
_plain_dark_minor_color = dark_minor
|
||||
_plain_dark_major_color = dark_major
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not enabled or grid_size <= 0.0:
|
||||
return
|
||||
@@ -34,13 +57,22 @@ func _draw() -> void:
|
||||
var x := floorf(rect.position.x / grid_size) * grid_size
|
||||
while x <= rect.end.x:
|
||||
var grid_index := int(round(x / grid_size))
|
||||
var color := MAJOR_COLOR if grid_index % MAJOR_EVERY == 0 else GRID_COLOR
|
||||
var color: Color = _grid_color(grid_index % MAJOR_EVERY == 0)
|
||||
draw_line(Vector2(x, rect.position.y), Vector2(x, rect.end.y), color, line_width)
|
||||
x += grid_size
|
||||
|
||||
var y := floorf(rect.position.y / grid_size) * grid_size
|
||||
while y <= rect.end.y:
|
||||
var grid_index := int(round(y / grid_size))
|
||||
var color := MAJOR_COLOR if grid_index % MAJOR_EVERY == 0 else GRID_COLOR
|
||||
var color: Color = _grid_color(grid_index % MAJOR_EVERY == 0)
|
||||
draw_line(Vector2(rect.position.x, y), Vector2(rect.end.x, y), color, line_width)
|
||||
y += grid_size
|
||||
|
||||
|
||||
func _grid_color(is_major: bool) -> Color:
|
||||
if not _plain_background_active:
|
||||
return MAJOR_COLOR if is_major else GRID_COLOR
|
||||
var light_fill: bool = _plain_background_color.get_luminance() >= 0.55
|
||||
if light_fill:
|
||||
return _plain_light_major_color if is_major else _plain_light_minor_color
|
||||
return _plain_dark_major_color if is_major else _plain_dark_minor_color
|
||||
|
||||
@@ -3,7 +3,8 @@ extends RefCounted
|
||||
## JSON boundary for Sandbox Stage documents. This class deliberately has no
|
||||
## scene-tree dependencies, which makes rejected documents non-destructive.
|
||||
|
||||
const FILE_VERSION: String = "1.6"
|
||||
const FILE_VERSION: String = "1.7"
|
||||
const DEFAULT_PLAIN_COLOR: String = "#F5EFE0"
|
||||
const SURFACE_RENDERER := preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
||||
const CONTENT_PATHS := preload("res://scripts/shared/content_paths.gd")
|
||||
const ACTION_REGISTRY := preload("res://scripts/stage/action_registry.gd")
|
||||
@@ -185,11 +186,13 @@ static func _migrate_to_current(data: Dictionary) -> Dictionary:
|
||||
prop_entry["wobble_on_impact"] = false
|
||||
_assign_legacy_depths(current.get("objects", []))
|
||||
return current
|
||||
if version not in ["1.0", "1.1", "1.2", "1.3", "1.4", "1.5"]:
|
||||
if version not in ["1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6"]:
|
||||
return {}
|
||||
var migrated: Dictionary = data.duplicate(true)
|
||||
if version != "1.5":
|
||||
if version not in ["1.5", "1.6"]:
|
||||
migrated["sky_id"] = "sky_neutral"
|
||||
migrated["background_kind"] = "sky"
|
||||
migrated["plain_color"] = DEFAULT_PLAIN_COLOR
|
||||
for raw: Variant in migrated.get("objects", []):
|
||||
if not raw is Dictionary:
|
||||
continue
|
||||
@@ -203,9 +206,9 @@ static func _migrate_to_current(data: Dictionary) -> Dictionary:
|
||||
object["next_use_spot_number"] = object.get("next_use_spot_number", 1)
|
||||
if not object.has("wobble_on_impact"):
|
||||
object["wobble_on_impact"] = false
|
||||
if version != "1.5" and String(object.get("type", "")) == "terrain" and String(object.get("asset_id", "")) == "ground":
|
||||
if version not in ["1.5", "1.6"] and String(object.get("type", "")) == "terrain" and String(object.get("asset_id", "")) == "ground":
|
||||
object["finish_id"] = "finish_ground_legacy"
|
||||
if String(object.get("type", "")) == "wall_panel":
|
||||
if version != "1.6" and String(object.get("type", "")) == "wall_panel":
|
||||
_migrate_legacy_wall_tint(object)
|
||||
migrated["version"] = FILE_VERSION
|
||||
_assign_legacy_depths(migrated.get("objects", []))
|
||||
@@ -254,9 +257,15 @@ static func _assign_legacy_depths(raw_objects: Variant) -> void:
|
||||
roles["depth"] = PROP_STAGE_ROLES.DEPTH_FRONT if characters_before > characters_after else PROP_STAGE_ROLES.DEPTH_BEHIND
|
||||
|
||||
static func _validate_root(data: Dictionary) -> Dictionary:
|
||||
var sky_id: Variant = data.get("sky_id", "sky_neutral")
|
||||
var background_kind: Variant = data.get("background_kind", null)
|
||||
if not background_kind is String or not ["sky", "plain"].has(String(background_kind)):
|
||||
return {"ok": false, "error": "Scene background kind is invalid."}
|
||||
var sky_id: Variant = data.get("sky_id", null)
|
||||
if not sky_id is String or not SKY_IDS.has(String(sky_id)):
|
||||
return {"ok": false, "error": "Scene Sky is invalid."}
|
||||
var plain_color: Variant = data.get("plain_color", null)
|
||||
if not _valid_opaque_hex_color(plain_color):
|
||||
return {"ok": false, "error": "Scene Plain Background color is invalid."}
|
||||
var seen: Dictionary = {}
|
||||
var seen_stage_ids: Dictionary = {}
|
||||
for raw: Variant in data["objects"]:
|
||||
@@ -346,6 +355,19 @@ static func _valid_color(value: Variant) -> bool:
|
||||
return true
|
||||
|
||||
|
||||
static func _valid_opaque_hex_color(value: Variant) -> bool:
|
||||
if not value is String:
|
||||
return false
|
||||
var raw: String = String(value).strip_edges()
|
||||
if raw.length() != 7 or not raw.begins_with("#"):
|
||||
return false
|
||||
const HEX_DIGITS: String = "0123456789abcdefABCDEF"
|
||||
for index: int in range(1, raw.length()):
|
||||
if not HEX_DIGITS.contains(raw.substr(index, 1)):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
static func _valid_rect_data(value: Variant) -> bool:
|
||||
if not value is Dictionary:
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user