Add new test file for phase 19 scenery with unique identifier

This commit is contained in:
2026-10-17 12:53:38 -04:00
parent 36032ebb1e
commit a0510e6405
74 changed files with 40948 additions and 149 deletions
+364 -31
View File
@@ -36,7 +36,13 @@ const SETTINGS_PATH: String = "user://prop_editor_settings.json"
const RECENT_MAX: int = 5
const OFF_ORIGIN_WARNING_MIN_DISTANCE: float = 1024.0
const OFF_ORIGIN_WARNING_BOUNDS_MULTIPLIER: float = 2.0
const NORMALIZED_MAX_EXTENT: float = 96.0
const STAGE_REFERENCE_HEIGHT: float = 939.5 # WholeStickmanPreview head top (-463.5 - 100) to ankle line (376).
const STAGE_SIZE_STEP: float = 1.1
const ART_TAG_IDS: Array[String] = ["nature", "buildings", "walls", "roads", "objects"]
const ART_TAG_LABELS: Array[String] = ["Nature", "Buildings", "Walls", "Roads", "Objects"]
signal artwork_saved(path: String)
signal return_requested
const MENU_NEW := 0
const MENU_LOAD := 2
@@ -113,6 +119,7 @@ var _current_path: String = ""
var _dirty: bool = false
var _clipboard: Dictionary = {}
var _anchors: Dictionary = PropAnchorDataScript.default_anchors()
var _tags: Array[String] = []
var _selected_anchor_id: String = ""
var _thumb: PropThumbnail
var _thumb_cache: ThumbnailCache
@@ -138,15 +145,26 @@ var _params_box: VBoxContainer
var _param_spins: Dictionary = {}
var _add_button: Button
var _updating_inspector: bool = false
var _tag_checks: Dictionary = {}
var _tag_section: VBoxContainer
var _stage_authoring: bool = false
var _stage_return_pending: bool = false
var _stage_place_button: Button
# Grid settings dialog (removed Round 4 - replaced by top-right controls)
var _grid_spin: SpinBox
var _snap_check: CheckBox
var _center_prop_button: Button
var _normalize_size_button: Button
var _size_for_stage_button: Button
var _stage_size_controls: FlowContainer
var _stage_size_status: Label
var _show_stickman_guide_check: CheckBox
var _show_stickman_guide: bool = false
var _whole_prop_resize_button: Button
var _stage_size_preview_active: bool = false
var _stage_size_preview_factor: float = 1.0
var _stage_size_entry_extent: float = 0.0
var _pending_save_after_stage_size: int = -1
# Edit-mode toolbar (Round 2/3)
var _mode_group: ButtonGroup
@@ -192,7 +210,8 @@ var _recent_props: Array[String] = []
# ---------------------------------------------------------------------------
func _ready() -> void:
get_tree().auto_accept_quit = false
if not _stage_authoring:
get_tree().auto_accept_quit = false
_theme = PropThemeScript.new()
_theme.load_from(PropThemeScript.DEFAULT_PATH)
_thumb_cache = ThumbnailCacheScript.new()
@@ -225,6 +244,7 @@ func _ready() -> void:
_canvas.whole_prop_resize_requested.connect(_on_whole_prop_resize_requested)
_canvas.whole_prop_move_requested.connect(_on_whole_prop_move_requested)
_canvas.whole_prop_rotate_requested.connect(_on_whole_prop_rotate_requested)
_canvas.stage_size_cancel_requested.connect(func() -> void: _finish_stage_size_preview(false))
_canvas.set_grid_size(_theme.grid_size())
_canvas.set_snap_enabled(false)
_canvas.set_has_clipboard(false)
@@ -250,12 +270,24 @@ func _ready() -> void:
_canvas.call_deferred("reset_view")
call_deferred("_initialize_inspector_split")
_refresh_center_prop_button()
_check_autosave_recovery()
if _stage_authoring:
if _canvas.get_edit_mode() == PropCanvasScript.EditMode.ATTACH:
_canvas.set_edit_mode(PropCanvasScript.EditMode.TRANSFORM)
_do_new()
_build_stage_authoring_controls()
_apply_authoring_context()
else:
_check_autosave_recovery()
func configure_stage_authoring() -> void:
_stage_authoring = true
process_mode = Node.PROCESS_MODE_ALWAYS
func _process(delta: float) -> void:
_update_cursor_coords()
if _autosave_remaining > 0.0:
if not _stage_authoring and _autosave_remaining > 0.0:
_autosave_remaining -= delta
if _autosave_remaining <= 0.0:
_autosave_remaining = 0.0
@@ -266,6 +298,17 @@ func _unhandled_key_input(event: InputEvent) -> void:
if event is InputEventKey:
var key := event as InputEventKey
if key.pressed and not key.echo and key.keycode == KEY_ESCAPE:
if _stage_size_preview_active:
_finish_stage_size_preview(false)
get_viewport().set_input_as_handled()
return
if _stage_authoring:
var stage_focus: Control = get_viewport().gui_get_focus_owner() if get_viewport() != null else null
if stage_focus is LineEdit or stage_focus is TextEdit:
return
_request_stage_return()
get_viewport().set_input_as_handled()
return
var focus_owner: Control = get_viewport().gui_get_focus_owner() if get_viewport() != null else null
if not (focus_owner is LineEdit or focus_owner is TextEdit):
if _canvas.is_whole_prop_resize_dragging():
@@ -349,6 +392,11 @@ func _on_attach_mode_pressed() -> void:
func _on_canvas_mode_changed(mode: int) -> void:
if _stage_size_preview_active:
_cancel_stage_size_preview()
if _stage_authoring and mode == PropCanvasScript.EditMode.ATTACH:
_canvas.set_edit_mode(PropCanvasScript.EditMode.TRANSFORM)
return
if mode != PropCanvasScript.EditMode.TRANSFORM:
_set_whole_prop_resize_enabled(false)
var entering_attach: bool = mode == PropCanvasScript.EditMode.ATTACH and _last_canvas_mode != PropCanvasScript.EditMode.ATTACH
@@ -373,11 +421,11 @@ func _sync_mode_buttons(mode: int) -> void:
func _sync_mode_controls(mode: int) -> void:
if _whole_prop_resize_button != null:
_whole_prop_resize_button.visible = mode == PropCanvasScript.EditMode.TRANSFORM
if _normalize_size_button != null:
_normalize_size_button.visible = mode == PropCanvasScript.EditMode.TRANSFORM
_whole_prop_resize_button.visible = mode == PropCanvasScript.EditMode.TRANSFORM and not _stage_size_preview_active
if _size_for_stage_button != null:
_size_for_stage_button.visible = mode == PropCanvasScript.EditMode.TRANSFORM and not _stage_size_preview_active
if _attach_tools != null:
_attach_tools.visible = mode == PropCanvasScript.EditMode.ATTACH
_attach_tools.visible = mode == PropCanvasScript.EditMode.ATTACH and not _stage_authoring
if _add_section != null:
_add_section.visible = mode == PropCanvasScript.EditMode.TRANSFORM
if _delete_button != null:
@@ -385,9 +433,9 @@ func _sync_mode_controls(mode: int) -> void:
if _shape_section != null:
_shape_section.visible = mode != PropCanvasScript.EditMode.ATTACH and not _canvas.is_whole_prop_resize_enabled() and _canvas.get_selected_index() >= 0
if _material_section != null:
_material_section.visible = mode != PropCanvasScript.EditMode.ATTACH
_material_section.visible = mode != PropCanvasScript.EditMode.ATTACH and not _stage_authoring
if _anchor_section != null:
_anchor_section.visible = mode == PropCanvasScript.EditMode.ATTACH
_anchor_section.visible = mode == PropCanvasScript.EditMode.ATTACH and not _stage_authoring
if _attach_preview != null:
_attach_preview.visible = mode == PropCanvasScript.EditMode.ATTACH and (_show_preview_check == null or _show_preview_check.button_pressed)
if _attach_preview.has_method("set_document"):
@@ -666,11 +714,12 @@ func _build_size_tools() -> void:
_whole_prop_resize_button.custom_minimum_size.y = float(_theme.button_min_height())
_whole_prop_resize_button.toggled.connect(_set_whole_prop_resize_enabled)
_size_tools.add_child(_whole_prop_resize_button)
_normalize_size_button = Button.new()
_normalize_size_button.text = "📏 Normalize Size"
_normalize_size_button.custom_minimum_size.y = float(_theme.button_min_height())
_normalize_size_button.pressed.connect(_on_normalize_size_pressed)
_size_tools.add_child(_normalize_size_button)
_size_for_stage_button = Button.new()
_size_for_stage_button.text = "📏 Size for Stage"
_size_for_stage_button.tooltip_text = "Compare this drawing with a standing stickman."
_size_for_stage_button.custom_minimum_size = Vector2(148.0, maxf(44.0, float(_theme.button_min_height())))
_size_for_stage_button.pressed.connect(_on_size_for_stage_pressed)
_size_tools.add_child(_size_for_stage_button)
_center_prop_button = Button.new()
_center_prop_button.text = "🎯 Center Prop"
_center_prop_button.tooltip_text = "Move the whole prop and its anchors to (0,0)."
@@ -683,9 +732,57 @@ func _build_size_tools() -> void:
_show_stickman_guide_check.custom_minimum_size.y = float(_theme.button_min_height())
_show_stickman_guide_check.toggled.connect(_on_stickman_guide_toggled)
_size_tools.add_child(_show_stickman_guide_check)
_stage_size_controls = FlowContainer.new()
_stage_size_controls.name = "StageSizeControls"
_stage_size_controls.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_stage_size_controls.add_theme_constant_override("h_separation", 6)
_stage_size_controls.add_theme_constant_override("v_separation", 6)
_stage_size_controls.hide()
_size_tools.add_child(_stage_size_controls)
for preset: Dictionary in [
{"label": "Small", "factor": 0.35},
{"label": "Room", "factor": 1.0},
{"label": "Big", "factor": 2.0},
]:
var preset_button: Button = _make_stage_size_button(str(preset["label"]))
var preset_factor: float = float(preset["factor"])
preset_button.pressed.connect(func() -> void: _choose_stage_size(preset_factor))
_stage_size_controls.add_child(preset_button)
var smaller_button: Button = _make_stage_size_button("Make smaller")
smaller_button.pressed.connect(func() -> void: _adjust_stage_size(1.0 / STAGE_SIZE_STEP))
_stage_size_controls.add_child(smaller_button)
var bigger_button: Button = _make_stage_size_button("Make bigger")
bigger_button.pressed.connect(func() -> void: _adjust_stage_size(STAGE_SIZE_STEP))
_stage_size_controls.add_child(bigger_button)
var keep_button: Button = _make_stage_size_button("Keep size", true)
keep_button.pressed.connect(func() -> void: _finish_stage_size_preview(true))
_stage_size_controls.add_child(keep_button)
var cancel_button: Button = _make_stage_size_button("✕ Cancel")
cancel_button.pressed.connect(func() -> void: _finish_stage_size_preview(false))
_stage_size_controls.add_child(cancel_button)
_stage_size_status = Label.new()
_stage_size_status.name = "StageSizeStatus"
# This label lives in the size-tools FlowContainer. With an auto-wrapping
# label and no minimum width, FlowContainer can collapse it to a sliver and
# grow the toolbar to thousands of pixels tall when the preview opens.
_stage_size_status.custom_minimum_size.x = 360.0
_stage_size_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_stage_size_status.hide()
_size_tools.add_child(_stage_size_status)
_refresh_scale_buttons()
func _make_stage_size_button(label: String, prominent: bool = false) -> Button:
var button: Button = Button.new()
button.text = label
button.custom_minimum_size.y = maxf(44.0, float(_theme.button_min_height()))
button.focus_mode = Control.FOCUS_ALL
button.theme = _ui_theme
if prominent:
button.add_theme_color_override("font_color", _theme.accent_color())
return button
func _on_top_grid_changed(value: float) -> void:
_canvas.set_grid_size(int(value))
@@ -693,12 +790,110 @@ func _on_top_grid_changed(value: float) -> void:
func _on_top_snap_toggled(pressed: bool) -> void:
_canvas.set_snap_enabled(pressed)
func _on_normalize_size_pressed() -> void:
var bounds: Rect2 = _canvas.get_shapes_bounds()
var extent: float = maxf(bounds.size.x, bounds.size.y)
if is_finite(extent) and extent > 0.0:
_scale_document(NORMALIZED_MAX_EXTENT / extent)
func _on_size_for_stage_pressed() -> void:
if _stage_size_preview_active or _canvas.get_edit_mode() != PropCanvasScript.EditMode.TRANSFORM:
return
_set_whole_prop_resize_enabled(false)
var bounds: Rect2 = _canvas.get_visible_shapes_bounds()
_stage_size_entry_extent = maxf(bounds.size.x, bounds.size.y)
if not is_finite(_stage_size_entry_extent) or _stage_size_entry_extent <= 0.0:
return
if not _canvas.begin_stage_size_preview(_anchors):
return
_stage_size_preview_factor = 1.0
_stage_size_preview_active = true
_stage_size_status.text = "Compare the drawing with the guide, then keep it or cancel."
_update_stage_size_controls_visibility()
# Hiding the inspector and mode bar changes the canvas allocation. Wait for
# that layout pass before centering/fitting, otherwise the guide can be
# pushed outside the newly-sized viewport until a preset is clicked.
await get_tree().process_frame
if not _stage_size_preview_active:
return
_canvas.fit_stage_size_view(_canvas.get_stage_size_comparison_bounds(), 48.0, true)
func _choose_stage_size(relative_height: float) -> void:
if not _stage_size_preview_active or not is_finite(relative_height) or relative_height <= 0.0:
return
var factor: float = STAGE_REFERENCE_HEIGHT * relative_height / _stage_size_entry_extent
if _canvas.preview_stage_size(factor):
_stage_size_preview_factor = factor
_stage_size_status.text = ""
_canvas.fit_stage_size_view(_canvas.get_stage_size_comparison_bounds(), 48.0, true)
else:
_stage_size_status.text = "That size is too large to display. Try a smaller choice."
func _adjust_stage_size(multiplier: float) -> void:
if not _stage_size_preview_active:
return
var factor: float = _stage_size_preview_factor * multiplier
if _canvas.preview_stage_size(factor):
_stage_size_preview_factor = factor
_stage_size_status.text = ""
_canvas.fit_stage_size_view(_canvas.get_stage_size_comparison_bounds(), 48.0, false)
else:
_stage_size_status.text = "That size is outside the drawing area. Try a smaller change."
func _finish_stage_size_preview(keep: bool) -> void:
if not _stage_size_preview_active:
return
var pending_save: int = _pending_save_after_stage_size if keep else -1
_pending_save_after_stage_size = -1
if keep:
var result: Dictionary = _canvas.commit_stage_size_preview()
_anchors = (result.get("anchors", _anchors) as Dictionary).duplicate(true)
_selected_anchor_id = PropAnchorDataScript.preferred_editable_id(_anchors, _selected_anchor_id)
_canvas.set_anchor_data(_anchors)
_canvas.select_anchor(_selected_anchor_id)
_refresh_anchor_inspector()
_sync_attach_preview()
else:
_canvas.cancel_stage_size_preview()
_stage_size_preview_active = false
_stage_size_preview_factor = 1.0
_stage_size_entry_extent = 0.0
_stage_size_status.text = ""
_update_stage_size_controls_visibility()
_refresh_scale_buttons()
if pending_save >= 0:
_continue_save_after_stage_size(pending_save)
func _cancel_stage_size_preview() -> void:
if _stage_size_preview_active:
_pending_save_after_stage_size = -1
_finish_stage_size_preview(false)
func _request_save_after_stage_size(menu_id: int) -> void:
_pending_save_after_stage_size = menu_id
_stage_size_status.text = "Keep size before saving, or cancel to leave the drawing unchanged."
func _continue_save_after_stage_size(menu_id: int) -> void:
if menu_id == MENU_SAVE:
if _current_path.is_empty():
_save_dialog.popup_centered()
else:
_begin_save_request(_current_path, true)
elif menu_id == MENU_SAVE_AS:
_save_dialog.popup_centered()
func _update_stage_size_controls_visibility() -> void:
if _stage_size_controls == null:
return
_stage_size_controls.visible = _stage_size_preview_active
_stage_size_status.visible = _stage_size_preview_active
_size_for_stage_button.visible = not _stage_size_preview_active and _canvas.get_edit_mode() == PropCanvasScript.EditMode.TRANSFORM
_whole_prop_resize_button.visible = not _stage_size_preview_active and _canvas.get_edit_mode() == PropCanvasScript.EditMode.TRANSFORM
_center_prop_button.visible = not _stage_size_preview_active
_show_stickman_guide_check.visible = not _stage_size_preview_active and not _stage_authoring
_mode_bar.visible = not _stage_size_preview_active
_inspector_column.visible = not _stage_size_preview_active
func _on_stickman_guide_toggled(enabled: bool) -> void:
_show_stickman_guide = enabled
@@ -755,6 +950,9 @@ func _notification(what: int) -> void:
func _request_close() -> void:
if _stage_authoring:
_request_stage_return()
return
if not _dirty:
get_tree().quit()
return
@@ -765,6 +963,7 @@ func _request_close() -> void:
func _request_new() -> void:
_cancel_stage_size_preview()
if not _dirty:
_do_new()
return
@@ -774,6 +973,22 @@ func _request_new() -> void:
_exit_dialog.popup_centered()
func _request_stage_return() -> void:
if not _stage_authoring:
return
_cancel_stage_size_preview()
if not _dirty:
if not _current_path.is_empty():
artwork_saved.emit(_current_path)
return_requested.emit()
return
_stage_return_pending = true
_pending_new = false
_pending_quit = false
_exit_dialog.dialog_text = "Save your artwork before returning? Saving places it on the Stage."
_exit_dialog.popup_centered()
func _on_exit_save() -> void:
_exit_dialog.hide()
if _current_path.is_empty():
@@ -785,8 +1000,12 @@ func _on_exit_save() -> void:
func _on_exit_custom_action(action: StringName) -> void:
if action == &"discard":
_exit_dialog.hide()
_clear_autosave()
if _pending_quit:
if not _stage_authoring:
_clear_autosave()
if _stage_return_pending:
_stage_return_pending = false
return_requested.emit()
elif _pending_quit:
_pending_quit = false
get_tree().quit()
elif _pending_new:
@@ -797,6 +1016,7 @@ func _on_exit_custom_action(action: StringName) -> void:
func _on_exit_cancel() -> void:
_pending_quit = false
_pending_new = false
_stage_return_pending = false
_cancel_pending_save_request()
@@ -805,6 +1025,8 @@ func _do_new() -> void:
_set_whole_prop_resize_enabled(false)
_prop_name_edit.text = ""
_material_preset = PropBlockScript.MaterialPreset.NONE
_tags.clear()
_refresh_tag_controls()
_preset_option.selected = _material_preset
_update_physics_labels()
_canvas.clear()
@@ -820,21 +1042,29 @@ func _do_new() -> void:
_refresh_anchor_inspector()
_refresh_center_prop_button()
_update_save_status()
_refresh_tag_controls()
func _on_menu_id_pressed(id: int) -> void:
match id:
MENU_NEW:
_request_new()
MENU_LOAD:
_load_dialog.popup_centered()
MENU_SAVE:
if _stage_size_preview_active:
_request_save_after_stage_size(MENU_SAVE)
return
if _current_path.is_empty():
_save_dialog.popup_centered()
else:
_begin_save_request(_current_path, true)
MENU_SAVE_AS:
if _stage_size_preview_active:
_request_save_after_stage_size(MENU_SAVE_AS)
return
_save_dialog.popup_centered()
MENU_LOAD:
_cancel_stage_size_preview()
_load_dialog.popup_centered()
MENU_RESET_VIEWS:
_canvas.reset_view()
@@ -983,7 +1213,95 @@ func _build_inspector() -> void:
_apply_control_theme(_inspector_list)
_rebuild_primitive_params()
_build_tag_inspector()
_build_anchor_inspector()
_refresh_tag_controls()
func _build_stage_authoring_controls() -> void:
var top_bar: HBoxContainer = get_node_or_null(NodePath("TopBar")) as HBoxContainer
if top_bar == null:
return
var name_label: Label = top_bar.get_node_or_null(NodePath("NameLabel")) as Label
if name_label != null:
name_label.text = "Artwork name:"
_prop_name_edit.placeholder_text = "Name this artwork"
_stage_place_button = Button.new()
_stage_place_button.text = "✨ Save & Place as Scenery"
_stage_place_button.tooltip_text = "Save this drawing and put a picture of it on your Stage."
_stage_place_button.custom_minimum_size.y = float(_theme.button_min_height())
_stage_place_button.size_flags_horizontal = Control.SIZE_SHRINK_END
_stage_place_button.theme = _ui_theme
_stage_place_button.pressed.connect(_request_stage_return)
top_bar.add_child(_stage_place_button)
func _apply_authoring_context() -> void:
if not _stage_authoring:
return
if _material_section != null:
_material_section.hide()
if _anchor_section != null:
_anchor_section.hide()
if _attach_tools != null:
_attach_tools.hide()
if _attach_button != null:
_attach_button.hide()
if _show_stickman_guide_check != null:
_show_stickman_guide_check.hide()
if _tag_section != null:
_tag_section.show()
_sync_mode_buttons(_canvas.get_edit_mode())
_sync_mode_controls(_canvas.get_edit_mode())
_save_dialog.title = "Save Scenery Artwork"
func _build_tag_inspector() -> void:
_tag_section = VBoxContainer.new()
_tag_section.add_theme_constant_override("separation", 4)
_inspector_list.add_child(_tag_section)
var title := Label.new()
title.text = "Artwork Tags"
title.add_theme_font_size_override("font_size", _theme.font_header())
_tag_section.add_child(title)
var hint := Label.new()
hint.text = "Choose a few so this art is easy to find."
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_tag_section.add_child(hint)
var rows := FlowContainer.new()
rows.add_theme_constant_override("h_separation", 6)
rows.add_theme_constant_override("v_separation", 4)
_tag_section.add_child(rows)
for index: int in ART_TAG_IDS.size():
var tag_id: String = ART_TAG_IDS[index]
var check := CheckBox.new()
check.text = ART_TAG_LABELS[index]
check.custom_minimum_size.y = float(_theme.button_min_height())
check.toggled.connect(_on_art_tag_toggled.bind(tag_id))
rows.add_child(check)
_tag_checks[tag_id] = check
func _on_art_tag_toggled(enabled: bool, tag_id: String) -> void:
if _updating_inspector:
return
if enabled and not _tags.has(tag_id):
_tags.append(tag_id)
elif not enabled:
_tags.erase(tag_id)
_tags = PropFileScript.normalize_tags(_tags)
_mark_dirty()
func _refresh_tag_controls() -> void:
if _tag_checks.is_empty():
return
_updating_inspector = true
for tag_id: String in ART_TAG_IDS:
var check: CheckBox = _tag_checks.get(tag_id) as CheckBox
if check != null:
check.set_pressed_no_signal(_tags.has(tag_id))
_updating_inspector = false
func _build_anchor_inspector() -> void:
_anchor_section = VBoxContainer.new()
@@ -1313,11 +1631,12 @@ func _refresh_center_prop_button() -> void:
_refresh_scale_buttons()
func _refresh_scale_buttons() -> void:
if _normalize_size_button == null:
if _size_for_stage_button == null:
return
var bounds: Rect2 = _canvas.get_shapes_bounds()
var bounds: Rect2 = _canvas.get_visible_shapes_bounds()
var extent: float = maxf(bounds.size.x, bounds.size.y)
_normalize_size_button.disabled = not _canvas.has_shape_geometry() or not is_finite(extent) or extent <= 0.0
_size_for_stage_button.disabled = not _canvas.has_shape_geometry() or not is_finite(extent) or extent <= 0.0
_size_for_stage_button.tooltip_text = "Add visible artwork to use Size for Stage." if _size_for_stage_button.disabled else "Compare this drawing with a standing stickman."
if _whole_prop_resize_button != null:
_whole_prop_resize_button.disabled = _canvas.get_edit_mode() != PropCanvasScript.EditMode.TRANSFORM or not _canvas.has_shape_geometry()
@@ -1422,6 +1741,8 @@ func _write_autosave() -> void:
func _clear_autosave() -> void:
_autosave_remaining = 0.0
if _stage_authoring:
return
if FileAccess.file_exists(AUTOSAVE_PATH):
DirAccess.remove_absolute(AUTOSAVE_PATH)
@@ -1469,7 +1790,7 @@ func _check_autosave_recovery() -> void:
# ---------------------------------------------------------------------------
func _build_json_data() -> Dictionary:
return PropFileScript.to_file_dict(_prop_name_edit.text, _material_preset, _canvas.get_shape_data(), _anchors)
return PropFileScript.to_file_dict(_prop_name_edit.text, _material_preset, _canvas.get_shape_data(), _anchors, _tags)
func _apply_json_data(json: Variant, is_recovery: bool = false) -> String:
@@ -1479,6 +1800,8 @@ func _apply_json_data(json: Variant, is_recovery: bool = false) -> String:
if model.is_empty():
return "The file could not be read: unsupported version or malformed data."
_prop_name_edit.text = str(model.get("prop_name", ""))
_tags = model.get("tags", []) as Array[String]
_refresh_tag_controls()
_material_preset = int(model.get("material_preset", PropBlockScript.MaterialPreset.NONE))
if _preset_option != null:
_preset_option.selected = _material_preset
@@ -1500,6 +1823,9 @@ func _apply_json_data(json: Variant, is_recovery: bool = false) -> String:
func _save_to(path: String, with_thumbnail: bool = true) -> bool:
if _stage_size_preview_active:
_request_save_after_stage_size(MENU_SAVE)
return false
path = ContentPathsScript.canonicalize(path)
var ok: bool = PropFileScript.save(path, _build_json_data())
if not ok:
@@ -1507,7 +1833,8 @@ func _save_to(path: String, with_thumbnail: bool = true) -> bool:
return false
_current_path = path
_dirty = false
_clear_autosave()
if not _stage_authoring:
_clear_autosave()
_push_recent(path)
_update_save_status()
if with_thumbnail:
@@ -1554,21 +1881,27 @@ func _execute_pending_save(center_first: bool) -> void:
elif _pending_new:
_pending_new = false
_do_new()
elif _stage_return_pending:
_stage_return_pending = false
artwork_saved.emit(_current_path)
return_requested.emit()
func _cancel_pending_save_request() -> void:
_pending_save_path = ""
_pending_save_with_thumbnail = true
_pending_quit = false
_pending_new = false
_stage_return_pending = false
func _on_save_file_selected(path: String) -> void:
path = ContentPathsScript.canonicalize(path)
if path.get_extension().to_lower() != "prop":
path = path.get_basename() + ".prop"
_begin_save_request(path, not (_pending_quit or _pending_new))
_begin_save_request(path, not (_pending_quit or _pending_new or _stage_return_pending))
func _load_from(path: String) -> void:
_cancel_stage_size_preview()
path = ContentPathsScript.canonicalize(path)
var raw: Dictionary = PropFileScript.load(path)
if raw.is_empty():