Untitled

Anonymous
plain_text
02/18/2026 2:25 AM
5.2 KB
9
Indexable
extends Control

signal choice_selected(choice_index: int)

@onready var box: Panel = $Box
@onready var portrait_rect: TextureRect = $Box/BoxCol/ContentRow/PortraitContainer/Portrait
@onready var name_label: Label = $Box/BoxCol/ContentRow/TextCol/NameLabel
@onready var body_text: RichTextLabel = $Box/BoxCol/ContentRow/TextCol/BodyText
@onready var choices_col: VBoxContainer = $Box/BoxCol/ContentRow/TextCol/Choices
@onready var hint_label: Label = $Box/BoxCol/HintLabel

var _is_open := false
var _is_typing := false
var _full_text := ""
var _char_index := 0
var _type_timer := 0.0
var _type_speed := 0.03 # seconds per char
var _has_choices := false

func _ready() -> void:
	# Optional safety check
	if name_label == null or body_text == null or choices_col == null or hint_label == null or portrait_rect == null:
		push_error("DialogueUI: One or more UI node paths are wrong. Check DialogueUI tree.")

	# Ensure RichTextLabel is set up for fixed-height scrolling
	body_text.scroll_active = true
	body_text.scroll_following = true
	# Keep fixed height mode (Option B): do NOT enable fit content
	# If you had it enabled in the inspector, disable it there.
	# body_text.fit_content = false  # (property name differs by version; set in inspector)

	hide_dialogue()

func show_dialogue(speaker_name: String, text: String, portrait_tex: Texture2D, choices: Array) -> void:
	_is_open = true
	visible = true
	box.visible = true

	name_label.text = speaker_name

	# Portrait (set texture + show/hide)
	if portrait_tex != null:
		portrait_rect.texture = portrait_tex
		portrait_rect.visible = true
	else:
		portrait_rect.texture = null
		portrait_rect.visible = false

	# Typewriter init
	_full_text = text
	_char_index = 0
	_type_timer = 0.0
	_is_typing = true
	body_text.text = ""

	# Ensure scrolling is enabled every time we open
	body_text.scroll_active = true
	body_text.scroll_following = true
	await _scroll_body_to_bottom()

	# Choices
	_clear_choices()
	_has_choices = (choices.size() > 0)
	if _has_choices:
		_build_choices(choices)

	# Hint
	hint_label.visible = not _has_choices
	hint_label.text = "E: continue" if not _has_choices else ""

func hide_dialogue() -> void:
	_is_open = false
	visible = false

func is_open() -> bool:
	return _is_open

func is_typing() -> bool:
	return _is_typing

func fast_reveal() -> void:
	if not _is_typing:
		return
	_is_typing = false
	body_text.text = _full_text
	await _scroll_body_to_bottom()

func _process(delta: float) -> void:
	if not _is_open:
		return

	if _is_typing:
		var char_speed := _type_speed
		if Input.is_action_pressed("fast_text"):
			char_speed = 0.005

		_type_timer += delta
		var changed := false

		while _type_timer >= char_speed and _char_index < _full_text.length():
			_type_timer -= char_speed
			_char_index += 1
			body_text.text = _full_text.substr(0, _char_index)
			changed = true

		# Only scroll when the text actually changed (reduces jitter + work)
		if changed:
			await _scroll_body_to_bottom()

		if _char_index >= _full_text.length():
			_is_typing = false
			await _scroll_body_to_bottom()

func _unhandled_input(event: InputEvent) -> void:
	if not _is_open:
		return

	if event.is_action_pressed("interact"):
		if _is_typing:
			fast_reveal()
			return
		# If there are no choices, owner can listen for E and close/advance.
		# We intentionally do nothing here.

func _clear_choices() -> void:
	for c in choices_col.get_children():
		c.queue_free()

func _build_choices(choices: Array) -> void:
	# choices = [{ "label": "text" }, ...]
	for i in range(choices.size()):
		var b := Button.new()
		b.text = str(choices[i].get("label", ""))
		b.focus_mode = Control.FOCUS_ALL
		b.pressed.connect(func(): emit_signal("choice_selected", i))
		choices_col.add_child(b)

	if choices_col.get_child_count() > 0:
		choices_col.get_child(0).grab_focus()

func _scroll_body_to_bottom() -> void:
	await get_tree().process_frame
	if body_text.get_line_count() > 0:
		body_text.scroll_to_line(body_text.get_line_count() - 1)
Editor is loading...
Leave a Comment