Add tests for scenery surface caching and preview behavior
- Introduced `test_scenery_brick_preview.gd` to validate focused placement and CPU draw-cost for large brick walls. - Added `test_scenery_surface_cache.gd` to assess texture budget, visual equivalence, and cache invalidation. - Created `test_scenery_surface_preview.gd` to evaluate adaptive preview selection and placement behavior. - Implemented various helper functions to measure drawing performance and validate expected behaviors during tests. - Added unique identifiers for new test files to ensure proper tracking and management.
This commit is contained in:
@@ -62,6 +62,8 @@ const STAGE_PANEL_CHOICE_GROUP_SCRIPT := preload("res://scripts/shared/stage_ui/
|
||||
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 STAGE_DEPTH_CHOICE_PREVIEW_SCRIPT := preload("res://scripts/shared/stage_ui/stage_depth_choice_preview.gd")
|
||||
const PROP_FILE_SCRIPT := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const PROP_EDITOR_SCENE := preload("res://scenes/editors/prop/prop_editor.tscn")
|
||||
|
||||
# Phase 3c editor tools.
|
||||
@@ -287,10 +289,20 @@ var _scenery_depth_section: VBoxContainer
|
||||
var _scenery_depth_heading: Label
|
||||
var _scenery_depth_group: StagePanelChoiceGroup
|
||||
var _scenery_depth_helper: Label
|
||||
var _scenery_depth_choice_cues: Array[Control] = []
|
||||
var _scenery_depth_preview_texture: Texture2D
|
||||
var _scenery_depth_preview_path: String = ""
|
||||
var _scenery_depth_preview_busy: bool = false
|
||||
var _scenery_depth_preview_generation: int = 0
|
||||
var _scenery_depth_preview_scheduled_generation: int = -1
|
||||
var _scenery_depth_thumbnail_renderer: PropThumbnail
|
||||
var _scenery_depth_cursor_badge: PanelContainer
|
||||
var _scenery_depth_cursor_badge_label: Label
|
||||
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_background_ground_area_button: Button
|
||||
var _scenery_erase_piece_button: Button
|
||||
var _scenery_find_button: Button
|
||||
var _scenery_choose_button: Button
|
||||
@@ -300,7 +312,13 @@ 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_dialog: Control
|
||||
var _scenery_erase_piece_dialog_panel: PanelContainer
|
||||
var _scenery_erase_piece_dialog_title: Label
|
||||
var _scenery_erase_piece_dialog_message: Label
|
||||
var _scenery_erase_piece_dialog_actions: BoxContainer
|
||||
var _scenery_erase_piece_keep_button: Button
|
||||
var _scenery_erase_piece_remove_button: Button
|
||||
var _scenery_erase_piece_targets: Array[WeakRef] = []
|
||||
var _surface_color_dialog: StageSurfaceColorDialog
|
||||
var _surface_color_session: Dictionary = {}
|
||||
@@ -1376,6 +1394,7 @@ func _process(delta: float) -> void:
|
||||
_update_action_overlay()
|
||||
_sync_awaiting_cursor()
|
||||
_update_speech_badge_cursor()
|
||||
_update_scenery_depth_cursor_badge()
|
||||
if _toast_timer > 0.0:
|
||||
_toast_timer -= delta
|
||||
if _toast_timer <= 0.0:
|
||||
@@ -1521,6 +1540,11 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||
return
|
||||
if not (event is InputEventKey) or not event.pressed or event.echo:
|
||||
return
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible:
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
_dismiss_scenery_erase_piece_dialog()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _speech_composer_is_open():
|
||||
if event.keycode == KEY_ESCAPE:
|
||||
if bool(_speech_composer.call("is_message_dialog_open")):
|
||||
@@ -1692,9 +1716,7 @@ func _handle_world_click(mb: InputEventMouseButton) -> void:
|
||||
_refresh_scenery_inspector()
|
||||
_refresh_scenery_capture_bar()
|
||||
elif _scenery_controller != null and _scenery_controller.is_group_preview_pending():
|
||||
_scenery_controller.cancel_group()
|
||||
_scenery_group_mode = &""
|
||||
_refresh_scenery_capture_bar()
|
||||
_keep_scenery_group_preview()
|
||||
else:
|
||||
_exit_scenery_edit_mode()
|
||||
get_viewport().set_input_as_handled()
|
||||
@@ -2557,6 +2579,8 @@ func _save_scene_to_path(path: String, is_autosave: bool = false) -> bool:
|
||||
_pending_file_action = PendingFileAction.NONE
|
||||
return false
|
||||
if not is_autosave:
|
||||
if _scenery_controller != null:
|
||||
_scenery_controller.commit_placement_session()
|
||||
_paused_edit_autosave_remaining = -1.0
|
||||
_set_scene_dirty(false)
|
||||
_save_settings()
|
||||
@@ -3211,15 +3235,21 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_depth_section.add_child(_scenery_depth_heading)
|
||||
_scenery_depth_group = STAGE_PANEL_CHOICE_GROUP_SCRIPT.new() as StagePanelChoiceGroup
|
||||
_scenery_depth_group.name = "SceneryDepthChoices"
|
||||
_scenery_depth_group.add_choice(&"far", "Far")
|
||||
_scenery_depth_group.add_choice(&"near", "Near")
|
||||
_scenery_depth_group.add_choice(&"floor", "On the floor")
|
||||
_scenery_depth_group.configure_layout(true, 118.0, 24, 16, 1, Color("#087DBA"))
|
||||
_scenery_depth_choice_cues.clear()
|
||||
var back_cue: Control = _make_scenery_depth_choice_cue(&"far", Vector2(128.0, 82.0))
|
||||
var middle_cue: Control = _make_scenery_depth_choice_cue(&"near", Vector2(128.0, 82.0))
|
||||
var front_cue: Control = _make_scenery_depth_choice_cue(&"floor", Vector2(128.0, 82.0))
|
||||
_scenery_depth_choice_cues = [back_cue, middle_cue, front_cue]
|
||||
_scenery_depth_group.add_choice(&"far", "Back", back_cue, "Behind other scenery")
|
||||
_scenery_depth_group.add_choice(&"near", "Middle", middle_cue, "In front of Back · behind tiles")
|
||||
_scenery_depth_group.add_choice(&"floor", "Front", front_cue, "In front of ground tiles")
|
||||
_scenery_depth_group.apply_theme(_sandbox_theme as SandboxTheme)
|
||||
_scenery_depth_group.choice_selected.connect(_on_selected_scenery_depth_choice)
|
||||
_scenery_depth_section.add_child(_scenery_depth_group)
|
||||
_scenery_depth_helper = Label.new()
|
||||
_scenery_depth_helper.name = "SceneryDepthHelper"
|
||||
_scenery_depth_helper.text = "Far is behind Near. On the floor is in front of the ground."
|
||||
_scenery_depth_helper.text = "The pictures show what can cover the artwork."
|
||||
_scenery_depth_helper.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_scenery_depth_helper.add_theme_font_size_override("font_size", _stage_panel_integer("type.helper_size", 16, 12, 32))
|
||||
_scenery_depth_helper.add_theme_color_override("font_color", _stage_panel_color("type.muted_text_color", Color("#AAB2C0")))
|
||||
@@ -3237,9 +3267,14 @@ func _build_scenery_inspector(ui: CanvasLayer) -> void:
|
||||
_scenery_size_row.add_child(_scenery_bigger_button)
|
||||
body.add_child(_scenery_size_row)
|
||||
_scenery_erase_wall_area_button = _make_scenery_repair_button("EraseWallArea", "Erase wall area", true)
|
||||
_scenery_inspector.style_primary_action_button(_scenery_erase_wall_area_button)
|
||||
_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_background_ground_area_button = _make_scenery_repair_button("EraseBackgroundGroundArea", "Erase background ground area", true)
|
||||
_scenery_inspector.style_primary_action_button(_scenery_erase_background_ground_area_button)
|
||||
_scenery_erase_background_ground_area_button.pressed.connect(_begin_selected_background_ground_erase)
|
||||
body.add_child(_scenery_erase_background_ground_area_button)
|
||||
_scenery_erase_piece_button = _make_scenery_repair_button("EraseSceneryPiece", "Remove wall panel", false, 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)
|
||||
@@ -3272,16 +3307,7 @@ 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)
|
||||
_build_scenery_erase_piece_dialog(ui)
|
||||
_surface_color_dialog = STAGE_SURFACE_COLOR_DIALOG_SCRIPT.new() as StageSurfaceColorDialog
|
||||
_surface_color_dialog.name = "ScenerySurfaceColorDialog"
|
||||
ui.add_child(_surface_color_dialog)
|
||||
@@ -3301,10 +3327,314 @@ func _make_scenery_section_heading(label_text: String) -> Label:
|
||||
return heading
|
||||
|
||||
|
||||
func _make_scenery_depth_choice_cue(depth: StringName, cue_size: Vector2) -> Control:
|
||||
var cue: StageDepthChoicePreview = STAGE_DEPTH_CHOICE_PREVIEW_SCRIPT.new() as StageDepthChoicePreview
|
||||
cue.configure(depth, _scenery_depth_preview_texture, cue_size)
|
||||
return cue
|
||||
|
||||
|
||||
func _make_scenery_depth_capture_action(action_id: StringName, depth: StringName, label: String, selected_depth: StringName) -> Dictionary:
|
||||
return {
|
||||
"id": action_id,
|
||||
"label": label,
|
||||
"choice": true,
|
||||
"selected": depth == selected_depth,
|
||||
"cue": _make_scenery_depth_choice_cue(depth, Vector2(38.0, 34.0)),
|
||||
}
|
||||
|
||||
|
||||
func _set_scenery_depth_preview_path(path: String) -> void:
|
||||
var canonical_path: String = CONTENT_PATHS.canonicalize(path) if not path.is_empty() else ""
|
||||
if canonical_path == _scenery_depth_preview_path and (_scenery_depth_preview_texture != null or _scenery_depth_preview_busy or _scenery_depth_preview_scheduled_generation == _scenery_depth_preview_generation):
|
||||
return
|
||||
if canonical_path != _scenery_depth_preview_path:
|
||||
_scenery_depth_preview_path = canonical_path
|
||||
_scenery_depth_preview_texture = null
|
||||
_scenery_depth_preview_generation += 1
|
||||
_update_scenery_depth_choice_cues()
|
||||
if canonical_path.is_empty() or not FileAccess.file_exists(canonical_path):
|
||||
return
|
||||
if not _scenery_depth_preview_busy:
|
||||
_scenery_depth_preview_scheduled_generation = _scenery_depth_preview_generation
|
||||
call_deferred("_render_scenery_depth_preview", canonical_path, _scenery_depth_preview_generation)
|
||||
|
||||
|
||||
func _render_scenery_depth_preview(path: String, generation: int) -> void:
|
||||
if _scenery_depth_preview_busy or path.is_empty():
|
||||
return
|
||||
_scenery_depth_preview_scheduled_generation = -1
|
||||
_scenery_depth_preview_busy = true
|
||||
if _scenery_depth_thumbnail_renderer == null:
|
||||
_scenery_depth_thumbnail_renderer = PROP_THUMBNAIL.new() as PropThumbnail
|
||||
_scenery_depth_thumbnail_renderer.name = "SceneryDepthThumbnailRenderer"
|
||||
add_child(_scenery_depth_thumbnail_renderer)
|
||||
await get_tree().process_frame
|
||||
var parsed: Dictionary = PROP_FILE_SCRIPT.from_file_dict(PROP_FILE_SCRIPT.load(path))
|
||||
var shapes: Array[Dictionary] = parsed.get("shapes", []) as Array[Dictionary]
|
||||
var texture: Texture2D
|
||||
if not shapes.is_empty() and _scenery_depth_thumbnail_renderer != null:
|
||||
texture = await _scenery_depth_thumbnail_renderer.render_shapes(shapes)
|
||||
if generation == _scenery_depth_preview_generation and path == _scenery_depth_preview_path and texture != null:
|
||||
_scenery_depth_preview_texture = texture
|
||||
_update_scenery_depth_choice_cues()
|
||||
if _scenery_controller != null and _scenery_controller.get_active_tool() == &"artwork":
|
||||
_refresh_scenery_capture_bar()
|
||||
_scenery_depth_preview_busy = false
|
||||
if not _scenery_depth_preview_path.is_empty() and _scenery_depth_preview_path != path:
|
||||
_scenery_depth_preview_scheduled_generation = _scenery_depth_preview_generation
|
||||
call_deferred("_render_scenery_depth_preview", _scenery_depth_preview_path, _scenery_depth_preview_generation)
|
||||
|
||||
|
||||
func _update_scenery_depth_choice_cues() -> void:
|
||||
for cue_control: Control in _scenery_depth_choice_cues:
|
||||
if cue_control is StageDepthChoicePreview:
|
||||
(cue_control as StageDepthChoicePreview).set_artwork_texture(_scenery_depth_preview_texture)
|
||||
|
||||
|
||||
func _build_scenery_erase_piece_dialog(ui: CanvasLayer) -> void:
|
||||
_scenery_erase_piece_dialog = Control.new()
|
||||
_scenery_erase_piece_dialog.name = "EraseSceneryPieceDialog"
|
||||
_scenery_erase_piece_dialog.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
_scenery_erase_piece_dialog.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_scenery_erase_piece_dialog.z_index = 100
|
||||
_scenery_erase_piece_dialog.visible = false
|
||||
ui.add_child(_scenery_erase_piece_dialog)
|
||||
|
||||
var backdrop: ColorRect = ColorRect.new()
|
||||
backdrop.name = "DialogBackdrop"
|
||||
backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
backdrop.color = _confirmation_dialog_color("backdrop_color", Color("#10151D66"))
|
||||
backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_scenery_erase_piece_dialog.add_child(backdrop)
|
||||
|
||||
var center: CenterContainer = CenterContainer.new()
|
||||
center.name = "DialogCenter"
|
||||
center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_scenery_erase_piece_dialog.add_child(center)
|
||||
|
||||
_scenery_erase_piece_dialog_panel = PanelContainer.new()
|
||||
_scenery_erase_piece_dialog_panel.name = "DialogCard"
|
||||
center.add_child(_scenery_erase_piece_dialog_panel)
|
||||
|
||||
var margin: MarginContainer = MarginContainer.new()
|
||||
margin.name = "DialogMargin"
|
||||
var padding: int = _confirmation_dialog_integer("padding", 18, 12, 32)
|
||||
margin.add_theme_constant_override("margin_left", padding)
|
||||
margin.add_theme_constant_override("margin_right", padding)
|
||||
margin.add_theme_constant_override("margin_top", padding)
|
||||
margin.add_theme_constant_override("margin_bottom", padding)
|
||||
_scenery_erase_piece_dialog_panel.add_child(margin)
|
||||
|
||||
var layout: VBoxContainer = VBoxContainer.new()
|
||||
layout.name = "DialogLayout"
|
||||
layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
layout.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
layout.add_theme_constant_override("separation", _confirmation_dialog_integer("body_gap", 10, 4, 24))
|
||||
margin.add_child(layout)
|
||||
|
||||
_scenery_erase_piece_dialog_title = Label.new()
|
||||
_scenery_erase_piece_dialog_title.name = "DialogTitle"
|
||||
_scenery_erase_piece_dialog_title.text = "Remove this scenery?"
|
||||
_scenery_erase_piece_dialog_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_erase_piece_dialog_title.add_theme_font_size_override("font_size", _confirmation_dialog_integer("title_font_size", 26, 20, 40))
|
||||
_scenery_erase_piece_dialog_title.add_theme_color_override("font_color", _stage_panel_color("type.text_color", Color("#F5F7FA")))
|
||||
_apply_ui_font(_scenery_erase_piece_dialog_title)
|
||||
var title_font: Font = _ui_font
|
||||
if title_font != null:
|
||||
var strong_title_font: FontVariation = FontVariation.new()
|
||||
strong_title_font.base_font = title_font
|
||||
strong_title_font.variation_embolden = 0.55
|
||||
_scenery_erase_piece_dialog_title.add_theme_font_override("font", strong_title_font)
|
||||
layout.add_child(_scenery_erase_piece_dialog_title)
|
||||
|
||||
_scenery_erase_piece_dialog_message = Label.new()
|
||||
_scenery_erase_piece_dialog_message.name = "DialogMessage"
|
||||
_scenery_erase_piece_dialog_message.text = "Remove this whole piece? You can’t undo this."
|
||||
_scenery_erase_piece_dialog_message.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_scenery_erase_piece_dialog_message.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_erase_piece_dialog_message.add_theme_font_size_override("font_size", _confirmation_dialog_integer("message_font_size", 20, 16, 32))
|
||||
_scenery_erase_piece_dialog_message.add_theme_color_override("font_color", _stage_panel_color("type.text_color", Color("#F5F7FA")))
|
||||
_apply_ui_font(_scenery_erase_piece_dialog_message)
|
||||
layout.add_child(_scenery_erase_piece_dialog_message)
|
||||
var actions_spacer: Control = Control.new()
|
||||
actions_spacer.name = "ActionsSpacer"
|
||||
actions_spacer.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
layout.add_child(actions_spacer)
|
||||
|
||||
_scenery_erase_piece_dialog_actions = BoxContainer.new()
|
||||
_scenery_erase_piece_dialog_actions.name = "DialogActions"
|
||||
_scenery_erase_piece_dialog_actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
_scenery_erase_piece_dialog_actions.add_theme_constant_override("separation", _confirmation_dialog_integer("footer_gap", 12, 8, 24))
|
||||
layout.add_child(_scenery_erase_piece_dialog_actions)
|
||||
|
||||
_scenery_erase_piece_keep_button = Button.new()
|
||||
_scenery_erase_piece_keep_button.name = "KeepSceneryButton"
|
||||
_scenery_erase_piece_keep_button.text = "Keep it"
|
||||
_scenery_erase_piece_keep_button.focus_mode = Control.FOCUS_ALL
|
||||
_scenery_erase_piece_keep_button.pressed.connect(_dismiss_scenery_erase_piece_dialog)
|
||||
_scenery_erase_piece_dialog_actions.add_child(_scenery_erase_piece_keep_button)
|
||||
|
||||
_scenery_erase_piece_remove_button = Button.new()
|
||||
_scenery_erase_piece_remove_button.name = "RemoveSceneryButton"
|
||||
_scenery_erase_piece_remove_button.text = "Remove scenery"
|
||||
_scenery_erase_piece_remove_button.focus_mode = Control.FOCUS_ALL
|
||||
_scenery_erase_piece_remove_button.pressed.connect(_confirm_erase_scenery_from_dialog)
|
||||
_scenery_erase_piece_dialog_actions.add_child(_scenery_erase_piece_remove_button)
|
||||
_apply_scenery_erase_piece_dialog_style()
|
||||
if not get_tree().root.size_changed.is_connected(_refresh_scenery_erase_piece_dialog_size):
|
||||
get_tree().root.size_changed.connect(_refresh_scenery_erase_piece_dialog_size)
|
||||
|
||||
|
||||
func _apply_scenery_erase_piece_dialog_style() -> void:
|
||||
if _scenery_erase_piece_dialog == null:
|
||||
return
|
||||
var shell: StyleBoxFlat = StyleBoxFlat.new()
|
||||
shell.bg_color = _confirmation_dialog_color("surface_color", Color("#202633FA"))
|
||||
shell.border_color = _confirmation_dialog_color("border_color", Color("#AAB2C066"))
|
||||
shell.set_border_width_all(_confirmation_dialog_integer("border_width", 1, 1, 4))
|
||||
shell.set_corner_radius_all(_confirmation_dialog_integer("corner_radius", 16, 10, 28))
|
||||
_scenery_erase_piece_dialog_panel.add_theme_stylebox_override("panel", shell)
|
||||
_apply_scenery_erase_piece_action_style(_scenery_erase_piece_keep_button, true)
|
||||
_apply_scenery_erase_piece_action_style(_scenery_erase_piece_remove_button, false)
|
||||
|
||||
|
||||
func _apply_scenery_erase_piece_action_style(button: Button, primary: bool) -> void:
|
||||
if button == null:
|
||||
return
|
||||
var accent: Color = _confirmation_dialog_color("accent_color", Color("#22C6FF"))
|
||||
var blue: Color = _confirmation_dialog_color("action_color", Color("#087DBA"))
|
||||
var danger: Color = _confirmation_dialog_color("danger_color", Color("#FF626B"))
|
||||
var neutral: Color = _stage_panel_color("action.surface_color", Color("#171B24"))
|
||||
var hover_neutral: Color = _confirmation_dialog_color("danger_hover_surface", Color("#3B252C"))
|
||||
var pressed_neutral: Color = _confirmation_dialog_color("danger_pressed_surface", Color("#4A252D"))
|
||||
var border_width: int = _confirmation_dialog_integer("button_border_width", 2, 1, 5)
|
||||
var radius: int = _confirmation_dialog_integer("button_radius", 12, 8, 24)
|
||||
var normal_color: Color = blue if primary else neutral
|
||||
var hover_color: Color = blue.lightened(0.12) if primary else hover_neutral
|
||||
var pressed_color: Color = blue.darkened(0.12) if primary else pressed_neutral
|
||||
var edge_color: Color = accent if primary else danger
|
||||
button.add_theme_stylebox_override("normal", _make_scenery_erase_button_style(normal_color, edge_color, border_width, radius))
|
||||
button.add_theme_stylebox_override("hover", _make_scenery_erase_button_style(hover_color, edge_color.lightened(0.12), border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("pressed", _make_scenery_erase_button_style(pressed_color, edge_color, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("hover_pressed", _make_scenery_erase_button_style(pressed_color.lightened(0.05), edge_color, border_width + 1, radius))
|
||||
button.add_theme_stylebox_override("focus", _make_scenery_erase_button_style(normal_color, accent, border_width + 1, radius))
|
||||
var label_color: Color = Color.WHITE if primary else danger
|
||||
button.add_theme_color_override("font_color", label_color)
|
||||
button.add_theme_color_override("font_hover_color", label_color)
|
||||
button.add_theme_color_override("font_pressed_color", label_color)
|
||||
button.add_theme_font_size_override("font_size", _confirmation_dialog_integer("button_font_size", 20, 16, 32))
|
||||
button.custom_minimum_size = Vector2(
|
||||
float(_confirmation_dialog_integer("remove_button_width", 170, 128, 240)) if not primary else float(_confirmation_dialog_integer("keep_button_width", 132, 112, 220)),
|
||||
float(_confirmation_dialog_integer("button_height", 48, 44, 80))
|
||||
)
|
||||
var font: Font = _ui_font
|
||||
if primary and font != null:
|
||||
var strong_font: FontVariation = FontVariation.new()
|
||||
strong_font.base_font = font
|
||||
strong_font.variation_embolden = 0.55
|
||||
button.add_theme_font_override("font", strong_font)
|
||||
else:
|
||||
_apply_ui_font(button)
|
||||
|
||||
|
||||
func _make_scenery_erase_button_style(background: Color, border: Color, width: int, radius: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(width)
|
||||
style.set_corner_radius_all(radius)
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 8.0
|
||||
style.content_margin_bottom = 8.0
|
||||
return style
|
||||
|
||||
|
||||
func _confirmation_dialog_integer(token: String, fallback: int, minimum: int, maximum: int) -> int:
|
||||
return fallback if _sandbox_theme == null else _sandbox_theme.integer("popups.confirmation." + token, fallback, minimum, maximum)
|
||||
|
||||
|
||||
func _confirmation_dialog_color(token: String, fallback: Color) -> Color:
|
||||
return fallback if _sandbox_theme == null else _sandbox_theme.color("popups.confirmation." + token, fallback)
|
||||
|
||||
|
||||
func _open_scenery_erase_piece_dialog() -> void:
|
||||
if _scenery_erase_piece_dialog == null:
|
||||
return
|
||||
var popup_size: Vector2i = _scenery_erase_piece_dialog_size()
|
||||
_apply_scenery_erase_piece_dialog_layout(popup_size.x)
|
||||
if _scenery_erase_piece_dialog_panel != null:
|
||||
_scenery_erase_piece_dialog_panel.custom_minimum_size = Vector2(popup_size)
|
||||
_scenery_erase_piece_dialog.visible = true
|
||||
call_deferred("_focus_scenery_erase_piece_keep_button")
|
||||
|
||||
|
||||
func _scenery_erase_piece_dialog_size() -> Vector2i:
|
||||
var preferred: Vector2i = Vector2i(520, 208)
|
||||
var raw_size: Variant = _sandbox_theme.value("popups.confirmation.scenery_erase_size", [520, 208]) if _sandbox_theme != null else [520, 208]
|
||||
if raw_size is Array and (raw_size as Array).size() == 2:
|
||||
preferred = Vector2i(int((raw_size as Array)[0]), int((raw_size as Array)[1]))
|
||||
var available: Vector2i = Vector2i(get_tree().root.size)
|
||||
var usable_rect: Rect2i = DisplayServer.screen_get_usable_rect(DisplayServer.window_get_current_screen())
|
||||
if usable_rect.size.x > 0 and usable_rect.size.y > 0:
|
||||
available.x = mini(available.x, usable_rect.size.x)
|
||||
available.y = mini(available.y, usable_rect.size.y)
|
||||
available = Vector2i(maxi(1, available.x - 32), maxi(1, available.y - 32))
|
||||
var width: int = mini(preferred.x, available.x)
|
||||
var height: int = mini(preferred.y, available.y)
|
||||
if width < 440:
|
||||
height = mini(available.y, height + 56)
|
||||
return Vector2i(width, height)
|
||||
|
||||
|
||||
func _apply_scenery_erase_piece_dialog_layout(dialog_width: int) -> void:
|
||||
if _scenery_erase_piece_dialog_actions == null or _scenery_erase_piece_keep_button == null or _scenery_erase_piece_remove_button == null:
|
||||
return
|
||||
var stacked: bool = dialog_width < 440
|
||||
_scenery_erase_piece_dialog_actions.vertical = stacked
|
||||
_scenery_erase_piece_dialog_actions.alignment = BoxContainer.ALIGNMENT_END
|
||||
_scenery_erase_piece_dialog_actions.add_theme_constant_override("separation", 8 if stacked else _confirmation_dialog_integer("footer_gap", 12, 8, 24))
|
||||
if stacked:
|
||||
var button_width: float = maxf(1.0, float(dialog_width - _confirmation_dialog_integer("padding", 18, 12, 32) * 2))
|
||||
var button_height: float = float(_confirmation_dialog_integer("button_height", 48, 44, 80))
|
||||
_scenery_erase_piece_remove_button.custom_minimum_size = Vector2(button_width, button_height)
|
||||
_scenery_erase_piece_keep_button.custom_minimum_size = Vector2(button_width, button_height)
|
||||
else:
|
||||
_scenery_erase_piece_remove_button.custom_minimum_size.x = float(_confirmation_dialog_integer("remove_button_width", 170, 128, 240))
|
||||
_scenery_erase_piece_keep_button.custom_minimum_size.x = float(_confirmation_dialog_integer("keep_button_width", 132, 112, 220))
|
||||
|
||||
|
||||
func _refresh_scenery_erase_piece_dialog_size() -> void:
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible:
|
||||
_open_scenery_erase_piece_dialog()
|
||||
|
||||
|
||||
func _focus_scenery_erase_piece_keep_button() -> void:
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible and _scenery_erase_piece_keep_button != null:
|
||||
_scenery_erase_piece_keep_button.grab_focus()
|
||||
|
||||
|
||||
func _dismiss_scenery_erase_piece_dialog() -> void:
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
_clear_erase_scenery_targets()
|
||||
|
||||
|
||||
func _confirm_erase_scenery_from_dialog() -> void:
|
||||
_confirm_erase_selected_scenery()
|
||||
if _scenery_erase_piece_dialog != null and _scenery_erase_piece_dialog.visible:
|
||||
_scenery_erase_piece_dialog.hide()
|
||||
|
||||
|
||||
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.set_rounded_frame(
|
||||
float(_sandbox_theme.integer("popups.asset_selector.look_corner_radius", 8, 2, 20)) if _sandbox_theme != null else 8.0,
|
||||
_sandbox_theme.color("popups.style_defaults.bg_color", Color("#202633FA")) if _sandbox_theme != null else Color("#202633FA")
|
||||
)
|
||||
cue.configure(kind, finish, STAGE_SURFACE_RENDERER_SCRIPT.default_tint(finish))
|
||||
return cue
|
||||
|
||||
@@ -3319,14 +3649,17 @@ func _surface_color_presets() -> Array[Color]:
|
||||
return colors
|
||||
|
||||
|
||||
func _make_scenery_repair_button(node_name: String, label: String, prominent: bool) -> Button:
|
||||
func _make_scenery_repair_button(node_name: String, label: String, prominent: bool, destructive: bool = false) -> Button:
|
||||
var button: Button = Button.new()
|
||||
button.name = node_name
|
||||
button.text = label
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
button.custom_minimum_size.y = float(_stage_panel_integer("action.min_target_size", 48, 44, 96))
|
||||
_scenery_inspector.style_action_button(button, prominent)
|
||||
if destructive:
|
||||
_scenery_inspector.style_destructive_button(button)
|
||||
else:
|
||||
_scenery_inspector.style_action_button(button, prominent)
|
||||
return button
|
||||
|
||||
|
||||
@@ -3365,6 +3698,65 @@ func _build_use_target_capture_bar(ui: CanvasLayer) -> void:
|
||||
_use_target_capture_bar.change_prop_requested.connect(_choose_different_use_prop)
|
||||
_use_target_capture_bar.capture_action_requested.connect(_on_capture_action_requested)
|
||||
ui.add_child(_use_target_capture_bar)
|
||||
_build_scenery_depth_cursor_badge(ui)
|
||||
|
||||
|
||||
func _build_scenery_depth_cursor_badge(ui: CanvasLayer) -> void:
|
||||
_scenery_depth_cursor_badge = PanelContainer.new()
|
||||
_scenery_depth_cursor_badge.name = "SceneryDepthCursorBadge"
|
||||
_scenery_depth_cursor_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_scenery_depth_cursor_badge.z_index = 2
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = _stage_panel_color("choice.selected_surface", Color("#12394A"))
|
||||
style.border_color = _sandbox_theme.color("mode_colors.edit", Color("#22C6FF")) if _sandbox_theme != null else Color("#22C6FF")
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(10)
|
||||
style.content_margin_left = 9.0
|
||||
style.content_margin_right = 9.0
|
||||
style.content_margin_top = 3.0
|
||||
style.content_margin_bottom = 3.0
|
||||
_scenery_depth_cursor_badge.add_theme_stylebox_override("panel", style)
|
||||
_scenery_depth_cursor_badge_label = Label.new()
|
||||
_scenery_depth_cursor_badge_label.name = "DepthLabel"
|
||||
_scenery_depth_cursor_badge_label.text = "MIDDLE"
|
||||
_scenery_depth_cursor_badge_label.add_theme_font_size_override("font_size", 13)
|
||||
_scenery_depth_cursor_badge_label.add_theme_color_override("font_color", Color("#F5F7FA"))
|
||||
_apply_ui_font(_scenery_depth_cursor_badge_label)
|
||||
_scenery_depth_cursor_badge.add_child(_scenery_depth_cursor_badge_label)
|
||||
_scenery_depth_cursor_badge.visible = false
|
||||
ui.add_child(_scenery_depth_cursor_badge)
|
||||
|
||||
|
||||
func _update_scenery_depth_cursor_badge() -> void:
|
||||
if _scenery_depth_cursor_badge == null or _scenery_controller == null:
|
||||
return
|
||||
if current_mode != StageMode.EDIT or _scenery_controller.get_active_tool() != &"artwork":
|
||||
_scenery_depth_cursor_badge.visible = false
|
||||
return
|
||||
var mouse_position: Vector2 = get_viewport().get_mouse_position()
|
||||
if _use_target_capture_bar != null and _use_target_capture_bar.visible and _use_target_capture_bar.get_global_rect().has_point(mouse_position):
|
||||
_scenery_depth_cursor_badge.visible = false
|
||||
return
|
||||
var ghost_bounds: Rect2 = _scenery_controller.get_active_artwork_preview_bounds()
|
||||
if ghost_bounds.size.x <= 0.0 or ghost_bounds.size.y <= 0.0:
|
||||
_scenery_depth_cursor_badge.visible = false
|
||||
return
|
||||
var depth: StringName = _scenery_controller.get_artwork_depth()
|
||||
var depth_label: String = "Back" if depth == &"far" else "Front" if depth == &"floor" else "Middle"
|
||||
_scenery_depth_cursor_badge_label.text = depth_label.to_upper()
|
||||
var badge_size: Vector2 = _scenery_depth_cursor_badge.get_combined_minimum_size()
|
||||
_scenery_depth_cursor_badge.size = badge_size
|
||||
var viewport_size: Vector2 = get_viewport_rect().size
|
||||
var screen_top_left: Vector2 = _world_to_screen(ghost_bounds.position)
|
||||
var screen_bottom_right: Vector2 = _world_to_screen(ghost_bounds.end)
|
||||
var ghost_screen_rect: Rect2 = Rect2(screen_top_left, screen_bottom_right - screen_top_left).abs()
|
||||
var badge_position := Vector2(ghost_screen_rect.get_center().x - badge_size.x * 0.5, ghost_screen_rect.position.y - badge_size.y - 8.0)
|
||||
if badge_position.y < 8.0:
|
||||
badge_position.y = ghost_screen_rect.position.y + 8.0
|
||||
badge_position.x = clampf(badge_position.x, 8.0, maxf(8.0, viewport_size.x - badge_size.x - 8.0))
|
||||
badge_position.y = clampf(badge_position.y, 8.0, maxf(8.0, viewport_size.y - badge_size.y - 8.0))
|
||||
_scenery_depth_cursor_badge.position = badge_position
|
||||
_scenery_depth_cursor_badge.visible = true
|
||||
|
||||
|
||||
func _build_ground_finish_popup(ui: CanvasLayer) -> void:
|
||||
@@ -4527,7 +4919,7 @@ func _refresh_scenery_inspector() -> void:
|
||||
_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_helper.visible = is_wall
|
||||
_scenery_surface_colors.visible = false
|
||||
if surface != null:
|
||||
if is_wall:
|
||||
@@ -4535,7 +4927,6 @@ func _refresh_scenery_inspector() -> void:
|
||||
_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:
|
||||
@@ -4543,16 +4934,18 @@ func _refresh_scenery_inspector() -> void:
|
||||
_scenery_surface_colors.set_recent_colors(_recent_surface_colors)
|
||||
_scenery_depth_section.visible = is_artwork
|
||||
if artwork != null:
|
||||
_set_scenery_depth_preview_path(artwork.asset_path)
|
||||
_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 = is_wall
|
||||
_scenery_erase_background_ground_area_button.visible = is_background_ground
|
||||
_scenery_erase_piece_button.visible = surface != null
|
||||
if is_background_ground:
|
||||
_scenery_erase_piece_button.text = "Erase background ground"
|
||||
_scenery_erase_piece_button.text = "Remove background ground"
|
||||
else:
|
||||
_scenery_erase_piece_button.text = "Erase wall panel"
|
||||
_scenery_erase_piece_button.text = "Remove wall panel"
|
||||
_scenery_find_button.visible = is_missing
|
||||
_scenery_choose_button.visible = is_missing
|
||||
_scenery_keep_button.visible = is_missing
|
||||
@@ -4595,6 +4988,16 @@ 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 or node.get_script() == STAGE_BACKGROUND_GROUND_SCRIPT)
|
||||
|
||||
|
||||
func _has_remaining_scenery() -> bool:
|
||||
if _world == null or not is_instance_valid(_world):
|
||||
return false
|
||||
for child: Node in _world.get_children():
|
||||
var node: Node2D = child as Node2D
|
||||
if node != null and not node.is_queued_for_deletion() and _is_scenery_node(node):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _on_selected_surface_look_choice(finish: StringName) -> void:
|
||||
var surface: StageRectSurface = _selected_rect_surface()
|
||||
if surface == null:
|
||||
@@ -4737,10 +5140,10 @@ func _request_erase_selected_scenery() -> void:
|
||||
_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."
|
||||
_scenery_erase_piece_dialog_message.text = "Remove this whole 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()
|
||||
_scenery_erase_piece_dialog_message.text = "Remove these %d scenery pieces? You can’t undo this." % count
|
||||
_open_scenery_erase_piece_dialog()
|
||||
|
||||
|
||||
func _confirm_erase_selected_scenery() -> void:
|
||||
@@ -4764,7 +5167,10 @@ func _confirm_erase_selected_scenery() -> void:
|
||||
_director_visuals.mark_dirty()
|
||||
object_deleted.emit(erased)
|
||||
_mark_scene_dirty()
|
||||
_refresh_scenery_inspector()
|
||||
if not _has_remaining_scenery():
|
||||
_exit_scenery_edit_mode()
|
||||
else:
|
||||
_refresh_scenery_inspector()
|
||||
_refresh_status()
|
||||
|
||||
|
||||
@@ -4790,13 +5196,31 @@ func _begin_selected_wall_erase() -> void:
|
||||
_begin_wall_erase_tool()
|
||||
|
||||
|
||||
func _begin_selected_background_ground_erase() -> void:
|
||||
var selected: Array[Node2D] = _selection.get_selected() if _selection != null else []
|
||||
if selected.size() != 1 or selected[0].get_script() != STAGE_BACKGROUND_GROUND_SCRIPT or _scenery_controller == null:
|
||||
return
|
||||
_begin_background_ground_erase_tool()
|
||||
|
||||
|
||||
func _begin_wall_erase_tool() -> void:
|
||||
_begin_surface_erase_tool(&"wall")
|
||||
|
||||
|
||||
func _begin_background_ground_erase_tool() -> void:
|
||||
_begin_surface_erase_tool(&"background_ground")
|
||||
|
||||
|
||||
func _begin_surface_erase_tool(surface_kind: StringName) -> void:
|
||||
if _scenery_controller == null or not _scenery_edit_mode:
|
||||
return
|
||||
_selection.clear_selection()
|
||||
_scenery_inspector.hide()
|
||||
if not _scenery_controller.begin_wall_eraser(_viewport_to_world(get_viewport().get_mouse_position())):
|
||||
_show_toast("Could not start the wall eraser. Try again.")
|
||||
var pointer_world: Vector2 = _viewport_to_world(get_viewport().get_mouse_position())
|
||||
var started: bool = _scenery_controller.begin_surface_eraser(pointer_world, surface_kind)
|
||||
if not started:
|
||||
var surface_label: String = "wall and background ground" if surface_kind == &"all" else ("wall" if surface_kind == &"wall" else "background ground")
|
||||
_show_toast("Could not start the %s eraser. Try again." % surface_label)
|
||||
_refresh_scenery_capture_bar()
|
||||
|
||||
|
||||
@@ -4926,7 +5350,6 @@ func _on_scenery_placement_committed(node: Node2D) -> void:
|
||||
if node == null or not is_instance_valid(node):
|
||||
return
|
||||
_scenery_placed_count += 1
|
||||
_mark_scene_dirty()
|
||||
_refresh_scenery_capture_bar()
|
||||
|
||||
|
||||
@@ -4941,34 +5364,40 @@ func _refresh_scenery_capture_bar() -> void:
|
||||
_use_target_capture_bar.clear_horizontal_safe_bounds()
|
||||
var actions: Array[Dictionary] = []
|
||||
if _scenery_controller.get_active_tool() == &"artwork":
|
||||
actions.append({"id": "depth_far", "label": "Far"})
|
||||
actions.append({"id": "depth_near", "label": "Near"})
|
||||
actions.append({"id": "depth_floor", "label": "On the floor"})
|
||||
_set_scenery_depth_preview_path(_scenery_controller.get_active_artwork_path())
|
||||
var selected_depth: StringName = _scenery_controller.get_artwork_depth()
|
||||
actions.append(_make_scenery_depth_capture_action(&"depth_far", &"far", "Back", selected_depth))
|
||||
actions.append(_make_scenery_depth_capture_action(&"depth_near", &"near", "Middle", selected_depth))
|
||||
actions.append(_make_scenery_depth_capture_action(&"depth_floor", &"floor", "Front", selected_depth))
|
||||
var artwork_name: String = String(_scenery_controller.get_active_artwork_name())
|
||||
var prompt: String = "Click to place %s" % artwork_name if _scenery_placed_count == 0 else "Place another %s · placed pieces stay" % artwork_name
|
||||
var prompt: String = "Click to place %s" % artwork_name if _scenery_placed_count == 0 else "Place another · Cancel undoes"
|
||||
actions.append({"id": "done_tool", "label": "Done", "primary": true})
|
||||
actions.append({"id": "cancel_tool", "label": "✕ Cancel"})
|
||||
_use_target_capture_bar.set_capture_actions(prompt, actions)
|
||||
elif _scenery_controller.get_active_tool() == &"wall_eraser":
|
||||
var erase_prompt: String = "Drag over the wall · release to erase"
|
||||
if _scenery_controller.get_wall_areas_erased() > 0:
|
||||
erase_prompt = "Erase another area · earlier openings stay"
|
||||
actions.append({"id": "cancel_tool", "label": "Cancel"})
|
||||
_use_target_capture_bar.set_scenery_capture_actions(prompt, actions)
|
||||
elif _scenery_controller.get_active_tool() in [&"wall_eraser", &"background_ground_eraser", &"scenery_eraser"]:
|
||||
var erase_surface: String = _scenery_controller.get_active_eraser_surface_label()
|
||||
var erase_prompt: String = "Drag over the %s · release to erase" % erase_surface
|
||||
if _scenery_controller.get_active_tool() == &"scenery_eraser":
|
||||
erase_prompt = "Drag over wall or ground to erase"
|
||||
if _scenery_controller.get_surface_areas_erased() > 0:
|
||||
erase_prompt = "Erase another area · Cancel undoes"
|
||||
actions.append({"id": "done_tool", "label": "Done", "primary": true})
|
||||
actions.append({"id": "cancel_tool", "label": "✕ Cancel"})
|
||||
_use_target_capture_bar.set_capture_actions(erase_prompt, actions)
|
||||
actions.append({"id": "cancel_tool", "label": "Cancel"})
|
||||
_use_target_capture_bar.set_scenery_capture_actions(erase_prompt, actions)
|
||||
else:
|
||||
actions.append({"id": "choose_scenery", "label": "Choose scenery"})
|
||||
actions.append({"id": "done_tool", "label": "Done", "primary": true})
|
||||
actions.append({"id": "cancel_tool", "label": "✕ Cancel"})
|
||||
actions.append({"id": "cancel_tool", "label": "Cancel"})
|
||||
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)
|
||||
ground_prompt = "Draw another · Cancel undoes"
|
||||
_use_target_capture_bar.set_scenery_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)
|
||||
wall_prompt = "Draw another · Cancel undoes"
|
||||
_use_target_capture_bar.set_scenery_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()
|
||||
@@ -4976,52 +5405,24 @@ func _refresh_scenery_capture_bar() -> void:
|
||||
{"id": "keep_group", "label": "Keep changes", "primary": true},
|
||||
{"id": "cancel_group", "label": "✕ Cancel"},
|
||||
]
|
||||
var group_prompt: String = "Move together · Ground and Props stay put" if _scenery_group_mode == &"move" else "Resize together · Ground and Props stay put"
|
||||
_use_target_capture_bar.set_capture_actions(group_prompt, preview_actions)
|
||||
var group_count: int = _selection.get_selected().size()
|
||||
var group_verb: String = "Move" if _scenery_group_mode == &"move" else "Resize"
|
||||
var group_prompt: String = "%s %d scenery pieces\nGround and Props stay put" % [group_verb, group_count]
|
||||
_use_target_capture_bar.set_scenery_capture_actions(group_prompt, preview_actions)
|
||||
return
|
||||
if _scenery_controller != null and _scenery_controller.is_single_transform_active():
|
||||
_set_scenery_selection_capture_bounds()
|
||||
var target: Node2D = _scenery_controller.get_single_transform_node()
|
||||
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"
|
||||
var prompt_verb: String = "Move" if is_move else "Resize"
|
||||
var release_verb: String = "place" if is_move else "resize"
|
||||
var cancel_actions: Array[Dictionary] = [{"id": "cancel_single", "label": "✕ Cancel"}]
|
||||
_use_target_capture_bar.set_capture_actions("%s %s · release to %s" % [prompt_verb, target_name, release_verb], cancel_actions)
|
||||
_hide_scenery_capture_bar()
|
||||
return
|
||||
if _scenery_edit_mode:
|
||||
var count: int = _selection.get_selected().size()
|
||||
if count == 1:
|
||||
_set_scenery_selection_capture_bounds()
|
||||
else:
|
||||
_use_target_capture_bar.clear_horizontal_safe_bounds()
|
||||
var prompt: String = "%d scenery piece%s selected" % [count, "" if count == 1 else "s"]
|
||||
var edit_actions: Array[Dictionary] = []
|
||||
if count > 1:
|
||||
edit_actions.append({"id": "move_group", "label": "Move together"})
|
||||
edit_actions.append({"id": "resize_group", "label": "Resize together"})
|
||||
edit_actions.append({"id": "clear_selection", "label": "Clear selection"})
|
||||
else:
|
||||
edit_actions.append({"id": "select_several", "label": "Select several"})
|
||||
edit_actions.append({"id": "select_all", "label": "Select all scenery"})
|
||||
if count > 0:
|
||||
edit_actions.append({"id": "clear_selection", "label": "Clear selection"})
|
||||
else:
|
||||
edit_actions.append({"id": "erase_wall_area", "label": "Erase wall area"})
|
||||
edit_actions.append({"id": "done_edit", "label": "Done editing", "primary": true})
|
||||
_use_target_capture_bar.set_capture_actions(prompt, edit_actions)
|
||||
_set_scenery_selection_capture_bounds(count == 1)
|
||||
_use_target_capture_bar.set_scenery_edit_state(count)
|
||||
return
|
||||
_use_target_capture_bar.clear_horizontal_safe_bounds()
|
||||
_use_target_capture_bar.hide_capture()
|
||||
|
||||
|
||||
func _set_scenery_selection_capture_bounds() -> void:
|
||||
func _set_scenery_selection_capture_bounds(include_inspector: bool = true) -> void:
|
||||
if _use_target_capture_bar == null:
|
||||
return
|
||||
var viewport_width: float = get_viewport_rect().size.x
|
||||
@@ -5030,7 +5431,9 @@ func _set_scenery_selection_capture_bounds() -> void:
|
||||
var palette_width: float = float(palette_config.get("dock_width", 116.0))
|
||||
var panel_width: float = float(_stage_panel_integer("shell.preferred_width", 380, 220, 720))
|
||||
var left: float = palette_width + inset
|
||||
var right: float = viewport_width - panel_width - inset * 2.0
|
||||
var right: float = viewport_width - inset
|
||||
if include_inspector:
|
||||
right = viewport_width - panel_width - inset * 2.0
|
||||
_use_target_capture_bar.set_horizontal_safe_bounds(left, right)
|
||||
|
||||
|
||||
@@ -5042,6 +5445,8 @@ func _hide_scenery_capture_bar() -> void:
|
||||
|
||||
func _on_capture_action_requested(action_id: StringName) -> void:
|
||||
match action_id:
|
||||
&"choose_scenery":
|
||||
_open_selector("scenery")
|
||||
&"depth_far":
|
||||
_scenery_controller.set_depth(&"far")
|
||||
_refresh_scenery_capture_bar()
|
||||
@@ -5051,9 +5456,12 @@ func _on_capture_action_requested(action_id: StringName) -> void:
|
||||
&"depth_floor":
|
||||
_scenery_controller.set_depth(&"floor")
|
||||
_refresh_scenery_capture_bar()
|
||||
&"done_tool", &"cancel_tool":
|
||||
&"done_tool":
|
||||
_scenery_controller.exit_tool()
|
||||
_hide_scenery_capture_bar()
|
||||
&"cancel_tool":
|
||||
_scenery_controller.cancel_active_tool()
|
||||
_hide_scenery_capture_bar()
|
||||
&"select_several":
|
||||
_scenery_multi_select = true
|
||||
_show_toast("Tap scenery pieces to add them. Shift-click also works.")
|
||||
@@ -5065,27 +5473,30 @@ func _on_capture_action_requested(action_id: StringName) -> void:
|
||||
_refresh_scenery_capture_bar()
|
||||
&"erase_wall_area":
|
||||
_begin_wall_erase_tool()
|
||||
&"erase_area":
|
||||
_begin_surface_erase_tool(&"all")
|
||||
&"move_group":
|
||||
_begin_scenery_group_transform(&"move")
|
||||
&"resize_group":
|
||||
_begin_scenery_group_transform(&"resize")
|
||||
&"keep_group":
|
||||
_scenery_controller.commit_group()
|
||||
_scenery_group_mode = &""
|
||||
_refresh_scenery_capture_bar()
|
||||
_keep_scenery_group_preview()
|
||||
&"cancel_group":
|
||||
_scenery_controller.cancel_group()
|
||||
_scenery_group_mode = &""
|
||||
_refresh_scenery_capture_bar()
|
||||
&"cancel_single":
|
||||
_scenery_controller.cancel_single_transform()
|
||||
_scenery_single_dragging = false
|
||||
_refresh_scenery_inspector()
|
||||
_refresh_scenery_capture_bar()
|
||||
&"done_edit":
|
||||
_exit_scenery_edit_mode()
|
||||
|
||||
|
||||
func _keep_scenery_group_preview() -> void:
|
||||
if _scenery_controller == null or not _scenery_controller.is_group_preview_pending():
|
||||
return
|
||||
_scenery_controller.commit_group()
|
||||
_scenery_group_mode = &""
|
||||
_refresh_scenery_capture_bar()
|
||||
|
||||
|
||||
func _begin_scenery_group_transform(kind: StringName) -> void:
|
||||
var selected: Array[Node2D] = _selection.get_selected().duplicate()
|
||||
if _scenery_controller == null or not _scenery_controller.begin_group_transform(kind, selected):
|
||||
|
||||
@@ -157,7 +157,7 @@ func _scenery_background_entries() -> Array[Dictionary]:
|
||||
{"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": "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": "🌿"}
|
||||
{"id": "background_ground_setup", "name": "Background ground", "category": "Scenery", "gallery_tab": "backgrounds", "background_kind": "background_ground_setup", "icon": "🌿"}
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -60,6 +60,8 @@ func collect(
|
||||
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)
|
||||
if not background.motif_origin_offset.is_zero_approx():
|
||||
entry["motif_origin_offset"] = STAGE_SCENE_IO.vector_to_data(background.motif_origin_offset)
|
||||
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)
|
||||
@@ -182,6 +184,8 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
StringName(String(entry.get("finish_id", "finish_background_grass"))),
|
||||
STAGE_SCENE_IO.color_from_data(entry.get("color", "#72A84CFF"), Color("#72A84C"))
|
||||
)
|
||||
if entry.has("motif_origin_offset") and STAGE_SCENE_IO.is_vector_data(entry.get("motif_origin_offset")):
|
||||
background.motif_origin_offset = STAGE_SCENE_IO.data_to_vector(entry.get("motif_origin_offset"), Vector2.ZERO)
|
||||
world.add_child(background)
|
||||
node = background
|
||||
"scenery_artwork":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class_name StageSceneryController
|
||||
extends Node
|
||||
## Owns inert Scenery placement drafts and snapshot-based group transforms.
|
||||
## Owns scenery placement transactions, eraser transactions, and snapshot-based transforms.
|
||||
|
||||
signal placement_committed(node: Node2D)
|
||||
signal document_change_requested
|
||||
@@ -17,7 +17,6 @@ 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
|
||||
@@ -28,6 +27,7 @@ var _camera: Camera2D
|
||||
var _ghost: Node2D
|
||||
var _active_tool: StringName = &""
|
||||
var _asset_path: String = ""
|
||||
var _placement_nodes: Array[Node2D] = []
|
||||
var _depth: StringName = &"near"
|
||||
var _wall_finish: StringName = &"finish_wall_paint"
|
||||
var _wall_tint: Color = Color("#5A9D70")
|
||||
@@ -38,7 +38,12 @@ var _wall_anchor_active: bool = false
|
||||
var _wall_erase_start: Vector2 = Vector2.INF
|
||||
var _wall_erase_dragging: bool = false
|
||||
var _wall_erase_pointer: Vector2 = Vector2.ZERO
|
||||
var _wall_areas_erased: int = 0
|
||||
var _surface_erase_kind: StringName = &"wall"
|
||||
var _surface_areas_erased: int = 0
|
||||
# Keep original surfaces detached during an eraser session so Cancel can restore them.
|
||||
var _surface_erase_originals: Array[Dictionary] = []
|
||||
var _surface_erase_detached: Array[Node2D] = []
|
||||
var _surface_erase_changed: bool = false
|
||||
var _snap_enabled: bool = false
|
||||
var _grid_size: float = 15.0
|
||||
var _group_kind: StringName = &""
|
||||
@@ -102,7 +107,21 @@ func get_active_tool() -> StringName:
|
||||
|
||||
|
||||
func get_wall_areas_erased() -> int:
|
||||
return _wall_areas_erased
|
||||
return _surface_areas_erased
|
||||
|
||||
|
||||
func get_surface_areas_erased() -> int:
|
||||
return _surface_areas_erased
|
||||
|
||||
|
||||
func is_surface_eraser_active() -> bool:
|
||||
return _is_surface_eraser()
|
||||
|
||||
|
||||
func get_active_eraser_surface_label() -> String:
|
||||
if _surface_erase_kind == &"all":
|
||||
return "wall or ground"
|
||||
return "background ground" if _surface_erase_kind == &"background_ground" else "wall"
|
||||
|
||||
|
||||
func get_group_bounds() -> Rect2:
|
||||
@@ -115,6 +134,20 @@ func get_active_artwork_name() -> String:
|
||||
return _asset_path.get_file().get_basename()
|
||||
|
||||
|
||||
func get_active_artwork_path() -> String:
|
||||
return _asset_path if _active_tool == &"artwork" else ""
|
||||
|
||||
|
||||
func get_active_artwork_preview_bounds() -> Rect2:
|
||||
if _active_tool != &"artwork" or _ghost == null or not is_instance_valid(_ghost) or _ghost.get_script() != SCENERY_ARTWORK:
|
||||
return Rect2()
|
||||
return (_ghost as StageSceneryArtwork).get_visual_world_bounds()
|
||||
|
||||
|
||||
func get_artwork_depth() -> StringName:
|
||||
return _depth
|
||||
|
||||
|
||||
func get_active_surface_label() -> String:
|
||||
if _active_tool == &"background_ground":
|
||||
return "background ground"
|
||||
@@ -123,7 +156,7 @@ func get_active_surface_label() -> String:
|
||||
|
||||
func handle_key(event: InputEventKey) -> bool:
|
||||
if is_active() and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
exit_tool()
|
||||
cancel_active_tool()
|
||||
return true
|
||||
if _group_pending and event.pressed and not event.echo and event.keycode == KEY_ESCAPE:
|
||||
cancel_group()
|
||||
@@ -171,13 +204,12 @@ func _begin_rect_surface(tool: StringName, finish: StringName, initial_world_pos
|
||||
_wall_dragging = false
|
||||
_wall_anchor_active = false
|
||||
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.interactive_preview = true
|
||||
panel.configure(DEFAULT_PANEL_SIZE, _wall_finish, _wall_tint)
|
||||
panel.modulate = Color(1.0, 1.0, 1.0, 0.45)
|
||||
_ghost_holder.add_child(panel)
|
||||
_ghost = panel
|
||||
@@ -188,16 +220,37 @@ func _begin_rect_surface(tool: StringName, finish: StringName, initial_world_pos
|
||||
|
||||
|
||||
func begin_wall_eraser(initial_world_position: Vector2) -> bool:
|
||||
return begin_surface_eraser(initial_world_position, &"wall")
|
||||
|
||||
|
||||
func begin_background_ground_eraser(initial_world_position: Vector2) -> bool:
|
||||
return begin_surface_eraser(initial_world_position, &"background_ground")
|
||||
|
||||
|
||||
func begin_surface_eraser(initial_world_position: Vector2, surface_kind: StringName) -> bool:
|
||||
if _world == null or _ghost_holder == null:
|
||||
return false
|
||||
if surface_kind not in [&"wall", &"background_ground", &"all"]:
|
||||
return false
|
||||
exit_tool()
|
||||
_active_tool = &"wall_eraser"
|
||||
_surface_erase_kind = surface_kind
|
||||
match surface_kind:
|
||||
&"background_ground":
|
||||
_active_tool = &"background_ground_eraser"
|
||||
&"all":
|
||||
_active_tool = &"scenery_eraser"
|
||||
_:
|
||||
_active_tool = &"wall_eraser"
|
||||
_wall_erase_start = Vector2.INF
|
||||
_wall_erase_dragging = false
|
||||
_wall_areas_erased = 0
|
||||
_surface_areas_erased = 0
|
||||
_surface_erase_originals.clear()
|
||||
_surface_erase_detached.clear()
|
||||
_surface_erase_changed = false
|
||||
_capture_surface_eraser_snapshot(surface_kind)
|
||||
_wall_erase_pointer = initial_world_position
|
||||
var preview := Node2D.new()
|
||||
preview.name = "WallEraserPreview"
|
||||
preview.name = "BackgroundGroundEraserPreview" if surface_kind == &"background_ground" else "WallEraserPreview"
|
||||
var fill := Polygon2D.new()
|
||||
fill.name = "EraseArea"
|
||||
fill.color = Color("#FF5B62", 0.2)
|
||||
@@ -237,7 +290,7 @@ func update_pointer(world_position: Vector2) -> void:
|
||||
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":
|
||||
elif _is_surface_eraser():
|
||||
_wall_erase_pointer = world_position
|
||||
if _wall_erase_dragging:
|
||||
_set_wall_eraser_preview_rect(_drag_rect(_wall_erase_start, _snap(world_position)))
|
||||
@@ -252,13 +305,17 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
if not is_active():
|
||||
return false
|
||||
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
|
||||
if _wall_dragging or _wall_anchor_active:
|
||||
cancel_active_gesture()
|
||||
exit_tool()
|
||||
# Right-click finishes placement and area-erasure sessions. This commits
|
||||
# completed work while dropping any unfinished preview gesture. Cancel
|
||||
# and Escape remain the explicit rollback paths.
|
||||
if _is_placement_tool() or _is_surface_eraser():
|
||||
exit_tool()
|
||||
else:
|
||||
cancel_active_tool()
|
||||
return true
|
||||
var completes_drag: bool = not event.pressed and event.button_index == MOUSE_BUTTON_LEFT and (
|
||||
(_is_rect_placement_tool() and _wall_dragging)
|
||||
or (_active_tool == &"wall_eraser" and _wall_erase_dragging)
|
||||
or (_is_surface_eraser() and _wall_erase_dragging)
|
||||
)
|
||||
if (over_ui and not completes_drag) or event.button_index != MOUSE_BUTTON_LEFT:
|
||||
return false
|
||||
@@ -292,7 +349,7 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
_wall_anchor_active = false
|
||||
_restore_rect_surface_ghost(world_position)
|
||||
return true
|
||||
if _active_tool == &"wall_eraser":
|
||||
if _is_surface_eraser():
|
||||
if event.pressed:
|
||||
_wall_erase_start = _snap(world_position) if _snap_enabled else world_position
|
||||
_wall_erase_dragging = true
|
||||
@@ -304,10 +361,10 @@ func handle_mouse_button(event: InputEventMouseButton, world_position: Vector2,
|
||||
_wall_erase_dragging = false
|
||||
_wall_erase_start = Vector2.INF
|
||||
if erase_rect.size.x >= MIN_WALL_ERASE_SIZE and erase_rect.size.y >= MIN_WALL_ERASE_SIZE:
|
||||
var changed: int = _erase_wall_region(erase_rect)
|
||||
var changed: int = _erase_surface_region(erase_rect, _surface_erase_kind)
|
||||
if changed > 0:
|
||||
_wall_areas_erased += 1
|
||||
document_change_requested.emit()
|
||||
_surface_areas_erased += 1
|
||||
_surface_erase_changed = true
|
||||
update_pointer(world_position)
|
||||
return true
|
||||
return false
|
||||
@@ -329,13 +386,17 @@ func cancel_active_gesture() -> void:
|
||||
_wall_erase_start = Vector2.INF
|
||||
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):
|
||||
elif _is_surface_eraser() and _ghost != null and is_instance_valid(_ghost):
|
||||
_set_wall_eraser_preview_rect(Rect2(_wall_erase_pointer, WALL_ERASER_CURSOR_SIZE))
|
||||
|
||||
|
||||
func exit_tool() -> void:
|
||||
if _single_active:
|
||||
cancel_single_transform()
|
||||
if _is_placement_tool():
|
||||
commit_placement_session()
|
||||
if _is_surface_eraser():
|
||||
_commit_surface_eraser()
|
||||
cancel_active_gesture()
|
||||
_free_ghost()
|
||||
_active_tool = &""
|
||||
@@ -346,6 +407,115 @@ func exit_tool() -> void:
|
||||
preview_state_changed.emit(false)
|
||||
|
||||
|
||||
func cancel_active_tool() -> void:
|
||||
if _is_surface_eraser():
|
||||
cancel_surface_eraser()
|
||||
return
|
||||
if _is_placement_tool():
|
||||
_rollback_placement_session()
|
||||
exit_tool()
|
||||
|
||||
|
||||
func commit_placement_session() -> void:
|
||||
if not _is_placement_tool():
|
||||
return
|
||||
var has_placed_nodes: bool = false
|
||||
for node: Node2D in _placement_nodes:
|
||||
if node != null and is_instance_valid(node) and node.get_parent() != null:
|
||||
has_placed_nodes = true
|
||||
_placement_nodes.clear()
|
||||
if has_placed_nodes:
|
||||
document_change_requested.emit()
|
||||
|
||||
|
||||
func _rollback_placement_session() -> void:
|
||||
for node: Node2D in _placement_nodes:
|
||||
if node == null or not is_instance_valid(node):
|
||||
continue
|
||||
var parent: Node = node.get_parent()
|
||||
if parent != null:
|
||||
parent.remove_child(node)
|
||||
node.queue_free()
|
||||
_placement_nodes.clear()
|
||||
|
||||
|
||||
func cancel_surface_eraser() -> void:
|
||||
if not _is_surface_eraser():
|
||||
return
|
||||
if _surface_erase_changed:
|
||||
_restore_surface_eraser_snapshot()
|
||||
_surface_areas_erased = 0
|
||||
_clear_surface_eraser_transaction()
|
||||
exit_tool()
|
||||
|
||||
|
||||
func _commit_surface_eraser() -> void:
|
||||
if _surface_erase_changed:
|
||||
document_change_requested.emit()
|
||||
for detached: Node2D in _surface_erase_detached:
|
||||
if detached != null and is_instance_valid(detached):
|
||||
detached.queue_free()
|
||||
_clear_surface_eraser_transaction()
|
||||
|
||||
|
||||
func _clear_surface_eraser_transaction() -> void:
|
||||
_surface_erase_originals.clear()
|
||||
_surface_erase_detached.clear()
|
||||
_surface_erase_changed = false
|
||||
|
||||
|
||||
func _capture_surface_eraser_snapshot(surface_kind: StringName) -> void:
|
||||
if _world == null or not is_instance_valid(_world):
|
||||
return
|
||||
var target_scripts: Array[Script] = _surface_erase_target_scripts(surface_kind)
|
||||
for child: Node in _world.get_children():
|
||||
if not target_scripts.has(child.get_script()):
|
||||
continue
|
||||
var surface: Node2D = child as Node2D
|
||||
if surface == null:
|
||||
continue
|
||||
_surface_erase_originals.append({
|
||||
"node": surface,
|
||||
"parent": surface.get_parent(),
|
||||
"index": surface.get_index(),
|
||||
})
|
||||
|
||||
|
||||
func _restore_surface_eraser_snapshot() -> void:
|
||||
if _world == null or not is_instance_valid(_world):
|
||||
return
|
||||
var original_ids: Dictionary = {}
|
||||
for snapshot: Dictionary in _surface_erase_originals:
|
||||
var original: Node2D = snapshot.get("node") as Node2D
|
||||
if original != null and is_instance_valid(original):
|
||||
original_ids[original.get_instance_id()] = true
|
||||
var target_scripts: Array[Script] = _surface_erase_target_scripts(_surface_erase_kind)
|
||||
for child: Node in _world.get_children().duplicate():
|
||||
if not target_scripts.has(child.get_script()):
|
||||
continue
|
||||
_world.remove_child(child)
|
||||
if not original_ids.has(child.get_instance_id()):
|
||||
child.queue_free()
|
||||
for detached: Node2D in _surface_erase_detached:
|
||||
if detached == null or not is_instance_valid(detached):
|
||||
continue
|
||||
if not original_ids.has(detached.get_instance_id()):
|
||||
detached.queue_free()
|
||||
var restore_order: Array[Dictionary] = _surface_erase_originals.duplicate()
|
||||
restore_order.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return int(a.get("index", 0)) < int(b.get("index", 0)))
|
||||
for snapshot: Dictionary in restore_order:
|
||||
var original: Node2D = snapshot.get("node") as Node2D
|
||||
var parent: Node = snapshot.get("parent") as Node
|
||||
if original == null or not is_instance_valid(original):
|
||||
continue
|
||||
if parent == null or not is_instance_valid(parent):
|
||||
parent = _world
|
||||
if original.get_parent() != null:
|
||||
original.get_parent().remove_child(original)
|
||||
parent.add_child(original)
|
||||
parent.move_child(original, clampi(int(snapshot.get("index", 0)), 0, parent.get_child_count() - 1))
|
||||
|
||||
|
||||
func begin_group_transform(kind: StringName, members: Array[Node2D]) -> bool:
|
||||
if _single_active or members.is_empty() or kind not in [&"move", &"resize"]:
|
||||
return false
|
||||
@@ -591,8 +761,8 @@ func _commit_artwork(position: Vector2) -> void:
|
||||
artwork.set_depth(_depth)
|
||||
artwork.global_position = _snap_visual_center(artwork, position)
|
||||
_world.add_child(artwork)
|
||||
_placement_nodes.append(artwork)
|
||||
placement_committed.emit(artwork)
|
||||
document_change_requested.emit()
|
||||
|
||||
|
||||
func _commit_rect_surface(first: Vector2, second: Vector2) -> void:
|
||||
@@ -605,8 +775,8 @@ func _commit_rect_surface(first: Vector2, second: Vector2) -> void:
|
||||
panel.configure(size, _wall_finish, _wall_tint)
|
||||
panel.global_position = (first + second) * 0.5
|
||||
_world.add_child(panel)
|
||||
_placement_nodes.append(panel)
|
||||
placement_committed.emit(panel)
|
||||
document_change_requested.emit()
|
||||
|
||||
|
||||
func _restore_rect_surface_ghost(world_position: Vector2) -> void:
|
||||
@@ -620,23 +790,39 @@ func _is_rect_placement_tool() -> bool:
|
||||
return _active_tool in [&"wall", &"background_ground"]
|
||||
|
||||
|
||||
func _is_placement_tool() -> bool:
|
||||
return _active_tool in [&"artwork", &"wall", &"background_ground"]
|
||||
|
||||
|
||||
func _default_size_for_active_surface() -> Vector2:
|
||||
return DEFAULT_BACKGROUND_SIZE if _active_tool == &"background_ground" else DEFAULT_PANEL_SIZE
|
||||
return DEFAULT_PANEL_SIZE
|
||||
|
||||
|
||||
func _is_surface_eraser() -> bool:
|
||||
return _active_tool in [&"wall_eraser", &"background_ground_eraser", &"scenery_eraser"]
|
||||
|
||||
|
||||
func _erase_wall_region(world_rect: Rect2) -> int:
|
||||
return _erase_surface_region(world_rect, &"wall")
|
||||
|
||||
|
||||
func _erase_surface_region(world_rect: Rect2, surface_kind: StringName) -> int:
|
||||
if _world == null or world_rect.size.x <= 0.0 or world_rect.size.y <= 0.0:
|
||||
return 0
|
||||
var changed_panels: int = 0
|
||||
var target_scripts: Array[Script] = _surface_erase_target_scripts(surface_kind)
|
||||
for child: Node in _world.get_children().duplicate():
|
||||
if child.get_script() != WALL_PANEL:
|
||||
if not target_scripts.has(child.get_script()):
|
||||
continue
|
||||
var wall: StageWallPanel = child as StageWallPanel
|
||||
var panel_rect: Rect2 = wall.get_local_bounds()
|
||||
var surface: StageRectSurface = child as StageRectSurface
|
||||
if surface == null:
|
||||
continue
|
||||
var is_background_ground: bool = child.get_script() == BACKGROUND_GROUND
|
||||
var panel_rect: Rect2 = surface.get_local_bounds()
|
||||
var erase_corners: Array[Vector2] = [world_rect.position, Vector2(world_rect.end.x, world_rect.position.y), world_rect.end, Vector2(world_rect.position.x, world_rect.end.y)]
|
||||
var local_erase := Rect2()
|
||||
for corner_index: int in erase_corners.size():
|
||||
var local_point: Vector2 = wall.to_local(erase_corners[corner_index])
|
||||
var local_point: Vector2 = surface.to_local(erase_corners[corner_index])
|
||||
if corner_index == 0:
|
||||
local_erase = Rect2(local_point, Vector2.ZERO)
|
||||
else:
|
||||
@@ -647,27 +833,52 @@ func _erase_wall_region(world_rect: Rect2) -> int:
|
||||
if cut.size.x <= 0.0 or cut.size.y <= 0.0:
|
||||
continue
|
||||
var fragments: Array[Rect2] = _wall_rect_fragments(panel_rect, cut)
|
||||
var parent: Node = wall.get_parent()
|
||||
var parent: Node = surface.get_parent()
|
||||
if parent == null:
|
||||
continue
|
||||
var sibling_index: int = wall.get_index()
|
||||
var source_transform: Transform2D = wall.transform
|
||||
var source_motif_origin: Vector2 = wall.get_effective_motif_origin_world()
|
||||
var sibling_index: int = surface.get_index()
|
||||
var source_transform: Transform2D = surface.transform
|
||||
var source_draw_origin: Vector2 = surface.get_surface_draw_origin_world()
|
||||
if surface is StageWallPanel:
|
||||
source_draw_origin = (surface as StageWallPanel).get_effective_motif_origin_world()
|
||||
for fragment_rect: Rect2 in fragments:
|
||||
var fragment := WALL_PANEL.new() as StageWallPanel
|
||||
var fragment: StageRectSurface
|
||||
if is_background_ground:
|
||||
fragment = BACKGROUND_GROUND.new() as StageBackgroundGround
|
||||
else:
|
||||
fragment = WALL_PANEL.new() as StageWallPanel
|
||||
fragment.transform = source_transform
|
||||
fragment.position += source_transform.basis_xform(fragment_rect.get_center())
|
||||
fragment.configure(fragment_rect.size, wall.finish_id, wall.tint)
|
||||
fragment.configure(fragment_rect.size, surface.finish_id, surface.tint)
|
||||
parent.add_child(fragment)
|
||||
fragment.motif_origin_offset = source_motif_origin - fragment.global_position
|
||||
if fragment is StageWallPanel:
|
||||
(fragment as StageWallPanel).motif_origin_offset = source_draw_origin - fragment.global_position
|
||||
elif fragment is StageBackgroundGround:
|
||||
(fragment as StageBackgroundGround).motif_origin_offset = source_draw_origin - fragment.global_position
|
||||
parent.move_child(fragment, sibling_index)
|
||||
sibling_index += 1
|
||||
parent.remove_child(wall)
|
||||
wall.queue_free()
|
||||
parent.remove_child(surface)
|
||||
if _is_surface_eraser():
|
||||
_surface_erase_detached.append(surface)
|
||||
else:
|
||||
surface.queue_free()
|
||||
changed_panels += 1
|
||||
return changed_panels
|
||||
|
||||
|
||||
func _surface_erase_target_scripts(surface_kind: StringName) -> Array[Script]:
|
||||
var target_scripts: Array[Script] = []
|
||||
match surface_kind:
|
||||
&"background_ground":
|
||||
target_scripts.append(BACKGROUND_GROUND)
|
||||
&"all":
|
||||
target_scripts.append(WALL_PANEL)
|
||||
target_scripts.append(BACKGROUND_GROUND)
|
||||
_:
|
||||
target_scripts.append(WALL_PANEL)
|
||||
return target_scripts
|
||||
|
||||
|
||||
func _wall_rect_fragments(panel_rect: Rect2, cut: Rect2) -> Array[Rect2]:
|
||||
var fragments: Array[Rect2] = []
|
||||
_add_wall_fragment(fragments, Rect2(panel_rect.position, Vector2(panel_rect.size.x, cut.position.y - panel_rect.position.y)))
|
||||
|
||||
@@ -7,12 +7,16 @@ signal change_prop_requested
|
||||
signal capture_action_requested(action_id: StringName)
|
||||
|
||||
const MAX_BAR_WIDTH: float = 800.0
|
||||
const SCENERY_CAPTURE_MAX_WIDTH: float = 900.0
|
||||
const VIEWPORT_WIDTH_RATIO: float = 0.68
|
||||
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
|
||||
const SCENERY_EDIT_MAX_WIDTH: float = 920.0
|
||||
const SCENERY_EDIT_BUTTON_HEIGHT: float = 54.0
|
||||
const SCENERY_CAPTURE_BUTTON_HEIGHT: float = 56.0
|
||||
|
||||
var _stage_theme: SandboxTheme
|
||||
var _top_bar_height: float = 64.0
|
||||
@@ -25,12 +29,28 @@ var _actions_row: HBoxContainer
|
||||
var _actions_column: VBoxContainer
|
||||
var _actions_parent: BoxContainer
|
||||
var _prompt: Label
|
||||
var _scenery_edit_layout: VBoxContainer
|
||||
var _scenery_edit_prompt: Label
|
||||
var _scenery_edit_count: Label
|
||||
var _scenery_edit_count_badge: PanelContainer
|
||||
var _scenery_selection_group: PanelContainer
|
||||
var _scenery_edit_actions: HBoxContainer
|
||||
var _scenery_selection_actions: HBoxContainer
|
||||
var _scenery_select_several_button: Button
|
||||
var _scenery_select_all_button: Button
|
||||
var _scenery_move_group_button: Button
|
||||
var _scenery_resize_group_button: Button
|
||||
var _scenery_clear_selection_button: Button
|
||||
var _scenery_erase_area_button: Button
|
||||
var _scenery_done_button: Button
|
||||
var _change_prop_button: Button
|
||||
var _cancel_button: Button
|
||||
var _custom_action_buttons: Array[Button] = []
|
||||
var _has_horizontal_safe_bounds: bool = false
|
||||
var _safe_left: float = 0.0
|
||||
var _safe_right: float = 0.0
|
||||
var _scenery_edit_active: bool = false
|
||||
var _scenery_capture_active: bool = false
|
||||
|
||||
|
||||
func configure(stage_theme: SandboxTheme, top_bar_height: float) -> void:
|
||||
@@ -51,6 +71,7 @@ func _ready() -> void:
|
||||
func set_capture_state(prop_selected: bool, prop_has_use_spots: bool = true) -> void:
|
||||
if _prompt == null:
|
||||
return
|
||||
_show_standard_capture_layout()
|
||||
_clear_custom_actions()
|
||||
if not prop_selected:
|
||||
_prompt.text = "Choose a prop to use"
|
||||
@@ -65,8 +86,17 @@ func set_capture_state(prop_selected: bool, prop_has_use_spots: bool = true) ->
|
||||
|
||||
|
||||
func set_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
_set_capture_actions(prompt, actions, false)
|
||||
|
||||
|
||||
func set_scenery_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
_set_capture_actions(prompt, actions, true)
|
||||
|
||||
|
||||
func _set_capture_actions(prompt: String, actions: Array[Dictionary], scenery_capture_style: bool) -> void:
|
||||
if _prompt == null:
|
||||
return
|
||||
_show_standard_capture_layout()
|
||||
_prompt.text = prompt
|
||||
_change_prop_button.visible = false
|
||||
_cancel_button.visible = false
|
||||
@@ -77,14 +107,75 @@ func set_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
continue
|
||||
var button := Button.new()
|
||||
button.name = "Action_%s" % String(action_id)
|
||||
button.text = String(action.get("label", action_id))
|
||||
var action_label: String = String(action.get("label", action_id))
|
||||
var cue: Control = action.get("cue") as Control
|
||||
button.text = "" if cue != null else action_label
|
||||
button.tooltip_text = button.text
|
||||
if cue != null:
|
||||
button.tooltip_text = action_label
|
||||
var margin := MarginContainer.new()
|
||||
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
margin.add_theme_constant_override("margin_left", 4)
|
||||
margin.add_theme_constant_override("margin_right", 4)
|
||||
margin.add_theme_constant_override("margin_top", 3)
|
||||
margin.add_theme_constant_override("margin_bottom", 3)
|
||||
margin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var cue_layout := BoxContainer.new()
|
||||
cue_layout.vertical = true
|
||||
cue_layout.add_theme_constant_override("separation", 2)
|
||||
cue_layout.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
cue_layout.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
cue.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
cue_layout.add_child(cue)
|
||||
var cue_label := Label.new()
|
||||
cue_label.text = action_label
|
||||
cue_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
cue_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
cue_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
cue_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
cue_layout.add_child(cue_label)
|
||||
margin.add_child(cue_layout)
|
||||
button.add_child(margin)
|
||||
button.custom_minimum_size.x = ACTION_BUTTON_WIDTH
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.set_meta("capture_primary", bool(action.get("primary", false)))
|
||||
button.set_meta("capture_choice", bool(action.get("choice", false)))
|
||||
button.set_meta("capture_selected", bool(action.get("selected", false)))
|
||||
button.pressed.connect(func() -> void: capture_action_requested.emit(action_id))
|
||||
_style_button(button, bool(action.get("primary", false)))
|
||||
_custom_action_buttons.append(button)
|
||||
_actions_parent.add_child(button)
|
||||
_scenery_capture_active = scenery_capture_style
|
||||
if _scenery_capture_active:
|
||||
_set_layout(true, false)
|
||||
visible = true
|
||||
apply_theme()
|
||||
_layout_capture_bar()
|
||||
|
||||
|
||||
func set_scenery_edit_state(selected_count: int) -> void:
|
||||
if _scenery_edit_layout == null:
|
||||
return
|
||||
_scenery_capture_active = false
|
||||
_scenery_edit_active = true
|
||||
_clear_custom_actions()
|
||||
if _active_layout.get_parent() == self:
|
||||
_active_layout.visible = false
|
||||
remove_child(_active_layout)
|
||||
_layout_stash.add_child(_active_layout)
|
||||
if _scenery_edit_layout.get_parent() == _layout_stash:
|
||||
_layout_stash.remove_child(_scenery_edit_layout)
|
||||
add_child(_scenery_edit_layout)
|
||||
_scenery_edit_layout.visible = true
|
||||
_scenery_edit_prompt.text = "Tap scenery to select it"
|
||||
_scenery_edit_count.text = "%d selected" % maxi(0, selected_count)
|
||||
var has_group: bool = selected_count > 1
|
||||
_scenery_select_several_button.visible = not has_group
|
||||
_scenery_select_all_button.visible = not has_group
|
||||
_scenery_move_group_button.visible = has_group
|
||||
_scenery_resize_group_button.visible = has_group
|
||||
_scenery_erase_area_button.visible = selected_count == 0
|
||||
_scenery_clear_selection_button.visible = selected_count > 0
|
||||
visible = true
|
||||
apply_theme()
|
||||
_layout_capture_bar()
|
||||
@@ -104,11 +195,18 @@ func clear_horizontal_safe_bounds() -> void:
|
||||
|
||||
func hide_capture() -> void:
|
||||
visible = false
|
||||
_show_standard_capture_layout()
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
if not is_node_ready() or _prompt == null:
|
||||
return
|
||||
if _scenery_edit_active:
|
||||
_apply_scenery_edit_theme()
|
||||
return
|
||||
if _scenery_capture_active:
|
||||
_apply_scenery_capture_theme()
|
||||
return
|
||||
theme = _stage_theme.control_theme() if _stage_theme != null else null
|
||||
var accent: Color = _color("mode_colors.direct", Color("#FFB300"))
|
||||
var shell: StyleBoxFlat = StyleBoxFlat.new()
|
||||
@@ -155,6 +253,197 @@ func apply_theme() -> void:
|
||||
_layout_capture_bar()
|
||||
|
||||
|
||||
func _apply_scenery_edit_theme() -> void:
|
||||
theme = _stage_theme.control_theme() if _stage_theme != null else null
|
||||
var edit_accent: Color = _color("mode_colors.edit", Color("#22C6FF"))
|
||||
var panel_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
panel_style.bg_color = _color("stage_panels.shell.surface_color", Color("#202633FA"))
|
||||
panel_style.border_color = Color("#667386")
|
||||
panel_style.set_border_width_all(2)
|
||||
panel_style.set_corner_radius_all(18)
|
||||
panel_style.content_margin_left = 18.0
|
||||
panel_style.content_margin_right = 18.0
|
||||
panel_style.content_margin_top = 14.0
|
||||
panel_style.content_margin_bottom = 14.0
|
||||
panel_style.shadow_color = Color(0.0, 0.0, 0.0, 0.34)
|
||||
panel_style.shadow_size = 8
|
||||
panel_style.shadow_offset = Vector2(0.0, 4.0)
|
||||
add_theme_stylebox_override("panel", panel_style)
|
||||
var font: Font = _stage_theme.font() if _stage_theme != null else ThemeDB.fallback_font
|
||||
if font != null:
|
||||
_scenery_edit_prompt.add_theme_font_override("font", font)
|
||||
_scenery_edit_count.add_theme_font_override("font", font)
|
||||
for button: Button in [_scenery_select_several_button, _scenery_select_all_button, _scenery_move_group_button, _scenery_resize_group_button, _scenery_clear_selection_button, _scenery_erase_area_button, _scenery_done_button]:
|
||||
if font != null:
|
||||
button.add_theme_font_override("font", font)
|
||||
button.add_theme_font_size_override("font_size", 19)
|
||||
button.custom_minimum_size.y = SCENERY_EDIT_BUTTON_HEIGHT
|
||||
_style_scenery_edit_button(button, button == _scenery_done_button, false)
|
||||
_scenery_edit_prompt.add_theme_font_size_override("font_size", 27)
|
||||
_scenery_edit_prompt.add_theme_color_override("font_color", _color("stage_panels.type.text_color", Color("#F5F7FA")))
|
||||
_scenery_edit_count.add_theme_font_size_override("font_size", 17)
|
||||
_scenery_edit_count.add_theme_color_override("font_color", Color("#F5F7FA"))
|
||||
var badge_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
badge_style.bg_color = edit_accent.darkened(0.78)
|
||||
badge_style.border_color = edit_accent
|
||||
badge_style.set_border_width_all(1)
|
||||
badge_style.set_corner_radius_all(14)
|
||||
badge_style.content_margin_left = 14.0
|
||||
badge_style.content_margin_right = 14.0
|
||||
badge_style.content_margin_top = 6.0
|
||||
badge_style.content_margin_bottom = 6.0
|
||||
_scenery_edit_count_badge.add_theme_stylebox_override("panel", badge_style)
|
||||
var group_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
group_style.bg_color = _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
group_style.border_color = Color("#667386")
|
||||
group_style.set_border_width_all(1)
|
||||
group_style.set_corner_radius_all(14)
|
||||
group_style.content_margin_left = 2.0
|
||||
group_style.content_margin_right = 2.0
|
||||
group_style.content_margin_top = 2.0
|
||||
group_style.content_margin_bottom = 2.0
|
||||
_scenery_selection_group.add_theme_stylebox_override("panel", group_style)
|
||||
_scenery_edit_layout.add_theme_constant_override("separation", 12)
|
||||
_scenery_selection_actions.add_theme_constant_override("separation", 0)
|
||||
_style_scenery_selection_segment(_scenery_select_several_button, true, false)
|
||||
_style_scenery_selection_segment(_scenery_select_all_button, false, true)
|
||||
_layout_capture_bar()
|
||||
|
||||
|
||||
func _apply_scenery_capture_theme() -> void:
|
||||
theme = _stage_theme.control_theme() if _stage_theme != null else null
|
||||
var panel_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
panel_style.bg_color = _color("stage_panels.shell.surface_color", Color("#202633FA"))
|
||||
panel_style.border_color = Color("#667386")
|
||||
panel_style.set_border_width_all(2)
|
||||
panel_style.set_corner_radius_all(18)
|
||||
panel_style.content_margin_left = 18.0
|
||||
panel_style.content_margin_right = 18.0
|
||||
panel_style.content_margin_top = 14.0
|
||||
panel_style.content_margin_bottom = 14.0
|
||||
panel_style.shadow_color = Color(0.0, 0.0, 0.0, 0.34)
|
||||
panel_style.shadow_size = 8
|
||||
panel_style.shadow_offset = Vector2(0.0, 4.0)
|
||||
add_theme_stylebox_override("panel", panel_style)
|
||||
var font: Font = _stage_theme.font() if _stage_theme != null else ThemeDB.fallback_font
|
||||
if font != null:
|
||||
_prompt.add_theme_font_override("font", font)
|
||||
_prompt.add_theme_font_size_override("font_size", 25)
|
||||
_prompt.add_theme_color_override("font_color", _color("stage_panels.type.text_color", Color("#F5F7FA")))
|
||||
for button: Button in _custom_action_buttons:
|
||||
if font != null:
|
||||
button.add_theme_font_override("font", font)
|
||||
button.add_theme_font_size_override("font_size", 19)
|
||||
button.custom_minimum_size.y = SCENERY_EDIT_BUTTON_HEIGHT
|
||||
if bool(button.get_meta("capture_choice", false)):
|
||||
_style_scenery_depth_choice(button, bool(button.get_meta("capture_selected", false)))
|
||||
var margin: MarginContainer = button.get_child(0) as MarginContainer if button.get_child_count() > 0 else null
|
||||
var cue_stack: VBoxContainer = margin.get_child(0) as VBoxContainer if margin != null and margin.get_child_count() > 0 else null
|
||||
if cue_stack != null and cue_stack.get_child_count() > 1:
|
||||
var cue_label: Label = cue_stack.get_child(1) as Label
|
||||
if font != null:
|
||||
cue_label.add_theme_font_override("font", font)
|
||||
cue_label.add_theme_font_size_override("font_size", 17)
|
||||
cue_label.add_theme_color_override("font_color", Color("#F5F7FA"))
|
||||
else:
|
||||
_style_scenery_edit_button(button, bool(button.get_meta("capture_primary", false)), false)
|
||||
_actions_row.add_theme_constant_override("separation", 12)
|
||||
_layout_capture_bar()
|
||||
|
||||
|
||||
func _style_scenery_depth_choice(button: Button, selected: bool) -> void:
|
||||
var accent: Color = _color("mode_colors.edit", Color("#22C6FF"))
|
||||
var background: Color = _color("stage_panels.action.primary_color", Color("#087DBA")) if selected else _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
var hover: Color = _color("stage_panels.action.primary_hover_color", Color("#1596D3")) if selected else _color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
var border: Color = accent if selected else Color("#667386")
|
||||
var border_width: int = 2 if selected else 1
|
||||
button.add_theme_stylebox_override("normal", _scenery_depth_choice_button_style(background, border, border_width))
|
||||
button.add_theme_stylebox_override("hover", _scenery_depth_choice_button_style(hover, accent, 2))
|
||||
button.add_theme_stylebox_override("pressed", _scenery_depth_choice_button_style(background, accent, 2))
|
||||
button.add_theme_stylebox_override("hover_pressed", _scenery_depth_choice_button_style(hover, accent, 2))
|
||||
button.add_theme_stylebox_override("focus", _scenery_depth_choice_button_style(background, accent, 2))
|
||||
button.add_theme_color_override("font_color", Color("#F5F7FA"))
|
||||
button.add_theme_color_override("font_hover_color", Color("#F5F7FA"))
|
||||
button.add_theme_color_override("font_pressed_color", Color("#F5F7FA"))
|
||||
button.add_theme_color_override("font_focus_color", Color("#F5F7FA"))
|
||||
|
||||
|
||||
func _scenery_depth_choice_button_style(background: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = _scenery_button_style(background, border, border_width)
|
||||
style.content_margin_left = 6.0
|
||||
style.content_margin_right = 6.0
|
||||
style.content_margin_top = 4.0
|
||||
style.content_margin_bottom = 4.0
|
||||
return style
|
||||
|
||||
|
||||
func _style_scenery_edit_button(button: Button, primary: bool, destructive: bool) -> void:
|
||||
var accent: Color = _color("mode_colors.edit", Color("#22C6FF"))
|
||||
var danger: Color = _color("stage_panels.action.destructive_color", Color("#FF626B"))
|
||||
var border: Color = danger if destructive else (accent if primary else Color("#667386"))
|
||||
var background: Color = _color("stage_panels.action.primary_color", Color("#087DBA")) if primary else _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
var hover_background: Color = _color("stage_panels.action.primary_hover_color", Color("#1596D3")) if primary else _color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
var pressed_background: Color = _color("stage_panels.action.primary_pressed_color", Color("#066B9F")) if primary else _color("stage_panels.action.selected_surface", Color("#12394A"))
|
||||
var text_color: Color = Color("#F5F7FA")
|
||||
button.add_theme_stylebox_override("normal", _scenery_button_style(background, border, 2 if primary or destructive else 1))
|
||||
button.add_theme_stylebox_override("hover", _scenery_button_style(hover_background, border.lightened(0.12), 2))
|
||||
button.add_theme_stylebox_override("pressed", _scenery_button_style(pressed_background, border, 2))
|
||||
button.add_theme_stylebox_override("hover_pressed", _scenery_button_style(hover_background, border.lightened(0.12), 2))
|
||||
button.add_theme_stylebox_override("focus", _scenery_button_style(background, accent, 2))
|
||||
button.add_theme_color_override("font_color", text_color)
|
||||
button.add_theme_color_override("font_hover_color", text_color)
|
||||
button.add_theme_color_override("font_pressed_color", text_color)
|
||||
button.add_theme_color_override("font_focus_color", text_color)
|
||||
|
||||
|
||||
func _style_scenery_selection_segment(button: Button, is_first: bool, is_last: bool) -> void:
|
||||
var base: Color = _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
var hover: Color = _color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
var pressed: Color = _color("stage_panels.action.selected_surface", Color("#12394A"))
|
||||
var divider: Color = Color("#667386")
|
||||
var accent: Color = _color("mode_colors.edit", Color("#22C6FF"))
|
||||
button.add_theme_stylebox_override("normal", _scenery_segment_style(base, divider, is_first, is_last))
|
||||
button.add_theme_stylebox_override("hover", _scenery_segment_style(hover, divider, is_first, is_last))
|
||||
button.add_theme_stylebox_override("pressed", _scenery_segment_style(pressed, divider, is_first, is_last))
|
||||
button.add_theme_stylebox_override("hover_pressed", _scenery_segment_style(hover, divider, is_first, is_last))
|
||||
button.add_theme_stylebox_override("focus", _scenery_segment_style(base, accent, is_first, is_last, true))
|
||||
|
||||
|
||||
func _scenery_segment_style(background: Color, border: Color, is_first: bool, is_last: bool, is_focused: bool = false) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
if is_focused:
|
||||
style.set_border_width_all(2)
|
||||
else:
|
||||
style.border_width_left = 0 if is_first else 1
|
||||
style.border_width_right = 0
|
||||
style.border_width_top = 0
|
||||
style.border_width_bottom = 0
|
||||
style.corner_radius_top_left = 12 if is_first else 0
|
||||
style.corner_radius_bottom_left = 12 if is_first else 0
|
||||
style.corner_radius_top_right = 12 if is_last else 0
|
||||
style.corner_radius_bottom_right = 12 if is_last else 0
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 6.0
|
||||
style.content_margin_bottom = 6.0
|
||||
return style
|
||||
|
||||
|
||||
func _scenery_button_style(background: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(border_width)
|
||||
style.set_corner_radius_all(14)
|
||||
style.content_margin_left = 12.0
|
||||
style.content_margin_right = 12.0
|
||||
style.content_margin_top = 6.0
|
||||
style.content_margin_bottom = 6.0
|
||||
return style
|
||||
|
||||
|
||||
func _build_controls() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_layout_stash = Node.new()
|
||||
@@ -207,6 +496,100 @@ func _build_controls() -> void:
|
||||
_cancel_button.focus_mode = Control.FOCUS_ALL
|
||||
_cancel_button.pressed.connect(func() -> void: cancel_requested.emit())
|
||||
_actions_row.add_child(_cancel_button)
|
||||
_build_scenery_edit_controls()
|
||||
|
||||
|
||||
func _build_scenery_edit_controls() -> void:
|
||||
_scenery_edit_layout = VBoxContainer.new()
|
||||
_scenery_edit_layout.name = "SceneryEditLayout"
|
||||
_scenery_edit_layout.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_edit_layout.visible = false
|
||||
_layout_stash.add_child(_scenery_edit_layout)
|
||||
var header_row: HBoxContainer = HBoxContainer.new()
|
||||
header_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header_row.add_theme_constant_override("separation", 12)
|
||||
_scenery_edit_layout.add_child(header_row)
|
||||
_scenery_edit_prompt = Label.new()
|
||||
_scenery_edit_prompt.name = "SceneryPrompt"
|
||||
_scenery_edit_prompt.text = "Tap scenery to select it"
|
||||
_scenery_edit_prompt.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_scenery_edit_prompt.max_lines_visible = 2
|
||||
_scenery_edit_prompt.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_edit_prompt.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_scenery_edit_prompt.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
header_row.add_child(_scenery_edit_prompt)
|
||||
_scenery_edit_count_badge = PanelContainer.new()
|
||||
_scenery_edit_count_badge.name = "ScenerySelectionCountBadge"
|
||||
header_row.add_child(_scenery_edit_count_badge)
|
||||
_scenery_edit_count = Label.new()
|
||||
_scenery_edit_count.name = "ScenerySelectionCount"
|
||||
_scenery_edit_count.text = "0 selected"
|
||||
_scenery_edit_count.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_scenery_edit_count_badge.add_child(_scenery_edit_count)
|
||||
_scenery_clear_selection_button = _make_scenery_action_button("ClearSelection", "Clear selection", &"clear_selection")
|
||||
_scenery_clear_selection_button.custom_minimum_size.x = 150.0
|
||||
_scenery_clear_selection_button.visible = false
|
||||
header_row.add_child(_scenery_clear_selection_button)
|
||||
_scenery_edit_actions = HBoxContainer.new()
|
||||
_scenery_edit_actions.name = "SceneryEditActions"
|
||||
_scenery_edit_actions.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_scenery_edit_actions.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
_scenery_edit_actions.add_theme_constant_override("separation", 10)
|
||||
_scenery_edit_layout.add_child(_scenery_edit_actions)
|
||||
_scenery_selection_group = PanelContainer.new()
|
||||
_scenery_selection_group.name = "SelectionActionsGroup"
|
||||
_scenery_selection_group.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
_scenery_edit_actions.add_child(_scenery_selection_group)
|
||||
_scenery_selection_actions = HBoxContainer.new()
|
||||
_scenery_selection_actions.add_theme_constant_override("separation", 6)
|
||||
_scenery_selection_group.add_child(_scenery_selection_actions)
|
||||
_scenery_select_several_button = _make_scenery_action_button("SelectSeveral", "Select several", &"select_several")
|
||||
_scenery_select_several_button.custom_minimum_size.x = 158.0
|
||||
_scenery_selection_actions.add_child(_scenery_select_several_button)
|
||||
_scenery_select_all_button = _make_scenery_action_button("SelectAll", "Select all", &"select_all")
|
||||
_scenery_select_all_button.custom_minimum_size.x = 148.0
|
||||
_scenery_selection_actions.add_child(_scenery_select_all_button)
|
||||
_scenery_move_group_button = _make_scenery_action_button("MoveGroup", "Move together", &"move_group")
|
||||
_scenery_move_group_button.custom_minimum_size.x = 168.0
|
||||
_scenery_move_group_button.visible = false
|
||||
_scenery_selection_actions.add_child(_scenery_move_group_button)
|
||||
_scenery_resize_group_button = _make_scenery_action_button("ResizeGroup", "Resize together", &"resize_group")
|
||||
_scenery_resize_group_button.custom_minimum_size.x = 182.0
|
||||
_scenery_resize_group_button.visible = false
|
||||
_scenery_selection_actions.add_child(_scenery_resize_group_button)
|
||||
_scenery_erase_area_button = _make_scenery_action_button("EraseArea", "Erase Area", &"erase_area")
|
||||
_scenery_erase_area_button.custom_minimum_size.x = 152.0
|
||||
_scenery_edit_actions.add_child(_scenery_erase_area_button)
|
||||
_scenery_done_button = _make_scenery_action_button("DoneEditing", "Done editing", &"done_edit")
|
||||
_scenery_done_button.custom_minimum_size.x = 160.0
|
||||
_scenery_edit_actions.add_child(_scenery_done_button)
|
||||
|
||||
|
||||
func _make_scenery_action_button(button_name: String, label: String, action_id: StringName) -> Button:
|
||||
var button: Button = Button.new()
|
||||
button.name = button_name
|
||||
button.text = label
|
||||
button.tooltip_text = label
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.custom_minimum_size = Vector2(144.0, SCENERY_EDIT_BUTTON_HEIGHT)
|
||||
button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
button.pressed.connect(func() -> void: capture_action_requested.emit(action_id))
|
||||
return button
|
||||
|
||||
|
||||
func _show_standard_capture_layout() -> void:
|
||||
_scenery_edit_active = false
|
||||
_scenery_capture_active = false
|
||||
if _scenery_edit_layout != null and _scenery_edit_layout.get_parent() == self:
|
||||
_scenery_edit_layout.visible = false
|
||||
remove_child(_scenery_edit_layout)
|
||||
_layout_stash.add_child(_scenery_edit_layout)
|
||||
if _active_layout != null and _active_layout.get_parent() == _layout_stash:
|
||||
_layout_stash.remove_child(_active_layout)
|
||||
add_child(_active_layout)
|
||||
_active_layout.visible = true
|
||||
if is_node_ready():
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _clear_custom_actions() -> void:
|
||||
@@ -255,6 +638,12 @@ func _layout_capture_bar() -> void:
|
||||
if not is_node_ready() or _prompt == null:
|
||||
return
|
||||
var viewport_size: Vector2 = get_viewport_rect().size
|
||||
if _scenery_edit_active:
|
||||
_layout_scenery_edit_capture_bar(viewport_size)
|
||||
return
|
||||
if _scenery_capture_active:
|
||||
_layout_scenery_capture_bar(viewport_size)
|
||||
return
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var safe_left: float = inset
|
||||
var safe_right: float = viewport_size.x - inset
|
||||
@@ -281,6 +670,123 @@ func _layout_capture_bar() -> void:
|
||||
call_deferred("_fit_height_to_content")
|
||||
|
||||
|
||||
func _layout_scenery_edit_capture_bar(viewport_size: Vector2) -> void:
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var safe_left: float = inset
|
||||
var safe_right: float = viewport_size.x - inset
|
||||
if _has_horizontal_safe_bounds:
|
||||
safe_left = clampf(_safe_left, inset, maxf(inset, viewport_size.x - inset))
|
||||
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(SCENERY_EDIT_MAX_WIDTH, available_width)
|
||||
var narrow: bool = width < 700.0
|
||||
custom_minimum_size.x = width
|
||||
size.x = width
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
clip_contents = false
|
||||
position = Vector2(safe_left + (available_width - width) * 0.5, _top_bar_height + 56.0)
|
||||
_scenery_edit_prompt.add_theme_font_size_override("font_size", 27 if width >= 700.0 else (21 if width >= 520.0 else 18))
|
||||
if narrow and _scenery_erase_area_button.get_parent() == _scenery_edit_actions:
|
||||
_scenery_edit_actions.remove_child(_scenery_erase_area_button)
|
||||
_scenery_edit_layout.get_child(0).add_child(_scenery_erase_area_button)
|
||||
elif not narrow and _scenery_erase_area_button.get_parent() != _scenery_edit_actions:
|
||||
var header_row: HBoxContainer = _scenery_edit_layout.get_child(0) as HBoxContainer
|
||||
header_row.remove_child(_scenery_erase_area_button)
|
||||
_scenery_edit_actions.add_child(_scenery_erase_area_button)
|
||||
_scenery_edit_actions.move_child(_scenery_erase_area_button, 1)
|
||||
_scenery_selection_actions.add_theme_constant_override("separation", 0)
|
||||
_scenery_select_several_button.custom_minimum_size.x = 134.0 if narrow else 158.0
|
||||
_scenery_select_all_button.custom_minimum_size.x = 124.0 if narrow else 148.0
|
||||
_scenery_move_group_button.custom_minimum_size.x = 146.0 if narrow else 168.0
|
||||
_scenery_resize_group_button.custom_minimum_size.x = 158.0 if narrow else 182.0
|
||||
_scenery_done_button.custom_minimum_size.x = 142.0 if narrow else 160.0
|
||||
_scenery_erase_area_button.custom_minimum_size.x = 136.0 if narrow else 152.0
|
||||
_scenery_clear_selection_button.custom_minimum_size.x = 128.0 if narrow else 150.0
|
||||
for button: Button in [_scenery_select_several_button, _scenery_select_all_button, _scenery_move_group_button, _scenery_resize_group_button, _scenery_clear_selection_button, _scenery_erase_area_button, _scenery_done_button]:
|
||||
button.add_theme_font_size_override("font_size", 19 if width >= 700.0 else 16)
|
||||
button.custom_minimum_size.y = 52.0 if narrow else SCENERY_EDIT_BUTTON_HEIGHT
|
||||
button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
_scenery_edit_actions.add_theme_constant_override("separation", 8 if narrow else 10)
|
||||
call_deferred("_fit_scenery_edit_height")
|
||||
|
||||
|
||||
func _layout_scenery_capture_bar(viewport_size: Vector2) -> void:
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var available_width: float = maxf(1.0, viewport_size.x - inset * 2.0)
|
||||
var width: float = minf(SCENERY_CAPTURE_MAX_WIDTH, available_width)
|
||||
var button_count: int = maxi(1, _custom_action_buttons.size())
|
||||
var has_depth_choices: bool = button_count >= 5
|
||||
var horizontal_depth_cards: bool = has_depth_choices and width >= 700.0
|
||||
var compact_spacing: float = 8.0 if has_depth_choices and width < 760.0 else 12.0
|
||||
var minimum_row_width: float = 40.0 + 5.0 * 84.0 + float(button_count - 1) * compact_spacing
|
||||
var stack_actions: bool = width < ACTION_ROW_MIN_WIDTH or (has_depth_choices and width < minimum_row_width)
|
||||
custom_minimum_size.x = width
|
||||
size.x = width
|
||||
size_flags_horizontal = Control.SIZE_SHRINK_CENTER
|
||||
clip_contents = false
|
||||
position = Vector2((viewport_size.x - width) * 0.5, _top_bar_height + 56.0)
|
||||
_prompt.add_theme_font_size_override("font_size", 25 if width >= 520.0 else 19)
|
||||
_set_layout(true, stack_actions)
|
||||
_actions_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
var available_button_width: float = maxf(1.0, width - 40.0 - compact_spacing * float(button_count - 1))
|
||||
var depth_button_width: float = available_button_width
|
||||
var secondary_button_width: float = available_button_width
|
||||
if stack_actions:
|
||||
depth_button_width = maxf(120.0, width - 72.0)
|
||||
secondary_button_width = depth_button_width
|
||||
elif has_depth_choices:
|
||||
secondary_button_width = clampf(available_button_width * 0.16, 84.0, 132.0)
|
||||
depth_button_width = maxf(84.0, (available_button_width - secondary_button_width * 2.0) / 3.0)
|
||||
else:
|
||||
depth_button_width = minf(ACTION_BUTTON_WIDTH, maxf(120.0, available_button_width / float(button_count)))
|
||||
secondary_button_width = depth_button_width
|
||||
var cue_size := Vector2(56.0, 42.0)
|
||||
var choice_button_height: float = 84.0
|
||||
if width >= 560.0:
|
||||
cue_size = Vector2(64.0, 48.0)
|
||||
choice_button_height = 88.0
|
||||
if width >= 700.0:
|
||||
cue_size = Vector2(66.0, 50.0)
|
||||
choice_button_height = 100.0
|
||||
if width >= 800.0:
|
||||
cue_size = Vector2(96.0, 72.0)
|
||||
choice_button_height = 112.0
|
||||
for button: Button in _custom_action_buttons:
|
||||
var is_depth_choice: bool = bool(button.get_meta("capture_choice", false))
|
||||
button.custom_minimum_size = Vector2(depth_button_width if is_depth_choice else secondary_button_width, choice_button_height if has_depth_choices else SCENERY_CAPTURE_BUTTON_HEIGHT)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL if stack_actions else Control.SIZE_SHRINK_CENTER
|
||||
if is_depth_choice:
|
||||
var margin: MarginContainer = button.get_child(0) as MarginContainer if button.get_child_count() > 0 else null
|
||||
var cue_layout: BoxContainer = margin.get_child(0) as BoxContainer if margin != null and margin.get_child_count() > 0 else null
|
||||
if cue_layout != null and cue_layout.get_child_count() > 1:
|
||||
cue_layout.vertical = not horizontal_depth_cards
|
||||
cue_layout.add_theme_constant_override("separation", 8 if horizontal_depth_cards else 2)
|
||||
var cue: Control = cue_layout.get_child(0) as Control
|
||||
if cue != null:
|
||||
cue.custom_minimum_size = cue_size
|
||||
var cue_label: Label = cue_layout.get_child(1) as Label
|
||||
if cue_label != null:
|
||||
cue_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT if horizontal_depth_cards else HORIZONTAL_ALIGNMENT_CENTER
|
||||
cue_label.add_theme_font_size_override("font_size", 20 if width >= 800.0 else (18 if width >= 620.0 else 17))
|
||||
button.add_theme_font_size_override("font_size", 17 if width < 760.0 else 19)
|
||||
_actions_row.add_theme_constant_override("separation", int(compact_spacing))
|
||||
call_deferred("_fit_scenery_capture_height_after_layout")
|
||||
|
||||
|
||||
func _fit_scenery_capture_height_after_layout() -> void:
|
||||
await get_tree().process_frame
|
||||
_fit_height_to_content()
|
||||
|
||||
|
||||
func _fit_scenery_edit_height() -> void:
|
||||
if not is_inside_tree() or not visible or not _scenery_edit_active:
|
||||
return
|
||||
var panel_style: StyleBox = get_theme_stylebox("panel")
|
||||
var panel_height: float = panel_style.get_minimum_size().y if panel_style != null else 0.0
|
||||
custom_minimum_size.y = _scenery_edit_layout.get_combined_minimum_size().y + panel_height
|
||||
size.y = custom_minimum_size.y
|
||||
|
||||
|
||||
func _set_layout(compact: bool, stack_actions: bool, wrapped: bool = false) -> void:
|
||||
var target_layout: Container = _wrapped_layout if wrapped else (_vertical_layout if compact else _horizontal_layout)
|
||||
var target_actions: BoxContainer = _actions_column if stack_actions else _actions_row
|
||||
@@ -348,12 +854,13 @@ func _apply_compact_action_labels(compact: bool) -> void:
|
||||
"cancel_group": "Cancel",
|
||||
"done_tool": "Done",
|
||||
"cancel_tool": "Cancel",
|
||||
"cancel_single": "Cancel",
|
||||
"depth_far": "Far",
|
||||
"depth_near": "Near",
|
||||
"depth_floor": "Floor",
|
||||
"depth_far": "Back",
|
||||
"depth_near": "Middle",
|
||||
"depth_floor": "Front",
|
||||
}
|
||||
for button: Button in _custom_action_buttons:
|
||||
if bool(button.get_meta("capture_choice", false)):
|
||||
continue
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user