Merge pull request #23658 from Eoin-ONeill-Yokai/rich-text-plus

RichTextLabel - New Real Time Text Effects and Custom BBCode Extensions
This commit is contained in:
Rémi Verschelde 2019-09-04 14:49:55 +02:00 committed by GitHub
commit 3d76eb8938
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 795 additions and 29 deletions

View file

@ -0,0 +1,122 @@
/*************************************************************************/
/* rich_text_effect.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "rich_text_effect.h"
#include "core/script_language.h"
void RichTextEffect::_bind_methods() {
BIND_VMETHOD(MethodInfo(Variant::INT, "_process_custom_fx", PropertyInfo(Variant::OBJECT, "char_fx", PROPERTY_HINT_RESOURCE_TYPE, "CustomFXChar")));
}
Variant RichTextEffect::get_bbcode() const {
Variant r;
if (get_script_instance()) {
if (!get_script_instance()->get("bbcode", r)) {
String path = get_script_instance()->get_script()->get_path();
r = path.get_file().get_basename();
}
}
return r;
}
bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) {
bool return_value = false;
if (get_script_instance()) {
Variant v = get_script_instance()->call("_process_custom_fx", p_cfx);
if (v.get_type() != Variant::BOOL) {
return_value = false;
} else {
return_value = (bool)v;
}
}
return return_value;
}
RichTextEffect::RichTextEffect() {
}
void CharFXTransform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_relative_index"), &CharFXTransform::get_relative_index);
ClassDB::bind_method(D_METHOD("set_relative_index", "index"), &CharFXTransform::set_relative_index);
ClassDB::bind_method(D_METHOD("get_absolute_index"), &CharFXTransform::get_absolute_index);
ClassDB::bind_method(D_METHOD("set_absolute_index", "index"), &CharFXTransform::set_absolute_index);
ClassDB::bind_method(D_METHOD("get_elapsed_time"), &CharFXTransform::get_elapsed_time);
ClassDB::bind_method(D_METHOD("set_elapsed_time", "time"), &CharFXTransform::set_elapsed_time);
ClassDB::bind_method(D_METHOD("is_visible"), &CharFXTransform::is_visible);
ClassDB::bind_method(D_METHOD("set_visibility", "visibility"), &CharFXTransform::set_visibility);
ClassDB::bind_method(D_METHOD("get_offset"), &CharFXTransform::get_offset);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &CharFXTransform::set_offset);
ClassDB::bind_method(D_METHOD("get_color"), &CharFXTransform::get_color);
ClassDB::bind_method(D_METHOD("set_color", "color"), &CharFXTransform::set_color);
ClassDB::bind_method(D_METHOD("get_environment"), &CharFXTransform::get_environment);
ClassDB::bind_method(D_METHOD("set_environment", "environment"), &CharFXTransform::set_environment);
ClassDB::bind_method(D_METHOD("get_character"), &CharFXTransform::get_character);
ClassDB::bind_method(D_METHOD("set_character", "character"), &CharFXTransform::set_character);
ClassDB::bind_method(D_METHOD("get_value_or", "key", "default_value"), &CharFXTransform::get_value_or);
ADD_PROPERTY(PropertyInfo(Variant::INT, "relative_index"), "set_relative_index", "get_relative_index");
ADD_PROPERTY(PropertyInfo(Variant::INT, "absolute_index"), "set_absolute_index", "get_absolute_index");
ADD_PROPERTY(PropertyInfo(Variant::REAL, "elapsed_time"), "set_elapsed_time", "get_elapsed_time");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visibility", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "env"), "set_environment", "get_environment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "character"), "set_character", "get_character");
}
Variant CharFXTransform::get_value_or(String p_key, Variant p_default_value) {
if (!this->environment.has(p_key))
return p_default_value;
Variant r = environment[p_key];
if (r.get_type() != p_default_value.get_type())
return p_default_value;
return r;
}
CharFXTransform::CharFXTransform() {
relative_index = 0;
absolute_index = 0;
visibility = true;
offset = Point2();
color = Color();
character = 0;
}

View file

@ -0,0 +1,87 @@
/*************************************************************************/
/* rich_text_effect.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#ifndef RICH_TEXT_EFFECT_H
#define RICH_TEXT_EFFECT_H
#include "core/resource.h"
class RichTextEffect : public Resource {
GDCLASS(RichTextEffect, Resource);
OBJ_SAVE_TYPE(RichTextEffect);
protected:
static void _bind_methods();
public:
Variant get_bbcode() const;
bool _process_effect_impl(Ref<class CharFXTransform> p_cfx);
RichTextEffect();
};
class CharFXTransform : public Reference {
GDCLASS(CharFXTransform, Reference);
protected:
static void _bind_methods();
public:
uint64_t relative_index;
uint64_t absolute_index;
bool visibility;
Point2 offset;
Color color;
CharType character;
float elapsed_time;
Dictionary environment;
CharFXTransform();
uint64_t get_relative_index() { return relative_index; }
void set_relative_index(uint64_t p_index) { relative_index = p_index; }
uint64_t get_absolute_index() { return absolute_index; }
void set_absolute_index(uint64_t p_index) { absolute_index = p_index; }
float get_elapsed_time() { return elapsed_time; }
void set_elapsed_time(float p_elapsed_time) { elapsed_time = p_elapsed_time; }
bool is_visible() { return visibility; }
void set_visibility(bool p_vis) { visibility = p_vis; }
Point2 get_offset() { return offset; }
void set_offset(Point2 p_offset) { offset = p_offset; }
Color get_color() { return color; }
void set_color(Color p_color) { color = p_color; }
int get_character() { return (int)character; }
void set_character(int p_char) { character = (CharType)p_char; }
Dictionary get_environment() { return environment; }
void set_environment(Dictionary p_environment) { environment = p_environment; }
Variant get_value_or(String p_key, Variant p_default_value);
};
#endif // RICH_TEXT_EFFECT_H

View file

@ -30,10 +30,11 @@
#include "rich_text_label.h"
#include "core/math/math_defs.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "modules/regex/regex.h"
#include "scene/scene_string_names.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_scale.h"
#endif
@ -139,6 +140,7 @@ Rect2 RichTextLabel::_get_text_rect() {
Ref<StyleBox> style = get_stylebox("normal");
return Rect2(style->get_offset(), get_size() - style->get_minimum_size());
}
int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &y, int p_width, int p_line, ProcessMode p_mode, const Ref<Font> &p_base_font, const Color &p_base_color, const Color &p_font_color_shadow, bool p_shadow_as_outline, const Point2 &shadow_ofs, const Point2i &p_click_pos, Item **r_click_item, int *r_click_char, bool *r_outside, int p_char_count) {
RID ci;
@ -292,7 +294,6 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
Color selection_bg;
if (p_mode == PROCESS_DRAW) {
selection_fg = get_color("font_color_selected");
selection_bg = get_color("selection_color");
}
@ -343,18 +344,24 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
Color font_color_shadow;
bool underline = false;
bool strikethrough = false;
ItemFade *fade = NULL;
int it_char_start = p_char_count;
Vector<ItemFX *> fx_stack = Vector<ItemFX *>();
bool custom_fx_ok = true;
if (p_mode == PROCESS_DRAW) {
color = _find_color(text, p_base_color);
font_color_shadow = _find_color(text, p_font_color_shadow);
if (_find_underline(text) || (_find_meta(text, &meta) && underline_meta)) {
underline = true;
} else if (_find_strikethrough(text)) {
strikethrough = true;
}
fade = _fetch_by_type<ItemFade>(text, ITEM_FADE);
_fetch_item_stack<ItemFX>(text, fx_stack);
} else if (p_mode == PROCESS_CACHE) {
l.char_count += text->text.length();
}
@ -431,8 +438,11 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
ofs += cw;
} else if (p_mode == PROCESS_DRAW) {
bool selected = false;
Color fx_color = Color(color);
Point2 fx_offset;
CharType fx_char = c[i];
if (selection.active) {
int cofs = (&c[i]) - cf;
@ -442,8 +452,78 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
}
int cw = 0;
int c_item_offset = p_char_count - it_char_start;
float faded_visibility = 1.0f;
if (fade) {
if (c_item_offset >= fade->starting_index) {
faded_visibility -= (float)(c_item_offset - fade->starting_index) / (float)fade->length;
faded_visibility = faded_visibility < 0.0f ? 0.0f : faded_visibility;
}
fx_color.a = faded_visibility;
}
bool visible = visible_characters < 0 || ((p_char_count < visible_characters && YRANGE_VISIBLE(y + lh - line_descent - line_ascent, line_ascent + line_descent)) &&
faded_visibility > 0.0f);
for (int j = 0; j < fx_stack.size(); j++) {
ItemCustomFX *item_custom = Object::cast_to<ItemCustomFX>(fx_stack[j]);
ItemShake *item_shake = Object::cast_to<ItemShake>(fx_stack[j]);
ItemWave *item_wave = Object::cast_to<ItemWave>(fx_stack[j]);
ItemTornado *item_tornado = Object::cast_to<ItemTornado>(fx_stack[j]);
ItemRainbow *item_rainbow = Object::cast_to<ItemRainbow>(fx_stack[j]);
if (item_custom && custom_fx_ok) {
Ref<CharFXTransform> charfx = Ref<CharFXTransform>(memnew(CharFXTransform));
Ref<RichTextEffect> custom_effect = _get_custom_effect_by_code(item_custom->identifier);
if (!custom_effect.is_null()) {
charfx->elapsed_time = item_custom->elapsed_time;
charfx->environment = item_custom->environment;
charfx->relative_index = c_item_offset;
charfx->absolute_index = p_char_count;
charfx->visibility = visible;
charfx->offset = fx_offset;
charfx->color = fx_color;
charfx->character = fx_char;
bool effect_status = custom_effect->_process_effect_impl(charfx);
custom_fx_ok = effect_status;
fx_offset += charfx->offset;
fx_color = charfx->color;
visible &= charfx->visibility;
fx_char = charfx->character;
}
} else if (item_shake) {
uint64_t char_current_rand = item_shake->offset_random(c_item_offset);
uint64_t char_previous_rand = item_shake->offset_previous_random(c_item_offset);
uint64_t max_rand = 2147483647;
double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
n_time = (n_time > 1.0) ? 1.0 : n_time;
fx_offset += Point2(Math::lerp(Math::sin(previous_offset),
Math::sin(current_offset),
n_time),
Math::lerp(Math::cos(previous_offset),
Math::cos(current_offset),
n_time)) *
(float)item_shake->strength / 10.0f;
} else if (item_wave) {
double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + pofs) / 50)) * (item_wave->amplitude / 10.0f);
fx_offset += Point2(0, 1) * value;
} else if (item_tornado) {
double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + pofs) / 50)) * (item_tornado->radius);
double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + pofs) / 50)) * (item_tornado->radius);
fx_offset += Point2(torn_x, torn_y);
} else if (item_rainbow) {
fx_color = fx_color.from_hsv(item_rainbow->frequency * (item_rainbow->elapsed_time + ((p_ofs.x + pofs) / 50)),
item_rainbow->saturation,
item_rainbow->value,
fx_color.a);
}
}
bool visible = visible_characters < 0 || (p_char_count < visible_characters && YRANGE_VISIBLE(y + lh - line_descent - line_ascent, line_ascent + line_descent));
if (visible)
line_is_blank = false;
@ -451,27 +531,28 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
visible = false;
if (visible) {
if (selected) {
cw = font->get_char_size(c[i], c[i + 1]).x;
cw = font->get_char_size(fx_char, c[i + 1]).x;
draw_rect(Rect2(p_ofs.x + pofs, p_ofs.y + y, cw, lh), selection_bg);
}
if (p_font_color_shadow.a > 0) {
float x_ofs_shadow = align_ofs + pofs;
float y_ofs_shadow = y + lh - line_descent;
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, c[i], c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + shadow_ofs, fx_char, c[i + 1], p_font_color_shadow);
if (p_shadow_as_outline) {
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c[i], c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(shadow_ofs.x, -shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
font->draw_char(ci, Point2(x_ofs_shadow, y_ofs_shadow) + Vector2(-shadow_ofs.x, -shadow_ofs.y), fx_char, c[i + 1], p_font_color_shadow);
}
}
if (selected) {
drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], override_selected_font_color ? selection_fg : color);
drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), fx_char, c[i + 1], override_selected_font_color ? selection_fg : fx_color);
} else {
cw = drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent), c[i], c[i + 1], color);
cw = drawer.draw_char(ci, p_ofs + Point2(align_ofs + pofs, y + lh - line_descent) + fx_offset, fx_char, c[i + 1], fx_color);
}
}
@ -800,6 +881,31 @@ void RichTextLabel::_update_scroll() {
}
}
void RichTextLabel::_update_fx(RichTextLabel::ItemFrame *p_frame, float p_delta_time) {
Item *it = p_frame;
while (it) {
ItemFX *ifx = Object::cast_to<ItemFX>(it);
if (!ifx) {
it = _get_next_item(it, true);
continue;
}
ifx->elapsed_time += p_delta_time;
ItemShake *shake = Object::cast_to<ItemShake>(it);
if (shake) {
bool cycle = (shake->elapsed_time > (1.0f / shake->rate));
if (cycle) {
shake->elapsed_time -= (1.0f / shake->rate);
shake->reroll_random();
}
}
it = _get_next_item(it, true);
}
}
void RichTextLabel::_notification(int p_what) {
switch (p_what) {
@ -873,6 +979,15 @@ void RichTextLabel::_notification(int p_what) {
from_line++;
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
float dt = get_process_delta_time();
for (int i = 0; i < custom_effects.size(); i++) {
}
_update_fx(main, dt);
update();
}
}
}
@ -1026,15 +1141,11 @@ void RichTextLabel::_gui_input(Ref<InputEvent> p_event) {
}
if (b->get_button_index() == BUTTON_WHEEL_UP) {
if (scroll_active)
vscroll->set_value(vscroll->get_value() - vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
if (b->get_button_index() == BUTTON_WHEEL_DOWN) {
if (scroll_active)
vscroll->set_value(vscroll->get_value() + vscroll->get_page() * b->get_factor() * 0.5 / 8);
}
}
@ -1285,8 +1396,19 @@ bool RichTextLabel::_find_strikethrough(Item *p_item) {
return false;
}
bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) {
bool RichTextLabel::_find_by_type(Item *p_item, ItemType p_type) {
Item *item = p_item;
while (item) {
if (item->type == p_type) {
return true;
}
item = item->parent;
}
return false;
}
bool RichTextLabel::_find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item) {
Item *item = p_item;
while (item) {
@ -1618,6 +1740,49 @@ void RichTextLabel::push_table(int p_columns) {
_add_item(item, true, true);
}
void RichTextLabel::push_fade(int p_start_index, int p_length) {
ItemFade *item = memnew(ItemFade);
item->starting_index = p_start_index;
item->length = p_length;
_add_item(item, true);
}
void RichTextLabel::push_shake(int p_strength = 10, float p_rate = 24.0f) {
ItemShake *item = memnew(ItemShake);
item->strength = p_strength;
item->rate = p_rate;
_add_item(item, true);
}
void RichTextLabel::push_wave(float p_frequency = 1.0f, float p_amplitude = 10.0f) {
ItemWave *item = memnew(ItemWave);
item->frequency = p_frequency;
item->amplitude = p_amplitude;
_add_item(item, true);
}
void RichTextLabel::push_tornado(float p_frequency = 1.0f, float p_radius = 10.0f) {
ItemTornado *item = memnew(ItemTornado);
item->frequency = p_frequency;
item->radius = p_radius;
_add_item(item, true);
}
void RichTextLabel::push_rainbow(float p_saturation, float p_value, float p_frequency) {
ItemRainbow *item = memnew(ItemRainbow);
item->frequency = p_frequency;
item->saturation = p_saturation;
item->value = p_value;
_add_item(item, true);
}
void RichTextLabel::push_customfx(String p_identifier, Dictionary p_environment) {
ItemCustomFX *item = memnew(ItemCustomFX);
item->identifier = p_identifier;
item->environment = p_environment;
_add_item(item, true);
}
void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) {
ERR_FAIL_COND(current->type != ITEM_TABLE);
@ -1762,6 +1927,8 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
bool in_bold = false;
bool in_italics = false;
set_process_internal(false);
while (pos < p_bbcode.length()) {
int brk_pos = p_bbcode.find("[", pos);
@ -1785,7 +1952,6 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
}
String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1);
if (tag.begins_with("/") && tag_stack.size()) {
bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length());
@ -1798,9 +1964,8 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
indent_level--;
if (!tag_ok) {
add_text("[");
pos++;
add_text("[" + tag);
pos = brk_end;
continue;
}
@ -1992,12 +2157,147 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
pos = brk_end + 1;
tag_stack.push_front("font");
} else {
} else if (tag.begins_with("fade")) {
Vector<String> tags = tag.split(" ", false);
int startIndex = 0;
int length = 10;
if (tags.size() > 1) {
tags.remove(0);
for (int i = 0; i < tags.size(); i++) {
String expr = tags[i];
if (expr.begins_with("start=")) {
String start_str = expr.substr(6, expr.length());
startIndex = start_str.to_int();
} else if (expr.begins_with("length=")) {
String end_str = expr.substr(7, expr.length());
length = end_str.to_int();
}
}
}
push_fade(startIndex, length);
pos = brk_end + 1;
tag_stack.push_front("fade");
} else if (tag.begins_with("shake")) {
Vector<String> tags = tag.split(" ", false);
int strength = 5;
float rate = 20.0f;
if (tags.size() > 1) {
tags.remove(0);
for (int i = 0; i < tags.size(); i++) {
String expr = tags[i];
if (expr.begins_with("level=")) {
String str_str = expr.substr(6, expr.length());
strength = str_str.to_int();
} else if (expr.begins_with("rate=")) {
String rate_str = expr.substr(5, expr.length());
rate = rate_str.to_float();
}
}
}
push_shake(strength, rate);
pos = brk_end + 1;
tag_stack.push_front("shake");
set_process_internal(true);
} else if (tag.begins_with("wave")) {
Vector<String> tags = tag.split(" ", false);
float amplitude = 20.0f;
float period = 5.0f;
if (tags.size() > 1) {
tags.remove(0);
for (int i = 0; i < tags.size(); i++) {
String expr = tags[i];
if (expr.begins_with("amp=")) {
String amp_str = expr.substr(4, expr.length());
amplitude = amp_str.to_float();
} else if (expr.begins_with("freq=")) {
String period_str = expr.substr(5, expr.length());
period = period_str.to_float();
}
}
}
push_wave(period, amplitude);
pos = brk_end + 1;
tag_stack.push_front("wave");
set_process_internal(true);
} else if (tag.begins_with("tornado")) {
Vector<String> tags = tag.split(" ", false);
float radius = 10.0f;
float frequency = 1.0f;
if (tags.size() > 1) {
tags.remove(0);
for (int i = 0; i < tags.size(); i++) {
String expr = tags[i];
if (expr.begins_with("radius=")) {
String amp_str = expr.substr(7, expr.length());
radius = amp_str.to_float();
} else if (expr.begins_with("freq=")) {
String period_str = expr.substr(5, expr.length());
frequency = period_str.to_float();
}
}
}
push_tornado(frequency, radius);
pos = brk_end + 1;
tag_stack.push_front("tornado");
set_process_internal(true);
} else if (tag.begins_with("rainbow")) {
Vector<String> tags = tag.split(" ", false);
float saturation = 0.8f;
float value = 0.8f;
float frequency = 1.0f;
if (tags.size() > 1) {
tags.remove(0);
for (int i = 0; i < tags.size(); i++) {
String expr = tags[i];
if (expr.begins_with("sat=")) {
String sat_str = expr.substr(4, expr.length());
saturation = sat_str.to_float();
} else if (expr.begins_with("val=")) {
String val_str = expr.substr(4, expr.length());
value = val_str.to_float();
} else if (expr.begins_with("freq=")) {
String freq_str = expr.substr(5, expr.length());
frequency = freq_str.to_float();
}
}
}
push_rainbow(saturation, value, frequency);
pos = brk_end + 1;
tag_stack.push_front("rainbow");
set_process_internal(true);
} else {
Vector<String> expr = tag.split(" ", false);
if (expr.size() < 1) {
add_text("[");
pos = brk_pos + 1;
} else {
String identifier = expr[0];
expr.remove(0);
Dictionary properties = parse_expressions_for_values(expr);
Ref<RichTextEffect> effect = _get_custom_effect_by_code(identifier);
if (!effect.is_null()) {
push_customfx(identifier, properties);
pos = brk_end + 1;
tag_stack.push_front(identifier);
set_process_internal(true);
} else {
add_text("["); //ignore
pos = brk_pos + 1;
}
}
}
}
return OK;
}
@ -2204,6 +2504,34 @@ float RichTextLabel::get_percent_visible() const {
return percent_visible;
}
void RichTextLabel::set_effects(const Vector<Variant> &effects) {
custom_effects.clear();
for (int i = 0; i < effects.size(); i++) {
Ref<RichTextEffect> effect = Ref<RichTextEffect>(effects[i]);
custom_effects.push_back(effect);
}
parse_bbcode(bbcode);
}
Vector<Variant> RichTextLabel::get_effects() {
Vector<Variant> r;
for (int i = 0; i < custom_effects.size(); i++) {
r.push_back(custom_effects[i].get_ref_ptr());
}
return r;
}
void RichTextLabel::install_effect(const Variant effect) {
Ref<RichTextEffect> rteffect;
rteffect = effect;
if (rteffect.is_valid()) {
custom_effects.push_back(effect);
parse_bbcode(bbcode);
}
}
int RichTextLabel::get_content_height() {
int total_height = 0;
if (main->lines.size())
@ -2280,6 +2608,12 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_content_height"), &RichTextLabel::get_content_height);
ClassDB::bind_method(D_METHOD("parse_expressions_for_values", "expressions"), &RichTextLabel::parse_expressions_for_values);
ClassDB::bind_method(D_METHOD("set_effects", "effects"), &RichTextLabel::set_effects);
ClassDB::bind_method(D_METHOD("get_effects"), &RichTextLabel::get_effects);
ClassDB::bind_method(D_METHOD("install_effect", "effect"), &RichTextLabel::install_effect);
ADD_GROUP("BBCode", "bbcode_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bbcode_enabled"), "set_use_bbcode", "is_using_bbcode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "bbcode_text", PROPERTY_HINT_MULTILINE_TEXT), "set_bbcode", "get_bbcode");
@ -2297,6 +2631,8 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "custom_effects", (PropertyHint)(PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE), "17/17:RichTextEffect", PROPERTY_USAGE_DEFAULT, "RichTextEffect"), "set_effects", "get_effects");
ADD_SIGNAL(MethodInfo("meta_clicked", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
@ -2322,11 +2658,16 @@ void RichTextLabel::_bind_methods() {
BIND_ENUM_CONSTANT(ITEM_INDENT);
BIND_ENUM_CONSTANT(ITEM_LIST);
BIND_ENUM_CONSTANT(ITEM_TABLE);
BIND_ENUM_CONSTANT(ITEM_FADE);
BIND_ENUM_CONSTANT(ITEM_SHAKE);
BIND_ENUM_CONSTANT(ITEM_WAVE);
BIND_ENUM_CONSTANT(ITEM_TORNADO);
BIND_ENUM_CONSTANT(ITEM_RAINBOW);
BIND_ENUM_CONSTANT(ITEM_CUSTOMFX);
BIND_ENUM_CONSTANT(ITEM_META);
}
void RichTextLabel::set_visible_characters(int p_visible) {
visible_characters = p_visible;
update();
}
@ -2358,6 +2699,77 @@ Size2 RichTextLabel::get_minimum_size() const {
return Size2();
}
Ref<RichTextEffect> RichTextLabel::_get_custom_effect_by_code(String p_bbcode_identifier) {
Ref<RichTextEffect> r;
for (int i = 0; i < custom_effects.size(); i++) {
if (!custom_effects[i].is_valid())
continue;
if (custom_effects[i]->get_bbcode() == p_bbcode_identifier) {
r = custom_effects[i];
}
}
return r;
}
Dictionary RichTextLabel::parse_expressions_for_values(Vector<String> p_expressions) {
Dictionary d = Dictionary();
for (int i = 0; i < p_expressions.size(); i++) {
String expression = p_expressions[i];
Array a = Array();
Vector<String> parts = expression.split("=", true);
String key = parts[0];
if (parts.size() != 2) {
return d;
}
Vector<String> values = parts[1].split(",", false);
RegEx color = RegEx();
color.compile("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
RegEx nodepath = RegEx();
nodepath.compile("^\\$");
RegEx boolean = RegEx();
boolean.compile("^(true|false)$");
RegEx decimal = RegEx();
decimal.compile("^-?^.?\\d+(\\.\\d+?)?$");
RegEx numerical = RegEx();
numerical.compile("^\\d+$");
for (int j = 0; j < values.size(); j++) {
if (!color.search(values[j]).is_null()) {
a.append(Color::html(values[j]));
} else if (!nodepath.search(values[j]).is_null()) {
if (values[j].begins_with("$")) {
String v = values[j].substr(1, values[j].length());
a.append(NodePath(v));
}
} else if (!boolean.search(values[j]).is_null()) {
if (values[j] == "true") {
a.append(true);
} else if (values[j] == "false") {
a.append(false);
}
} else if (!decimal.search(values[j]).is_null()) {
a.append(values[j].to_double());
} else if (!numerical.search(values[j]).is_null()) {
a.append(values[j].to_int());
} else {
a.append(values[j]);
}
}
if (values.size() > 1) {
d[key] = a;
} else if (values.size() == 1) {
d[key] = a[0];
}
}
return d;
}
RichTextLabel::RichTextLabel() {
main = memnew(ItemFrame);

View file

@ -31,6 +31,7 @@
#ifndef RICH_TEXT_LABEL_H
#define RICH_TEXT_LABEL_H
#include "rich_text_effect.h"
#include "scene/gui/scroll_bar.h"
class RichTextLabel : public Control {
@ -67,7 +68,13 @@ public:
ITEM_INDENT,
ITEM_LIST,
ITEM_TABLE,
ITEM_META
ITEM_FADE,
ITEM_SHAKE,
ITEM_WAVE,
ITEM_TORNADO,
ITEM_RAINBOW,
ITEM_META,
ITEM_CUSTOMFX
};
protected:
@ -96,7 +103,7 @@ private:
}
};
struct Item {
struct Item : public Object {
int index;
Item *parent;
@ -214,6 +221,101 @@ private:
ItemTable() { type = ITEM_TABLE; }
};
struct ItemFade : public Item {
int starting_index;
int length;
ItemFade() { type = ITEM_FADE; }
};
struct ItemFX : public Item {
float elapsed_time;
ItemFX() {
elapsed_time = 0.0f;
}
};
struct ItemShake : public ItemFX {
int strength;
float rate;
uint64_t _current_rng;
uint64_t _previous_rng;
ItemShake() {
strength = 0;
rate = 0.0f;
_current_rng = 0;
type = ITEM_SHAKE;
}
void reroll_random() {
_previous_rng = _current_rng;
_current_rng = Math::rand();
}
uint64_t offset_random(int index) {
return (_current_rng >> (index % 64)) |
(_current_rng << (64 - (index % 64)));
}
uint64_t offset_previous_random(int index) {
return (_previous_rng >> (index % 64)) |
(_previous_rng << (64 - (index % 64)));
}
};
struct ItemWave : public ItemFX {
float frequency;
float amplitude;
ItemWave() {
frequency = 1.0f;
amplitude = 1.0f;
type = ITEM_WAVE;
}
};
struct ItemTornado : public ItemFX {
float radius;
float frequency;
ItemTornado() {
radius = 1.0f;
frequency = 1.0f;
type = ITEM_TORNADO;
}
};
struct ItemRainbow : public ItemFX {
float saturation;
float value;
float frequency;
ItemRainbow() {
saturation = 0.8f;
value = 0.8f;
frequency = 1.0f;
type = ITEM_RAINBOW;
}
};
struct ItemCustomFX : public ItemFX {
String identifier;
Dictionary environment;
ItemCustomFX() {
identifier = "";
environment = Dictionary();
type = ITEM_CUSTOMFX;
}
virtual ~ItemCustomFX() {
_clear_children();
environment.clear();
}
};
ItemFrame *main;
Item *current;
ItemFrame *current_frame;
@ -239,6 +341,8 @@ private:
ItemMeta *meta_hovering;
Variant current_meta;
Vector<Ref<RichTextEffect> > custom_effects;
void _invalidate_current_line(ItemFrame *p_frame);
void _validate_line_caches(ItemFrame *p_frame);
@ -246,7 +350,6 @@ private:
void _remove_item(Item *p_item, const int p_line, const int p_subitem_line);
struct ProcessState {
int line_width;
};
@ -287,8 +390,36 @@ private:
bool _find_strikethrough(Item *p_item);
bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = NULL);
bool _find_layout_subitem(Item *from, Item *to);
bool _find_by_type(Item *p_item, ItemType p_type);
template <typename T>
T *_fetch_by_type(Item *p_item, ItemType p_type) {
Item *item = p_item;
T *result = NULL;
while (item) {
if (item->type == p_type) {
result = Object::cast_to<T>(item);
if (result)
return result;
}
item = item->parent;
}
return result;
};
template <typename T>
void _fetch_item_stack(Item *p_item, Vector<T *> &r_stack) {
Item *item = p_item;
while (item) {
T *found = Object::cast_to<T>(item);
if (found) {
r_stack.push_back(found);
}
item = item->parent;
}
}
void _update_scroll();
void _update_fx(ItemFrame *p_frame, float p_delta_time);
void _scroll_changed(double);
void _gui_input(Ref<InputEvent> p_event);
@ -296,6 +427,8 @@ private:
Item *_get_prev_item(Item *p_item, bool p_free = false);
Rect2 _get_text_rect();
Ref<RichTextEffect> _get_custom_effect_by_code(String p_bbcode_identifier);
virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions);
bool use_bbcode;
String bbcode;
@ -322,6 +455,12 @@ public:
void push_list(ListType p_list);
void push_meta(const Variant &p_meta);
void push_table(int p_columns);
void push_fade(int p_start_index, int p_length);
void push_shake(int p_level, float p_rate);
void push_wave(float p_frequency, float p_amplitude);
void push_tornado(float p_frequency, float p_radius);
void push_rainbow(float p_saturation, float p_value, float p_frequency);
void push_customfx(String p_identifier, Dictionary p_environment);
void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1);
int get_current_table_column() const;
void push_cell();
@ -380,6 +519,11 @@ public:
void set_percent_visible(float p_percent);
float get_percent_visible() const;
void set_effects(const Vector<Variant> &effects);
Vector<Variant> get_effects();
void install_effect(const Variant effect);
void set_fixed_size_to_width(int p_width);
virtual Size2 get_minimum_size() const;

View file

@ -343,6 +343,7 @@ void register_scene_types() {
ClassDB::register_class<ColorPicker>();
ClassDB::register_class<ColorPickerButton>();
ClassDB::register_class<RichTextLabel>();
ClassDB::register_class<RichTextEffect>();
ClassDB::register_class<PopupDialog>();
ClassDB::register_class<WindowDialog>();
ClassDB::register_class<AcceptDialog>();