Skip to content

Instantly share code, notes, and snippets.

@Capital-EX
Last active December 8, 2021 22:15
Show Gist options
  • Save Capital-EX/f86dee1b426bb1dbd672f6ff515f9b42 to your computer and use it in GitHub Desktop.
Save Capital-EX/f86dee1b426bb1dbd672f6ff515f9b42 to your computer and use it in GitHub Desktop.
# This is code to implement a text box. This
# was used in Godot <= 3.1 when rich text was still
# broken.
tool
extends PanelContainer
export (float, 0.0, 1.0, 0.001) var percent_visible := 1.0 setget set_percent_visible, get_percent_visible
export (String, MULTILINE) var text := "" setget set_text, get_text
export (int) var glyphs_per_line := 30 setget set_glyphs_per_line, get_glyphs_per_line
export (int) var lines_shown := 3
export (int, "ALIGN_BEGIN", "ALIGN_CENTER", "ALIGN_END") var alignment := 0 setget set_alignment, get_alignment
export (float, 0.0, 3.0, 0.01) var shake_amount := 3.0
export (float, 0.0, 3.0, 0.01) var wave_amplitude := 3.0
export (float, 1.0, 20.0, 0.01) var wave_frequency := 2.0
var labels := []
var words := []
var effect_labels := []
var old_percent_visible := percent_visible
var is_ready := false
var longest_line_length := 0
var last_word_index := 0
var _wave_time := 0.0
func _enter_tree():
print("Enter Tree")
func _ready():
print("Ready")
is_ready = true
_build_labels(words, float(len(text)))
# Process Text Effects
func _process(delta):
_wave_time += delta
var label_number = 0
for effect_label in effect_labels:
if effect_label.label.visible:
var shake_offset = Vector2(0, 0)
var wave_offset = Vector2(0, 0)
var color = Color.white
for effect in effect_label.effects:
match effect:
"shk":
var shake_x = 1.0/effect_label.label.get_parent().rect_size.x * shake_amount
var shake_y = 1.0/effect_label.label.get_parent().rect_size.y * shake_amount
shake_offset.x += randf() * shake_x - shake_x / 2.0
shake_offset.y += randf() * shake_y - shake_y / 2.0
"wav":
var wavy_y = 1.0/effect_label.label.get_parent().rect_size.y * wave_amplitude
wave_offset.y += sin(wave_frequency * _wave_time + label_number) * wavy_y
label_number += 1
effect_label.label.anchor_top = wave_offset.y + shake_offset.y
effect_label.label.anchor_left = shake_offset.x
func _parse(text: String) -> Array:
var in_tag := false
var remove_effect := false
var lines := []
var glyphs := []
var words := []
var effects := []
var effect := ""
for character_index in range(0, len(text)):
var character = text[character_index]
if character == "{":
in_tag = true
elif character == "}":
in_tag = false
if not effect.empty():
var index = effects.find(effect)
if index == -1:
effects.append(effect)
else:
effects.remove(index)
effect = ""
elif character == " ":
if len(glyphs) > 0:
words.append(glyphs)
words.append([{character = " ", effects = []}])
glyphs = []
elif character == "\n":
if len(glyphs) > 0:
words.append(glyphs)
words.append([{character = "\n", effects = []}])
glyphs = []
else:
if in_tag:
effect += character
else:
glyphs.append({character = character, effects = effects.duplicate()})
if not glyphs.empty():
words.append(glyphs)
return words
func create_line(alignment: int) -> HBoxContainer:
var line := HBoxContainer.new()
line.add_constant_override("separation", 0)
line.alignment = alignment
line.add_constant_override("separation", 0)
return line
func create_label(text: String, is_visible: bool, color: Color) -> Label:
var label = Label.new()
label.text = text
label.self_modulate = color
label.visible = is_visible
return label
func get_color_from_effects(effects: Array) -> Color:
for effect in effects:
if effect.begins_with("#"):
match len(effect):
4, 7, 9:
return Color(effect)
return Color.white
func _build_labels(words: Array, character_count: float, rebuild := false):
if find_node("Lines") == null:
return
if is_ready or Engine.editor_hint:
effect_labels = []
labels = []
longest_line_length = 0 if rebuild else longest_line_length
var glyphs_on_line := 0
var lines := 0
var wave_offset := Vector2(0,0)
var shake_offset := Vector2(0,0)
var color := Color(1,1,1)
var letter_index := 0
var current_line_length := 0
var font = get_font("")
var current_line = create_line(alignment)
for child in $Lines.get_children():
$Lines.remove_child(child)
child.queue_free()
for word_index in range(last_word_index, len(words)):
var word = words[word_index]
# Wrap text by moving down a line, this prevents word breaks
# However, a word that is longer than the line length will be broken
# ADDITIONALLY, newlines *do not* count towards the number of
# characters on a line.
if glyphs_on_line + len(word) > glyphs_per_line and len(word) < glyphs_on_line and word[0].character != "\n":
longest_line_length = max(current_line_length, longest_line_length)
current_line_length = 0
$Lines.add_child(current_line)
current_line = create_line(alignment)
lines += 1
glyphs_on_line = 0
if lines + 1 > lines_shown:
last_word_index = word_index
break
var number_of_glyphs = len(word)
for glyph_index in range(0, number_of_glyphs):
letter_index += 1
var current_glyph = word[glyph_index]
if current_glyph.character == "\n":
longest_line_length = max(current_line_length, longest_line_length)
current_line_length = 0
$Lines.add_child(current_line)
current_line = create_line(alignment)
lines += 1
glyphs_on_line = 0
continue
var label = create_label(
current_glyph.character,
letter_index / character_count <= percent_visible,
get_color_from_effects(current_glyph.effects))
labels.append(label)
current_line.add_child(label)
current_line_length += font.get_string_size(label.text).x
if not current_glyph.effects.empty():
effect_labels.append({
label = label,
position = Vector2(label.margin_left,
label.margin_top),
effects = current_glyph.effects})
glyphs_on_line += 1
if glyphs_on_line == glyphs_per_line:
longest_line_length = max(current_line_length, longest_line_length)
current_line_length = 0
$Lines.add_child(current_line)
lines += 1
current_line = create_line(alignment)
glyphs_on_line = 0
print(lines)
longest_line_length = max(current_line_length, longest_line_length)
rect_size.x = 0
rect_size.y = 0
$Lines.rect_size.x = 0
$Lines.rect_size.y = 0
$Lines.rect_min_size.x = longest_line_length
minimum_size_changed()
$Lines.add_child(current_line)
func set_text(new_text: String) -> void:
text = new_text
words = _parse(text)
_build_labels(words, len(text), true)
func get_text() -> String:
return text
func set_alignment(new_alignment):
alignment = new_alignment
if find_node("Lines") == null:
return
if is_ready or Engine.editor_hint:
for child in $Lines.get_children():
child.alignment = alignment
func get_alignment():
return alignment
func set_percent_visible(new_percent_visible: float):
old_percent_visible = percent_visible
percent_visible = new_percent_visible
if find_node("Lines") == null:
return
var characters := 0
if is_ready or Engine.editor_hint:
for line in $Lines.get_children():
characters += line.get_child_count()
var label_index := 0
for index in range(ceil(characters * percent_visible), characters):
labels[index].visible = false
for index in range(ceil(characters * old_percent_visible), ceil(characters * percent_visible)):
labels[index].visible = true
func get_percent_visible():
return percent_visible
func get_glyphs_per_line() -> int:
return glyphs_per_line
func set_glyphs_per_line(new_glyphs_per_line: int):
glyphs_per_line = new_glyphs_per_line
_build_labels(words, len(text))
func _input(event):
if event is InputEventMouseButton:
if event.is_pressed():
_build_labels(words, float(len(words)))
func _on_script_changed():
print("?")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment