Add Stage Surface Color Dialog and Background Ground Functionality
- Implemented StageSurfaceColorDialog for custom color selection with live preview. - Created StageBackgroundGround class for non-walkable scenery behind the set. - Introduced StageRectSurface for shared rectangular geometry and tint preview. - Added StageSurfacePreview for rendering surface previews. - Developed comprehensive tests for surface color selection, rendering, and serialization.
This commit is contained in:
@@ -53,9 +53,15 @@ const STAGE_SCENERY_CONTROLLER_SCRIPT := preload("res://scripts/editors/stage/st
|
||||
const STAGE_SKY_VIEW_SCRIPT := preload("res://scripts/stage/scenery/stage_sky_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")
|
||||
const STAGE_RECT_SURFACE_SCRIPT := preload("res://scripts/stage/scenery/stage_rect_surface.gd")
|
||||
const STAGE_SCENERY_ARTWORK_SCRIPT := preload("res://scripts/stage/scenery/stage_scenery_artwork.gd")
|
||||
const STAGE_SURFACE_RENDERER_SCRIPT := preload("res://scripts/stage/scenery/stage_surface_renderer.gd")
|
||||
const STAGE_INSPECTOR_PANEL_SCRIPT := preload("res://scripts/shared/stage_ui/stage_inspector_panel.gd")
|
||||
const STAGE_PANEL_CHOICE_GROUP_SCRIPT := preload("res://scripts/shared/stage_ui/stage_panel_choice_group.gd")
|
||||
const STAGE_SURFACE_COLOR_CONTROLS_SCRIPT := preload("res://scripts/shared/stage_ui/stage_surface_color_controls.gd")
|
||||
const STAGE_SURFACE_COLOR_DIALOG_SCRIPT := preload("res://scripts/shared/stage_ui/stage_surface_color_dialog.gd")
|
||||
const STAGE_SURFACE_PREVIEW_SCRIPT := preload("res://scripts/stage/scenery/stage_surface_preview.gd")
|
||||
const PROP_EDITOR_SCENE := preload("res://scenes/editors/prop/prop_editor.tscn")
|
||||
|
||||
# Phase 3c editor tools.
|
||||
@@ -125,6 +131,7 @@ const SPEECH_DRAFT_DEBOUNCE: float = STAGE_SPEECH_SESSION_SCRIPT.DRAFT_DEBOUNCE
|
||||
const SPEECH_DRAFT_SCHEMA: int = STAGE_SPEECH_SESSION_SCRIPT.DRAFT_SCHEMA
|
||||
const SPEECH_BUBBLE_DRAG_THRESHOLD: float = STAGE_SPEECH_SESSION_SCRIPT.BUBBLE_DRAG_THRESHOLD
|
||||
const RECENT_SCENE_LIMIT: int = STAGE_DOCUMENT_CONTROLLER_SCRIPT.RECENT_LIMIT
|
||||
const RECENT_SURFACE_COLOR_LIMIT: int = 8
|
||||
const DEFAULT_SCENE_NAME: String = "Untitled"
|
||||
const DEFAULT_CAMERA_POSITION: Vector2 = Vector2(0.0, -400.0)
|
||||
const DEFAULT_CAMERA_ZOOM: float = 0.5
|
||||
@@ -248,6 +255,7 @@ var _suppressed_contact_pairs: Dictionary:
|
||||
var _spawner: StageSpawner
|
||||
var _selection: StageSelection
|
||||
var _gizmos: StageGizmos
|
||||
var _stage_input_ready: bool = false
|
||||
var _grid: StageGrid
|
||||
var _spatial_index: StageSpatialIndex
|
||||
var _terrain_painter: StageTerrainPainter
|
||||
@@ -268,6 +276,13 @@ var _scenery_group_start: Vector2 = Vector2.ZERO
|
||||
var _scenery_placed_count: int = 0
|
||||
var _scenery_inspector: StageInspectorPanel
|
||||
var _scenery_repair_message: Label
|
||||
var _scenery_surface_section: VBoxContainer
|
||||
var _scenery_wall_look_heading: Label
|
||||
var _scenery_wall_look_group: StagePanelChoiceGroup
|
||||
var _scenery_background_ground_look_heading: Label
|
||||
var _scenery_background_ground_look_group: StagePanelChoiceGroup
|
||||
var _scenery_surface_helper: Label
|
||||
var _scenery_surface_colors: StageSurfaceColorControls
|
||||
var _scenery_depth_section: VBoxContainer
|
||||
var _scenery_depth_heading: Label
|
||||
var _scenery_depth_group: StagePanelChoiceGroup
|
||||
@@ -276,6 +291,7 @@ var _scenery_size_row: HBoxContainer
|
||||
var _scenery_smaller_button: Button
|
||||
var _scenery_bigger_button: Button
|
||||
var _scenery_erase_wall_area_button: Button
|
||||
var _scenery_erase_piece_button: Button
|
||||
var _scenery_find_button: Button
|
||||
var _scenery_choose_button: Button
|
||||
var _scenery_keep_button: Button
|
||||
@@ -284,6 +300,11 @@ var _scenery_repair_candidate: StageSceneryArtwork
|
||||
var _scenery_repair_path: String = ""
|
||||
var _missing_scenery_dialog: ConfirmationDialog
|
||||
var _scenery_repair_confirm_dialog: ConfirmationDialog
|
||||
var _scenery_erase_piece_dialog: ConfirmationDialog
|
||||
var _scenery_erase_piece_targets: Array[WeakRef] = []
|
||||
var _surface_color_dialog: StageSurfaceColorDialog
|
||||
var _surface_color_session: Dictionary = {}
|
||||
var _recent_surface_colors: Array[Color] = []
|
||||
var _group_preview_save_dialog: ConfirmationDialog
|
||||
var _pending_group_save_path: String = ""
|
||||
var _rule_engine: StageRuleEngine
|
||||
@@ -1330,6 +1351,7 @@ func _ready() -> void:
|
||||
_update_scene_chrome()
|
||||
_handle_cli_startup(OS.get_cmdline_user_args())
|
||||
_check_speech_recovery()
|
||||
_stage_input_ready = true
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
@@ -1400,7 +1422,7 @@ func _update_throw_preview_cache() -> void:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if _hosted_prop_editor != null:
|
||||
if _hosted_prop_editor != null or not _stage_input_ready:
|
||||
return
|
||||
if _speech_session != null:
|
||||
_speech_session.set_camera_panning(_panning)
|
||||
@@ -1530,6 +1552,17 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||
_exit_scenery_edit_mode()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if event.keycode in [KEY_DELETE, KEY_BACKSPACE] and _scenery_edit_mode and current_mode == StageMode.EDIT:
|
||||
var selected_scenery: Array[Node2D] = _selection.get_selected() if _selection != null else []
|
||||
var has_surface: bool = false
|
||||
for selected_node: Node2D in selected_scenery:
|
||||
if selected_node.get_script() == STAGE_WALL_PANEL_SCRIPT or selected_node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT:
|
||||
has_surface = true
|
||||
break
|
||||
if has_surface:
|
||||
_request_erase_selected_scenery()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
match event.keycode:
|
||||
KEY_DELETE, KEY_BACKSPACE:
|
||||
if current_mode == StageMode.EDIT:
|
||||
@@ -1795,6 +1828,7 @@ func set_mode(mode: StageMode) -> void:
|
||||
_show_toast("Finish or cancel speech editing first.")
|
||||
_sync_mode_buttons()
|
||||
return
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_controller != null:
|
||||
if _scenery_controller.is_group_preview_pending():
|
||||
_scenery_controller.cancel_group()
|
||||
@@ -2555,6 +2589,10 @@ func _validate_queues_for_save() -> String:
|
||||
|
||||
func _reset_scene_content() -> void:
|
||||
set_mode(StageMode.EDIT)
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_erase_piece_dialog != null:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
_clear_erase_scenery_targets()
|
||||
if _scenery_repair_confirm_dialog != null:
|
||||
_scenery_repair_confirm_dialog.hide()
|
||||
_cancel_scenery_repair_preview()
|
||||
@@ -3033,6 +3071,8 @@ func _build_ui() -> void:
|
||||
_asset_browser.make_artwork_requested.connect(_open_stage_artwork_editor)
|
||||
_asset_browser.build_ui(ui, _ui_font, _emoji_font, _sandbox_theme)
|
||||
_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)
|
||||
_build_ground_finish_popup(ui)
|
||||
|
||||
_modal_views.build_file_dialogs(ui)
|
||||
@@ -3122,6 +3162,43 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_repair_message.name = "MissingArtworkMessage"
|
||||
_scenery_repair_message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
body.add_child(_scenery_repair_message)
|
||||
_scenery_surface_section = VBoxContainer.new()
|
||||
_scenery_surface_section.name = "ScenerySurfaceSettings"
|
||||
_scenery_surface_section.add_theme_constant_override("separation", _stage_panel_integer("shell.section_gap", 12, 0, 32))
|
||||
_scenery_wall_look_heading = _make_scenery_section_heading("Look")
|
||||
_scenery_surface_section.add_child(_scenery_wall_look_heading)
|
||||
_scenery_wall_look_group = STAGE_PANEL_CHOICE_GROUP_SCRIPT.new() as StagePanelChoiceGroup
|
||||
_scenery_wall_look_group.name = "WallPanelLooks"
|
||||
_scenery_wall_look_group.add_choice(&"finish_wall_paint", "Paint", _make_surface_choice_cue(&"wall", &"finish_wall_paint"))
|
||||
_scenery_wall_look_group.add_choice(&"finish_wall_brick", "Brick", _make_surface_choice_cue(&"wall", &"finish_wall_brick"))
|
||||
_scenery_wall_look_group.add_choice(&"finish_wall_wood", "Wood", _make_surface_choice_cue(&"wall", &"finish_wall_wood"))
|
||||
_scenery_wall_look_group.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
_scenery_wall_look_group.choice_selected.connect(_on_selected_surface_look_choice)
|
||||
_scenery_surface_section.add_child(_scenery_wall_look_group)
|
||||
_scenery_background_ground_look_heading = _make_scenery_section_heading("Look")
|
||||
_scenery_surface_section.add_child(_scenery_background_ground_look_heading)
|
||||
_scenery_background_ground_look_group = STAGE_PANEL_CHOICE_GROUP_SCRIPT.new() as StagePanelChoiceGroup
|
||||
_scenery_background_ground_look_group.name = "BackgroundGroundLooks"
|
||||
_scenery_background_ground_look_group.add_choice(&"finish_background_grass", "Grass", _make_surface_choice_cue(&"background_ground", &"finish_background_grass"))
|
||||
_scenery_background_ground_look_group.add_choice(&"finish_background_concrete", "Concrete", _make_surface_choice_cue(&"background_ground", &"finish_background_concrete"))
|
||||
_scenery_background_ground_look_group.add_choice(&"finish_background_carpet", "Carpet", _make_surface_choice_cue(&"background_ground", &"finish_background_carpet"))
|
||||
_scenery_background_ground_look_group.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
_scenery_background_ground_look_group.choice_selected.connect(_on_selected_surface_look_choice)
|
||||
_scenery_surface_section.add_child(_scenery_background_ground_look_group)
|
||||
_scenery_surface_helper = Label.new()
|
||||
_scenery_surface_helper.name = "ScenerySurfaceHelper"
|
||||
_scenery_surface_helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_scenery_surface_helper.add_theme_font_size_override("font_size", _stage_panel_integer("type.helper_size", 16, 12, 32))
|
||||
_scenery_surface_helper.add_theme_color_override("font_color", _stage_panel_color("type.muted_text_color", Color("#AAB2C0")))
|
||||
_apply_ui_font(_scenery_surface_helper)
|
||||
_scenery_surface_section.add_child(_scenery_surface_helper)
|
||||
_scenery_surface_colors = STAGE_SURFACE_COLOR_CONTROLS_SCRIPT.new() as StageSurfaceColorControls
|
||||
_scenery_surface_colors.name = "ScenerySurfaceColors"
|
||||
_scenery_surface_colors.configure(Color.WHITE, _recent_surface_colors, _surface_color_presets(), _sandbox_theme as SandboxTheme)
|
||||
_scenery_surface_colors.color_selected.connect(_on_selected_surface_color_selected)
|
||||
_scenery_surface_colors.custom_color_requested.connect(_open_selected_surface_color_dialog)
|
||||
_scenery_surface_section.add_child(_scenery_surface_colors)
|
||||
body.add_child(_scenery_surface_section)
|
||||
_scenery_depth_section = VBoxContainer.new()
|
||||
_scenery_depth_section.name = "SceneryDepthSection"
|
||||
_scenery_depth_section.add_theme_constant_override("separation", 6)
|
||||
@@ -3162,6 +3239,9 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_erase_wall_area_button = _make_scenery_repair_button("EraseWallArea", "Erase wall area", true)
|
||||
_scenery_erase_wall_area_button.pressed.connect(_begin_selected_wall_erase)
|
||||
body.add_child(_scenery_erase_wall_area_button)
|
||||
_scenery_erase_piece_button = _make_scenery_repair_button("EraseSceneryPiece", "Erase wall panel", true)
|
||||
_scenery_erase_piece_button.pressed.connect(_request_erase_selected_scenery)
|
||||
body.add_child(_scenery_erase_piece_button)
|
||||
_scenery_find_button = _make_scenery_repair_button("FindArtwork", "Find artwork…", true)
|
||||
_scenery_find_button.pressed.connect(func() -> void: _begin_scenery_repair(true))
|
||||
body.add_child(_scenery_find_button)
|
||||
@@ -3192,10 +3272,53 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_repair_confirm_dialog.canceled.connect(_cancel_scenery_repair_preview)
|
||||
ui.add_child(_scenery_repair_confirm_dialog)
|
||||
_apply_dialog_theme(_scenery_repair_confirm_dialog)
|
||||
_scenery_erase_piece_dialog = ConfirmationDialog.new()
|
||||
_scenery_erase_piece_dialog.name = "EraseSceneryPieceDialog"
|
||||
_scenery_erase_piece_dialog.title = "Erase this piece?"
|
||||
_scenery_erase_piece_dialog.ok_button_text = "Erase"
|
||||
_scenery_erase_piece_dialog.cancel_button_text = "Keep it"
|
||||
_scenery_erase_piece_dialog.min_size = _theme_dialog_size("confirm_size", Vector2i(520, 260))
|
||||
_scenery_erase_piece_dialog.confirmed.connect(_confirm_erase_selected_scenery)
|
||||
_scenery_erase_piece_dialog.canceled.connect(_clear_erase_scenery_targets)
|
||||
ui.add_child(_scenery_erase_piece_dialog)
|
||||
_apply_dialog_theme(_scenery_erase_piece_dialog)
|
||||
_surface_color_dialog = STAGE_SURFACE_COLOR_DIALOG_SCRIPT.new() as StageSurfaceColorDialog
|
||||
_surface_color_dialog.name = "ScenerySurfaceColorDialog"
|
||||
ui.add_child(_surface_color_dialog)
|
||||
_surface_color_dialog.preview_color_changed.connect(_preview_selected_surface_color)
|
||||
_surface_color_dialog.color_confirmed.connect(_confirm_selected_surface_color)
|
||||
_surface_color_dialog.cancelled.connect(_on_surface_color_dialog_cancelled)
|
||||
if not get_viewport().size_changed.is_connected(_refresh_scenery_inspector_layout):
|
||||
get_viewport().size_changed.connect(_refresh_scenery_inspector_layout)
|
||||
|
||||
|
||||
func _make_scenery_section_heading(label_text: String) -> Label:
|
||||
var heading := Label.new()
|
||||
heading.text = label_text
|
||||
heading.add_theme_font_size_override("font_size", _stage_panel_integer("type.section_size", 20, 12, 40))
|
||||
heading.add_theme_color_override("font_color", _stage_panel_color("type.text_color", Color("#F5F7FA")))
|
||||
_apply_ui_font(heading)
|
||||
return heading
|
||||
|
||||
|
||||
func _make_surface_choice_cue(kind: StringName, finish: StringName) -> StageSurfacePreview:
|
||||
var cue: StageSurfacePreview = STAGE_SURFACE_PREVIEW_SCRIPT.new() as StageSurfacePreview
|
||||
cue.custom_minimum_size = Vector2(64.0, 40.0)
|
||||
cue.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
cue.configure(kind, finish, STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish))
|
||||
return cue
|
||||
|
||||
|
||||
func _surface_color_presets() -> Array[Color]:
|
||||
var colors: Array[Color] = []
|
||||
var raw_presets: Variant = _sandbox_theme.value("widgets.swatches", []) if _sandbox_theme != null else []
|
||||
if raw_presets is Array:
|
||||
for raw_color: Variant in raw_presets as Array:
|
||||
if raw_color is String and STAGE_SCENE_IO._valid_color(raw_color):
|
||||
colors.append(STAGE_SCENE_IO.color_from_data(raw_color, Color.WHITE))
|
||||
return colors
|
||||
|
||||
|
||||
func _make_scenery_repair_button(node_name: String, label: String, prominent: bool) -> Button:
|
||||
var button: Button = Button.new()
|
||||
button.name = node_name
|
||||
@@ -4221,6 +4344,8 @@ func _open_selector(kind: String) -> void:
|
||||
_scenery_group_mode = &""
|
||||
for pid: String in _palette_buttons:
|
||||
(_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)
|
||||
if kind == "stickman":
|
||||
var selected_stickman_path: String = String(selector_state.get("selected_stickman_path", _spawner.get_selected_stickman_path()))
|
||||
@@ -4260,12 +4385,25 @@ func _on_asset_selected(entry: Dictionary) -> void:
|
||||
if background_kind == "wall":
|
||||
_close_selector()
|
||||
if _scenery_controller != null:
|
||||
var finish_id: StringName = StringName(String(entry.get("finish_id", "finish_wall_paint")))
|
||||
var tint_value: Variant = entry.get("color", STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish_id))
|
||||
var tint: Color = tint_value if tint_value is Color else STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish_id)
|
||||
_scenery_controller.begin_wall(
|
||||
StringName(String(entry.get("finish_id", "finish_wall_paint"))),
|
||||
_viewport_to_world(get_viewport().get_mouse_position())
|
||||
finish_id,
|
||||
_viewport_to_world(get_viewport().get_mouse_position()),
|
||||
tint
|
||||
)
|
||||
_refresh_scenery_capture_bar()
|
||||
return
|
||||
if background_kind == "background_ground":
|
||||
_close_selector()
|
||||
if _scenery_controller != null:
|
||||
var finish_id: StringName = StringName(String(entry.get("finish_id", "finish_background_grass")))
|
||||
var tint_value: Variant = entry.get("color", STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish_id))
|
||||
var tint: Color = tint_value if tint_value is Color else STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish_id)
|
||||
_scenery_controller.begin_background_ground(finish_id, _viewport_to_world(get_viewport().get_mouse_position()), tint)
|
||||
_refresh_scenery_capture_bar()
|
||||
return
|
||||
var art_path: String = String(entry.get("path", entry.get("id", "")))
|
||||
_close_selector()
|
||||
if _scenery_controller == null or not _scenery_controller.begin_artwork(art_path, &"near"):
|
||||
@@ -4362,8 +4500,13 @@ func _refresh_scenery_inspector() -> void:
|
||||
_scenery_depth_section.visible = false
|
||||
if _scenery_depth_group != null:
|
||||
_scenery_depth_group.clear_no_signal()
|
||||
if _scenery_wall_look_group != null:
|
||||
_scenery_wall_look_group.clear_no_signal()
|
||||
if _scenery_background_ground_look_group != null:
|
||||
_scenery_background_ground_look_group.clear_no_signal()
|
||||
var selected: Array[Node2D] = _selection.get_selected() if _selection != null else []
|
||||
if not _scenery_edit_mode or selected.size() != 1 or not _is_scenery_node(selected[0]):
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_repair_candidate != null:
|
||||
_cancel_scenery_repair_preview()
|
||||
_scenery_repair_target = null
|
||||
@@ -4371,15 +4514,45 @@ func _refresh_scenery_inspector() -> void:
|
||||
return
|
||||
var node: Node2D = selected[0]
|
||||
var is_artwork: bool = node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT
|
||||
var is_wall: bool = node.get_script() == STAGE_WALL_PANEL_SCRIPT
|
||||
var is_background_ground: bool = node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT
|
||||
var surface: StageRectSurface = node as StageRectSurface if is_wall or is_background_ground else null
|
||||
var artwork: StageSceneryArtwork = node as StageSceneryArtwork if is_artwork else null
|
||||
var is_missing: bool = artwork != null and artwork.missing
|
||||
var surface_is_selected_dialog_target: bool = _surface_color_session_targets(surface)
|
||||
if not _surface_color_session.is_empty() and not surface_is_selected_dialog_target:
|
||||
_cancel_surface_color_preview()
|
||||
_scenery_surface_section.visible = surface != null
|
||||
_scenery_wall_look_heading.visible = is_wall
|
||||
_scenery_wall_look_group.visible = is_wall
|
||||
_scenery_background_ground_look_heading.visible = is_background_ground
|
||||
_scenery_background_ground_look_group.visible = is_background_ground
|
||||
_scenery_surface_helper.visible = surface != null
|
||||
_scenery_surface_colors.visible = false
|
||||
if surface != null:
|
||||
if is_wall:
|
||||
_scenery_wall_look_group.select_no_signal(surface.finish_id)
|
||||
_scenery_surface_helper.text = "Looks like a wall · Stickmen can pass through."
|
||||
else:
|
||||
_scenery_background_ground_look_group.select_no_signal(surface.finish_id)
|
||||
_scenery_surface_helper.text = "Decorative only · Stickmen don’t walk here."
|
||||
var color_editable: bool = is_wall or surface.finish_id == &"finish_background_carpet"
|
||||
_scenery_surface_colors.visible = color_editable
|
||||
if color_editable:
|
||||
_scenery_surface_colors.set_selected_no_signal(surface.tint)
|
||||
_scenery_surface_colors.set_recent_colors(_recent_surface_colors)
|
||||
_scenery_depth_section.visible = is_artwork
|
||||
if artwork != null:
|
||||
_scenery_depth_group.select_no_signal(artwork.depth)
|
||||
_scenery_size_row.visible = not is_missing or artwork.has_known_bounds
|
||||
_scenery_smaller_button.disabled = is_missing and not artwork.has_known_bounds
|
||||
_scenery_bigger_button.disabled = is_missing and not artwork.has_known_bounds
|
||||
_scenery_erase_wall_area_button.visible = not is_artwork
|
||||
_scenery_erase_wall_area_button.visible = is_wall
|
||||
_scenery_erase_piece_button.visible = surface != null
|
||||
if is_background_ground:
|
||||
_scenery_erase_piece_button.text = "Erase background ground"
|
||||
else:
|
||||
_scenery_erase_piece_button.text = "Erase wall panel"
|
||||
_scenery_find_button.visible = is_missing
|
||||
_scenery_choose_button.visible = is_missing
|
||||
_scenery_keep_button.visible = is_missing
|
||||
@@ -4390,7 +4563,7 @@ func _refresh_scenery_inspector() -> void:
|
||||
_scenery_repair_message.text = "“%s” is missing its picture file. Choose artwork to put it back in this spot." % artwork.display_name
|
||||
else:
|
||||
_scenery_repair_target = null
|
||||
var scenery_name: String = (artwork.display_name if artwork != null else "Wall panel")
|
||||
var scenery_name: String = "Wall panel" if is_wall else "Background ground" if is_background_ground else artwork.display_name
|
||||
_scenery_inspector.set_header(scenery_name, "🖼️" if artwork != null else "🧱")
|
||||
_scenery_repair_message.text = "Make this scenery smaller or bigger. The other scenery stays where it is."
|
||||
_scenery_inspector.set_safe_rect(_prop_settings_safe_rect())
|
||||
@@ -4419,7 +4592,184 @@ func _on_selected_scenery_depth_choice(id: StringName) -> void:
|
||||
|
||||
|
||||
func _is_scenery_node(node: Node2D) -> bool:
|
||||
return node != null and (node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT or node.get_script() == STAGE_WALL_PANEL_SCRIPT)
|
||||
return node != null and (node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT or node.get_script() == STAGE_WALL_PANEL_SCRIPT or node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT)
|
||||
|
||||
|
||||
func _on_selected_surface_look_choice(finish: StringName) -> void:
|
||||
var surface: StageRectSurface = _selected_rect_surface()
|
||||
if surface == null:
|
||||
_refresh_scenery_inspector()
|
||||
return
|
||||
var is_wall: bool = surface.get_script() == STAGE_WALL_PANEL_SCRIPT
|
||||
var valid_finish: bool = STAGE_SURFACE_RENDERER_SCRIPT.is_wall_finish(finish) if is_wall else STAGE_SURFACE_RENDERER_SCRIPT.is_background_finish(finish)
|
||||
if not valid_finish:
|
||||
_refresh_scenery_inspector()
|
||||
return
|
||||
if surface.finish_id != finish:
|
||||
surface.configure(surface.panel_size, finish, surface.tint)
|
||||
_mark_scene_dirty()
|
||||
_refresh_scenery_inspector()
|
||||
|
||||
|
||||
func _selected_rect_surface() -> StageRectSurface:
|
||||
if not _scenery_edit_mode or current_mode != StageMode.EDIT:
|
||||
return null
|
||||
var selected: Array[Node2D] = _selection.get_selected() if _selection != null else []
|
||||
if selected.size() != 1 or not (selected[0].get_script() == STAGE_WALL_PANEL_SCRIPT or selected[0].get_script() == STAGE_BACKGROUND_GROUND_SCRIPT):
|
||||
return null
|
||||
return selected[0] as StageRectSurface
|
||||
|
||||
|
||||
func _on_selected_surface_color_selected(color: Color) -> void:
|
||||
var surface: StageRectSurface = _selected_rect_surface()
|
||||
if surface == null:
|
||||
_refresh_scenery_inspector()
|
||||
return
|
||||
var color_changed: bool = not surface.tint.is_equal_approx(color)
|
||||
if color_changed:
|
||||
surface.clear_preview_tint()
|
||||
surface.configure(surface.panel_size, surface.finish_id, color)
|
||||
_mark_scene_dirty()
|
||||
_record_recent_surface_color(color)
|
||||
_scenery_surface_colors.set_selected_no_signal(surface.tint)
|
||||
|
||||
|
||||
func _open_selected_surface_color_dialog() -> void:
|
||||
var surface: StageRectSurface = _selected_rect_surface()
|
||||
if surface == null or _surface_color_dialog == null:
|
||||
return
|
||||
if surface.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT and surface.finish_id != &"finish_background_carpet":
|
||||
return
|
||||
_cancel_surface_color_preview()
|
||||
var kind: StringName = &"background_ground" if surface.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT else &"wall"
|
||||
_surface_color_session = {
|
||||
"target": weakref(surface),
|
||||
"original_color": surface.tint,
|
||||
"tentative_color": surface.tint,
|
||||
"surface_kind": kind,
|
||||
"finish_id": surface.finish_id
|
||||
}
|
||||
_surface_color_dialog.configure(kind, surface.finish_id, surface.tint, _sandbox_theme as SandboxTheme)
|
||||
_surface_color_dialog.open_dialog()
|
||||
|
||||
|
||||
func _surface_color_session_targets(surface: StageRectSurface) -> bool:
|
||||
if _surface_color_session.is_empty() or surface == null:
|
||||
return false
|
||||
var target_ref: WeakRef = _surface_color_session.get("target") as WeakRef
|
||||
return target_ref != null and target_ref.get_ref() == surface
|
||||
|
||||
|
||||
func _preview_selected_surface_color(color: Color) -> void:
|
||||
if _surface_color_session.is_empty():
|
||||
return
|
||||
var target_ref: WeakRef = _surface_color_session.get("target") as WeakRef
|
||||
var surface: StageRectSurface = target_ref.get_ref() as StageRectSurface if target_ref != null else null
|
||||
if surface == null or not _is_surface_live_world_child(surface):
|
||||
_cancel_surface_color_preview()
|
||||
return
|
||||
_surface_color_session["tentative_color"] = color
|
||||
surface.set_preview_tint(color)
|
||||
|
||||
|
||||
func _confirm_selected_surface_color(color: Color) -> void:
|
||||
if _surface_color_session.is_empty():
|
||||
return
|
||||
var target_ref: WeakRef = _surface_color_session.get("target") as WeakRef
|
||||
var surface: StageRectSurface = target_ref.get_ref() as StageRectSurface if target_ref != null else null
|
||||
if surface == null or not _is_surface_live_world_child(surface):
|
||||
_cancel_surface_color_preview()
|
||||
return
|
||||
var color_changed: bool = not surface.tint.is_equal_approx(color)
|
||||
surface.clear_preview_tint()
|
||||
if color_changed:
|
||||
surface.configure(surface.panel_size, surface.finish_id, color)
|
||||
_surface_color_session.clear()
|
||||
_record_recent_surface_color(color)
|
||||
if color_changed:
|
||||
_mark_scene_dirty()
|
||||
_refresh_scenery_inspector()
|
||||
call_deferred("_focus_surface_color_control")
|
||||
|
||||
|
||||
func _on_surface_color_dialog_cancelled() -> void:
|
||||
_cancel_surface_color_preview()
|
||||
call_deferred("_focus_surface_color_control")
|
||||
|
||||
|
||||
func _focus_surface_color_control() -> void:
|
||||
if _scenery_inspector != null and _scenery_inspector.visible and _scenery_surface_colors != null and _scenery_surface_colors.visible:
|
||||
_scenery_surface_colors.focus_custom_button()
|
||||
|
||||
|
||||
func _cancel_surface_color_preview() -> void:
|
||||
if _surface_color_session.is_empty():
|
||||
if _surface_color_dialog != null and _surface_color_dialog.visible:
|
||||
_surface_color_dialog.hide()
|
||||
return
|
||||
var target_ref: WeakRef = _surface_color_session.get("target") as WeakRef
|
||||
var surface: StageRectSurface = target_ref.get_ref() as StageRectSurface if target_ref != null else null
|
||||
if surface != null and is_instance_valid(surface):
|
||||
surface.clear_preview_tint()
|
||||
_surface_color_session.clear()
|
||||
if _surface_color_dialog != null and _surface_color_dialog.visible:
|
||||
_surface_color_dialog.hide()
|
||||
if _scenery_inspector != null and is_instance_valid(_scenery_inspector):
|
||||
_scenery_inspector.queue_sort()
|
||||
|
||||
|
||||
func _is_surface_live_world_child(surface: StageRectSurface) -> bool:
|
||||
return surface != null and is_instance_valid(surface) and _world != null and is_instance_valid(_world) and surface.get_parent() == _world and _scenery_edit_mode and current_mode == StageMode.EDIT
|
||||
|
||||
|
||||
func _request_erase_selected_scenery() -> void:
|
||||
var selected: Array[Node2D] = _selection.get_selected() if _selection != null else []
|
||||
if selected.is_empty() or not _scenery_edit_mode or current_mode != StageMode.EDIT:
|
||||
return
|
||||
var targets: Array[Node2D] = []
|
||||
for node: Node2D in selected:
|
||||
if _is_scenery_node(node) and node.get_parent() == _world:
|
||||
targets.append(node)
|
||||
if targets.is_empty():
|
||||
return
|
||||
_clear_erase_scenery_targets()
|
||||
for node: Node2D in targets:
|
||||
_scenery_erase_piece_targets.append(weakref(node))
|
||||
var count: int = targets.size()
|
||||
if count == 1:
|
||||
_scenery_erase_piece_dialog.dialog_text = "Erase this piece? You can’t undo this."
|
||||
else:
|
||||
_scenery_erase_piece_dialog.dialog_text = "Erase these %d scenery pieces? You can’t undo this." % count
|
||||
_scenery_erase_piece_dialog.popup_centered()
|
||||
|
||||
|
||||
func _confirm_erase_selected_scenery() -> void:
|
||||
var erased: Array[Node2D] = []
|
||||
if not _scenery_edit_mode or current_mode != StageMode.EDIT or _world == null or not is_instance_valid(_world):
|
||||
_clear_erase_scenery_targets()
|
||||
return
|
||||
for target_ref: WeakRef in _scenery_erase_piece_targets:
|
||||
var node: Node2D = target_ref.get_ref() as Node2D if target_ref != null else null
|
||||
if node == null or not is_instance_valid(node) or node.get_parent() != _world or not _is_scenery_node(node):
|
||||
continue
|
||||
_clear_object_state(node)
|
||||
node.queue_free()
|
||||
erased.append(node)
|
||||
_clear_erase_scenery_targets()
|
||||
if erased.is_empty():
|
||||
return
|
||||
if _selection != null:
|
||||
_selection.clear_selection()
|
||||
_selection.clear_hover()
|
||||
_director_visuals.mark_dirty()
|
||||
object_deleted.emit(erased)
|
||||
_mark_scene_dirty()
|
||||
_refresh_scenery_inspector()
|
||||
_refresh_status()
|
||||
|
||||
|
||||
func _clear_erase_scenery_targets() -> void:
|
||||
_scenery_erase_piece_targets.clear()
|
||||
|
||||
|
||||
func _step_selected_scenery_size(factor: float) -> void:
|
||||
@@ -4546,6 +4896,10 @@ func _enter_scenery_edit_mode() -> void:
|
||||
|
||||
|
||||
func _exit_scenery_edit_mode() -> void:
|
||||
_cancel_surface_color_preview()
|
||||
if _scenery_erase_piece_dialog != null:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
_clear_erase_scenery_targets()
|
||||
if _scenery_repair_confirm_dialog != null:
|
||||
_scenery_repair_confirm_dialog.hide()
|
||||
_cancel_scenery_repair_preview()
|
||||
@@ -4605,7 +4959,16 @@ func _refresh_scenery_capture_bar() -> void:
|
||||
else:
|
||||
actions.append({"id": "done_tool", "label": "Done", "primary": true})
|
||||
actions.append({"id": "cancel_tool", "label": "✕ Cancel"})
|
||||
_use_target_capture_bar.set_capture_actions("Drag to draw wall · Stickmen can pass through", actions)
|
||||
if _scenery_controller.get_active_tool() == &"background_ground":
|
||||
var ground_prompt: String = "Draw background ground · No walking"
|
||||
if _scenery_placed_count > 0:
|
||||
ground_prompt = "Draw another · placed pieces stay"
|
||||
_use_target_capture_bar.set_capture_actions(ground_prompt, actions)
|
||||
else:
|
||||
var wall_prompt: String = "Draw wall panel · Stickmen pass through"
|
||||
if _scenery_placed_count > 0:
|
||||
wall_prompt = "Draw another · placed pieces stay"
|
||||
_use_target_capture_bar.set_capture_actions(wall_prompt, actions)
|
||||
return
|
||||
if _scenery_controller != null and _scenery_controller.is_group_preview_pending():
|
||||
_use_target_capture_bar.clear_horizontal_safe_bounds()
|
||||
@@ -4622,6 +4985,8 @@ func _refresh_scenery_capture_bar() -> void:
|
||||
var target_name: String = "scenery"
|
||||
if target != null and target.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT:
|
||||
target_name = (target as StageSceneryArtwork).display_name
|
||||
elif target != null and target.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT:
|
||||
target_name = "background ground"
|
||||
elif target != null:
|
||||
target_name = "wall panel"
|
||||
var is_move: bool = _scenery_controller.get_single_transform_kind() == &"move"
|
||||
@@ -4737,7 +5102,7 @@ func _select_all_scenery() -> void:
|
||||
_selection.clear_selection()
|
||||
for child: Node in _world.get_children():
|
||||
var node: Node2D = child as Node2D
|
||||
if node != null and (node.get_script() == STAGE_WALL_PANEL_SCRIPT or node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT):
|
||||
if node != null and (node.get_script() == STAGE_WALL_PANEL_SCRIPT or node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT or node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT):
|
||||
_selection.add_to_selection(node)
|
||||
_refresh_scenery_capture_bar()
|
||||
|
||||
@@ -6920,7 +7285,7 @@ func _world_children_selectable() -> Array[Node2D]:
|
||||
continue
|
||||
if node.is_in_group("stickman_ragdoll_container") or bool(node.get_meta("stickman_ragdoll_container", false)):
|
||||
continue
|
||||
if node.get_script() == STAGE_WALL_PANEL_SCRIPT or node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT:
|
||||
if node.get_script() == STAGE_WALL_PANEL_SCRIPT or node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT or node.get_script() == STAGE_SCENERY_ARTWORK_SCRIPT:
|
||||
continue
|
||||
result.append(node)
|
||||
return result
|
||||
@@ -6984,8 +7349,24 @@ func _load_settings() -> void:
|
||||
_grid_size = clampf(_grid_size, MIN_GRID_SIZE, MAX_GRID_SIZE)
|
||||
_snap_enabled = bool(d.get("snap_to_grid", false))
|
||||
_show_grid = bool(d.get("show_grid", true))
|
||||
var loaded_recent_scenes: Array[String] = []
|
||||
var settings_migrated: bool = false
|
||||
_recent_surface_colors.clear()
|
||||
var raw_surface_colors: Variant = d.get("recent_surface_colors", [])
|
||||
var normalized_surface_colors: Array[String] = []
|
||||
if raw_surface_colors is Array:
|
||||
for raw_color: Variant in raw_surface_colors as Array:
|
||||
if not raw_color is String or not STAGE_SCENE_IO._valid_color(raw_color):
|
||||
continue
|
||||
var parsed_color: Color = STAGE_SCENE_IO.color_from_data(raw_color, Color.WHITE)
|
||||
var canonical_color: String = _canonical_surface_color(parsed_color)
|
||||
if not normalized_surface_colors.has(canonical_color):
|
||||
normalized_surface_colors.append(canonical_color)
|
||||
_recent_surface_colors.append(Color.from_string(canonical_color, Color.WHITE))
|
||||
if normalized_surface_colors.size() >= RECENT_SURFACE_COLOR_LIMIT:
|
||||
break
|
||||
if normalized_surface_colors != (raw_surface_colors as Array):
|
||||
settings_migrated = true
|
||||
var loaded_recent_scenes: Array[String] = []
|
||||
var raw_recents: Variant = d.get("recent_scenes", [])
|
||||
if raw_recents is Array:
|
||||
for raw: Variant in raw_recents:
|
||||
@@ -7009,8 +7390,49 @@ func _save_settings() -> void:
|
||||
"snap_to_grid": _snap_enabled,
|
||||
"show_grid": _show_grid,
|
||||
"recent_scenes": _recent_scenes,
|
||||
"recent_surface_colors": _surface_color_history_strings(),
|
||||
}
|
||||
var file := FileAccess.open(SETTINGS_PATH, FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(JSON.stringify(data, "\t", false))
|
||||
file.close()
|
||||
|
||||
|
||||
func _canonical_surface_color(color: Color) -> String:
|
||||
var opaque: Color = Color(color.r, color.g, color.b, 1.0)
|
||||
return "#" + opaque.to_html(false).to_upper()
|
||||
|
||||
|
||||
func _surface_color_history_strings() -> Array[String]:
|
||||
var result: Array[String] = []
|
||||
for color: Color in _recent_surface_colors:
|
||||
result.append(_canonical_surface_color(color))
|
||||
return result
|
||||
|
||||
|
||||
func _record_recent_surface_color(color: Color) -> void:
|
||||
var opaque: Color = Color(color.r, color.g, color.b, 1.0)
|
||||
var canonical: String = _canonical_surface_color(opaque)
|
||||
var retained: Array[Color] = []
|
||||
retained.append(opaque)
|
||||
for existing: Color in _recent_surface_colors:
|
||||
if _canonical_surface_color(existing) == canonical:
|
||||
continue
|
||||
retained.append(existing)
|
||||
if retained.size() >= RECENT_SURFACE_COLOR_LIMIT:
|
||||
break
|
||||
if _surface_color_history_strings() == _colors_to_strings(retained):
|
||||
return
|
||||
_recent_surface_colors = retained
|
||||
_save_settings()
|
||||
if _asset_browser != null:
|
||||
_asset_browser.set_surface_color_history(_recent_surface_colors)
|
||||
if _scenery_surface_colors != null:
|
||||
_scenery_surface_colors.set_recent_colors(_recent_surface_colors)
|
||||
|
||||
|
||||
func _colors_to_strings(colors: Array[Color]) -> Array[String]:
|
||||
var result: Array[String] = []
|
||||
for color: Color in colors:
|
||||
result.append(_canonical_surface_color(color))
|
||||
return result
|
||||
|
||||
@@ -8,6 +8,7 @@ signal selector_cancel_requested
|
||||
signal refresh_requested
|
||||
signal make_artwork_requested
|
||||
signal edit_scenery_requested
|
||||
signal surface_color_confirmed(color: Color)
|
||||
|
||||
const ASSET_SELECTOR_SCENE := preload("res://scenes/stage/asset_selector.tscn")
|
||||
const STICKMAN_LIBRARY_SCRIPT := preload("res://scripts/characters/stickman/stickman_library.gd")
|
||||
@@ -59,6 +60,7 @@ func build_ui(ui_layer: CanvasLayer, ui_font: Font, emoji_font: Font, sandbox_th
|
||||
selector.refresh_requested.connect(func() -> void: refresh_requested.emit())
|
||||
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))
|
||||
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)
|
||||
@@ -84,6 +86,11 @@ func scan_entries(kind: String) -> Array[Dictionary]:
|
||||
return PROP_LIBRARY_SCRIPT.scan()
|
||||
|
||||
|
||||
func set_surface_color_history(colors: Array[Color]) -> void:
|
||||
if selector != null:
|
||||
selector.set_surface_color_history(colors)
|
||||
|
||||
|
||||
func _starter_scene_entries() -> Array[Dictionary]:
|
||||
return [
|
||||
{
|
||||
@@ -148,9 +155,8 @@ func _scenery_background_entries() -> Array[Dictionary]:
|
||||
{"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": "🌅"},
|
||||
{"id": "sky_starry_night", "name": "Starry Night", "category": "Sky", "gallery_tab": "backgrounds", "background_kind": "sky", "icon": "🌙"},
|
||||
{"id": "finish_wall_paint", "name": "Wall panel · Paint", "category": "Wall finish", "gallery_tab": "backgrounds", "background_kind": "wall", "finish_id": "finish_wall_paint", "icon": "🟩"},
|
||||
{"id": "finish_wall_brick", "name": "Wall panel · Brick", "category": "Wall finish", "gallery_tab": "backgrounds", "background_kind": "wall", "finish_id": "finish_wall_brick", "icon": "🧱"},
|
||||
{"id": "finish_wall_wood", "name": "Wall panel · Wood", "category": "Wall finish", "gallery_tab": "backgrounds", "background_kind": "wall", "finish_id": "finish_wall_wood", "icon": "🪵"}
|
||||
{"id": "wall_panel_setup", "name": "Wall panel", "category": "Scenery", "gallery_tab": "backgrounds", "background_kind": "wall_setup", "icon": "🧱"},
|
||||
{"id": "background_ground_setup", "name": "Background ground", "category": "Scenery", "description": "Decorative · No walking", "gallery_tab": "backgrounds", "background_kind": "background_ground_setup", "icon": "🌿"}
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ signal reset_requested
|
||||
|
||||
const TOP_BAR_HEIGHT: float = 64.0
|
||||
const STATUS_BAR_HEIGHT: float = 38.0
|
||||
const COMPACT_TOP_BAR_WIDTH: float = 900.0
|
||||
|
||||
var _stage_theme: SandboxTheme
|
||||
var _current_mode: int = 0
|
||||
@@ -43,6 +44,8 @@ var _mode_buttons: Dictionary = {}
|
||||
var _palette_buttons: Dictionary = {}
|
||||
var _palette_dock: PanelContainer
|
||||
var _file_menu: MenuButton
|
||||
var _mode_center: CenterContainer
|
||||
var _right_controls: HBoxContainer
|
||||
var _status_label: Label
|
||||
var _status_cursor_coords: Label
|
||||
var _direct_hint_label: Label
|
||||
@@ -405,10 +408,13 @@ func _build_top_bar() -> void:
|
||||
right_group.offset_bottom = TOP_BAR_HEIGHT - 8.0
|
||||
right_group.alignment = BoxContainer.ALIGNMENT_END
|
||||
right_group.add_theme_constant_override("separation", 8)
|
||||
_right_controls = right_group
|
||||
chrome.add_child(right_group)
|
||||
_build_file_menu(left_group)
|
||||
_build_mode_switcher(chrome)
|
||||
_build_grid_controls(right_group)
|
||||
get_viewport().size_changed.connect(_apply_responsive_top_bar_layout)
|
||||
_apply_responsive_top_bar_layout()
|
||||
|
||||
|
||||
func _build_file_menu(parent: HBoxContainer) -> void:
|
||||
@@ -442,6 +448,7 @@ func _build_mode_switcher(parent: Control) -> void:
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
|
||||
center.offset_bottom = TOP_BAR_HEIGHT
|
||||
_mode_center = center
|
||||
parent.add_child(center)
|
||||
var switcher := HBoxContainer.new()
|
||||
switcher.name = "ModeSwitcher"
|
||||
@@ -492,6 +499,52 @@ func _build_grid_controls(parent: HBoxContainer) -> void:
|
||||
parent.add_child(_grid_size_spin)
|
||||
|
||||
|
||||
func _apply_responsive_top_bar_layout() -> void:
|
||||
if _file_menu == null or _mode_center == null or _right_controls == null:
|
||||
return
|
||||
var viewport_width: float = get_viewport().get_visible_rect().size.x
|
||||
var compact: bool = viewport_width <= COMPACT_TOP_BAR_WIDTH
|
||||
if compact:
|
||||
_file_menu.custom_minimum_size = Vector2(64.0, 48.0)
|
||||
_file_menu.add_theme_font_size_override("font_size", 18)
|
||||
_mode_center.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||
_mode_center.offset_left = 76.0
|
||||
_mode_center.offset_top = 8.0
|
||||
_mode_center.offset_right = 342.0
|
||||
_mode_center.offset_bottom = TOP_BAR_HEIGHT - 8.0
|
||||
_right_controls.add_theme_constant_override("separation", 4)
|
||||
_grid_check.add_theme_font_size_override("font_size", 16)
|
||||
_snap_check.add_theme_font_size_override("font_size", 16)
|
||||
_grid_size_label.visible = false
|
||||
_grid_size_spin.custom_minimum_size = Vector2(56.0, 0.0)
|
||||
_grid_size_spin.add_theme_font_size_override("font_size", 16)
|
||||
else:
|
||||
_file_menu.custom_minimum_size = Vector2(78.0, 48.0)
|
||||
_file_menu.add_theme_font_size_override(
|
||||
"font_size", _theme_integer("fonts.sizes.file_menu", 22, 12, 48)
|
||||
)
|
||||
_mode_center.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
|
||||
_mode_center.offset_bottom = TOP_BAR_HEIGHT
|
||||
_right_controls.add_theme_constant_override("separation", 8)
|
||||
_grid_check.remove_theme_font_size_override("font_size")
|
||||
_snap_check.remove_theme_font_size_override("font_size")
|
||||
_grid_size_label.visible = true
|
||||
_grid_size_spin.custom_minimum_size = Vector2.ZERO
|
||||
_grid_size_spin.remove_theme_font_size_override("font_size")
|
||||
for mode: int in _mode_buttons:
|
||||
var button: Button = _mode_buttons[mode] as Button
|
||||
if compact:
|
||||
button.custom_minimum_size = Vector2(86.0, 48.0)
|
||||
button.add_theme_font_size_override("font_size", 18)
|
||||
else:
|
||||
button.custom_minimum_size = Vector2(
|
||||
_theme_number("chrome.mode_switcher.segment_min_width", 124.0, 90.0, 220.0), 48.0
|
||||
)
|
||||
button.add_theme_font_size_override(
|
||||
"font_size", _theme_integer("fonts.sizes.mode_switcher", 24, 12, 48)
|
||||
)
|
||||
|
||||
|
||||
func _build_palette(entries: Array[Dictionary]) -> void:
|
||||
_palette_dock = PanelContainer.new()
|
||||
_palette_dock.name = "PaletteDock"
|
||||
|
||||
@@ -11,6 +11,7 @@ const PROP_LIBRARY := preload("res://scripts/props/prop_library.gd")
|
||||
const PROP_STAGE_ROLES := preload("res://scripts/props/prop_stage_roles.gd")
|
||||
const PROP_USE_SPOT_DATA := preload("res://scripts/props/prop_use_spot_data.gd")
|
||||
const STAGE_WALL_PANEL := preload("res://scripts/stage/scenery/stage_wall_panel.gd")
|
||||
const STAGE_BACKGROUND_GROUND := preload("res://scripts/stage/scenery/stage_background_ground.gd")
|
||||
const STAGE_SCENERY_ARTWORK := preload("res://scripts/stage/scenery/stage_scenery_artwork.gd")
|
||||
const STAGE_DRAW_ORDER := preload("res://scripts/stage/stage_draw_order.gd")
|
||||
|
||||
@@ -24,7 +25,7 @@ func collect(
|
||||
) -> Dictionary:
|
||||
var objects: Array[Dictionary] = []
|
||||
var runtime_to_file: Dictionary = {}
|
||||
var counters: Dictionary = {"terrain": 0, "prop": 0, "stickman": 0, "area": 0, "wall_panel": 0, "scenery_artwork": 0}
|
||||
var counters: Dictionary = {"terrain": 0, "prop": 0, "stickman": 0, "area": 0, "wall_panel": 0, "background_ground": 0, "scenery_artwork": 0}
|
||||
for node: Node in world.get_children():
|
||||
if not node is Node2D:
|
||||
continue
|
||||
@@ -54,6 +55,11 @@ func collect(
|
||||
if stage_node is TerrainBlock and String(stage_node.get("spawn_id")) == "ground":
|
||||
var ground_finish: StringName = (stage_node as TerrainBlock).finish_id
|
||||
entry["finish_id"] = String(ground_finish) if not ground_finish.is_empty() else "finish_ground_legacy"
|
||||
elif stage_node.get_script() == STAGE_BACKGROUND_GROUND:
|
||||
var background: StageBackgroundGround = stage_node as StageBackgroundGround
|
||||
entry["size"] = STAGE_SCENE_IO.vector_to_data(background.panel_size)
|
||||
entry["finish_id"] = String(background.finish_id)
|
||||
entry["color"] = "#" + background.tint.to_html(true)
|
||||
elif stage_node.get_script() == STAGE_WALL_PANEL:
|
||||
var wall: StageWallPanel = stage_node as StageWallPanel
|
||||
entry["size"] = STAGE_SCENE_IO.vector_to_data(wall.panel_size)
|
||||
@@ -168,6 +174,16 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
wall.motif_origin_offset = STAGE_SCENE_IO.data_to_vector(entry.get("motif_origin_offset"), Vector2.ZERO)
|
||||
world.add_child(wall)
|
||||
node = wall
|
||||
"background_ground":
|
||||
var background := STAGE_BACKGROUND_GROUND.new() as StageBackgroundGround
|
||||
background.position = position
|
||||
background.configure(
|
||||
STAGE_SCENE_IO.data_to_vector(entry.get("size"), Vector2(800.0, 300.0)),
|
||||
StringName(String(entry.get("finish_id", "finish_background_grass"))),
|
||||
STAGE_SCENE_IO.color_from_data(entry.get("color", "#72A84CFF"), Color("#72A84C"))
|
||||
)
|
||||
world.add_child(background)
|
||||
node = background
|
||||
"scenery_artwork":
|
||||
var artwork := STAGE_SCENERY_ARTWORK.new() as StageSceneryArtwork
|
||||
var raw_bounds: Variant = entry.get("last_known_local_bounds", {})
|
||||
@@ -285,6 +301,8 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
func _object_type(node: Node2D) -> String:
|
||||
if node.get_script() == STAGE_WALL_PANEL:
|
||||
return "wall_panel"
|
||||
if node.get_script() == STAGE_BACKGROUND_GROUND:
|
||||
return "background_ground"
|
||||
if node.get_script() == STAGE_SCENERY_ARTWORK:
|
||||
return "scenery_artwork"
|
||||
if node is TerrainBlock:
|
||||
|
||||
@@ -8,6 +8,8 @@ signal preview_state_changed(active: bool)
|
||||
signal single_gesture_changed(active: bool)
|
||||
|
||||
const WALL_PANEL := preload("res://scripts/stage/scenery/stage_wall_panel.gd")
|
||||
const BACKGROUND_GROUND := preload("res://scripts/stage/scenery/stage_background_ground.gd")
|
||||
const RECT_SURFACE := preload("res://scripts/stage/scenery/stage_rect_surface.gd")
|
||||
const SCENERY_ARTWORK := preload("res://scripts/stage/scenery/stage_scenery_artwork.gd")
|
||||
const PROP_FILE := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
||||
@@ -15,6 +17,7 @@ const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
||||
const MIN_PANEL_SIZE: float = 24.0
|
||||
const MIN_WALL_ERASE_SIZE: float = 8.0
|
||||
const DEFAULT_PANEL_SIZE: Vector2 = Vector2(240.0, 280.0)
|
||||
const DEFAULT_BACKGROUND_SIZE: Vector2 = Vector2(800.0, 320.0)
|
||||
const WALL_ERASER_CURSOR_SIZE: Vector2 = Vector2(120.0, 120.0)
|
||||
const MIN_ART_SCALE: float = 0.08
|
||||
const MAX_ART_SCALE: float = 12.0
|
||||
@@ -112,6 +115,12 @@ func get_active_artwork_name() -> String:
|
||||
return _asset_path.get_file().get_basename()
|
||||
|
||||
|
||||
func get_active_surface_label() -> String:
|
||||
if _active_tool == &"background_ground":
|
||||
return "background ground"
|
||||
return "wall"
|
||||
|
||||
|
||||
func handle_key(event: InputEventKey) -> bool:
|
||||
if is_active() and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
exit_tool()
|
||||
@@ -144,17 +153,31 @@ func begin_artwork(path: String, depth: StringName = &"near") -> bool:
|
||||
|
||||
|
||||
func begin_wall(finish: StringName, initial_world_position: Vector2, tint: Color = Color("#5A9D70")) -> void:
|
||||
_begin_rect_surface(&"wall", finish, initial_world_position, tint)
|
||||
|
||||
|
||||
func begin_background_ground(finish: StringName, initial_world_position: Vector2, tint: Color) -> void:
|
||||
_begin_rect_surface(&"background_ground", finish, initial_world_position, tint)
|
||||
|
||||
|
||||
func _begin_rect_surface(tool: StringName, finish: StringName, initial_world_position: Vector2, tint: Color) -> void:
|
||||
if _world == null or _ghost_holder == null:
|
||||
return
|
||||
exit_tool()
|
||||
_active_tool = &"wall"
|
||||
_active_tool = tool
|
||||
_wall_finish = finish
|
||||
_wall_tint = tint
|
||||
_wall_anchor = Vector2.INF
|
||||
_wall_dragging = false
|
||||
_wall_anchor_active = false
|
||||
var panel := WALL_PANEL.new() as StageWallPanel
|
||||
panel.configure(DEFAULT_PANEL_SIZE, _wall_finish, _wall_tint)
|
||||
var panel: StageRectSurface
|
||||
var initial_size: Vector2 = DEFAULT_PANEL_SIZE
|
||||
if tool == &"background_ground":
|
||||
panel = BACKGROUND_GROUND.new() as StageBackgroundGround
|
||||
initial_size = DEFAULT_BACKGROUND_SIZE
|
||||
else:
|
||||
panel = WALL_PANEL.new() as StageWallPanel
|
||||
panel.configure(initial_size, _wall_finish, _wall_tint)
|
||||
panel.modulate = Color(1.0, 1.0, 1.0, 0.45)
|
||||
_ghost_holder.add_child(panel)
|
||||
_ghost = panel
|
||||
@@ -204,14 +227,14 @@ func update_pointer(world_position: Vector2) -> void:
|
||||
if not is_active() or _ghost == null or not is_instance_valid(_ghost):
|
||||
return
|
||||
var snapped_position: Vector2 = _snap(world_position)
|
||||
if _active_tool == &"wall" and _wall_dragging:
|
||||
if _is_rect_placement_tool() and _wall_dragging:
|
||||
var size: Vector2 = Vector2(absf(snapped_position.x - _wall_drag_start.x), absf(snapped_position.y - _wall_drag_start.y))
|
||||
size.x = maxf(MIN_PANEL_SIZE, size.x)
|
||||
size.y = maxf(MIN_PANEL_SIZE, size.y)
|
||||
(_ghost as StageWallPanel).configure(size, _wall_finish, _wall_tint)
|
||||
(_ghost as StageRectSurface).configure(size, _wall_finish, _wall_tint)
|
||||
var top_left: Vector2 = Vector2(minf(_wall_drag_start.x, snapped_position.x), minf(_wall_drag_start.y, snapped_position.y))
|
||||
_ghost.global_position = top_left + size * 0.5
|
||||
elif _active_tool == &"wall":
|
||||
elif _is_rect_placement_tool():
|
||||
var preview_top_left: Vector2 = _snap(world_position) if _snap_enabled else world_position
|
||||
_ghost.global_position = _move_node_visible_top_left(_ghost, preview_top_left)
|
||||
elif _active_tool == &"wall_eraser":
|
||||
@@ -234,7 +257,7 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
exit_tool()
|
||||
return true
|
||||
var completes_drag: bool = not event.pressed and event.button_index == MOUSE_BUTTON_LEFT and (
|
||||
(_active_tool == &"wall" and _wall_dragging)
|
||||
(_is_rect_placement_tool() and _wall_dragging)
|
||||
or (_active_tool == &"wall_eraser" and _wall_erase_dragging)
|
||||
)
|
||||
if (over_ui and not completes_drag) or event.button_index != MOUSE_BUTTON_LEFT:
|
||||
@@ -243,7 +266,7 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
if event.pressed:
|
||||
_commit_artwork(world_position)
|
||||
return true
|
||||
if _active_tool == &"wall":
|
||||
if _is_rect_placement_tool():
|
||||
if event.pressed:
|
||||
# Refresh from the press coordinates in case no mouse-motion event was
|
||||
# delivered after the palette closed. The visible ghost corner is the
|
||||
@@ -262,12 +285,12 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
if size.x < MIN_PANEL_SIZE or size.y < MIN_PANEL_SIZE:
|
||||
_wall_anchor_active = false
|
||||
_wall_anchor = Vector2.INF
|
||||
_restore_wall_ghost(world_position)
|
||||
_restore_rect_surface_ghost(world_position)
|
||||
return true
|
||||
_commit_wall(_wall_drag_start, end)
|
||||
_commit_rect_surface(_wall_drag_start, end)
|
||||
_wall_anchor = Vector2.INF
|
||||
_wall_anchor_active = false
|
||||
_restore_wall_ghost(world_position)
|
||||
_restore_rect_surface_ghost(world_position)
|
||||
return true
|
||||
if _active_tool == &"wall_eraser":
|
||||
if event.pressed:
|
||||
@@ -304,8 +327,8 @@ func cancel_active_gesture() -> void:
|
||||
_wall_anchor_active = false
|
||||
_wall_erase_dragging = false
|
||||
_wall_erase_start = Vector2.INF
|
||||
if _active_tool == &"wall" and _ghost != null and is_instance_valid(_ghost):
|
||||
(_ghost as StageWallPanel).configure(DEFAULT_PANEL_SIZE, _wall_finish, _wall_tint)
|
||||
if _is_rect_placement_tool() and _ghost != null and is_instance_valid(_ghost):
|
||||
(_ghost as StageRectSurface).configure(_default_size_for_active_surface(), _wall_finish, _wall_tint)
|
||||
elif _active_tool == &"wall_eraser" and _ghost != null and is_instance_valid(_ghost):
|
||||
_set_wall_eraser_preview_rect(Rect2(_wall_erase_pointer, WALL_ERASER_CURSOR_SIZE))
|
||||
|
||||
@@ -334,10 +357,10 @@ func begin_group_transform(kind: StringName, members: Array[Node2D]) -> bool:
|
||||
return false
|
||||
var snapshot: Dictionary = {"node": member, "position": member.global_position, "rotation": member.global_rotation, "scale": member.scale}
|
||||
var bounds: Rect2
|
||||
if member.get_script() == WALL_PANEL:
|
||||
var wall: StageWallPanel = member as StageWallPanel
|
||||
snapshot["panel_size"] = wall.panel_size
|
||||
bounds = member.global_transform * wall.get_local_bounds()
|
||||
if member is StageRectSurface:
|
||||
var surface: StageRectSurface = member as StageRectSurface
|
||||
snapshot["panel_size"] = surface.panel_size
|
||||
bounds = member.global_transform * surface.get_local_bounds()
|
||||
elif member.get_script() == SCENERY_ARTWORK:
|
||||
var artwork: StageSceneryArtwork = member as StageSceneryArtwork
|
||||
bounds = STAGE_SELECTION.get_world_aabb(member)
|
||||
@@ -369,7 +392,7 @@ func preview_group(delta: Vector2, factor: float) -> bool:
|
||||
if member == null or not is_instance_valid(member):
|
||||
return false
|
||||
if _group_kind == &"resize":
|
||||
if member.get_script() == WALL_PANEL:
|
||||
if member is StageRectSurface:
|
||||
var next_size: Vector2 = (snapshot["panel_size"] as Vector2) * factor
|
||||
if next_size.x < MIN_PANEL_SIZE or next_size.y < MIN_PANEL_SIZE:
|
||||
return false
|
||||
@@ -389,9 +412,9 @@ func preview_group(delta: Vector2, factor: float) -> bool:
|
||||
var new_scale: Vector2 = art_scale * factor
|
||||
member.global_position = anchor + ((snapshot["position"] as Vector2) - anchor) * factor
|
||||
member.global_rotation = float(snapshot["rotation"])
|
||||
if member.get_script() == WALL_PANEL:
|
||||
var wall: StageWallPanel = member as StageWallPanel
|
||||
wall.configure((snapshot["panel_size"] as Vector2) * factor, wall.finish_id, wall.tint)
|
||||
if member is StageRectSurface:
|
||||
var surface: StageRectSurface = member as StageRectSurface
|
||||
surface.configure((snapshot["panel_size"] as Vector2) * factor, surface.finish_id, surface.tint)
|
||||
else:
|
||||
member.scale = new_scale
|
||||
return true
|
||||
@@ -410,7 +433,7 @@ func commit_group() -> void:
|
||||
func begin_single_transform(node: Node2D, kind: StringName, pointer: Vector2, dragged_corner: Vector2 = Vector2.INF) -> bool:
|
||||
if _single_active or _group_pending or node == null or not is_instance_valid(node) or kind not in [&"move", &"resize"]:
|
||||
return false
|
||||
if node.get_script() != WALL_PANEL and node.get_script() != SCENERY_ARTWORK:
|
||||
if not (node is StageRectSurface) and node.get_script() != SCENERY_ARTWORK:
|
||||
return false
|
||||
if not pointer.is_finite():
|
||||
return false
|
||||
@@ -434,8 +457,8 @@ func begin_single_transform(node: Node2D, kind: StringName, pointer: Vector2, dr
|
||||
"dragged_corner": corners[corner_index],
|
||||
"pivot": _opposite_corner(bounds, corner_index),
|
||||
}
|
||||
if node.get_script() == WALL_PANEL:
|
||||
snapshot["panel_size"] = (node as StageWallPanel).panel_size
|
||||
if node is StageRectSurface:
|
||||
snapshot["panel_size"] = (node as StageRectSurface).panel_size
|
||||
else:
|
||||
var initial_scale: Vector2 = node.scale
|
||||
if not initial_scale.is_finite() or initial_scale.x <= 0.0 or initial_scale.y <= 0.0:
|
||||
@@ -502,7 +525,7 @@ func cancel_single_transform() -> void:
|
||||
func step_single_size(node: Node2D, factor: float) -> bool:
|
||||
if node == null or not is_instance_valid(node) or not is_finite(factor) or factor <= 0.0 or _single_active or _group_pending:
|
||||
return false
|
||||
if node.get_script() != WALL_PANEL and node.get_script() != SCENERY_ARTWORK:
|
||||
if not (node is StageRectSurface) and node.get_script() != SCENERY_ARTWORK:
|
||||
return false
|
||||
var bounds: Rect2 = _world_bounds(node)
|
||||
if bounds.size.x <= 0.0 or bounds.size.y <= 0.0:
|
||||
@@ -518,7 +541,7 @@ func step_single_size(node: Node2D, factor: float) -> bool:
|
||||
"scale": node.scale,
|
||||
"bounds": bounds,
|
||||
"pivot": bounds.position,
|
||||
"panel_size": (node as StageWallPanel).panel_size if node.get_script() == WALL_PANEL else Vector2.ZERO,
|
||||
"panel_size": (node as StageRectSurface).panel_size if node is StageRectSurface else Vector2.ZERO,
|
||||
}
|
||||
var pivot: Vector2 = bounds.position
|
||||
if not _can_apply_single_scale(snapshot, factor):
|
||||
@@ -540,9 +563,9 @@ func cancel_group() -> void:
|
||||
member.global_position = snapshot["position"] as Vector2
|
||||
member.global_rotation = float(snapshot["rotation"])
|
||||
member.scale = snapshot["scale"] as Vector2
|
||||
if member.get_script() == WALL_PANEL and snapshot.has("panel_size"):
|
||||
var wall: StageWallPanel = member as StageWallPanel
|
||||
wall.configure(snapshot["panel_size"] as Vector2, wall.finish_id, wall.tint)
|
||||
if member is StageRectSurface and snapshot.has("panel_size"):
|
||||
var surface: StageRectSurface = member as StageRectSurface
|
||||
surface.configure(snapshot["panel_size"] as Vector2, surface.finish_id, surface.tint)
|
||||
_group_pending = false
|
||||
_group_snapshots.clear()
|
||||
_group_kind = &""
|
||||
@@ -553,7 +576,7 @@ static func bounds_for(members: Array[Node2D]) -> Rect2:
|
||||
var result: Rect2 = Rect2()
|
||||
var has_rect: bool = false
|
||||
for member: Node2D in members:
|
||||
var rect: Rect2 = STAGE_SELECTION.get_world_aabb(member) if member.get_script() == SCENERY_ARTWORK else (member.global_transform * (member as StageWallPanel).get_local_bounds())
|
||||
var rect: Rect2 = (member as StageRectSurface).global_transform * (member as StageRectSurface).get_local_bounds() if member is StageRectSurface else STAGE_SELECTION.get_world_aabb(member)
|
||||
result = rect if not has_rect else result.merge(rect)
|
||||
has_rect = true
|
||||
return result
|
||||
@@ -572,9 +595,13 @@ func _commit_artwork(position: Vector2) -> void:
|
||||
document_change_requested.emit()
|
||||
|
||||
|
||||
func _commit_wall(first: Vector2, second: Vector2) -> void:
|
||||
func _commit_rect_surface(first: Vector2, second: Vector2) -> void:
|
||||
var size: Vector2 = Vector2(maxf(MIN_PANEL_SIZE, absf(second.x - first.x)), maxf(MIN_PANEL_SIZE, absf(second.y - first.y)))
|
||||
var panel := WALL_PANEL.new() as StageWallPanel
|
||||
var panel: StageRectSurface
|
||||
if _active_tool == &"background_ground":
|
||||
panel = BACKGROUND_GROUND.new() as StageBackgroundGround
|
||||
else:
|
||||
panel = WALL_PANEL.new() as StageWallPanel
|
||||
panel.configure(size, _wall_finish, _wall_tint)
|
||||
panel.global_position = (first + second) * 0.5
|
||||
_world.add_child(panel)
|
||||
@@ -582,13 +609,21 @@ func _commit_wall(first: Vector2, second: Vector2) -> void:
|
||||
document_change_requested.emit()
|
||||
|
||||
|
||||
func _restore_wall_ghost(world_position: Vector2) -> void:
|
||||
if _ghost == null or not is_instance_valid(_ghost) or _ghost.get_script() != WALL_PANEL:
|
||||
func _restore_rect_surface_ghost(world_position: Vector2) -> void:
|
||||
if _ghost == null or not is_instance_valid(_ghost) or not (_ghost is StageRectSurface):
|
||||
return
|
||||
(_ghost as StageWallPanel).configure(DEFAULT_PANEL_SIZE, _wall_finish, _wall_tint)
|
||||
(_ghost as StageRectSurface).configure(_default_size_for_active_surface(), _wall_finish, _wall_tint)
|
||||
update_pointer(world_position)
|
||||
|
||||
|
||||
func _is_rect_placement_tool() -> bool:
|
||||
return _active_tool in [&"wall", &"background_ground"]
|
||||
|
||||
|
||||
func _default_size_for_active_surface() -> Vector2:
|
||||
return DEFAULT_BACKGROUND_SIZE if _active_tool == &"background_ground" else DEFAULT_PANEL_SIZE
|
||||
|
||||
|
||||
func _erase_wall_region(world_rect: Rect2) -> int:
|
||||
if _world == null or world_rect.size.x <= 0.0 or world_rect.size.y <= 0.0:
|
||||
return 0
|
||||
@@ -695,7 +730,7 @@ func _can_apply_single_scale(snapshot: Dictionary, factor: float) -> bool:
|
||||
var next_position: Vector2 = (snapshot["pivot"] as Vector2) + ((snapshot["position"] as Vector2) - (snapshot["pivot"] as Vector2)) * factor
|
||||
if not next_position.is_finite():
|
||||
return false
|
||||
if node.get_script() == WALL_PANEL:
|
||||
if node is StageRectSurface:
|
||||
var next_size: Vector2 = (snapshot["panel_size"] as Vector2) * factor
|
||||
return next_size.is_finite() and next_size.x >= MIN_PANEL_SIZE and next_size.y >= MIN_PANEL_SIZE
|
||||
var start_scale: Vector2 = snapshot["scale"] as Vector2
|
||||
@@ -707,10 +742,10 @@ func _apply_single_scale_to_snapshot(snapshot: Dictionary, factor: float, pivot:
|
||||
var node: Node2D = snapshot["node"] as Node2D
|
||||
node.global_position = pivot + ((snapshot["position"] as Vector2) - pivot) * factor
|
||||
node.global_rotation = float(snapshot["rotation"])
|
||||
if node.get_script() == WALL_PANEL:
|
||||
var wall: StageWallPanel = node as StageWallPanel
|
||||
wall.scale = snapshot["scale"] as Vector2
|
||||
wall.configure((snapshot["panel_size"] as Vector2) * factor, wall.finish_id, wall.tint)
|
||||
if node is StageRectSurface:
|
||||
var surface: StageRectSurface = node as StageRectSurface
|
||||
surface.scale = snapshot["scale"] as Vector2
|
||||
surface.configure((snapshot["panel_size"] as Vector2) * factor, surface.finish_id, surface.tint)
|
||||
else:
|
||||
node.scale = (snapshot["scale"] as Vector2) * factor
|
||||
|
||||
@@ -722,9 +757,9 @@ func _restore_single_snapshot() -> void:
|
||||
node.global_position = _single_snapshot["position"] as Vector2
|
||||
node.global_rotation = float(_single_snapshot["rotation"])
|
||||
node.scale = _single_snapshot["scale"] as Vector2
|
||||
if node.get_script() == WALL_PANEL:
|
||||
var wall: StageWallPanel = node as StageWallPanel
|
||||
wall.configure(_single_snapshot["panel_size"] as Vector2, wall.finish_id, wall.tint)
|
||||
if node is StageRectSurface:
|
||||
var surface: StageRectSurface = node as StageRectSurface
|
||||
surface.configure(_single_snapshot["panel_size"] as Vector2, surface.finish_id, surface.tint)
|
||||
|
||||
|
||||
func _single_transform_changed() -> bool:
|
||||
@@ -739,14 +774,14 @@ func _snapshot_matches_current(snapshot: Dictionary) -> bool:
|
||||
return false
|
||||
if not is_equal_approx(node.global_rotation, float(snapshot["rotation"])) or not node.scale.is_equal_approx(snapshot["scale"] as Vector2):
|
||||
return false
|
||||
if node.get_script() == WALL_PANEL:
|
||||
return (node as StageWallPanel).panel_size.is_equal_approx(snapshot["panel_size"] as Vector2)
|
||||
if node is StageRectSurface:
|
||||
return (node as StageRectSurface).panel_size.is_equal_approx(snapshot["panel_size"] as Vector2)
|
||||
return true
|
||||
|
||||
|
||||
func _world_bounds(node: Node2D) -> Rect2:
|
||||
if node.get_script() == WALL_PANEL:
|
||||
return node.global_transform * (node as StageWallPanel).get_local_bounds()
|
||||
if node is StageRectSurface:
|
||||
return node.global_transform * (node as StageRectSurface).get_local_bounds()
|
||||
if node.get_script() == SCENERY_ARTWORK:
|
||||
return (node as StageSceneryArtwork).get_visual_world_bounds()
|
||||
return STAGE_SELECTION.get_world_aabb(node)
|
||||
@@ -789,7 +824,7 @@ func _snap_visual_center(node: Node2D, desired_center: Vector2) -> Vector2:
|
||||
|
||||
|
||||
func _move_node_visible_top_left(node: Node2D, desired_top_left: Vector2) -> Vector2:
|
||||
if node.get_script() not in [WALL_PANEL, SCENERY_ARTWORK]:
|
||||
if not (node is StageRectSurface) and node.get_script() != SCENERY_ARTWORK:
|
||||
return desired_top_left
|
||||
var bounds: Rect2 = _world_bounds(node)
|
||||
return node.global_position + desired_top_left - bounds.position
|
||||
|
||||
@@ -12,9 +12,11 @@ const HORIZONTAL_LAYOUT_MIN_WIDTH: float = 760.0
|
||||
const ACTION_ROW_MIN_WIDTH: float = 480.0
|
||||
const ACTION_BUTTON_WIDTH: float = 200.0
|
||||
const DEFAULT_BUTTON_HEIGHT: float = 56.0
|
||||
const NARROW_COMPACT_WIDTH: float = 244.0
|
||||
|
||||
var _stage_theme: SandboxTheme
|
||||
var _top_bar_height: float = 64.0
|
||||
var _layout_stash: Node
|
||||
var _horizontal_layout: HBoxContainer
|
||||
var _vertical_layout: VBoxContainer
|
||||
var _wrapped_layout: VBoxContainer
|
||||
@@ -76,6 +78,7 @@ func set_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
var button := Button.new()
|
||||
button.name = "Action_%s" % String(action_id)
|
||||
button.text = String(action.get("label", action_id))
|
||||
button.tooltip_text = button.text
|
||||
button.custom_minimum_size.x = ACTION_BUTTON_WIDTH
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.pressed.connect(func() -> void: capture_action_requested.emit(action_id))
|
||||
@@ -154,15 +157,22 @@ func apply_theme() -> void:
|
||||
|
||||
func _build_controls() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_layout_stash = Node.new()
|
||||
_layout_stash.name = "LayoutStash"
|
||||
add_child(_layout_stash)
|
||||
_horizontal_layout = HBoxContainer.new()
|
||||
_horizontal_layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_horizontal_layout.add_theme_constant_override("separation", 20)
|
||||
_vertical_layout = VBoxContainer.new()
|
||||
_vertical_layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_vertical_layout.add_theme_constant_override("separation", 12)
|
||||
_vertical_layout.visible = false
|
||||
_wrapped_layout = VBoxContainer.new()
|
||||
_wrapped_layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_wrapped_layout.add_theme_constant_override("separation", 10)
|
||||
_wrapped_layout.visible = false
|
||||
_layout_stash.add_child(_vertical_layout)
|
||||
_layout_stash.add_child(_wrapped_layout)
|
||||
_active_layout = _horizontal_layout
|
||||
add_child(_active_layout)
|
||||
_prompt = Label.new()
|
||||
@@ -178,8 +188,11 @@ func _build_controls() -> void:
|
||||
_actions_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
_actions_column = VBoxContainer.new()
|
||||
_actions_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_actions_column.visible = false
|
||||
_layout_stash.add_child(_actions_column)
|
||||
_actions_parent = _actions_row
|
||||
_active_layout.add_child(_actions_parent)
|
||||
_actions_row.visible = true
|
||||
_change_prop_button = Button.new()
|
||||
_change_prop_button.name = "ChangeProp"
|
||||
_change_prop_button.text = "Change prop"
|
||||
@@ -250,8 +263,13 @@ func _layout_capture_bar() -> void:
|
||||
safe_right = clampf(_safe_right, safe_left + 1.0, maxf(safe_left + 1.0, viewport_size.x - inset))
|
||||
var available_width: float = maxf(1.0, safe_right - safe_left)
|
||||
var width: float = minf(MAX_BAR_WIDTH, minf(viewport_size.x * VIEWPORT_WIDTH_RATIO, available_width))
|
||||
var narrow_compact: bool = width < NARROW_COMPACT_WIDTH
|
||||
custom_minimum_size.x = width
|
||||
size.x = width
|
||||
clip_contents = true
|
||||
_prompt.add_theme_font_size_override("font_size", 18 if narrow_compact else clampi(_integer("stage_panels.type.title_size", 24, 18, 36) + 4, 24, 34))
|
||||
_prompt.max_lines_visible = 2 if narrow_compact else 3
|
||||
_set_compact_panel_style(narrow_compact)
|
||||
var left: float = safe_left + (available_width - width) * 0.5 if not _has_horizontal_safe_bounds else safe_left
|
||||
position = Vector2(left, _top_bar_height + 56.0)
|
||||
var many_actions: bool = _custom_action_buttons.size() > 3
|
||||
@@ -259,6 +277,7 @@ func _layout_capture_bar() -> void:
|
||||
var wrapped: bool = (_has_horizontal_safe_bounds and width < HORIZONTAL_LAYOUT_MIN_WIDTH and width >= ACTION_ROW_MIN_WIDTH) or (many_actions and not stack_actions)
|
||||
_set_layout(width < HORIZONTAL_LAYOUT_MIN_WIDTH or wrapped, stack_actions, wrapped)
|
||||
_fit_custom_action_widths(width, wrapped)
|
||||
_apply_compact_action_labels(narrow_compact)
|
||||
call_deferred("_fit_height_to_content")
|
||||
|
||||
|
||||
@@ -273,8 +292,12 @@ func _set_layout(compact: bool, stack_actions: bool, wrapped: bool = false) -> v
|
||||
previous_actions.remove_child(_change_prop_button)
|
||||
previous_actions.remove_child(_cancel_button)
|
||||
_active_layout.remove_child(previous_actions)
|
||||
previous_actions.visible = false
|
||||
_layout_stash.add_child(previous_actions)
|
||||
_actions_parent = target_actions
|
||||
_layout_stash.remove_child(_actions_parent)
|
||||
_active_layout.add_child(_actions_parent)
|
||||
_actions_parent.visible = true
|
||||
_actions_parent.add_child(_change_prop_button)
|
||||
_actions_parent.add_child(_cancel_button)
|
||||
for button: Button in _custom_action_buttons:
|
||||
@@ -282,9 +305,13 @@ func _set_layout(compact: bool, stack_actions: bool, wrapped: bool = false) -> v
|
||||
if _active_layout != target_layout:
|
||||
_active_layout.remove_child(_prompt)
|
||||
_active_layout.remove_child(_actions_parent)
|
||||
_active_layout.visible = false
|
||||
remove_child(_active_layout)
|
||||
_layout_stash.add_child(_active_layout)
|
||||
_active_layout = target_layout
|
||||
_layout_stash.remove_child(_active_layout)
|
||||
add_child(_active_layout)
|
||||
_active_layout.visible = true
|
||||
_active_layout.add_child(_prompt)
|
||||
_active_layout.add_child(_actions_parent)
|
||||
_change_prop_button.custom_minimum_size.x = 0.0 if stack_actions else ACTION_BUTTON_WIDTH
|
||||
@@ -297,18 +324,60 @@ func _fit_custom_action_widths(width: float, wrapped: bool) -> void:
|
||||
var button_width: float = ACTION_BUTTON_WIDTH
|
||||
var body_size: int = clampi(_integer("stage_panels.type.body_size", 20, 16, 32) + 2, 20, 30)
|
||||
var action_font_size: int = maxi(18, body_size - (4 if wrapped else 0))
|
||||
if width < NARROW_COMPACT_WIDTH:
|
||||
button_width = 0.0
|
||||
action_font_size = 16
|
||||
if wrapped and not _custom_action_buttons.is_empty():
|
||||
var gaps: float = float(maxi(0, _custom_action_buttons.size() - 1) * 12)
|
||||
button_width = minf(ACTION_BUTTON_WIDTH, maxf(144.0, (width - 44.0 - gaps) / float(_custom_action_buttons.size())))
|
||||
for button: Button in _custom_action_buttons:
|
||||
button.custom_minimum_size.x = button_width
|
||||
button.add_theme_font_size_override("font_size", action_font_size)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL if width < NARROW_COMPACT_WIDTH else Control.SIZE_FILL
|
||||
|
||||
|
||||
func _apply_compact_action_labels(compact: bool) -> void:
|
||||
var compact_labels: Dictionary = {
|
||||
"select_several": "Multi",
|
||||
"select_all": "All",
|
||||
"clear_selection": "Clear",
|
||||
"done_edit": "Done",
|
||||
"move_group": "Move",
|
||||
"resize_group": "Size",
|
||||
"keep_group": "Keep",
|
||||
"cancel_group": "Cancel",
|
||||
"done_tool": "Done",
|
||||
"cancel_tool": "Cancel",
|
||||
"cancel_single": "Cancel",
|
||||
"depth_far": "Far",
|
||||
"depth_near": "Near",
|
||||
"depth_floor": "Floor",
|
||||
}
|
||||
for button: Button in _custom_action_buttons:
|
||||
var action_id: String = button.name.trim_prefix("Action_")
|
||||
var full_label: String = button.tooltip_text
|
||||
button.text = String(compact_labels.get(action_id, full_label)) if compact else full_label
|
||||
|
||||
|
||||
func _set_compact_panel_style(compact: bool) -> void:
|
||||
var panel_style: StyleBoxFlat = get_theme_stylebox("panel") as StyleBoxFlat
|
||||
if panel_style == null:
|
||||
return
|
||||
panel_style.border_width_left = 4 if compact else 8
|
||||
panel_style.content_margin_left = 8.0 if compact else 20.0
|
||||
panel_style.content_margin_right = 8.0 if compact else 18.0
|
||||
panel_style.content_margin_top = 8.0 if compact else 14.0
|
||||
panel_style.content_margin_bottom = 8.0 if compact else 14.0
|
||||
|
||||
|
||||
func _fit_height_to_content() -> void:
|
||||
if not is_inside_tree() or not visible:
|
||||
return
|
||||
custom_minimum_size.y = get_combined_minimum_size().y
|
||||
var target_height: float = _active_layout.get_combined_minimum_size().y
|
||||
var panel_style: StyleBox = get_theme_stylebox("panel")
|
||||
if panel_style != null:
|
||||
target_height += panel_style.get_minimum_size().y
|
||||
custom_minimum_size.y = target_height
|
||||
size.y = custom_minimum_size.y
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user