RichTextLabel is the GUI control for displaying formatted text in Godot. It supports mixed font styles, inline images, tables, lists, animated text effects, meta links, and BBCode parsing. This page covers the internal architecture of RichTextLabel, its item/tag system, layout pipeline, custom effects API, and relevant integration points.
For plain text display without formatting, see Label & LineEdit. For the underlying text shaping primitives used by RichTextLabel, see Text Shaping System.
RichTextLabel is a Control subclass located at scene/gui/rich_text_label.h and scene/gui/rich_text_label.cpp It holds a VScrollBar child and optionally a PopupMenu for right-click context menus.
Internal layout diagram:
Sources: scene/gui/rich_text_label.h553-655 scene/gui/rich_text_label.h537-539
RichTextLabel represents its content as a tree of Item structs. These are not scene nodes — they are plain C++ structs managed entirely within the class. A RID_PtrOwner<Item> named items owns all items by RID.
The document root is an ItemFrame named main. Each paragraph in the document maps to a Line entry inside an ItemFrame's lines vector. Each Line contains a TextParagraph (text_buf) that holds the shaped text for that paragraph.
Item ownership and traversal:
Sources: scene/gui/rich_text_label.h160-198 scene/gui/rich_text_label.h200-226 scene/gui/rich_text_label.h228-252 scene/gui/rich_text_label.h537-610
Item Base StructEvery item carries:
| Field | Type | Description |
|---|---|---|
index | int | Sequential ID |
char_ofs | int | Character position in the document |
parent | Item* | Enclosing item tag (interval predecessor, not tree parent) |
type | ItemType | Enum identifying the concrete item type |
subitems | List<Item*> | Children |
E | List<Item*>::Element* | Iterator position in parent's subitem list |
rid | RID | Key in the items RID owner |
Sources: scene/gui/rich_text_label.h200-226
The ItemType enum defines every possible node in the tag tree. These correspond directly to BBCode tags and programmatic push_* calls.
Sources: scene/gui/rich_text_label.h74-105 scene/gui/rich_text_label.h254-512
Key concrete item structs:
| Struct | Extra Fields |
|---|---|
ItemFrame | lines, first_invalid_line, padding, odd_row_bg, even_row_bg |
ItemText | text (String) |
ItemImage | image, size, rq_size, inline_align, region, key, pad, alt_text |
ItemTable | columns (Column structs), rows, total_width, total_height |
ItemMeta | meta (Variant), underline (MetaUnderline), tooltip |
ItemFX | elapsed_time, connected — base for all animated effects |
ItemCustomFX | char_fx_transform (CharFXTransform), custom_effect (RichTextEffect) |
Sources: scene/gui/rich_text_label.h228-511
Content is added either by setting text (with bbcode_enabled) or through the programmatic push/pop/add API.
push_* methods open a formatting region. pop() closes the most recent open tag. pop_all() closes all open tags. pop_context() / push_context() provide a save/restore mechanism for the tag stack.
Key push methods:
| Method | Item Created | BBCode Tag |
|---|---|---|
push_font(font, size) | ItemFont | [font] |
push_font_size(size) | ItemFontSize | [font_size] |
push_bold() | ItemFont (def=RTL_BOLD_FONT) | [b] |
push_italics() | ItemFont (def=RTL_ITALICS_FONT) | [i] |
push_mono() | ItemFont (def=RTL_MONO_FONT) | [code] |
push_color(color) | ItemColor | [color] |
push_underline(color) | ItemUnderline | [u] |
push_strikethrough(color) | ItemStrikethrough | [s] |
push_meta(meta, underline, tooltip) | ItemMeta | [url] |
push_paragraph(align, ...) | ItemParagraph | [p] |
push_indent(level) | ItemIndent | [indent] |
push_list(level, type, capitalize) | ItemList | [ol], [ul] |
push_table(columns, align) | ItemTable | [table] |
push_fade(start, length) | ItemFade | [fade] |
push_shake(strength, rate) | ItemShake | [shake] |
push_wave(freq, amp) | ItemWave | [wave] |
push_tornado(radius, freq) | ItemTornado | [tornado] |
push_rainbow(sat, val, freq) | ItemRainbow | [rainbow] |
push_pulse(color, freq, ease) | ItemPulse | [pulse] |
push_dropcap(text, font, size, ...) | ItemDropcap | [dropcap] |
push_hint(desc) | ItemHint | [hint] |
push_language(lang) | ItemLanguage | [lang] |
Sources: scene/gui/rich_text_label.h828-900 doc/classes/RichTextLabel.xml19-750
add_text(text) creates an ItemText. add_image(...) creates an ItemImage. add_newline() creates an ItemNewline and starts a new Line in current_frame. add_hr(...) creates a horizontal rule image item.
parse_bbcode(bbcode) clears the stack and rebuilds from BBCode. append_text(bbcode) appends parsed content without clearing. Both are driven by the internal BBCode parser in scene/gui/rich_text_label.cpp which tokenizes tag names and their parameters (including quoted values) and calls the corresponding push/add methods.
Note: Assignments to
textclear the tag stack and re-parse viaparse_bbcode. Tags opened viapush_*before settingtextare erased.
Sources: scene/gui/rich_text_label.cpp doc/classes/RichTextLabel.xml66-83
Text layout occurs in multiple passes, managed by _validate_line_caches() and _process_line_caches().
Layout pipeline:
Sources: scene/gui/rich_text_label.cpp602-827 scene/gui/rich_text_label.cpp377-463 scene/gui/rich_text_label.cpp465-565
_shape_linescene/gui/rich_text_label.cpp602-827 iterates all items in a paragraph:
ITEM_TEXT → l.text_buf->add_string(text, font, size, lang, rid)ITEM_IMAGE → l.text_buf->add_object(rid, img_size, inline_align, 1)ITEM_TABLE → shapes nested frames recursively, then l.text_buf->add_object(...)ITEM_NEWLINE → l.text_buf->add_string(zero-width-space, font, size)ITEM_DROPCAP → l.text_buf->set_dropcap(...)Font selection walks the item's parent chain using _find_font() and _find_font_size().
_resize_linescene/gui/rich_text_label.cpp465-565 recalculates widths without reshaping text. Used when the control is resized. Handles percentage-width images and table column distribution.
ItemTable triggers two-pass column sizing:
_update_table_column_width() — distributes available width across columns using expand ratios and min/max constraints scene/gui/rich_text_label.cpp829-908_update_table_size() — computes per-cell offsets, row heights, and baselines scene/gui/rich_text_label.cpp910-973_add_list_prefixes() scene/gui/rich_text_label.cpp273-375 generates list markers (numbers, letters, roman numerals, bullets) as TextLine objects stored in Line::text_prefix. The maximum marker width for a list level is cached in ItemList::max_width.
_draw_line() scene/gui/rich_text_label.cpp976 renders a single paragraph through multiple ordered passes defined by RTLDrawStep:
| Pass | RTLDrawStep | What is drawn |
|---|---|---|
| 0 | DRAW_STEP_BACKGROUND | ITEM_BGCOLOR background rects |
| 1 | DRAW_STEP_SHADOW_OUTLINE | Shadow outline pass |
| 2 | DRAW_STEP_SHADOW | Font shadow glyphs |
| 3 | DRAW_STEP_OUTLINE | Font outline glyphs |
| 4 | DRAW_STEP_TEXT | Main text glyphs, images, FX |
| 5 | DRAW_STEP_FOREGROUND | ITEM_FGCOLOR foreground rects |
Sources: scene/gui/rich_text_label.h50-58 scene/gui/rich_text_label.cpp976
visible_characters and visible_ratio control how many characters are rendered. The visible_chars_behavior (TextServer::VisibleCharactersBehavior) selects one of four modes:
| Mode | Constant | Behavior |
|---|---|---|
| Before shaping | VC_CHARS_BEFORE_SHAPING | Text truncated before shaping; shapes only visible portion |
| After shaping (chars) | VC_CHARS_AFTER_SHAPING | Shapes all, skips glyphs beyond char count |
| Glyphs LTR | VC_GLYPHS_LTR | Skips glyphs counted left-to-right |
| Glyphs RTL | VC_GLYPHS_RTL | Skips glyphs counted right-to-left |
Sources: scene/gui/rich_text_label.h660-662 scene/gui/rich_text_label.cpp995-999
RichTextLabel supports per-character animated effects through a plugin-like RichTextEffect / CharFXTransform system.
Effect system diagram:
Sources: scene/gui/rich_text_label.h428-508 scene/gui/rich_text_label.cpp54-64
RichTextEffect subclass sets its bbcode property to the tag name it handles.install_effect(effect) or the custom_effects property.[bbcode_name]...[/bbcode_name]. This creates an ItemCustomFX wrapping that effect._update_fx() traverses items. For each ItemCustomFX, it populates char_fx_transform with per-character data and calls custom_effect._process_custom_fx(char_fx_transform).char_fx_transform.offset, color, font_size, visible, etc._draw_line(), these values override the default glyph draw parameters.Built-in effects (ItemShake, ItemWave, ItemTornado, ItemRainbow, ItemPulse) are computed directly inside _draw_line() without going through RichTextEffect.
Sources: scene/gui/rich_text_label.cpp doc/classes/RichTextLabel.xml311-338
push_meta(meta, underline_mode, tooltip) wraps content in an ItemMeta. When the user clicks or hovers over the region, RichTextLabel emits signals:
| Signal | When |
|---|---|
meta_clicked(meta) | Mouse pressed on meta region |
meta_hover_started(meta) | Mouse entered meta region |
meta_hover_ended(meta) | Mouse left meta region |
MetaUnderline controls underline rendering:
| Value | Behavior |
|---|---|
META_UNDERLINE_NEVER | No underline |
META_UNDERLINE_ALWAYS | Always underlined |
META_UNDERLINE_ON_HOVER | Underlined only when hovered |
Hit detection walks the item tree via _find_click() → _find_click_in_line(), checking glyph bounds against the click position.
Sources: scene/gui/rich_text_label.h68-72 scene/gui/rich_text_label.h352-357 doc/classes/RichTextLabel.xml
RichTextLabel contains a VScrollBar* vscroll. Scroll position is maintained in pixel units. Key scroll properties:
| Property | Type | Description |
|---|---|---|
scroll_active | bool | Whether scrolling is enabled |
scroll_follow | bool | Auto-scroll to bottom on new content |
scroll_follow_visible_characters | bool | Follow visible_characters cursor |
fit_content | bool | Expand control height to fit all content |
get_v_scroll_bar() returns the internal VScrollBar (removing it causes crashes).
scroll_to_line(line) and scroll_to_paragraph(paragraph) programmatically position the scroll.
Sources: scene/gui/rich_text_label.h558-571 doc/classes/RichTextLabel.xml
Selection state is tracked in the Selection struct:
struct Selection {
ItemFrame *click_frame, *from_frame, *to_frame;
int click_line, from_line, to_line;
Item *click_item, *from_item, *to_item;
int click_char, from_char, to_char;
bool active, enabled, double_click, drag_attempt;
};
selection_enabled enables mouse text selection.get_selected_text() walks from from_item/from_char to to_item/to_char and extracts plain text.deselect_on_focus_loss_enabled clears selection when focus is lost.selection_modifier Callable can transform selected text on copy.Sources: scene/gui/rich_text_label.h617-642
When threaded = true, text shaping is offloaded to a WorkerThreadPool task. The _thread_function() runs _process_line_caches() on the background thread. Atomic variables coordinate state:
| Atomic | Type | Meaning |
|---|---|---|
stop_thread | atomic<bool> | Signals the thread to abort |
updating | atomic<bool> | Thread is currently processing |
validating | atomic<bool> | Validation in progress |
loaded | atomic<double> | Fraction of content shaped (0.0–1.0) |
parsing_bbcode | atomic<bool> | BBCode parse in progress |
is_finished() returns true when the background thread is done or when threading is disabled.
A progress indicator uses progress_bg_style and progress_fg_style theme items to display loading progress when progress_delay milliseconds have elapsed.
Sources: scene/gui/rich_text_label.h541-551 doc/classes/RichTextLabel.xml346-362
RichTextLabel reads many theme items via _update_theme_item_cache() into ThemeCache:
| Category | Theme Items |
|---|---|
| Fonts | normal_font, bold_font, italics_font, bold_italics_font, mono_font and their *_font_size variants |
| Colors | default_color, font_selected_color, selection_color, font_outline_color, font_shadow_color, outline_color |
| Spacing | line_separation, paragraph_separation, text_highlight_h_padding, text_highlight_v_padding |
| Shadow | shadow_outline_size, shadow_offset_x, shadow_offset_y |
| Tables | table_h_separation, table_v_separation, table_odd_row_bg, table_even_row_bg, table_border |
| Styles | normal_style, focus_style, progress_bg_style, progress_fg_style |
| Icon | horizontal_rule (Texture2D for add_hr) |
Font overrides can be added at runtime with add_theme_font_override("bold_font", my_font), as demonstrated in EditorLog::_update_theme() editor/editor_log.cpp71-108
Sources: scene/gui/rich_text_label.h774-821 editor/editor_log.cpp71-108
EditorLog uses RichTextLabel as its output display editor/editor_log.h132 and also instantiates a hidden RichTextLabel (bbcode_parser) to strip BBCode tags during search editor/editor_log.cpp369-378 Key patterns from this usage:
log->push_color(color) / log->add_image(icon) / log->push_bold() / log->add_text(text) / log->pop() — to format a structured log entry.log->remove_paragraph(log->get_paragraph_count() - 2) — to replace the last line in-place.log->get_parsed_text() — to get plain text for clipboard copy.log->is_updating() — checked before adding content to avoid data corruption during redraw.Sources: editor/editor_log.cpp384-450 editor/editor_log.cpp243-253
| Method | Purpose |
|---|---|
parse_bbcode(bbcode) | Clear and re-parse full BBCode content |
append_text(bbcode) | Append parsed BBCode without clearing |
add_text(text) | Append raw (unformatted) text |
add_image(texture, ...) | Embed an inline image |
add_hr(...) | Add a horizontal rule |
push_*(...) / pop() | Open/close formatting regions |
push_context() / pop_context() | Save/restore the tag stack state |
clear() | Remove all content from the tag stack |
remove_paragraph(idx) | Remove a specific paragraph |
invalidate_paragraph(idx) | Force re-shape from a paragraph onward |
install_effect(effect) | Register a RichTextEffect plugin |
get_parsed_text() | Return plain text without BBCode |
get_selected_text() | Return the current selection as plain text |
scroll_to_line(line) | Scroll to a specific wrapped line |
get_v_scroll_bar() | Access the internal VScrollBar |
is_finished() | Check if threaded shaping is complete |
Sources: scene/gui/rich_text_label.h827-960 doc/classes/RichTextLabel.xml
Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.