feat: add Phase 20a stage identities and overlap selection

This commit is contained in:
2026-10-26 15:10:28 -04:00
parent 0cc882ef4b
commit 35eb0e2503
74 changed files with 3854 additions and 217 deletions
+132 -10
View File
@@ -3,6 +3,8 @@ extends PanelContainer
## Reusable shell for an in-stage selection inspector.
signal close_requested
signal rename_requested
signal collapsed_changed(collapsed: bool)
var _stage_theme: SandboxTheme = null
var _safe_rect := Rect2()
@@ -12,9 +14,16 @@ var _layout: VBoxContainer
var _header: HBoxContainer
var _icon: Label
var _title: Label
var _rename_button: Button
var _close_button: Button
var _scroll: ScrollContainer
var _body: VBoxContainer
var _collapsible: bool = false
var _collapsed: bool = false
var _close_visible: bool = true
var _rename_enabled: bool = false
var _body_visible: bool = true
var _single_line_title: bool = false
func _ready() -> void:
@@ -29,8 +38,13 @@ func _unhandled_key_input(event: InputEvent) -> void:
return
var key_event := event as InputEventKey
if key_event.pressed and not key_event.echo and key_event.keycode == KEY_ESCAPE:
close_requested.emit()
get_viewport().set_input_as_handled()
if _collapsible:
if not _collapsed:
set_collapsed(true)
get_viewport().set_input_as_handled()
elif _close_visible:
close_requested.emit()
get_viewport().set_input_as_handled()
func apply_theme(stage_theme: SandboxTheme) -> void:
@@ -48,8 +62,9 @@ func apply_theme(stage_theme: SandboxTheme) -> void:
_margin.add_theme_constant_override("margin_" + side, padding)
_layout.add_theme_constant_override("separation", _integer("stage_panels.shell.header_gap", 8, 0, 32))
_header.add_theme_constant_override("separation", _integer("stage_panels.shell.header_gap", 8, 0, 32))
_rename_button.custom_minimum_size = Vector2(92.0, float(_integer("stage_panels.action.min_target_size", 48, 44, 96)))
_close_button.custom_minimum_size = Vector2(
float(_integer("stage_panels.action.close_width", 88, 72, 180)),
float(_integer("stage_panels.action.close_width", 88, 72, 180)) if not _collapsible else float(_integer("stage_panels.action.min_target_size", 48, 44, 96)),
float(_integer("stage_panels.action.min_target_size", 48, 44, 96))
)
_title.add_theme_font_size_override("font_size", _integer("stage_panels.type.title_size", 24, 12, 48))
@@ -58,11 +73,14 @@ func apply_theme(stage_theme: SandboxTheme) -> void:
var ui_font := stage_theme.font()
if ui_font != null:
_title.add_theme_font_override("font", ui_font)
_rename_button.add_theme_font_override("font", ui_font)
_close_button.add_theme_font_override("font", ui_font)
var emoji_font := stage_theme.emoji_font()
if emoji_font != null:
_icon.add_theme_font_override("font", emoji_font)
_close_button.add_theme_font_size_override("font_size", _integer("stage_panels.type.body_size", 20, 14, 40))
_rename_button.add_theme_font_size_override("font_size", _integer("stage_panels.type.body_size", 20, 14, 40))
_style_action_button(_rename_button, false)
_style_action_button(_close_button, false)
request_fit()
@@ -77,6 +95,74 @@ func set_header(title: String, icon_text: String = "") -> void:
request_fit()
func set_title_single_line(enabled: bool) -> void:
_single_line_title = enabled
_apply_title_layout()
request_fit()
func set_rename_enabled(enabled: bool) -> void:
_rename_enabled = enabled
if _rename_button == null:
return
_rename_button.visible = enabled
request_fit()
func set_collapsible(enabled: bool, collapsed: bool = false) -> void:
_collapsible = enabled
if _close_button == null:
_collapsed = collapsed if enabled else false
return
_close_button.visible = enabled or _close_visible
_close_button.custom_minimum_size.x = float(_integer("stage_panels.action.close_width", 88, 72, 180)) if not enabled else float(_integer("stage_panels.action.min_target_size", 48, 44, 96))
if enabled:
set_collapsed(collapsed, false)
else:
_collapsed = false
_close_button.text = "Close"
_close_button.tooltip_text = "Close inspector"
if _scroll != null:
_scroll.visible = _body_visible
_apply_title_layout()
request_fit()
func set_close_visible(enabled: bool) -> void:
_close_visible = enabled
if _close_button == null:
return
_close_button.visible = _collapsible or enabled
request_fit()
func set_body_visible(enabled: bool) -> void:
_body_visible = enabled
if _scroll != null:
_scroll.visible = enabled and not _collapsed
request_fit()
func set_collapsed(collapsed: bool, notify: bool = true) -> void:
if not _collapsible:
return
var changed: bool = _collapsed != collapsed
_collapsed = collapsed
if _scroll != null:
_scroll.visible = not collapsed and _body_visible
if _close_button != null:
_close_button.text = "▾" if collapsed else "▴"
_close_button.tooltip_text = "Expand settings" if collapsed else "Collapse settings"
_apply_title_layout()
request_fit()
if changed and notify:
collapsed_changed.emit(collapsed)
func is_collapsed() -> bool:
return _collapsed
func body() -> VBoxContainer:
return _body
@@ -109,7 +195,10 @@ func focus_first_control() -> void:
continue
control.grab_focus()
return
_close_button.grab_focus()
if _rename_button.visible:
_rename_button.grab_focus()
elif _close_button.visible:
_close_button.grab_focus()
func style_action_button(button: Button, prominent: bool = false) -> void:
@@ -184,27 +273,43 @@ func _build_shell() -> void:
_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
_header.add_child(_icon)
_title = Label.new()
_title.name = "InspectorTitle"
_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_title.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_title.max_lines_visible = 2
_title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
_title.mouse_filter = Control.MOUSE_FILTER_IGNORE
_title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
_header.add_child(_title)
_apply_title_layout()
_rename_button = Button.new()
_rename_button.name = "RenameStageObject"
_rename_button.text = "Rename"
_rename_button.custom_minimum_size = Vector2(92.0, float(_integer("stage_panels.action.min_target_size", 48, 44, 96)))
_rename_button.visible = false
_rename_button.pressed.connect(func() -> void: rename_requested.emit())
_rename_button.visible = _rename_enabled
_header.add_child(_rename_button)
_close_button = Button.new()
_close_button.text = "Close"
_close_button.name = "CollapseStageInspector"
_close_button.text = "▾" if _collapsible and _collapsed else "▴" if _collapsible else "Close"
_close_button.tooltip_text = "Expand settings" if _collapsible and _collapsed else "Collapse settings" if _collapsible else "Close inspector"
_close_button.visible = _collapsible or _close_visible
_close_button.custom_minimum_size = Vector2(
float(_integer("stage_panels.action.close_width", 88, 72, 180)),
float(_integer("stage_panels.action.close_width", 88, 72, 180)) if not _collapsible else float(_integer("stage_panels.action.min_target_size", 48, 44, 96)),
float(_integer("stage_panels.action.min_target_size", 48, 44, 96))
)
_close_button.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
_close_button.focus_mode = Control.FOCUS_ALL
_close_button.pressed.connect(func() -> void: close_requested.emit())
_close_button.pressed.connect(_on_close_button_pressed)
_header.add_child(_close_button)
_scroll = ScrollContainer.new()
_scroll.name = "InspectorBodyScroll"
_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_scroll.visible = _body_visible and not _collapsed
_layout.add_child(_scroll)
_body = VBoxContainer.new()
_body.size_flags_horizontal = Control.SIZE_EXPAND_FILL
@@ -240,11 +345,12 @@ func _fit_to_content() -> void:
var gap := float(_integer("stage_panels.shell.header_gap", 8, 0, 32))
var header_height := maxf(44.0, _header.get_combined_minimum_size().y)
var body_height := _body.get_combined_minimum_size().y
var fixed_height := header_height + gap + padding * 2.0
var show_body: bool = not _collapsed and _body_visible
var fixed_height := header_height + (gap if show_body else 0.0) + padding * 2.0
var available_height := maxf(72.0, rect.size.y - inset * 2.0)
var natural_height := fixed_height + body_height
var natural_height := fixed_height + (body_height if show_body else 0.0)
var card_height := minf(natural_height, available_height)
var scroll_height := maxf(0.0, card_height - fixed_height)
var scroll_height := maxf(0.0, card_height - fixed_height) if show_body else 0.0
# Update the scroll minimum before shrinking the PanelContainer. Otherwise its
# previous full-content minimum can clamp the new height until another layout.
_scroll.custom_minimum_size.y = scroll_height
@@ -254,6 +360,22 @@ func _fit_to_content() -> void:
_fit_pending = false
func _on_close_button_pressed() -> void:
if _collapsible:
set_collapsed(not _collapsed)
else:
close_requested.emit()
func _apply_title_layout() -> void:
if _title == null:
return
var one_line: bool = _single_line_title or (_collapsible and _collapsed)
_title.autowrap_mode = TextServer.AUTOWRAP_OFF if one_line else TextServer.AUTOWRAP_WORD_SMART
_title.max_lines_visible = 1 if one_line else 2
_title.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
func _style_action_button(button: Button, prominent: bool) -> void:
if button == null:
return