feat: harmonize Prop and Stickman editor UI
This commit is contained in:
+276
-118
@@ -13,6 +13,8 @@ extends Control
|
||||
const PropFileScript := preload("res://scripts/editors/prop/prop_file.gd")
|
||||
const PropThemeScript := preload("res://scripts/editors/prop/prop_theme.gd")
|
||||
const EditorVisualThemeScript := preload("res://scripts/shared/editor_ui/editor_visual_theme.gd")
|
||||
const SandboxThemeScript := preload("res://scripts/stage/sandbox_theme.gd")
|
||||
const StageDialogStylerScript := preload("res://scripts/shared/stage_ui/stage_dialog_styler.gd")
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
const PropCanvasScript := preload("res://scripts/editors/prop/prop_canvas.gd")
|
||||
const PropThumbnailScript := preload("res://scripts/shared/thumbnails/prop_thumbnail.gd")
|
||||
@@ -20,6 +22,7 @@ const ThumbnailCacheScript := preload("res://scripts/shared/thumbnails/thumbnail
|
||||
const PropAnchorDataScript := preload("res://scripts/props/prop_anchor_data.gd")
|
||||
const PropAttachPreviewScene := preload("res://scenes/editors/prop/prop_attach_preview.tscn")
|
||||
const ContentPathsScript := preload("res://scripts/shared/content_paths.gd")
|
||||
const PropInspectorIconsScript := preload("res://scripts/editors/prop/prop_inspector_icons.gd")
|
||||
|
||||
const PRESET_LABELS: PackedStringArray = ["None", "Wood", "Rubber", "Cardboard", "Metal"]
|
||||
const PRIMITIVE_IDS: PackedStringArray = ["box", "plank", "triangle", "line", "circle"]
|
||||
@@ -116,6 +119,7 @@ static func should_warn_off_origin(has_geometry: bool, bounds: Rect2) -> bool:
|
||||
|
||||
var _theme: PropTheme
|
||||
var _editor_visual_theme: EditorVisualTheme
|
||||
var _dialog_theme: SandboxTheme
|
||||
var _material_preset: int = PropBlockScript.MaterialPreset.NONE
|
||||
var _current_path: String = ""
|
||||
var _dirty: bool = false
|
||||
@@ -128,11 +132,14 @@ var _thumb_cache: ThumbnailCache
|
||||
var _ui_theme: Theme
|
||||
|
||||
# Inspector controls
|
||||
var _preset_option: OptionButton
|
||||
var _material_section: VBoxContainer
|
||||
var _preset_option: PropInspectorDropdown
|
||||
var _material_section: PanelContainer
|
||||
var _mass_label: Label
|
||||
var _mass_value: Label
|
||||
var _friction_label: Label
|
||||
var _friction_value: Label
|
||||
var _bounce_label: Label
|
||||
var _bounce_value: Label
|
||||
var _fill_button: ColorPickerButton
|
||||
var _outline_button: ColorPickerButton
|
||||
var _width_spin: SpinBox
|
||||
@@ -143,14 +150,14 @@ var _vertex_label: Label
|
||||
var _delete_button: Button
|
||||
var _create_point_button: Button
|
||||
var _create_point_hint: Label
|
||||
var _shape_section: VBoxContainer
|
||||
var _primitive_option: OptionButton
|
||||
var _shape_section: PanelContainer
|
||||
var _primitive_option: PropInspectorDropdown
|
||||
var _params_box: VBoxContainer
|
||||
var _param_spins: Dictionary = {}
|
||||
var _add_button: Button
|
||||
var _updating_inspector: bool = false
|
||||
var _tag_checks: Dictionary = {}
|
||||
var _tag_section: VBoxContainer
|
||||
var _tag_section: PanelContainer
|
||||
var _stage_authoring: bool = false
|
||||
var _stage_return_pending: bool = false
|
||||
var _stage_place_button: Button
|
||||
@@ -177,7 +184,7 @@ var _points_button: Button
|
||||
var _attach_button: Button
|
||||
|
||||
# Add-shape inspector section (hidden in Reshape mode, Round 3)
|
||||
var _add_section: VBoxContainer
|
||||
var _add_section: PanelContainer
|
||||
var _anchor_section: VBoxContainer
|
||||
var _anchor_list: ItemList
|
||||
var _anchor_role: OptionButton
|
||||
@@ -218,6 +225,8 @@ func _ready() -> void:
|
||||
get_tree().auto_accept_quit = false
|
||||
_editor_visual_theme = EditorVisualThemeScript.new()
|
||||
_editor_visual_theme.load_from(EditorVisualTheme.DEFAULT_PATH)
|
||||
_dialog_theme = SandboxThemeScript.new()
|
||||
_dialog_theme.load_from(SandboxThemeScript.DEFAULT_PATH)
|
||||
_theme = PropThemeScript.new()
|
||||
_theme.load_from(PropThemeScript.DEFAULT_PATH)
|
||||
_thumb_cache = ThumbnailCacheScript.new()
|
||||
@@ -254,6 +263,7 @@ func _ready() -> void:
|
||||
_canvas.set_grid_size(_theme.grid_size())
|
||||
_canvas.set_snap_enabled(false)
|
||||
_canvas.set_has_clipboard(false)
|
||||
_canvas.set_context_menu_visual_theme(_editor_visual_theme, &"prop")
|
||||
_canvas.set_context_menu_font_size(_theme.font_context_menu())
|
||||
_canvas.set_anchor_data(_anchors)
|
||||
var origin_marker_color: Color = _theme.text_muted_color()
|
||||
@@ -503,26 +513,26 @@ func _setup_menu_bar() -> void:
|
||||
var file_menu := PopupMenu.new()
|
||||
file_menu.name = "FileMenu"
|
||||
file_menu.add_item("🆕 New", MENU_NEW)
|
||||
file_menu.add_item("💾 Save", MENU_SAVE)
|
||||
file_menu.add_item("📄 Save As...", MENU_SAVE_AS)
|
||||
file_menu.add_item("📂 Load...", MENU_LOAD)
|
||||
file_menu.add_separator()
|
||||
file_menu.add_item("📂 Load…", MENU_LOAD)
|
||||
var recent_popup := PopupMenu.new()
|
||||
recent_popup.name = "RecentProps"
|
||||
recent_popup.id_pressed.connect(_on_recent_prop_selected)
|
||||
file_menu.add_child(recent_popup)
|
||||
file_menu.add_submenu_item("🕑 Recent", "RecentProps")
|
||||
file_menu.add_submenu_item("🕑 Open Recent", "RecentProps")
|
||||
file_menu.add_separator()
|
||||
file_menu.add_item("💾 Save", MENU_SAVE)
|
||||
file_menu.add_item("📄 Save As…", MENU_SAVE_AS)
|
||||
file_menu.id_pressed.connect(_on_menu_id_pressed)
|
||||
file_menu.about_to_popup.connect(_update_recent_menu)
|
||||
_menu_bar.add_child(file_menu)
|
||||
_menu_bar.set_menu_title(_menu_bar.get_menu_count() - 1, "📁 File")
|
||||
_menu_bar.set_menu_title(_menu_bar.get_menu_count() - 1, "File")
|
||||
|
||||
var view_menu := PopupMenu.new()
|
||||
view_menu.name = "ViewMenu"
|
||||
view_menu.add_item("🔍 Reset View", MENU_RESET_VIEWS)
|
||||
view_menu.add_item("🔍 Reset Views", MENU_RESET_VIEWS)
|
||||
view_menu.id_pressed.connect(_on_menu_id_pressed)
|
||||
_menu_bar.add_child(view_menu)
|
||||
_menu_bar.set_menu_title(_menu_bar.get_menu_count() - 1, "👁️ View")
|
||||
_menu_bar.set_menu_title(_menu_bar.get_menu_count() - 1, "View")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -617,25 +627,31 @@ func _setup_dialogs() -> void:
|
||||
_editor_visual_theme.style_button_role(_save_dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
_editor_visual_theme.style_button_role(_load_dialog.get_ok_button(), &"primary", &"prop")
|
||||
_editor_visual_theme.style_button_role(_load_dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
_editor_visual_theme.style_button_role(_error_dialog.get_ok_button(), &"secondary", &"prop")
|
||||
_error_dialog.get_ok_button().text = "Got it"
|
||||
StageDialogStylerScript.apply(
|
||||
_error_dialog, _dialog_theme, _dialog_theme.font(),
|
||||
_error_dialog.get_ok_button()
|
||||
)
|
||||
_build_exit_dialog()
|
||||
_rename_anchor_dialog = ConfirmationDialog.new()
|
||||
_rename_anchor_dialog.title = "Rename Anchor"
|
||||
_rename_anchor_dialog.ok_button_text = "Rename"
|
||||
_rename_anchor_dialog.theme = _ui_theme
|
||||
_editor_visual_theme.style_button_role(_rename_anchor_dialog.get_ok_button(), &"primary", &"prop")
|
||||
_editor_visual_theme.style_button_role(_rename_anchor_dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
_rename_anchor_dialog.set_meta(StageDialogStylerScript.ICON_META, "📝")
|
||||
_rename_anchor_edit = LineEdit.new()
|
||||
_rename_anchor_edit.placeholder_text = "Anchor name"
|
||||
_rename_anchor_dialog.add_child(_rename_anchor_edit)
|
||||
add_child(_rename_anchor_dialog)
|
||||
StageDialogStylerScript.apply(
|
||||
_rename_anchor_dialog, _dialog_theme, _dialog_theme.font(),
|
||||
_rename_anchor_dialog.get_ok_button()
|
||||
)
|
||||
_rename_anchor_edit.text_changed.connect(_update_rename_anchor_confirmation)
|
||||
_rename_anchor_edit.text_submitted.connect(_on_rename_anchor_text_submitted)
|
||||
_rename_anchor_dialog.confirmed.connect(_commit_anchor_rename)
|
||||
_rename_anchor_dialog.canceled.connect(_clear_pending_anchor_rename)
|
||||
|
||||
func _on_rename_anchor_text_submitted(_text: String) -> void:
|
||||
if _rename_anchor_dialog == null or not _rename_anchor_dialog.visible:
|
||||
if _rename_anchor_dialog == null or not StageDialogStylerScript.is_open(_rename_anchor_dialog):
|
||||
return
|
||||
var ok_button := _rename_anchor_dialog.get_ok_button()
|
||||
if not ok_button.disabled:
|
||||
@@ -886,30 +902,31 @@ func _on_whole_prop_rotate_requested(angle_rad: float) -> void:
|
||||
|
||||
func _build_exit_dialog() -> void:
|
||||
_exit_dialog = ConfirmationDialog.new()
|
||||
_exit_dialog.title = "⚠️ Unsaved Changes"
|
||||
_exit_dialog.title = "Unsaved Changes"
|
||||
_exit_dialog.set_meta(StageDialogStylerScript.ICON_META, "💾")
|
||||
_exit_dialog.dialog_text = "You have unsaved changes. Save them before quitting?"
|
||||
_exit_dialog.ok_button_text = "💾 Save"
|
||||
var discard_button: Button = _exit_dialog.add_button("🗑️ Discard", false, "discard")
|
||||
_exit_dialog.get_cancel_button().text = "❌ Cancel"
|
||||
_exit_dialog.theme = _ui_theme
|
||||
_editor_visual_theme.style_button_role(_exit_dialog.get_ok_button(), &"primary", &"prop")
|
||||
_editor_visual_theme.style_button_role(discard_button, &"destructive", &"prop")
|
||||
_editor_visual_theme.style_button_role(_exit_dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
_exit_dialog.ok_button_text = "Save"
|
||||
var discard_button: Button = _exit_dialog.add_button("Discard", false, "discard")
|
||||
_exit_dialog.get_cancel_button().text = "Cancel"
|
||||
add_child(_exit_dialog)
|
||||
StageDialogStylerScript.apply(
|
||||
_exit_dialog, _dialog_theme, _dialog_theme.font(),
|
||||
_exit_dialog.get_ok_button(), [discard_button]
|
||||
)
|
||||
_exit_dialog.confirmed.connect(_on_exit_save)
|
||||
_exit_dialog.custom_action.connect(_on_exit_custom_action)
|
||||
_exit_dialog.canceled.connect(_on_exit_cancel)
|
||||
_off_origin_save_dialog = ConfirmationDialog.new()
|
||||
_off_origin_save_dialog.title = "🎯 Center Prop?"
|
||||
_off_origin_save_dialog.dialog_text = "This prop is far from (0,0). Centering it keeps its shapes and anchors together.\n\nWhat would you like to do?"
|
||||
_off_origin_save_dialog.ok_button_text = "🎯 Center & Save"
|
||||
var save_anyway_button: Button = _off_origin_save_dialog.add_button("💾 Save Anyway", false, "save_anyway")
|
||||
_off_origin_save_dialog.get_cancel_button().text = "❌ Cancel"
|
||||
_off_origin_save_dialog.theme = _ui_theme
|
||||
_editor_visual_theme.style_button_role(_off_origin_save_dialog.get_ok_button(), &"primary", &"prop")
|
||||
_editor_visual_theme.style_button_role(save_anyway_button, &"secondary", &"prop")
|
||||
_editor_visual_theme.style_button_role(_off_origin_save_dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
_off_origin_save_dialog.ok_button_text = "Center & Save"
|
||||
var save_anyway_button: Button = _off_origin_save_dialog.add_button("Save Anyway", false, "save_anyway")
|
||||
_off_origin_save_dialog.get_cancel_button().text = "Cancel"
|
||||
add_child(_off_origin_save_dialog)
|
||||
StageDialogStylerScript.apply(
|
||||
_off_origin_save_dialog, _dialog_theme, _dialog_theme.font(),
|
||||
_off_origin_save_dialog.get_ok_button(), [], [save_anyway_button]
|
||||
)
|
||||
_off_origin_save_dialog.confirmed.connect(func() -> void: await _execute_pending_save(true))
|
||||
_off_origin_save_dialog.custom_action.connect(func(action: StringName) -> void:
|
||||
if action == &"save_anyway": await _execute_pending_save(false))
|
||||
@@ -931,7 +948,7 @@ func _request_close() -> void:
|
||||
_pending_quit = true
|
||||
_pending_new = false
|
||||
_exit_dialog.dialog_text = "You have unsaved changes. Save them before quitting?"
|
||||
_exit_dialog.popup_centered()
|
||||
StageDialogStylerScript.popup(_exit_dialog)
|
||||
|
||||
|
||||
func _request_new() -> void:
|
||||
@@ -942,7 +959,7 @@ func _request_new() -> void:
|
||||
_pending_new = true
|
||||
_pending_quit = false
|
||||
_exit_dialog.dialog_text = "You have unsaved changes. Save them before starting a new prop?"
|
||||
_exit_dialog.popup_centered()
|
||||
StageDialogStylerScript.popup(_exit_dialog)
|
||||
|
||||
|
||||
func _request_stage_return() -> void:
|
||||
@@ -958,11 +975,11 @@ func _request_stage_return() -> void:
|
||||
_pending_new = false
|
||||
_pending_quit = false
|
||||
_exit_dialog.dialog_text = "Save your artwork before returning? Saving places it on the Stage."
|
||||
_exit_dialog.popup_centered()
|
||||
StageDialogStylerScript.popup(_exit_dialog)
|
||||
|
||||
|
||||
func _on_exit_save() -> void:
|
||||
_exit_dialog.hide()
|
||||
StageDialogStylerScript.hide(_exit_dialog)
|
||||
if _current_path.is_empty():
|
||||
_save_dialog.popup_centered()
|
||||
return
|
||||
@@ -971,7 +988,7 @@ func _on_exit_save() -> void:
|
||||
|
||||
func _on_exit_custom_action(action: StringName) -> void:
|
||||
if action == &"discard":
|
||||
_exit_dialog.hide()
|
||||
StageDialogStylerScript.hide(_exit_dialog)
|
||||
if not _stage_authoring:
|
||||
_clear_autosave()
|
||||
if _stage_return_pending:
|
||||
@@ -1046,47 +1063,60 @@ func _on_menu_id_pressed(id: int) -> void:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _build_inspector() -> void:
|
||||
var header_size: int = _theme.font_header()
|
||||
var row_height: int = _theme.row_min_height()
|
||||
_inspector_list.add_theme_constant_override("separation", 10)
|
||||
_inspector_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
|
||||
# Material preset section
|
||||
_material_section = VBoxContainer.new()
|
||||
_material_section.add_theme_constant_override("separation", 6)
|
||||
_material_section = _make_inspector_card()
|
||||
_inspector_list.add_child(_material_section)
|
||||
var material_title := Label.new()
|
||||
material_title.text = "Material"
|
||||
material_title.add_theme_font_size_override("font_size", header_size)
|
||||
_material_section.add_child(material_title)
|
||||
var material_content: VBoxContainer = _inspector_card_content(_material_section)
|
||||
material_content.add_child(_make_inspector_header("Material", PropInspectorIconsScript.section("material")))
|
||||
|
||||
_preset_option = OptionButton.new()
|
||||
for label: String in PRESET_LABELS:
|
||||
_preset_option.add_item(label)
|
||||
_preset_option = PropInspectorDropdown.new()
|
||||
for index: int in PRESET_LABELS.size():
|
||||
_preset_option.add_icon_item(
|
||||
PropInspectorIconsScript.material(index, _theme.material_tint(index, "fill")),
|
||||
PRESET_LABELS[index]
|
||||
)
|
||||
_preset_option.selected = _material_preset
|
||||
_preset_option.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_preset_option.configure(
|
||||
_ui_theme,
|
||||
_theme.inspector_background_color(),
|
||||
_editor_visual_theme.chrome_color(&"selected_surface", &"prop"),
|
||||
_theme.border_color(),
|
||||
_theme.accent_color(),
|
||||
_editor_visual_theme.chrome_color(&"focus", &"prop"),
|
||||
_theme.text_color(),
|
||||
_theme.font_body()
|
||||
)
|
||||
_preset_option.item_selected.connect(_on_preset_selected)
|
||||
_material_section.add_child(_preset_option)
|
||||
material_content.add_child(_preset_option)
|
||||
|
||||
_mass_label = Label.new()
|
||||
_friction_label = Label.new()
|
||||
_bounce_label = Label.new()
|
||||
_material_section.add_child(_mass_label)
|
||||
_material_section.add_child(_friction_label)
|
||||
_material_section.add_child(_bounce_label)
|
||||
var stats := HBoxContainer.new()
|
||||
stats.add_theme_constant_override("separation", 6)
|
||||
material_content.add_child(stats)
|
||||
var mass_tile: Array[Label] = _make_stat_tile(stats, "Mass")
|
||||
_mass_label = mass_tile[0]
|
||||
_mass_value = mass_tile[1]
|
||||
var grip_tile: Array[Label] = _make_stat_tile(stats, "Surface grip")
|
||||
_friction_label = grip_tile[0]
|
||||
_friction_value = grip_tile[1]
|
||||
var bounce_tile: Array[Label] = _make_stat_tile(stats, "Bounce")
|
||||
_bounce_label = bounce_tile[0]
|
||||
_bounce_value = bounce_tile[1]
|
||||
_update_physics_labels()
|
||||
|
||||
# Selected-shape section
|
||||
_shape_section = VBoxContainer.new()
|
||||
_shape_section.add_theme_constant_override("separation", 6)
|
||||
_shape_section = _make_inspector_card()
|
||||
_inspector_list.add_child(_shape_section)
|
||||
|
||||
var shape_title := Label.new()
|
||||
shape_title.text = "Selected Shape"
|
||||
shape_title.add_theme_font_size_override("font_size", header_size)
|
||||
_shape_section.add_child(shape_title)
|
||||
var shape_content: VBoxContainer = _inspector_card_content(_shape_section)
|
||||
shape_content.add_child(_make_inspector_header("Selected Shape", PropInspectorIconsScript.section("selected_shape")))
|
||||
|
||||
var fill_row := HBoxContainer.new()
|
||||
fill_row.custom_minimum_size.y = float(row_height)
|
||||
_shape_section.add_child(fill_row)
|
||||
shape_content.add_child(fill_row)
|
||||
var fill_label := Label.new()
|
||||
fill_label.text = "Fill:"
|
||||
fill_label.custom_minimum_size = Vector2(90.0, 0.0)
|
||||
@@ -1099,7 +1129,7 @@ func _build_inspector() -> void:
|
||||
|
||||
var outline_row := HBoxContainer.new()
|
||||
outline_row.custom_minimum_size.y = float(row_height)
|
||||
_shape_section.add_child(outline_row)
|
||||
shape_content.add_child(outline_row)
|
||||
var outline_label := Label.new()
|
||||
outline_label.text = "Outline:"
|
||||
outline_label.custom_minimum_size = Vector2(90.0, 0.0)
|
||||
@@ -1112,7 +1142,7 @@ func _build_inspector() -> void:
|
||||
|
||||
var width_row := HBoxContainer.new()
|
||||
width_row.custom_minimum_size.y = float(row_height)
|
||||
_shape_section.add_child(width_row)
|
||||
shape_content.add_child(width_row)
|
||||
var width_label := Label.new()
|
||||
width_label.text = "Width:"
|
||||
width_label.custom_minimum_size = Vector2(90.0, 0.0)
|
||||
@@ -1128,7 +1158,7 @@ func _build_inspector() -> void:
|
||||
|
||||
_radius_row = HBoxContainer.new()
|
||||
_radius_row.custom_minimum_size.y = float(row_height)
|
||||
_shape_section.add_child(_radius_row)
|
||||
shape_content.add_child(_radius_row)
|
||||
var radius_label := Label.new()
|
||||
radius_label.text = "Radius:"
|
||||
radius_label.custom_minimum_size = Vector2(90.0, 0.0)
|
||||
@@ -1145,10 +1175,10 @@ func _build_inspector() -> void:
|
||||
_reshaped_note = Label.new()
|
||||
_reshaped_note.text = "✏️ This shape was reshaped"
|
||||
_reshaped_note.add_theme_color_override("font_color", _theme.text_muted_color())
|
||||
_shape_section.add_child(_reshaped_note)
|
||||
shape_content.add_child(_reshaped_note)
|
||||
|
||||
_vertex_label = Label.new()
|
||||
_shape_section.add_child(_vertex_label)
|
||||
shape_content.add_child(_vertex_label)
|
||||
|
||||
_create_point_button = Button.new()
|
||||
_create_point_button.text = "📍 Create Point"
|
||||
@@ -1156,46 +1186,55 @@ func _build_inspector() -> void:
|
||||
_create_point_button.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_editor_visual_theme.style_button_role(_create_point_button, &"primary", &"prop")
|
||||
_create_point_button.pressed.connect(_on_create_point_pressed)
|
||||
_shape_section.add_child(_create_point_button)
|
||||
shape_content.add_child(_create_point_button)
|
||||
_create_point_hint = Label.new()
|
||||
_create_point_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_create_point_hint.add_theme_color_override("font_color", _theme.text_muted_color())
|
||||
_shape_section.add_child(_create_point_hint)
|
||||
shape_content.add_child(_create_point_hint)
|
||||
|
||||
_delete_button = Button.new()
|
||||
_delete_button.text = "Erase Shape"
|
||||
_delete_button.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_editor_visual_theme.style_button_role(_delete_button, &"destructive", &"prop")
|
||||
_delete_button.pressed.connect(_on_delete_shape_pressed)
|
||||
_shape_section.add_child(_delete_button)
|
||||
shape_content.add_child(_delete_button)
|
||||
|
||||
# Add-shape section (hidden in Reshape mode, Round 3)
|
||||
_add_section = VBoxContainer.new()
|
||||
_add_section.add_theme_constant_override("separation", 8)
|
||||
_add_section = _make_inspector_card()
|
||||
_inspector_list.add_child(_add_section)
|
||||
var add_content: VBoxContainer = _inspector_card_content(_add_section)
|
||||
add_content.add_child(_make_inspector_header("Add Shape", PropInspectorIconsScript.section("add_shape")))
|
||||
|
||||
var add_title := Label.new()
|
||||
add_title.text = "Add Shape"
|
||||
add_title.add_theme_font_size_override("font_size", header_size)
|
||||
_add_section.add_child(add_title)
|
||||
|
||||
_primitive_option = OptionButton.new()
|
||||
for label: String in PRIMITIVE_LABELS:
|
||||
_primitive_option.add_item(label)
|
||||
_primitive_option = PropInspectorDropdown.new()
|
||||
for index: int in PRIMITIVE_LABELS.size():
|
||||
_primitive_option.add_icon_item(
|
||||
PropInspectorIconsScript.shape(PRIMITIVE_IDS[index], _primitive_icon_color(PRIMITIVE_IDS[index])),
|
||||
PRIMITIVE_LABELS[index]
|
||||
)
|
||||
_primitive_option.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_primitive_option.configure(
|
||||
_ui_theme,
|
||||
_theme.inspector_background_color(),
|
||||
_editor_visual_theme.chrome_color(&"selected_surface", &"prop"),
|
||||
_theme.border_color(),
|
||||
_theme.accent_color(),
|
||||
_editor_visual_theme.chrome_color(&"focus", &"prop"),
|
||||
_theme.text_color(),
|
||||
_theme.font_body()
|
||||
)
|
||||
_primitive_option.item_selected.connect(_on_primitive_selected)
|
||||
_add_section.add_child(_primitive_option)
|
||||
add_content.add_child(_primitive_option)
|
||||
|
||||
_params_box = VBoxContainer.new()
|
||||
_params_box.add_theme_constant_override("separation", 4)
|
||||
_add_section.add_child(_params_box)
|
||||
add_content.add_child(_params_box)
|
||||
|
||||
_add_button = Button.new()
|
||||
_add_button.text = "Add a Shape"
|
||||
_add_button.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_editor_visual_theme.style_button_role(_add_button, &"primary", &"prop")
|
||||
_add_button.pressed.connect(_on_add_pressed)
|
||||
_add_section.add_child(_add_button)
|
||||
add_content.add_child(_add_button)
|
||||
|
||||
_apply_control_theme(_inspector_list)
|
||||
_rebuild_primitive_params()
|
||||
@@ -1204,6 +1243,117 @@ func _build_inspector() -> void:
|
||||
_refresh_tag_controls()
|
||||
|
||||
|
||||
func _make_inspector_card() -> PanelContainer:
|
||||
var card := PanelContainer.new()
|
||||
card.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = _theme.inspector_background_color().darkened(0.07)
|
||||
style.border_color = _theme.border_color()
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(10)
|
||||
style.content_margin_left = 10.0
|
||||
style.content_margin_top = 10.0
|
||||
style.content_margin_right = 10.0
|
||||
style.content_margin_bottom = 10.0
|
||||
card.add_theme_stylebox_override("panel", style)
|
||||
var content := VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 8)
|
||||
card.add_child(content)
|
||||
return card
|
||||
|
||||
|
||||
func _inspector_card_content(card: PanelContainer) -> VBoxContainer:
|
||||
return card.get_child(0) as VBoxContainer
|
||||
|
||||
|
||||
func _make_inspector_header(title: String, icon: Texture2D) -> HBoxContainer:
|
||||
var header := HBoxContainer.new()
|
||||
header.add_theme_constant_override("separation", 8)
|
||||
var icon_view := TextureRect.new()
|
||||
icon_view.texture = icon
|
||||
icon_view.custom_minimum_size = Vector2(30.0, 30.0)
|
||||
icon_view.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
icon_view.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
icon_view.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
header.add_child(icon_view)
|
||||
var label := Label.new()
|
||||
label.text = title
|
||||
label.add_theme_font_size_override("font_size", _theme.font_header())
|
||||
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
header.add_child(label)
|
||||
return header
|
||||
|
||||
|
||||
func _make_stat_tile(parent: Control, caption_text: String) -> Array[Label]:
|
||||
var tile := PanelContainer.new()
|
||||
tile.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = _editor_visual_theme.chrome_color(&"background", &"prop").darkened(0.02)
|
||||
style.border_color = _theme.border_color()
|
||||
style.set_border_width_all(1)
|
||||
style.set_corner_radius_all(8)
|
||||
style.content_margin_left = 7.0
|
||||
style.content_margin_top = 7.0
|
||||
style.content_margin_right = 7.0
|
||||
style.content_margin_bottom = 7.0
|
||||
tile.add_theme_stylebox_override("panel", style)
|
||||
parent.add_child(tile)
|
||||
var content := VBoxContainer.new()
|
||||
content.add_theme_constant_override("separation", 1)
|
||||
tile.add_child(content)
|
||||
var caption := Label.new()
|
||||
caption.text = caption_text
|
||||
caption.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
caption.add_theme_font_size_override("font_size", 14)
|
||||
caption.add_theme_color_override("font_color", _theme.text_muted_color())
|
||||
content.add_child(caption)
|
||||
var value := Label.new()
|
||||
value.add_theme_font_size_override("font_size", 22)
|
||||
content.add_child(value)
|
||||
return [caption, value]
|
||||
|
||||
|
||||
func _style_tag_button(button: Button) -> void:
|
||||
var text_color: Color = _theme.text_color()
|
||||
var surface: Color = _editor_visual_theme.chrome_color(&"background", &"prop")
|
||||
var border: Color = _theme.border_color()
|
||||
var accent: Color = _theme.accent_color()
|
||||
button.add_theme_stylebox_override("normal", _make_tag_style(surface, border, 1))
|
||||
button.add_theme_stylebox_override("hover", _make_tag_style(surface.lightened(0.12), accent, 1))
|
||||
button.add_theme_stylebox_override("pressed", _make_tag_style(_editor_visual_theme.chrome_color(&"selected_surface", &"prop"), accent, 2))
|
||||
button.add_theme_stylebox_override("focus", _make_tag_style(Color(0.0, 0.0, 0.0, 0.0), _editor_visual_theme.chrome_color(&"focus", &"prop"), 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("icon_normal_color", Color.WHITE)
|
||||
button.add_theme_color_override("icon_hover_color", Color.WHITE)
|
||||
button.add_theme_color_override("icon_pressed_color", Color.WHITE)
|
||||
|
||||
|
||||
func _make_tag_style(fill: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = fill
|
||||
style.border_color = border
|
||||
style.set_border_width_all(border_width)
|
||||
style.set_corner_radius_all(8)
|
||||
style.content_margin_left = 7.0
|
||||
style.content_margin_top = 5.0
|
||||
style.content_margin_right = 7.0
|
||||
style.content_margin_bottom = 5.0
|
||||
return style
|
||||
|
||||
|
||||
func _primitive_icon_color(primitive_id: String) -> Color:
|
||||
match primitive_id:
|
||||
"box": return Color("#70C8F0")
|
||||
"plank": return Color("#F3B65D")
|
||||
"triangle": return Color("#66C681")
|
||||
"line": return Color("#DDE7F5")
|
||||
"circle": return Color("#C9A3FF")
|
||||
return _theme.accent_color()
|
||||
|
||||
|
||||
func _build_stage_authoring_controls() -> void:
|
||||
var top_bar: HBoxContainer = get_node_or_null(NodePath("ChromeRoot/TopBar")) as HBoxContainer
|
||||
if top_bar == null:
|
||||
@@ -1244,29 +1394,30 @@ func _apply_authoring_context() -> void:
|
||||
|
||||
|
||||
func _build_tag_inspector() -> void:
|
||||
_tag_section = VBoxContainer.new()
|
||||
_tag_section.add_theme_constant_override("separation", 4)
|
||||
_tag_section = _make_inspector_card()
|
||||
_inspector_list.add_child(_tag_section)
|
||||
var title := Label.new()
|
||||
title.text = "Artwork Tags"
|
||||
title.add_theme_font_size_override("font_size", _theme.font_header())
|
||||
_tag_section.add_child(title)
|
||||
var tag_content: VBoxContainer = _inspector_card_content(_tag_section)
|
||||
tag_content.add_child(_make_inspector_header("Artwork Tags", PropInspectorIconsScript.section("tags")))
|
||||
var hint := Label.new()
|
||||
hint.text = "Choose a few so this art is easy to find."
|
||||
hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_tag_section.add_child(hint)
|
||||
hint.add_theme_color_override("font_color", _theme.text_muted_color())
|
||||
tag_content.add_child(hint)
|
||||
var rows := FlowContainer.new()
|
||||
rows.add_theme_constant_override("h_separation", 6)
|
||||
rows.add_theme_constant_override("v_separation", 4)
|
||||
_tag_section.add_child(rows)
|
||||
rows.add_theme_constant_override("v_separation", 6)
|
||||
tag_content.add_child(rows)
|
||||
for index: int in ART_TAG_IDS.size():
|
||||
var tag_id: String = ART_TAG_IDS[index]
|
||||
var check := CheckBox.new()
|
||||
check.text = ART_TAG_LABELS[index]
|
||||
check.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
check.toggled.connect(_on_art_tag_toggled.bind(tag_id))
|
||||
rows.add_child(check)
|
||||
_tag_checks[tag_id] = check
|
||||
var tag_button := Button.new()
|
||||
tag_button.text = ART_TAG_LABELS[index]
|
||||
tag_button.toggle_mode = true
|
||||
tag_button.custom_minimum_size = Vector2(122.0, 50.0)
|
||||
tag_button.icon = PropInspectorIconsScript.tag(tag_id)
|
||||
tag_button.toggled.connect(_on_art_tag_toggled.bind(tag_id))
|
||||
_style_tag_button(tag_button)
|
||||
rows.add_child(tag_button)
|
||||
_tag_checks[tag_id] = tag_button
|
||||
|
||||
|
||||
func _on_art_tag_toggled(enabled: bool, tag_id: String) -> void:
|
||||
@@ -1285,9 +1436,9 @@ func _refresh_tag_controls() -> void:
|
||||
return
|
||||
_updating_inspector = true
|
||||
for tag_id: String in ART_TAG_IDS:
|
||||
var check: CheckBox = _tag_checks.get(tag_id) as CheckBox
|
||||
if check != null:
|
||||
check.set_pressed_no_signal(_tags.has(tag_id))
|
||||
var tag_button: Button = _tag_checks.get(tag_id) as Button
|
||||
if tag_button != null:
|
||||
tag_button.set_pressed_no_signal(_tags.has(tag_id))
|
||||
_updating_inspector = false
|
||||
|
||||
func _build_anchor_inspector() -> void:
|
||||
@@ -1397,13 +1548,17 @@ func _on_rename_anchor_pressed() -> void:
|
||||
_rename_anchor_source_id = _selected_anchor_id
|
||||
_rename_anchor_edit.text = _rename_anchor_source_id
|
||||
_update_rename_anchor_confirmation(_rename_anchor_edit.text)
|
||||
_rename_anchor_dialog.popup_centered(Vector2i(420, 150))
|
||||
StageDialogStylerScript.popup(_rename_anchor_dialog)
|
||||
_rename_anchor_edit.call_deferred("grab_focus")
|
||||
_rename_anchor_edit.call_deferred("select_all")
|
||||
|
||||
func _update_rename_anchor_confirmation(value: String) -> void:
|
||||
if _rename_anchor_dialog != null:
|
||||
_rename_anchor_dialog.get_ok_button().disabled = value.strip_edges().is_empty()
|
||||
var native_button: Button = _rename_anchor_dialog.get_ok_button()
|
||||
native_button.disabled = value.strip_edges().is_empty()
|
||||
var visible_button: Button = StageDialogStylerScript.visual_button(native_button)
|
||||
if visible_button != native_button:
|
||||
visible_button.disabled = native_button.disabled
|
||||
|
||||
func _commit_anchor_rename() -> void:
|
||||
if _rename_anchor_source_id.is_empty() or not _anchors.has(_rename_anchor_source_id):
|
||||
@@ -1463,10 +1618,10 @@ func _apply_control_theme(node: Node) -> void:
|
||||
|
||||
func _update_physics_labels() -> void:
|
||||
var mat: PhysicsMaterial = PropBlockScript.physics_material(_material_preset)
|
||||
_mass_label.text = "Mass: %.1f" % PropBlockScript.mass_for(_material_preset)
|
||||
_friction_label.text = "Surface grip: %.2f" % mat.friction
|
||||
_mass_value.text = "%.1f" % PropBlockScript.mass_for(_material_preset)
|
||||
_friction_value.text = "%.2f" % mat.friction
|
||||
_friction_label.tooltip_text = "Higher values make sliding harder."
|
||||
_bounce_label.text = "Bounce: %.2f" % mat.bounce
|
||||
_bounce_value.text = "%.2f" % mat.bounce
|
||||
|
||||
|
||||
func _on_preset_selected(index: int) -> void:
|
||||
@@ -1786,14 +1941,16 @@ func _check_autosave_recovery() -> void:
|
||||
|
||||
var dialog := ConfirmationDialog.new()
|
||||
dialog.title = "Restore Prop?"
|
||||
dialog.dialog_text = "An unsaved prop was recovered. Restore it?"
|
||||
dialog.ok_button_text = "🔄 Restore"
|
||||
var discard_button: Button = dialog.add_button("🗑️ Discard", false, "discard")
|
||||
dialog.theme = _ui_theme
|
||||
_editor_visual_theme.style_button_role(dialog.get_ok_button(), &"primary", &"prop")
|
||||
_editor_visual_theme.style_button_role(discard_button, &"destructive", &"prop")
|
||||
_editor_visual_theme.style_button_role(dialog.get_cancel_button(), &"secondary", &"prop")
|
||||
dialog.set_meta(StageDialogStylerScript.ICON_META, "🔄")
|
||||
dialog.dialog_text = "An unsaved prop was recovered. Restore this work?"
|
||||
dialog.ok_button_text = "Restore"
|
||||
var discard_button: Button = dialog.add_button("Discard", false, "discard")
|
||||
dialog.get_cancel_button().text = "Cancel"
|
||||
add_child(dialog)
|
||||
StageDialogStylerScript.apply(
|
||||
dialog, _dialog_theme, _dialog_theme.font(),
|
||||
dialog.get_ok_button(), [discard_button]
|
||||
)
|
||||
dialog.confirmed.connect(func() -> void:
|
||||
_apply_json_data(raw, true)
|
||||
_current_path = source_path
|
||||
@@ -1803,7 +1960,8 @@ func _check_autosave_recovery() -> void:
|
||||
if action == &"discard":
|
||||
_clear_autosave()
|
||||
dialog.queue_free())
|
||||
dialog.popup_centered()
|
||||
dialog.canceled.connect(func() -> void: dialog.queue_free())
|
||||
StageDialogStylerScript.popup(dialog)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1879,7 +2037,7 @@ func _begin_save_request(path: String, with_thumbnail: bool) -> void:
|
||||
_pending_save_path = path
|
||||
_pending_save_with_thumbnail = with_thumbnail
|
||||
if should_warn_off_origin(_canvas.has_shape_geometry(), _canvas.get_shapes_bounds()):
|
||||
_off_origin_save_dialog.popup_centered()
|
||||
StageDialogStylerScript.popup(_off_origin_save_dialog)
|
||||
return
|
||||
await _execute_pending_save(false)
|
||||
|
||||
@@ -1944,4 +2102,4 @@ func _on_load_file_selected(path: String) -> void:
|
||||
|
||||
func _show_error(message: String) -> void:
|
||||
_error_dialog.dialog_text = message
|
||||
_error_dialog.popup_centered()
|
||||
StageDialogStylerScript.popup(_error_dialog)
|
||||
|
||||
Reference in New Issue
Block a user