feat: add Phase 20a stage identities and overlap selection
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user