Unveränderter Ausgangsstand inklusive Masterprompt und Konzeptbildern unter assets/. .godot/ und docs/screenshots/ bleiben laut .gitignore unversioniert; letztere sind unter ../backups gesichert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
3.5 KiB
GDScript
97 lines
3.5 KiB
GDScript
class_name ChoiceGroup
|
|
extends BoxContainer
|
|
## Segmented choice with the OptionButton surface used by tools and tests:
|
|
## select(index) changes the display only, item_selected fires on player input.
|
|
## Selection shows a filled diamond, a bright frame and brighter text, never colour alone.
|
|
|
|
signal item_selected(index: int)
|
|
|
|
const MARK_ON := preload("res://assets/ui/controls/choice_on.svg")
|
|
const MARK_OFF := preload("res://assets/ui/controls/choice_off.svg")
|
|
|
|
var selected: int = -1
|
|
var _buttons: Array[Button] = []
|
|
var _marks: Array[TextureRect] = []
|
|
var _group := ButtonGroup.new()
|
|
|
|
func _init(horizontal: bool = true) -> void:
|
|
vertical = not horizontal
|
|
add_theme_constant_override("separation", 8)
|
|
|
|
func add_option(title: String, detail: String = "", tooltip: String = "") -> Button:
|
|
var button := Button.new()
|
|
button.theme_type_variation = "ChoiceButton"
|
|
button.toggle_mode = true
|
|
button.button_group = _group
|
|
button.focus_mode = Control.FOCUS_ALL
|
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
button.tooltip_text = tooltip
|
|
button.set_meta("title", title)
|
|
var row := HBoxContainer.new()
|
|
row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
row.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
row.offset_left = 14
|
|
row.offset_right = -12
|
|
row.add_theme_constant_override("separation", 12)
|
|
button.add_child(row)
|
|
var mark := TextureRect.new()
|
|
mark.texture = MARK_OFF
|
|
mark.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
mark.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
mark.custom_minimum_size = Vector2(16, 16)
|
|
mark.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
mark.size_flags_vertical = Control.SIZE_SHRINK_CENTER
|
|
row.add_child(mark)
|
|
var text := VBoxContainer.new()
|
|
text.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
text.alignment = BoxContainer.ALIGNMENT_CENTER
|
|
text.add_theme_constant_override("separation", 0)
|
|
text.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
row.add_child(text)
|
|
var heading := Label.new()
|
|
heading.text = title
|
|
heading.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
heading.add_theme_font_size_override("font_size", 18)
|
|
text.add_child(heading)
|
|
if not detail.is_empty():
|
|
var info := Label.new()
|
|
info.text = detail
|
|
info.theme_type_variation = "MutedLabel"
|
|
info.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
info.add_theme_font_size_override("font_size", 15)
|
|
info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
text.add_child(info)
|
|
var base_height: float = 62.0 if not detail.is_empty() else 46.0
|
|
button.custom_minimum_size = Vector2(0, base_height)
|
|
# Buttons do not size to child controls; follow the wrapped text height instead.
|
|
row.minimum_size_changed.connect(func() -> void:
|
|
button.custom_minimum_size.y = maxf(base_height, row.get_combined_minimum_size().y + 20.0))
|
|
var index: int = _buttons.size()
|
|
button.pressed.connect(func() -> void:
|
|
if selected != index:
|
|
select(index)
|
|
item_selected.emit(index))
|
|
add_child(button)
|
|
_buttons.append(button)
|
|
_marks.append(mark)
|
|
return button
|
|
|
|
func select(index: int) -> void:
|
|
if index < 0 or index >= _buttons.size():
|
|
return
|
|
selected = index
|
|
for i: int in range(_buttons.size()):
|
|
_buttons[i].set_pressed_no_signal(i == index)
|
|
_marks[i].texture = MARK_ON if i == index else MARK_OFF
|
|
_buttons[i].set_meta("selected", i == index)
|
|
|
|
func get_item_count() -> int:
|
|
return _buttons.size()
|
|
|
|
func get_button(index: int) -> Button:
|
|
return _buttons[index]
|
|
|
|
func focus_selected() -> void:
|
|
if selected >= 0 and selected < _buttons.size():
|
|
_buttons[selected].grab_focus()
|