Add visual harmonization tests for editor UI components and ground finish chooser
- Implemented `test_editor_visual_harmonization.gd` to validate the Stickman editor and prop editor UI layouts, ensuring proper functionality and visual consistency. - Created `test_editor_visual_theme.gd` to verify the loading and application of visual themes in the editor, including semantic overrides and default values. - Introduced `test_phase19_ground_finish_chooser.gd` to test the ground finish chooser's functionality, including layout responsiveness, selection behavior, and interaction with the stage. - Added new scripts for ground finish preview and chooser UI components to enhance the editor's visual representation of ground finishes.
This commit is contained in:
@@ -121,6 +121,7 @@ var _pan_offset: Vector2 = Vector2.ZERO
|
||||
var _grid_size: int = 15
|
||||
var _snap_enabled: bool = false
|
||||
var _has_clipboard: bool = false
|
||||
var _create_point_armed: bool = false
|
||||
var _context_menu: PopupMenu
|
||||
var _edit_mode: EditMode = EditMode.TRANSFORM
|
||||
var _anchor_overlay: RefCounted
|
||||
@@ -214,6 +215,8 @@ func set_edit_mode(mode: int) -> void:
|
||||
if _stage_size_preview_active:
|
||||
cancel_stage_size_preview()
|
||||
_edit_mode = m
|
||||
if _edit_mode != EditMode.POINTS:
|
||||
_create_point_armed = false
|
||||
if _edit_mode != EditMode.TRANSFORM:
|
||||
set_whole_prop_resize_enabled(false)
|
||||
_drag_mode = DragMode.NONE
|
||||
@@ -225,6 +228,21 @@ func set_edit_mode(mode: int) -> void:
|
||||
func get_edit_mode() -> int:
|
||||
return _edit_mode
|
||||
|
||||
|
||||
func arm_create_point() -> void:
|
||||
set_edit_mode(EditMode.POINTS)
|
||||
_create_point_armed = true
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func cancel_create_point() -> void:
|
||||
_create_point_armed = false
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func is_create_point_armed() -> bool:
|
||||
return _create_point_armed
|
||||
|
||||
func set_anchor_data(data: Variant) -> void:
|
||||
if _anchor_overlay == null: _anchor_overlay = PropAnchorOverlayScript.new()
|
||||
_anchor_overlay.set_anchor_data(data)
|
||||
@@ -1254,7 +1272,10 @@ func _on_canvas_gui_input(event: InputEvent) -> void:
|
||||
return
|
||||
if mb.button_index == MOUSE_BUTTON_LEFT:
|
||||
if mb.pressed:
|
||||
_handle_left_press(world_pos)
|
||||
if _create_point_armed and _edit_mode == EditMode.POINTS:
|
||||
_create_point_from_toolbar(world_pos)
|
||||
else:
|
||||
_handle_left_press(world_pos)
|
||||
else:
|
||||
_handle_left_release()
|
||||
|
||||
@@ -1464,6 +1485,21 @@ func _handle_left_press(world_pos: Vector2) -> void:
|
||||
shape_selected.emit(-1)
|
||||
|
||||
|
||||
func _create_point_from_toolbar(world_pos: Vector2) -> void:
|
||||
var shape_idx: int = hit_test_segment(world_pos)
|
||||
if shape_idx < 0:
|
||||
return
|
||||
_selected_shape_idx = shape_idx
|
||||
shape_selected.emit(shape_idx)
|
||||
var new_vertex_index: int = create_point_on_edge(world_pos)
|
||||
if new_vertex_index < 0:
|
||||
return
|
||||
_create_point_armed = false
|
||||
_drag_mode = DragMode.NONE
|
||||
_emit_changed()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _handle_drag_motion(mm: InputEventMouseMotion, world_pos: Vector2, ctrl_pressed: bool) -> void:
|
||||
match _drag_mode:
|
||||
DragMode.VERTEX:
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 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")
|
||||
@@ -92,10 +93,10 @@ static func should_warn_off_origin(has_geometry: bool, bounds: Rect2) -> bool:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@onready var _menu_bar: MenuBar = %MenuBar
|
||||
@onready var _top_right: HBoxContainer = %TopRightControls
|
||||
@onready var _top_right: FlowContainer = %TopRightControls
|
||||
@onready var _prop_name_edit: LineEdit = %PropNameEdit
|
||||
@onready var _canvas: PropCanvas = %PropCanvas
|
||||
@onready var _mode_bar: HBoxContainer = %ModeBar
|
||||
@onready var _mode_bar: FlowContainer = %ModeBar
|
||||
@onready var _size_tools: FlowContainer = %SizeTools
|
||||
@onready var _attach_tools: FlowContainer = %AttachTools
|
||||
@onready var _canvas_workspace: HSplitContainer = %CanvasWorkspace
|
||||
@@ -114,6 +115,7 @@ static func should_warn_off_origin(has_geometry: bool, bounds: Rect2) -> bool:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
var _theme: PropTheme
|
||||
var _editor_visual_theme: EditorVisualTheme
|
||||
var _material_preset: int = PropBlockScript.MaterialPreset.NONE
|
||||
var _current_path: String = ""
|
||||
var _dirty: bool = false
|
||||
@@ -139,6 +141,8 @@ var _radius_row: Control
|
||||
var _reshaped_note: Label
|
||||
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 _params_box: VBoxContainer
|
||||
@@ -212,6 +216,8 @@ var _recent_props: Array[String] = []
|
||||
func _ready() -> void:
|
||||
if not _stage_authoring:
|
||||
get_tree().auto_accept_quit = false
|
||||
_editor_visual_theme = EditorVisualThemeScript.new()
|
||||
_editor_visual_theme.load_from(EditorVisualTheme.DEFAULT_PATH)
|
||||
_theme = PropThemeScript.new()
|
||||
_theme.load_from(PropThemeScript.DEFAULT_PATH)
|
||||
_thumb_cache = ThumbnailCacheScript.new()
|
||||
@@ -260,7 +266,7 @@ func _ready() -> void:
|
||||
_cursor_coords.add_theme_font_size_override("font_size", _theme.font_status())
|
||||
_save_status.add_theme_font_size_override("font_size", _theme.font_status())
|
||||
|
||||
_mode_bar.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
_mode_bar.alignment = FlowContainer.ALIGNMENT_CENTER
|
||||
|
||||
_refresh_inspector_from_selection()
|
||||
_sync_mode_buttons(_canvas.get_edit_mode())
|
||||
@@ -311,7 +317,11 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||
return
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner() if get_viewport() != null else null
|
||||
if not (focus_owner is LineEdit or focus_owner is TextEdit):
|
||||
if _canvas.is_whole_prop_resize_dragging():
|
||||
if _canvas.is_create_point_armed():
|
||||
_canvas.cancel_create_point()
|
||||
_update_create_point_feedback()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif _canvas.is_whole_prop_resize_dragging():
|
||||
_canvas.cancel_whole_prop_resize()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif _canvas.is_whole_prop_resize_enabled():
|
||||
@@ -337,8 +347,11 @@ func _update_save_status() -> void:
|
||||
var name: String = _prop_name_edit.text if _prop_name_edit != null else ""
|
||||
if name.is_empty():
|
||||
name = "Unnamed Prop"
|
||||
var suffix: String = " • Modified" if _dirty else ""
|
||||
_save_status.text = "%s — %s%s" % [name, PRESET_LABELS[_material_preset], suffix]
|
||||
var has_saved_path: bool = not _current_path.is_empty()
|
||||
var state: String = "Unsaved changes" if _dirty else ("Saved" if has_saved_path else "Not saved yet")
|
||||
_save_status.text = "%s — %s — %s" % [name, PRESET_LABELS[_material_preset], state]
|
||||
var status_color: Color = _editor_visual_theme.chrome_color(&"dirty", &"prop") if _dirty else (_editor_visual_theme.chrome_color(&"saved", &"prop") if has_saved_path else _editor_visual_theme.chrome_color(&"text_muted", &"prop"))
|
||||
_save_status.add_theme_color_override("font_color", status_color)
|
||||
_update_window_title()
|
||||
|
||||
|
||||
@@ -372,10 +385,7 @@ func _make_mode_button(mode: int) -> Button:
|
||||
button.button_group = _mode_group
|
||||
button.custom_minimum_size.y = float(_theme.mode_button_min_height())
|
||||
button.add_theme_font_size_override("font_size", _theme.font_mode())
|
||||
# No keyboard focus: the focus stylebox otherwise draws over the pressed
|
||||
# stylebox, hiding the selected (accent-bordered) state until the cursor
|
||||
# moves away (Round 4 fix).
|
||||
button.focus_mode = Control.FOCUS_NONE
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
_mode_bar.add_child(button)
|
||||
return button
|
||||
|
||||
@@ -399,6 +409,8 @@ func _on_canvas_mode_changed(mode: int) -> void:
|
||||
return
|
||||
if mode != PropCanvasScript.EditMode.TRANSFORM:
|
||||
_set_whole_prop_resize_enabled(false)
|
||||
if mode != PropCanvasScript.EditMode.POINTS and _canvas.is_create_point_armed():
|
||||
_canvas.cancel_create_point()
|
||||
var entering_attach: bool = mode == PropCanvasScript.EditMode.ATTACH and _last_canvas_mode != PropCanvasScript.EditMode.ATTACH
|
||||
if entering_attach and _show_preview_check != null:
|
||||
_show_preview_check.set_pressed_no_signal(true)
|
||||
@@ -430,6 +442,11 @@ func _sync_mode_controls(mode: int) -> void:
|
||||
_add_section.visible = mode == PropCanvasScript.EditMode.TRANSFORM
|
||||
if _delete_button != null:
|
||||
_delete_button.visible = mode == PropCanvasScript.EditMode.TRANSFORM
|
||||
if _create_point_button != null:
|
||||
_create_point_button.visible = mode == PropCanvasScript.EditMode.POINTS and _canvas.get_selected_index() >= 0
|
||||
if _create_point_hint != null:
|
||||
_create_point_hint.visible = mode == PropCanvasScript.EditMode.POINTS and _canvas.get_selected_index() >= 0
|
||||
_update_create_point_feedback()
|
||||
if _shape_section != null:
|
||||
_shape_section.visible = mode != PropCanvasScript.EditMode.ATTACH and not _canvas.is_whole_prop_resize_enabled() and _canvas.get_selected_index() >= 0
|
||||
if _material_section != null:
|
||||
@@ -461,73 +478,15 @@ func _build_attach_tools() -> void:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func _build_control_theme() -> void:
|
||||
_ui_theme = Theme.new()
|
||||
var bg: Color = _theme.canvas_background_color()
|
||||
var panel := StyleBoxFlat.new()
|
||||
panel.bg_color = Color(0.12, 0.12, 0.12, 1.0)
|
||||
panel.set_corner_radius_all(6)
|
||||
panel.content_margin_left = 8
|
||||
panel.content_margin_right = 8
|
||||
panel.content_margin_top = 6
|
||||
panel.content_margin_bottom = 6
|
||||
panel.border_color = Color(1.0, 1.0, 1.0, 0.12)
|
||||
panel.set_border_width_all(1)
|
||||
var panel_dark := StyleBoxFlat.new()
|
||||
panel_dark.bg_color = bg.darkened(0.25)
|
||||
panel_dark.set_corner_radius_all(6)
|
||||
panel_dark.content_margin_left = 8
|
||||
panel_dark.content_margin_right = 8
|
||||
panel_dark.content_margin_top = 6
|
||||
panel_dark.content_margin_bottom = 6
|
||||
panel_dark.border_color = Color(1.0, 1.0, 1.0, 0.12)
|
||||
panel_dark.set_border_width_all(1)
|
||||
var panel_pressed := StyleBoxFlat.new()
|
||||
panel_pressed.bg_color = bg.darkened(0.25)
|
||||
panel_pressed.set_corner_radius_all(6)
|
||||
panel_pressed.content_margin_left = 8
|
||||
panel_pressed.content_margin_right = 8
|
||||
panel_pressed.content_margin_top = 6
|
||||
panel_pressed.content_margin_bottom = 6
|
||||
panel_pressed.border_color = _theme.accent_color()
|
||||
panel_pressed.set_border_width_all(2)
|
||||
_ui_theme.set_stylebox("normal", "Button", panel)
|
||||
_ui_theme.set_stylebox("hover", "Button", panel_dark)
|
||||
_ui_theme.set_stylebox("pressed", "Button", panel_pressed)
|
||||
_ui_theme.set_stylebox("focus", "Button", panel)
|
||||
var inspector_surface := StyleBoxFlat.new()
|
||||
inspector_surface.bg_color = _theme.inspector_background_color()
|
||||
inspector_surface.border_color = _theme.border_color()
|
||||
inspector_surface.set_border_width_all(1)
|
||||
inspector_surface.content_margin_left = 8
|
||||
inspector_surface.content_margin_right = 8
|
||||
_ui_theme = _editor_visual_theme.control_theme(&"prop")
|
||||
theme = _ui_theme
|
||||
var inspector_surface: StyleBoxFlat = _ui_theme.get_stylebox("panel", "PanelContainer").duplicate() as StyleBoxFlat
|
||||
_inspector_column.add_theme_stylebox_override("panel", inspector_surface)
|
||||
var splitter := StyleBoxFlat.new()
|
||||
splitter.bg_color = _theme.border_color().lightened(0.35)
|
||||
splitter.bg_color = _editor_visual_theme.chrome_color(&"border", &"prop").lightened(0.35)
|
||||
splitter.content_margin_left = 3
|
||||
splitter.content_margin_right = 3
|
||||
_canvas_workspace.add_theme_stylebox_override("grabber", splitter)
|
||||
_ui_theme.set_stylebox("normal", "OptionButton", panel)
|
||||
_ui_theme.set_stylebox("hover", "OptionButton", panel_dark)
|
||||
_ui_theme.set_stylebox("pressed", "OptionButton", panel_dark)
|
||||
_ui_theme.set_stylebox("normal", "SpinBox", panel_dark)
|
||||
_ui_theme.set_stylebox("normal", "LineEdit", panel_dark)
|
||||
_ui_theme.set_stylebox("normal", "ColorPickerButton", panel)
|
||||
_ui_theme.set_stylebox("hover", "ColorPickerButton", panel_dark)
|
||||
_ui_theme.set_stylebox("normal", "PopupMenu", panel_dark)
|
||||
_ui_theme.set_stylebox("panel", "MenuBar", panel_dark)
|
||||
_ui_theme.set_stylebox("normal", "CheckBox", panel)
|
||||
_ui_theme.set_stylebox("panel", "PanelContainer", panel_dark)
|
||||
_ui_theme.set_color("font_color", "Label", _theme.text_color())
|
||||
_ui_theme.set_color("font_color", "Button", _theme.text_color())
|
||||
_ui_theme.set_color("font_hover_color", "Button", Color.WHITE)
|
||||
_ui_theme.set_color("font_pressed_color", "Button", _theme.accent_color())
|
||||
_ui_theme.set_font_size("font_size", "Label", _theme.font_body())
|
||||
_ui_theme.set_font_size("font_size", "Button", _theme.font_body())
|
||||
_ui_theme.set_font_size("font_size", "OptionButton", _theme.font_body())
|
||||
_ui_theme.set_font_size("font_size", "SpinBox", _theme.font_body())
|
||||
_ui_theme.set_font_size("font_size", "LineEdit", _theme.font_body())
|
||||
_ui_theme.set_font_size("font_size", "PopupMenu", _theme.font_menu())
|
||||
_ui_theme.set_font_size("font_size", "MenuBar", _theme.font_menu_bar())
|
||||
_menu_bar.theme = _ui_theme
|
||||
_top_right.theme = _ui_theme
|
||||
_prop_name_edit.theme = _ui_theme
|
||||
@@ -654,11 +613,18 @@ func _setup_dialogs() -> void:
|
||||
_load_dialog.current_dir = content_dir
|
||||
_save_dialog.file_selected.connect(_on_save_file_selected)
|
||||
_load_dialog.file_selected.connect(_on_load_file_selected)
|
||||
_editor_visual_theme.style_button_role(_save_dialog.get_ok_button(), &"primary", &"prop")
|
||||
_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")
|
||||
_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_edit = LineEdit.new()
|
||||
_rename_anchor_edit.placeholder_text = "Anchor name"
|
||||
_rename_anchor_dialog.add_child(_rename_anchor_edit)
|
||||
@@ -708,8 +674,8 @@ func _build_top_right_controls() -> void:
|
||||
_top_right.add_child(_snap_check)
|
||||
func _build_size_tools() -> void:
|
||||
_whole_prop_resize_button = Button.new()
|
||||
_whole_prop_resize_button.text = "✥ Transform Whole Prop"
|
||||
_whole_prop_resize_button.tooltip_text = "Move, rotate, or resize the whole prop including grip markers."
|
||||
_whole_prop_resize_button.text = "Move, Turn, or Resize Prop"
|
||||
_whole_prop_resize_button.tooltip_text = "Move, turn, or resize the whole prop, including grip markers."
|
||||
_whole_prop_resize_button.toggle_mode = true
|
||||
_whole_prop_resize_button.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_whole_prop_resize_button.toggled.connect(_set_whole_prop_resize_enabled)
|
||||
@@ -923,9 +889,12 @@ func _build_exit_dialog() -> void:
|
||||
_exit_dialog.title = "⚠️ Unsaved Changes"
|
||||
_exit_dialog.dialog_text = "You have unsaved changes. Save them before quitting?"
|
||||
_exit_dialog.ok_button_text = "💾 Save"
|
||||
_exit_dialog.add_button("🗑️ Discard", false, "discard")
|
||||
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")
|
||||
add_child(_exit_dialog)
|
||||
_exit_dialog.confirmed.connect(_on_exit_save)
|
||||
_exit_dialog.custom_action.connect(_on_exit_custom_action)
|
||||
@@ -934,9 +903,12 @@ func _build_exit_dialog() -> void:
|
||||
_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"
|
||||
_off_origin_save_dialog.add_button("💾 Save Anyway", false, "save_anyway")
|
||||
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")
|
||||
add_child(_off_origin_save_dialog)
|
||||
_off_origin_save_dialog.confirmed.connect(func() -> void: await _execute_pending_save(true))
|
||||
_off_origin_save_dialog.custom_action.connect(func(action: StringName) -> void:
|
||||
@@ -1178,9 +1150,22 @@ func _build_inspector() -> void:
|
||||
_vertex_label = Label.new()
|
||||
_shape_section.add_child(_vertex_label)
|
||||
|
||||
_create_point_button = Button.new()
|
||||
_create_point_button.text = "📍 Create Point"
|
||||
_create_point_button.tooltip_text = "Add a point to the selected shape's edge."
|
||||
_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)
|
||||
_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)
|
||||
|
||||
_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)
|
||||
|
||||
@@ -1208,6 +1193,7 @@ func _build_inspector() -> void:
|
||||
_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)
|
||||
|
||||
@@ -1219,7 +1205,7 @@ func _build_inspector() -> void:
|
||||
|
||||
|
||||
func _build_stage_authoring_controls() -> void:
|
||||
var top_bar: HBoxContainer = get_node_or_null(NodePath("TopBar")) as HBoxContainer
|
||||
var top_bar: HBoxContainer = get_node_or_null(NodePath("ChromeRoot/TopBar")) as HBoxContainer
|
||||
if top_bar == null:
|
||||
return
|
||||
var name_label: Label = top_bar.get_node_or_null(NodePath("NameLabel")) as Label
|
||||
@@ -1232,6 +1218,7 @@ func _build_stage_authoring_controls() -> void:
|
||||
_stage_place_button.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
_stage_place_button.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
_stage_place_button.theme = _ui_theme
|
||||
_editor_visual_theme.style_button_role(_stage_place_button, &"primary", &"prop")
|
||||
_stage_place_button.pressed.connect(_request_stage_return)
|
||||
top_bar.add_child(_stage_place_button)
|
||||
|
||||
@@ -1312,6 +1299,8 @@ func _build_anchor_inspector() -> void:
|
||||
var actions := HBoxContainer.new(); _anchor_section.add_child(actions)
|
||||
for label: String in ["Add Anchor", "Rename", "Delete"]:
|
||||
var button := Button.new(); button.text = label; button.custom_minimum_size.y = float(_theme.button_min_height()); actions.add_child(button)
|
||||
var role: StringName = &"primary" if label == "Add Anchor" else (&"destructive" if label == "Delete" else &"secondary")
|
||||
_editor_visual_theme.style_button_role(button, role, &"prop")
|
||||
if label == "Add Anchor": button.pressed.connect(_on_add_anchor_pressed)
|
||||
elif label == "Rename":
|
||||
_rename_anchor_button = button
|
||||
@@ -1324,11 +1313,17 @@ func _build_anchor_inspector() -> void:
|
||||
_anchor_x = _anchor_spin("X", -100000.0, 100000.0, _on_anchor_x_changed)
|
||||
_anchor_y = _anchor_spin("Y", -100000.0, 100000.0, _on_anchor_y_changed)
|
||||
_anchor_rotation = _anchor_spin("Rotation", -180.0, 179.0, _on_anchor_rotation_changed)
|
||||
_anchor_z = _anchor_spin("Z Offset", -5.0, 5.0, _on_anchor_z_changed)
|
||||
_anchor_z = _anchor_spin("Held prop layer", -5.0, 5.0, _on_anchor_z_changed)
|
||||
_anchor_z.step = 1.0
|
||||
var anchor_layer_hint := Label.new()
|
||||
anchor_layer_hint.text = "Negative: behind · 0: normal order · Positive: farther in front."
|
||||
anchor_layer_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
anchor_layer_hint.add_theme_font_size_override("font_size", _theme.font_status())
|
||||
anchor_layer_hint.add_theme_color_override("font_color", _theme.text_muted_color())
|
||||
_anchor_section.add_child(anchor_layer_hint)
|
||||
var presets := FlowContainer.new(); _anchor_section.add_child(presets)
|
||||
for entry: Dictionary in [{"text":"One-Handed","id":"one_handed"}, {"text":"Two-Handed","id":"two_handed"}, {"text":"Tool / Nozzle","id":"tool_nozzle"}, {"text":"Melee Edge","id":"melee_edge"}]:
|
||||
var button := Button.new(); button.text = str(entry["text"]); button.pressed.connect(func() -> void: _apply_anchor_preset(str(entry["id"]))); presets.add_child(button)
|
||||
var button := Button.new(); button.text = str(entry["text"]); button.custom_minimum_size.y = float(_theme.button_min_height()); button.pressed.connect(func() -> void: _apply_anchor_preset(str(entry["id"]))); presets.add_child(button)
|
||||
_refresh_anchor_inspector()
|
||||
|
||||
func _anchor_option(label_text: String, entries: PackedStringArray, callback: Callable) -> OptionButton:
|
||||
@@ -1341,6 +1336,7 @@ func _anchor_option(label_text: String, entries: PackedStringArray, callback: Ca
|
||||
row.add_child(label)
|
||||
var option := OptionButton.new()
|
||||
option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
option.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
for entry: String in entries:
|
||||
option.add_item(entry)
|
||||
option.item_selected.connect(callback)
|
||||
@@ -1360,6 +1356,7 @@ func _anchor_spin(label_text: String, minimum: float, maximum: float, callback:
|
||||
spin.max_value = maximum
|
||||
spin.step = 1.0
|
||||
spin.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
spin.custom_minimum_size.y = float(_theme.button_min_height())
|
||||
spin.value_changed.connect(callback)
|
||||
row.add_child(spin)
|
||||
return spin
|
||||
@@ -1467,14 +1464,15 @@ 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 = "Friction: %.2f" % mat.friction
|
||||
_friction_label.text = "Surface grip: %.2f" % mat.friction
|
||||
_friction_label.tooltip_text = "Higher values make sliding harder."
|
||||
_bounce_label.text = "Bounce: %.2f" % mat.bounce
|
||||
|
||||
|
||||
func _on_preset_selected(index: int) -> void:
|
||||
_material_preset = clampi(index, 0, PRESET_LABELS.size() - 1)
|
||||
_update_physics_labels()
|
||||
_update_save_status()
|
||||
_mark_dirty()
|
||||
|
||||
|
||||
func _rebuild_primitive_params() -> void:
|
||||
@@ -1604,6 +1602,7 @@ func _refresh_inspector_from_selection() -> void:
|
||||
func _on_canvas_shape_changed(_shapes: Array[Dictionary]) -> void:
|
||||
_mark_dirty()
|
||||
_refresh_inspector_from_selection()
|
||||
_sync_mode_controls(_canvas.get_edit_mode())
|
||||
_refresh_anchor_inspector()
|
||||
_refresh_center_prop_button()
|
||||
_sync_attach_preview()
|
||||
@@ -1700,6 +1699,25 @@ func _center_prop_on_origin(focus_view: bool = true) -> bool:
|
||||
|
||||
func _on_canvas_shape_selected(_idx: int) -> void:
|
||||
_refresh_inspector_from_selection()
|
||||
_sync_mode_controls(_canvas.get_edit_mode())
|
||||
|
||||
|
||||
func _on_create_point_pressed() -> void:
|
||||
if _canvas.get_selected_index() < 0:
|
||||
return
|
||||
_canvas.arm_create_point()
|
||||
_sync_mode_buttons(_canvas.get_edit_mode())
|
||||
_sync_mode_controls(_canvas.get_edit_mode())
|
||||
_update_create_point_feedback()
|
||||
|
||||
|
||||
func _update_create_point_feedback() -> void:
|
||||
if _create_point_button == null or _create_point_hint == null or _canvas == null:
|
||||
return
|
||||
var armed: bool = _canvas.is_create_point_armed()
|
||||
_create_point_button.text = "Click an Edge to Place Point" if armed else "📍 Create Point"
|
||||
_create_point_button.tooltip_text = "Press Esc to cancel." if armed else "Add a point to the selected shape's edge."
|
||||
_create_point_hint.text = "Click near an edge to place the point. Press Esc to cancel." if armed else "Adds a point where you click on the selected edge."
|
||||
|
||||
|
||||
func _on_canvas_copy_requested(shape: Dictionary) -> void:
|
||||
@@ -1770,8 +1788,11 @@ func _check_autosave_recovery() -> void:
|
||||
dialog.title = "Restore Prop?"
|
||||
dialog.dialog_text = "An unsaved prop was recovered. Restore it?"
|
||||
dialog.ok_button_text = "🔄 Restore"
|
||||
dialog.add_button("🗑️ Discard", false, "discard")
|
||||
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")
|
||||
add_child(dialog)
|
||||
dialog.confirmed.connect(func() -> void:
|
||||
_apply_json_data(raw, true)
|
||||
|
||||
@@ -10,6 +10,7 @@ extends RefCounted
|
||||
## truth.
|
||||
|
||||
const PropBlockScript := preload("res://scripts/props/prop_block.gd")
|
||||
const EditorVisualThemeScript := preload("res://scripts/shared/editor_ui/editor_visual_theme.gd")
|
||||
|
||||
const SCHEMA_VERSION: String = "1.0"
|
||||
const DEFAULT_PATH: String = "res://prop_theme.json"
|
||||
@@ -39,19 +40,16 @@ const DEFAULT_CONFIG: Dictionary = {
|
||||
"background_color": "#1F1F1F",
|
||||
"grid_color": "#FFFFFF26",
|
||||
},
|
||||
"ui": {
|
||||
"fonts": {"body": 18, "header": 24, "menu": 22, "menu_bar": 22, "mode": 26, "status": 18, "prop_name": 20, "context_menu": 22},
|
||||
"hit_targets": {"button_min_height": 36, "row_min_height": 36, "mode_button_min_height": 56},
|
||||
"colors": {"text": "#F2F2F2", "text_muted": "#9A9A9A", "border": "#FFFFFF33", "accent": "#4FA3FF", "inspector_background": "#262C39"},
|
||||
},
|
||||
}
|
||||
|
||||
var _config: Dictionary = DEFAULT_CONFIG.duplicate(true)
|
||||
var _warned: Dictionary = {}
|
||||
var _editor_visual_theme: EditorVisualTheme = EditorVisualThemeScript.new()
|
||||
|
||||
|
||||
func load_from(path: String = DEFAULT_PATH) -> bool:
|
||||
invalidate_caches()
|
||||
_editor_visual_theme.load_from(EditorVisualTheme.DEFAULT_PATH)
|
||||
_config = DEFAULT_CONFIG.duplicate(true)
|
||||
if not FileAccess.file_exists(path):
|
||||
_warn_once(path, "PropTheme: '%s' was not found; using defaults." % path)
|
||||
@@ -155,71 +153,71 @@ func canvas_grid_color() -> Color:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kid-friendly UI tokens (Round 2)
|
||||
# Compatibility accessors: editor chrome is owned by EditorVisualTheme.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func font_body() -> int:
|
||||
return integer("ui.fonts.body", 18, 8, 72)
|
||||
return _editor_visual_theme.chrome_px(&"body", &"prop")
|
||||
|
||||
|
||||
func font_header() -> int:
|
||||
return integer("ui.fonts.header", 24, 8, 96)
|
||||
return _editor_visual_theme.chrome_px(&"section", &"prop")
|
||||
|
||||
|
||||
func font_menu() -> int:
|
||||
return integer("ui.fonts.menu", 22, 8, 72)
|
||||
return _editor_visual_theme.chrome_px(&"menu", &"prop")
|
||||
|
||||
|
||||
func font_status() -> int:
|
||||
return integer("ui.fonts.status", 18, 8, 72)
|
||||
return _editor_visual_theme.chrome_px(&"status", &"prop")
|
||||
|
||||
|
||||
func font_mode() -> int:
|
||||
return integer("ui.fonts.mode", 26, 8, 96)
|
||||
return _editor_visual_theme.chrome_px(&"mode", &"prop")
|
||||
|
||||
|
||||
func font_menu_bar() -> int:
|
||||
return integer("ui.fonts.menu_bar", 22, 8, 72)
|
||||
return _editor_visual_theme.chrome_px(&"menu", &"prop")
|
||||
|
||||
|
||||
func font_prop_name() -> int:
|
||||
return integer("ui.fonts.prop_name", 20, 8, 96)
|
||||
return _editor_visual_theme.chrome_px(&"prop_name", &"prop")
|
||||
|
||||
|
||||
func font_context_menu() -> int:
|
||||
return integer("ui.fonts.context_menu", 22, 8, 96)
|
||||
return _editor_visual_theme.chrome_px(&"context_menu", &"prop")
|
||||
|
||||
|
||||
func button_min_height() -> int:
|
||||
return integer("ui.hit_targets.button_min_height", 36, 16, 200)
|
||||
return _editor_visual_theme.chrome_px(&"button_min_height", &"prop")
|
||||
|
||||
|
||||
func row_min_height() -> int:
|
||||
return integer("ui.hit_targets.row_min_height", 36, 16, 200)
|
||||
return _editor_visual_theme.chrome_px(&"row_min_height", &"prop")
|
||||
|
||||
|
||||
func mode_button_min_height() -> int:
|
||||
return integer("ui.hit_targets.mode_button_min_height", 56, 16, 200)
|
||||
return _editor_visual_theme.chrome_px(&"mode_button_min_height", &"prop")
|
||||
|
||||
|
||||
func text_color() -> Color:
|
||||
return color("ui.colors.text", Color("#F2F2F2"))
|
||||
return _editor_visual_theme.chrome_color(&"text", &"prop")
|
||||
|
||||
|
||||
func text_muted_color() -> Color:
|
||||
return color("ui.colors.text_muted", Color("#9A9A9A"))
|
||||
return _editor_visual_theme.chrome_color(&"text_muted", &"prop")
|
||||
|
||||
|
||||
func border_color() -> Color:
|
||||
return color("ui.colors.border", Color(1.0, 1.0, 1.0, 0.2))
|
||||
return _editor_visual_theme.chrome_color(&"border", &"prop")
|
||||
|
||||
|
||||
func accent_color() -> Color:
|
||||
return color("ui.colors.accent", Color("#4FA3FF"))
|
||||
return _editor_visual_theme.chrome_color(&"edit", &"prop")
|
||||
|
||||
|
||||
func inspector_background_color() -> Color:
|
||||
return color("ui.colors.inspector_background", Color("#262C39"))
|
||||
return _editor_visual_theme.chrome_color(&"surface", &"prop")
|
||||
|
||||
|
||||
func invalidate_caches() -> void:
|
||||
|
||||
Reference in New Issue
Block a user