@tool class_name AdvancedRichText extends RichTextLabel @export_multiline var userText:String = "" # The text the user is asking for @export_multiline var _finalText:String = "" # The final text after processing (translation, wrapping, etc.) @export var _newLineIndexes:Array[int] = [] # The indexes of where each line starts in finalText @export var _lines:PackedStringArray = []; # Hides the original RichTextLabel text property func _set(property: StringName, value) -> bool: if property == "text": userText = value _recalcText() return true elif property == "richtextlabel_text": text = value return true return false func _get(property: StringName): if property == "text": return userText elif property == "richtextlabel_text": return text return null @export var translate:bool = true: set(value): translate = value _recalcText() get(): return translate @export var smartWrap:bool = true: set(value): smartWrap = value _recalcText() get(): return smartWrap @export var maxLines:int = -1: set(value): maxLines = value _recalcText() get(): return maxLines @export var startLine:int = 0: set(value): startLine = value _recalcText() get(): return startLine # Returns count of characters that can be displayed, assuming visible_chars = -1 func getCharactersDisplayedCount() -> int: # Count characters var count = 0 var lineCount = min(startLine + maxLines, _lines.size()) - startLine for i in range(startLine, startLine + lineCount): count += _lines[i].length() if lineCount > 1: count += lineCount - 1 # Add newlines return count func getFinalText() -> String: return _finalText func getTotalLineCount() -> int: return _lines.size() func _enter_tree() -> void: self.threaded = false; func _recalcText() -> void: _lines.clear() if userText.is_empty(): self.richtextlabel_text = "" return # Translate if needed var textTranslated = userText if self.translate: textTranslated = tr(textTranslated) # Replace input bb tags. var regex = RegEx.new() regex.compile(r"\[input action=(.*?)\](.*?)\[/input\]") var inputIconText = textTranslated for match in regex.search_all(textTranslated): var action = match.get_string(1).to_lower() var height:int = get_theme_font_size("normal_font_size") var img_tag = "[img height=%d valign=center,center]res://ui/input/%s.tres[/img]" % [ height, action ] inputIconText = inputIconText.replace(match.get_string(0), img_tag) # Perform smart wrapping var wrappedText = inputIconText if smartWrap: var unwrappedText = wrappedText.strip_edges() self.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART; self.richtextlabel_text = unwrappedText self.visible_characters = -1; self.fit_content = false; _newLineIndexes = []; # Determine where the wrapped newlines are var line = 0; var wasNewLine = false; for i in range(0, self.richtextlabel_text.length()): var tLine = self.get_character_line(i); if tLine == line: wasNewLine = false if self.richtextlabel_text[i] == "\n": wasNewLine = true continue; if !wasNewLine: _newLineIndexes.append(i); line = tLine; # Create fake pre-wrapped text. wrappedText = ""; for i in range(0, self.richtextlabel_text.length()): if _newLineIndexes.find(i) != -1 and i != 0: wrappedText += "\n"; wrappedText += self.richtextlabel_text[i]; # Handle max and start line(s) var maxText = wrappedText if maxLines > 0: _lines = maxText.split("\n", true); var selectedLines = []; for i in range(startLine, min(startLine + maxLines, _lines.size())): selectedLines.append(_lines[i]); maxText = "\n".join(selectedLines); _finalText = maxText self.richtextlabel_text = maxText print("Updated text")