RichTextLabel: BBCode, пользовательский текст и эффекты
Полный пример C#: literal-текст, разрешённая ссылка, постепенный показ и собственный RichTextEffect.
Запустить пример
Добавьте UiSampleStyle.cs, RichTextDemo.cs и BlueGlowEffect.cs. Прикрепите RichTextDemo к корню Control, соберите и запустите F6. Ссылка меняет статус внутри примера, ползунок показывает часть текста, строка с именем сохраняет введённые теги как символы, нижняя форматированная строка использует собственный эффект.
RichTextDemo.cs
using Reckfell;
public partial class RichTextDemo : Control
{
public RichTextLabel Info { get; private set; } = null!;
public RichTextLabel Log { get; private set; } = null!;
public Label Status { get; private set; } = null!;
public int LinkCount { get; private set; }
public BlueGlowEffect Effect { get; private set; } = null!;
public override void _Ready()
{
UiSampleStyle.Prepare(this);
var margin = new MarginContainer(); AddChild(margin); margin.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect, margin: 24);
var column = new VBoxContainer(); margin.AddChild(column);
Info = new RichTextLabel
{
BbcodeEnabled = true, FitContent = true, ScrollActive = false, Threaded = false,
Text = "[url=docs]" + UiSampleStyle.Text("Документация", "Documentation") + "[/url]\n[b]Reckfell[/b] [color=#668fff]C# UI[/color]",
VisibleCharactersBehavior = TextServer.VisibleCharactersBehavior.CharsAfterShaping
};
column.AddChild(Info);
Effect = new BlueGlowEffect(); Info.InstallEffect(Effect);
Info.AppendText("\n[blueglow speed=2]" + UiSampleStyle.Text("Свой эффект", "Custom effect") + "[/blueglow]");
var reveal = new HSlider { MinValue = 0, MaxValue = 1, Step = .01, Value = 1 }; column.AddChild(reveal);
reveal.ValueChanged += value => SetReveal((float)value);
Log = new RichTextLabel { BbcodeEnabled = true, SizeFlagsVertical = SizeFlags.ExpandFill, ScrollFollowing = true, Threaded = false }; column.AddChild(Log);
Status = new Label { Text = UiSampleStyle.Text("Ссылка обрабатывается внутри примера", "The example handles its link locally") }; column.AddChild(Status);
Info.MetaClicked += HandleMeta;
AddName("[b]Player[/b]");
}
public void AddName(string text)
{
Log.PushColor(new Color(.4f, .6f, 1)); Log.AddText(UiSampleStyle.Text("Имя: ", "Name: ")); Log.Pop();
Log.AddText(text); Log.Newline();
}
public void SetReveal(float ratio) { if (float.IsFinite(ratio)) Info.VisibleRatio = Mathf.Clamp(ratio, 0, 1); }
private void HandleMeta(Variant data)
{
if (data.VariantType != Variant.Type.String || data.AsString() != "docs") return;
LinkCount++; Status.Text = UiSampleStyle.Text("Выбрана документация", "Documentation selected");
}
}BlueGlowEffect.cs
using Reckfell;
[Tool]
public partial class BlueGlowEffect : RichTextEffect
{
[Export] public string bbcode { get; set; } = "blueglow";
public int Calls { get; private set; }
public override bool _ProcessCustomFX(CharFXTransform charFX)
{
Calls++;
double speed = 2;
if (charFX.Env.TryGetValue("speed", out Variant value) &&
(value.VariantType == Variant.Type.Float || value.VariantType == Variant.Type.Int))
{
double requested = value.AsDouble();
if (double.IsFinite(requested)) speed = System.Math.Clamp(requested, 0, 10);
}
Color color = charFX.Color;
color.A *= (float)(.75 + .25 * System.Math.Cos(charFX.ElapsedTime * speed));
color.R *= .5f; color.G *= .7f; charFX.Color = color;
return true;
}
}Три способа добавления
| Метод | Поведение |
|---|---|
| Text при BbcodeEnabled | Заменяет содержимое и разбирает разметку |
| AppendText | Добавляет разметку BBCode |
| AddText | Добавляет обычный текст без разбора тегов |
| PushColor/PushFont/PushMeta и Pop | Задаёт контекст для следующих добавлений; закрывайте каждый push |
Имена и сообщения пользователя передавайте AddText. Конкатенация непроверенного текста в BBCode позволяет ему менять форматирование и метаданные ссылок. Если всё же нужна строковая разметка с внешними значениями, экранируйте её по правилам BBCode; не используйте HTML-экранирование как замену. GetParsedText даёт текст без разобранных тегов и подходит для получения текста из уже построенного документа, но не является универсальным валидатором/фильтром произвольного BBCode.
Ссылка не должна автоматически запускать произвольную команду или URL. MetaClicked передаёт Variant; пример принимает только строку docs. Для внешней навигации задайте явный список допустимых схем/назначений и ожидаемое действие.
Краткий справочник разметки
| Назначение | Основные теги / параметры |
|---|---|
| Начертание | b, i, u, s, code; нужны подходящие шрифты |
| Цвет | color/bgcolor/fgcolor; именованный цвет или #RRGGBB/#RRGGBBAA |
| Шрифт и размер | font, font_size; используйте существующий ресурс и разумный размер |
| Абзац | p, center, left, right, fill, indent; направление/язык/интервалы проверяйте на реальном тексте |
| Списки | ul/ol; тип нумерации и bullet задают представление списка |
| Ссылка / подсказка | url, hint; поведение ссылки задаёт ваш обработчик |
| Изображение | img; путь, размер и вертикальное выравнивание; не рассчитывайте на автоматическую загрузку сетевого адреса |
| Таблица | table с числом колонок, cell; рамки, внутренние отступы, фон и ограничения ячейки |
| Горизонтальная линия | hr с параметрами толщины/цвета/ширины |
| Буквальные скобки | lb/rb внутри доверенной BBCode-строки |
| Встроенная анимация | pulse, wave, tornado, shake, fade, rainbow; параметры частоты/амплитуды/цвета зависят от эффекта |
Правильно вкладывайте и закрывайте теги; настройки абзаца применяйте на уровне абзаца. Не считайте тег color заменой контраста, а эффект wave — изменением layout. Полные параметры и соответствующие Push-методы находятся в RichTextLabel API.
Показ, прокрутка и стоимость
VisibleRatio от 0 до 1 управляет долей символов; 1 снимает ограничение. Это не длина BBCode-строки и не .NET string.Length. В примере используется CharsAfterShaping: сокращение текста до shaping может менять переносы и лигатуры. Для сценария typewriter учитывайте графемы, перевод и письменность; число глифов не равно числу Unicode-символов.
FitContent подгоняет размер под содержимое, ScrollActive включает внутреннюю прокрутку. Выберите ограниченную область либо внешнюю прокрутку и проверьте размеры контейнеров. ScrollFollowing удобно для журнала. Threaded позволяет фоновую подготовку, но завершение/измерения требуют IsFinished/Finished; пример отключает его для предсказуемого малого документа.
Не пересобирайте большой Text через += каждый кадр: это может заново разобрать/сформировать документ. Добавляйте порциями, ограничивайте длину журнала и профилируйте таблицы, изображения, fallback-шрифты и эффекты. Каждый push/pop должен оставлять ожидаемый стек для следующей порции.
Собственный эффект
BlueGlowEffect регистрирует имя через свойство bbcode; нижний регистр — контракт поиска движка. InstallEffect вызывается до добавления тега. Env содержит разобранные аргументы; проверяйте тип и диапазон. ElapsedTime — время эффекта, а не delta. GlyphIndex — индекс глифа шрифта, не код Unicode; для связи с исходным текстом нужны Range и данные shaping. Не выполняйте файловый ввод/вывод и тяжёлую логику для каждого глифа. Tool нужен для использования эффекта редактором; запуск игрового примера проверяется отдельно.