117 lines
3.5 KiB
GDScript
117 lines
3.5 KiB
GDScript
class_name DialogueChoiceBox extends PanelContainer
|
|
|
|
const SCENE:PackedScene = preload("res://ui/component/DialogueChoiceBox.tscn")
|
|
const MAX_WIDTH:float = 120.0
|
|
|
|
signal chosen(response:DialogueResponse)
|
|
|
|
var _responses:Array[DialogueResponse] = []
|
|
var _entity:Entity = null
|
|
var _selectedIndex:int = 0
|
|
var _hasLetGoOfInteract:bool = true
|
|
|
|
@onready var _list:VBoxContainer = $VBoxContainer/List
|
|
|
|
func _ready() -> void:
|
|
size.x = MAX_WIDTH
|
|
visible = false
|
|
|
|
func setup(responses:Array[DialogueResponse], entity:Entity) -> void:
|
|
_entity = entity
|
|
_responses = responses.filter(func(r): return r.is_allowed)
|
|
_selectedIndex = 0
|
|
_hasLetGoOfInteract = !Input.is_action_pressed("interact")
|
|
|
|
for child in _list.get_children():
|
|
child.queue_free()
|
|
|
|
var innerWidth:float = _labelWidth()
|
|
var sep:int = _list.get_theme_constant("separation")
|
|
var totalHeight:float = 0.0
|
|
|
|
for i in range(_responses.size()):
|
|
var label:Label = Label.new()
|
|
label.text = _responses[i].text
|
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
label.focus_mode = Control.FOCUS_ALL
|
|
_list.add_child(label)
|
|
label.size.x = innerWidth
|
|
totalHeight += label.get_minimum_size().y
|
|
if i > 0:
|
|
totalHeight += sep
|
|
var idx:int = i
|
|
label.focus_entered.connect(func(): _onFocused(idx))
|
|
|
|
# Prevent focus escaping the list at the edges
|
|
var count:int = _list.get_child_count()
|
|
for i in range(count):
|
|
var label:Label = _list.get_child(i)
|
|
label.focus_neighbor_top = _list.get_child(max(0, i - 1)).get_path()
|
|
label.focus_neighbor_bottom = _list.get_child(min(count - 1, i + 1)).get_path()
|
|
|
|
var style:StyleBox = get_theme_stylebox("panel")
|
|
if style:
|
|
totalHeight += style.get_margin(SIDE_TOP) + style.get_margin(SIDE_BOTTOM)
|
|
|
|
size = Vector2(MAX_WIDTH, totalHeight)
|
|
_updateWorldPosition()
|
|
visible = true
|
|
_updateSelection()
|
|
_list.get_child(0).grab_focus()
|
|
|
|
func _labelWidth() -> float:
|
|
var style:StyleBox = get_theme_stylebox("panel")
|
|
if style == null:
|
|
return MAX_WIDTH
|
|
return MAX_WIDTH - style.get_margin(SIDE_LEFT) - style.get_margin(SIDE_RIGHT)
|
|
|
|
func _process(_delta:float) -> void:
|
|
if not visible:
|
|
return
|
|
_updateWorldPosition()
|
|
|
|
func _input(event:InputEvent) -> void:
|
|
if not visible:
|
|
return
|
|
if event.is_action_released("interact"):
|
|
_hasLetGoOfInteract = true
|
|
return
|
|
if event.is_action_pressed("interact") and _hasLetGoOfInteract:
|
|
_confirm()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
func _onFocused(idx:int) -> void:
|
|
_selectedIndex = idx
|
|
_updateSelection()
|
|
|
|
func _confirm() -> void:
|
|
if not visible:
|
|
return
|
|
visible = false
|
|
chosen.emit(_responses[_selectedIndex])
|
|
queue_free()
|
|
|
|
func _updateSelection() -> void:
|
|
var children:Array = _list.get_children()
|
|
for i in children.size():
|
|
var label:Label = children[i]
|
|
if i == _selectedIndex:
|
|
label.text = "▶ " + _responses[i].text
|
|
label.add_theme_color_override("font_color", Color.YELLOW)
|
|
else:
|
|
label.text = _responses[i].text
|
|
label.remove_theme_color_override("font_color")
|
|
|
|
func _updateWorldPosition() -> void:
|
|
if _entity == null:
|
|
return
|
|
var camera:Camera3D = get_viewport().get_camera_3d()
|
|
if camera == null:
|
|
return
|
|
var worldPos:Vector3 = _entity.global_position + Vector3(0, 2.5, 0)
|
|
var screenPos:Vector2 = camera.unproject_position(worldPos)
|
|
var viewportSize:Vector2 = get_viewport().get_visible_rect().size
|
|
position = screenPos - size * 0.5
|
|
position.x = clamp(position.x, 0.0, viewportSize.x - size.x)
|
|
position.y = clamp(position.y, 0.0, viewportSize.y - size.y)
|