aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension
diff options
context:
space:
mode:
author hop311 <hop3114@gmail.com>2024-08-23 01:03:02 +0200
committer hop311 <hop3114@gmail.com>2024-08-23 01:03:02 +0200
commit95a6b95a5df63fa1bcb1d4680b3fdd7091a9c38e (patch)
tree7a487c2283eac8159ea0e25f93abdefa6b3ca0d1 /extension/src/openvic-extension
parent7c85ab11e840c281a2499dcc6dd3219c33e7d37f (diff)
Rework GUITextLabel to use custom text handling instead of RichTextLabel
Diffstat (limited to 'extension/src/openvic-extension')
-rw-r--r--extension/src/openvic-extension/classes/GUITextLabel.cpp558
-rw-r--r--extension/src/openvic-extension/classes/GUITextLabel.hpp71
-rw-r--r--extension/src/openvic-extension/utility/UITools.cpp6
3 files changed, 454 insertions, 181 deletions
diff --git a/extension/src/openvic-extension/classes/GUITextLabel.cpp b/extension/src/openvic-extension/classes/GUITextLabel.cpp
index 5baba70..508dbc8 100644
--- a/extension/src/openvic-extension/classes/GUITextLabel.cpp
+++ b/extension/src/openvic-extension/classes/GUITextLabel.cpp
@@ -22,9 +22,6 @@ void GUITextLabel::_bind_methods() {
OV_BIND_METHOD(GUITextLabel::set_substitution_dict, { "new_substitution_dict" });
OV_BIND_METHOD(GUITextLabel::clear_substitutions);
- OV_BIND_METHOD(GUITextLabel::get_max_lines);
- OV_BIND_METHOD(GUITextLabel::set_max_lines, { "new_max_lines" });
-
OV_BIND_METHOD(GUITextLabel::get_alignment);
OV_BIND_METHOD(GUITextLabel::get_gui_text_name);
@@ -34,19 +31,75 @@ void GUITextLabel::_bind_methods() {
void GUITextLabel::_notification(int what) {
switch (what) {
+ case NOTIFICATION_RESIZED:
case NOTIFICATION_TRANSLATION_CHANGED: {
- _queue_update();
+ _queue_line_update();
+ } break;
+ case NOTIFICATION_DRAW: {
+ const RID ci = get_canvas_item();
+ const Size2 size = get_size();
+
+ if (background.is_valid()) {
+ draw_style_box(background, Rect2 { {}, size });
+ }
+
+ if (font.is_null()) {
+ return;
+ }
+
+ const Vector2 content_max_size = get_content_max_size();
+
+ // Starting offset needed
+ static const Vector2 base_offset { 1.0_real, -1.0_real };
+ const Vector2 offset = base_offset + border_size;
+ Vector2 position = offset;
+
+ for (line_t const& line : lines) {
+ position.x = offset.x;
+ switch (alignment) {
+ case HORIZONTAL_ALIGNMENT_CENTER: {
+ position.x += (content_max_size.width - line.width + 1.0_real) / 2.0_real;
+ } break;
+ case HORIZONTAL_ALIGNMENT_RIGHT: {
+ position.x += content_max_size.width - line.width;
+ } break;
+ case HORIZONTAL_ALIGNMENT_LEFT:
+ default:
+ break;
+ }
+
+ position.y += font->get_ascent(font->get_fixed_size());
+
+ for (segment_t const& segment : line.segments) {
+ string_segment_t const* string_segment = std::get_if<string_segment_t>(&segment);
+
+ if (string_segment == nullptr) {
+ if (currency_texture.is_valid()) {
+ currency_texture->draw(
+ ci, position - Vector2 {
+ 1.0_real, static_cast<real_t>(currency_texture->get_height()) * 0.75_real
+ }
+ );
+ position.x += currency_texture->get_width();
+ }
+ } else {
+ font->draw_string(
+ ci, position, string_segment->text, HORIZONTAL_ALIGNMENT_LEFT, -1, font->get_fixed_size(),
+ string_segment->colour
+ );
+ position.x += string_segment->width;
+ }
+ }
+
+ position.y += font->get_descent(font->get_fixed_size());
+ }
+
} break;
}
}
GUITextLabel::GUITextLabel()
- : gui_text { nullptr }, alignment { HORIZONTAL_ALIGNMENT_LEFT }, font_height { 0.0_real }, colour_codes { nullptr },
- max_lines { 1 }, update_queued { false } {
- set_scroll_active(false);
- set_clip_contents(false);
- set_autowrap_mode(TextServer::AUTOWRAP_ARBITRARY);
-}
+ : gui_text { nullptr }, alignment { HORIZONTAL_ALIGNMENT_LEFT }, colour_codes { nullptr }, line_update_queued { false } {}
void GUITextLabel::clear() {
gui_text = nullptr;
@@ -54,17 +107,19 @@ void GUITextLabel::clear() {
text = String {};
substitution_dict.clear();
alignment = HORIZONTAL_ALIGNMENT_LEFT;
- max_lines = 1;
+ border_size = {};
+
+ _update_font(nullptr);
- static const StringName normal_theme = "normal";
- remove_theme_stylebox_override(normal_theme);
+ background.unref();
+ lines.clear();
- _update_font();
+ line_update_queued = false;
- update_queued = false;
+ queue_redraw();
}
-Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text) {
+Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text, GFX::Font::colour_codes_t const* override_colour_codes) {
if (gui_text == new_gui_text) {
return OK;
}
@@ -87,18 +142,14 @@ Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text) {
const decltype(format_map)::const_iterator it = format_map.find(gui_text->get_format());
alignment = it != format_map.end() ? it->second : HORIZONTAL_ALIGNMENT_LEFT;
- // TODO - detect max_lines based on gui_text? E.g. from total height vs line height?
- max_lines = 1;
+ set_custom_minimum_size(Utilities::to_godot_fvec2(gui_text->get_max_size()));
+ border_size = Utilities::to_godot_fvec2(gui_text->get_border_size());
- static const Vector2 default_padding { 1.0_real, -1.0_real };
- const Vector2 border_size = Utilities::to_godot_fvec2(gui_text->get_border_size()) + default_padding;
- const Vector2 max_size = Utilities::to_godot_fvec2(gui_text->get_max_size());
- set_position(get_position() + border_size);
- set_custom_minimum_size(max_size - 2 * border_size);
+ _queue_line_update();
- _queue_update();
+ Error err = _update_font(override_colour_codes);
- Error err = _update_font();
+ background.unref();
if (!gui_text->get_texture_file().empty()) {
AssetManager* asset_manager = AssetManager::get_singleton();
@@ -110,18 +161,14 @@ Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text) {
texture, FAILED, vformat("Failed to load texture \"%s\" for GUITextLabel %s", texture_path, get_name())
);
- Ref<StyleBoxTexture> stylebox;
- stylebox.instantiate();
- ERR_FAIL_NULL_V(stylebox, FAILED);
- stylebox->set_texture(texture);
-
- stylebox->set_texture_margin(SIDE_LEFT, border_size.x);
- stylebox->set_texture_margin(SIDE_RIGHT, border_size.x);
- stylebox->set_texture_margin(SIDE_TOP, border_size.y);
- stylebox->set_texture_margin(SIDE_BOTTOM, border_size.y);
+ background.instantiate();
+ ERR_FAIL_NULL_V(background, FAILED);
+ background->set_texture(texture);
- static const StringName normal_theme = "normal";
- add_theme_stylebox_override(normal_theme, stylebox);
+ background->set_texture_margin(SIDE_LEFT, border_size.x);
+ background->set_texture_margin(SIDE_RIGHT, border_size.x);
+ background->set_texture_margin(SIDE_TOP, border_size.y);
+ background->set_texture_margin(SIDE_BOTTOM, border_size.y);
}
return err;
@@ -131,204 +178,387 @@ String GUITextLabel::get_gui_text_name() const {
return gui_text != nullptr ? Utilities::std_to_godot_string(gui_text->get_name()) : String {};
}
-void GUITextLabel::set_text(godot::String const& new_text) {
+void GUITextLabel::set_text(String const& new_text) {
if (text != new_text) {
text = new_text;
- _queue_update();
+ _queue_line_update();
}
}
-void GUITextLabel::add_substitution(String const& key, godot::String const& value) {
+void GUITextLabel::add_substitution(String const& key, String const& value) {
Variant& existing_value = substitution_dict[key];
if (existing_value != value) {
existing_value = value;
- _queue_update();
+ _queue_line_update();
}
}
-void GUITextLabel::set_substitution_dict(godot::Dictionary const& new_substitution_dict) {
+void GUITextLabel::set_substitution_dict(Dictionary const& new_substitution_dict) {
substitution_dict = new_substitution_dict;
- _queue_update();
+ _queue_line_update();
}
void GUITextLabel::clear_substitutions() {
- substitution_dict.clear();
- _queue_update();
-}
-
-void GUITextLabel::set_max_lines(int32_t new_max_lines) {
- if (new_max_lines != max_lines) {
- max_lines = new_max_lines;
- _queue_update();
+ if (!substitution_dict.is_empty()) {
+ substitution_dict.clear();
+ _queue_line_update();
}
}
-Error GUITextLabel::_update_font() {
- static const StringName font_theme = "normal_font";
- static const StringName font_color_theme = "default_color";
+Vector2 GUITextLabel::get_content_max_size() const {
+ return get_size() - 2 * border_size;
+}
+Error GUITextLabel::_update_font(GFX::Font::colour_codes_t const* override_colour_codes) {
if (gui_text == nullptr || gui_text->get_font() == nullptr) {
- remove_theme_font_override(font_theme);
- remove_theme_color_override(font_color_theme);
- font_height = 0.0_real;
+ font.unref();
+ default_colour = {};
colour_codes = nullptr;
+ currency_texture.unref();
return OK;
}
- add_theme_color_override(font_color_theme, Utilities::to_godot_color(gui_text->get_font()->get_colour()));
- colour_codes = &gui_text->get_font()->get_colour_codes();
+ default_colour = Utilities::to_godot_color(gui_text->get_font()->get_colour());
+ colour_codes = override_colour_codes != nullptr ? override_colour_codes : &gui_text->get_font()->get_colour_codes();
AssetManager* asset_manager = AssetManager::get_singleton();
ERR_FAIL_NULL_V_MSG(asset_manager, FAILED, "Failed to get AssetManager singleton for GUITextLabel");
const StringName font_file = Utilities::std_to_godot_string(gui_text->get_font()->get_fontname());
- const Ref<Font> font = asset_manager->get_font(font_file);
-
+ font = asset_manager->get_font(font_file);
ERR_FAIL_NULL_V_MSG(font, FAILED, vformat("Failed to load font \"%s\" for GUITextLabel", font_file));
- add_theme_font_override(font_theme, font);
- font_height = font->get_height();
+ currency_texture = asset_manager->get_currency_texture(font->get_fixed_size());
+ ERR_FAIL_NULL_V(currency_texture, FAILED);
return OK;
}
-void GUITextLabel::_queue_update() {
- if (!update_queued) {
- update_queued = true;
+real_t GUITextLabel::get_string_width(String const& string) const {
+ return font->get_string_size(string, HORIZONTAL_ALIGNMENT_LEFT, -1, font->get_fixed_size()).x;
+}
- callable_mp(this, &GUITextLabel::_update_text).call_deferred();
+real_t GUITextLabel::get_segment_width(segment_t const& segment) const {
+ if (string_segment_t const* string_segment = std::get_if<string_segment_t>(&segment)) {
+ return string_segment->width;
+ } else if (currency_texture.is_valid()) {
+ return currency_texture->get_width();
+ } else {
+ return 0.0_real;
}
}
-void GUITextLabel::_update_text() {
- static constexpr char SUBSTITUTION_CHAR = '$';
- static constexpr char COLOUR_CHAR = '\xA7'; // §
+void GUITextLabel::_queue_line_update() {
+ if (!line_update_queued) {
+ line_update_queued = true;
+
+ callable_mp(this, &GUITextLabel::_update_lines).call_deferred();
+ }
+}
+
+void GUITextLabel::_update_lines() {
+ line_update_queued = false;
+ lines.clear();
+
+ if (text.is_empty() || font.is_null()) {
+ queue_redraw();
+ return;
+ }
String const& base_text = is_auto_translating() ? tr(text) : text;
- // Remove $keys$ and insert substitutions
- String substituted_text;
- {
- bool substitution_section = false;
- int64_t section_start = 0;
- for (int64_t idx = 0; idx < base_text.length(); ++idx) {
- if (static_cast<char>(base_text[idx]) == SUBSTITUTION_CHAR) {
- if (section_start < idx) {
- String section = base_text.substr(section_start, idx - section_start);
- if (substitution_section) {
- section = substitution_dict.get(section, String {});
- }
- substituted_text += section;
- }
- substitution_section = !substitution_section;
- section_start = idx + 1;
- }
- }
- if (!substitution_section && section_start < base_text.length()) {
- substituted_text += base_text.substr(section_start);
+ String const& substituted_text = generate_substituted_text(base_text);
+
+ auto const& [display_text, colour_instructions] = generate_display_text_and_colour_instructions(substituted_text);
+
+ std::vector<line_t> unwrapped_lines = generate_lines_and_segments(display_text, colour_instructions);
+
+ lines = wrap_lines(unwrapped_lines);
+
+ // TODO - trim and add ellipsis if necessary
+
+ queue_redraw();
+}
+
+String GUITextLabel::generate_substituted_text(String const& base_text) const {
+ static const String SUBSTITUTION_MARKER = String::chr(0x24); // $
+
+ String result;
+ int64_t start_pos = 0;
+ int64_t marker_start_pos;
+
+ while ((marker_start_pos = base_text.find(SUBSTITUTION_MARKER, start_pos)) != -1) {
+ result += base_text.substr(start_pos, marker_start_pos - start_pos);
+
+ int64_t marker_end_pos = base_text.find(SUBSTITUTION_MARKER, marker_start_pos + SUBSTITUTION_MARKER.length());
+ if (marker_end_pos == -1) {
+ return result;
}
+
+ String key = base_text.substr(
+ marker_start_pos + SUBSTITUTION_MARKER.length(), marker_end_pos - marker_start_pos - SUBSTITUTION_MARKER.length()
+ );
+ String value = substitution_dict.get(key, String {});
+
+ result += value;
+
+ start_pos = marker_end_pos + SUBSTITUTION_MARKER.length();
}
- // Separate out colour codes from displayed test
- String display_text;
+ result += base_text.substr(start_pos);
+
+ return result;
+}
+
+std::pair<String, GUITextLabel::colour_instructions_t> GUITextLabel::generate_display_text_and_colour_instructions(
+ String const& substituted_text
+) const {
+ static const String COLOUR_MARKER = String::chr(0xA7); // §
+
+ String result;
colour_instructions_t colour_instructions;
- {
- int64_t section_start = 0;
- for (int64_t idx = 0; idx < substituted_text.length(); ++idx) {
- if (static_cast<char>(substituted_text[idx]) == COLOUR_CHAR) {
- if (idx > section_start) {
- display_text += substituted_text.substr(section_start, idx - section_start);
+ int64_t start_pos = 0;
+ int64_t marker_pos;
+
+ while ((marker_pos = substituted_text.find(COLOUR_MARKER, start_pos)) != -1) {
+ result += substituted_text.substr(start_pos, marker_pos - start_pos);
+
+ if (marker_pos + COLOUR_MARKER.length() < substituted_text.length()) {
+ const char32_t colour_code = substituted_text[marker_pos + COLOUR_MARKER.length()];
+
+ // Check that the colour code can be safely cast to a char
+ if (colour_code >> sizeof(char) * CHAR_BIT == 0) {
+ colour_instructions.emplace_back(result.length(), static_cast<char>(colour_code));
+ }
+
+ start_pos = marker_pos + COLOUR_MARKER.length() + 1;
+ } else {
+ return { std::move(result), std::move(colour_instructions) };
+ }
+ }
+
+ result += substituted_text.substr(start_pos);
+
+ return { std::move(result), std::move(colour_instructions) };
+}
+
+std::vector<GUITextLabel::line_t> GUITextLabel::generate_lines_and_segments(
+ String const& display_text, colour_instructions_t const& colour_instructions
+) const {
+ static constexpr char RESET_COLOUR_CODE = '!';
+
+ std::vector<line_t> unwrapped_lines;
+ colour_instructions_t::const_iterator colour_it = colour_instructions.begin();
+ Color current_colour = default_colour;
+ int64_t section_start = 0;
+
+ unwrapped_lines.emplace_back();
+
+ for (int64_t idx = 0; idx < display_text.length(); ++idx) {
+ if (colour_it != colour_instructions.end() && idx == colour_it->first) {
+ Color new_colour = current_colour;
+ if (colour_it->second == RESET_COLOUR_CODE) {
+ new_colour = default_colour;
+ } else {
+ const GFX::Font::colour_codes_t::const_iterator it = colour_codes->find(colour_it->second);
+ if (it != colour_codes->end()) {
+ new_colour = Utilities::to_godot_color(it->second);
}
- if (++idx < substituted_text.length() && colour_codes != nullptr) {
- colour_instructions.emplace_back(display_text.length(), static_cast<char>(substituted_text[idx]));
+ }
+ ++colour_it;
+
+ if (current_colour != new_colour) {
+ if (section_start < idx) {
+ separate_lines(
+ display_text.substr(section_start, idx - section_start), current_colour, unwrapped_lines
+ );
+ section_start = idx;
}
- section_start = idx + 1;
+ current_colour = new_colour;
}
}
- if (section_start < substituted_text.length()) {
- display_text += substituted_text.substr(section_start);
- }
}
- _generate_text(display_text, colour_instructions);
+ if (section_start < display_text.length()) {
+ separate_lines(display_text.substr(section_start), current_colour, unwrapped_lines);
+ }
- // Trim and add ellipsis if text is too long
- if (max_lines > 0 && max_lines < get_line_count()) {
- int32_t visible_character_count = 0;
- while (
- visible_character_count < get_total_character_count() &&
- get_character_line(visible_character_count) < max_lines
- ) {
- ++visible_character_count;
- }
- static const String ellipsis = "...";
- if (visible_character_count > ellipsis.length()) {
- _generate_text(
- display_text.substr(0, visible_character_count - ellipsis.length()) + ellipsis, colour_instructions
- );
+ return unwrapped_lines;
+}
+
+void GUITextLabel::separate_lines(
+ String const& string, Color const& colour, std::vector<line_t>& unwrapped_lines
+) const {
+ static const String NEWLINE_MARKER = "\n";
+
+ int64_t start_pos = 0;
+ int64_t newline_pos;
+
+ while ((newline_pos = string.find(NEWLINE_MARKER, start_pos)) != -1) {
+ if (start_pos < newline_pos) {
+ separate_currency_segments(string.substr(start_pos, newline_pos - start_pos), colour, unwrapped_lines.back());
}
+
+ unwrapped_lines.emplace_back();
+
+ start_pos = newline_pos + NEWLINE_MARKER.length();
}
- update_queued = false;
+ if (start_pos < string.length()) {
+ separate_currency_segments(string.substr(start_pos), colour, unwrapped_lines.back());
+ }
}
-void GUITextLabel::_generate_text(String const& display_text, colour_instructions_t const& colour_instructions) {
- static constexpr char RESET_COLOUR_CHAR = '!';
- static constexpr char CURRENCY_CHAR = '\xA4'; // ¤
+void GUITextLabel::separate_currency_segments(
+ String const& string, Color const& colour, line_t& line
+) const {
+ static const String CURRENCY_MARKER = String::chr(0xA4); // ¤
- AssetManager const* asset_manager = AssetManager::get_singleton();
- Ref<GFXSpriteTexture> const& currency_texture =
- asset_manager != nullptr ? asset_manager->get_currency_texture(font_height) : Ref<GFXSpriteTexture> {};
+ const auto push_string_segment = [this, &string, &colour, &line](int64_t start, int64_t end) -> void {
+ String substring = string.substr(start, end - start);
+ const real_t width = get_string_width(substring);
+ line.segments.emplace_back(string_segment_t { std::move(substring), colour, width });
+ line.width += width;
+ };
- RichTextLabel::clear();
+ int64_t start_pos = 0;
+ int64_t marker_pos;
- push_paragraph(alignment);
+ const real_t currency_width = currency_texture.is_valid() ? currency_texture->get_width() : 0.0_real;
- // Add text, applying colour codes and inserting currency symbols
- {
- colour_instructions_t::const_iterator colour_it = colour_instructions.begin();
- bool has_colour = false;
- int64_t section_start = 0;
- for (int64_t idx = 0; idx < display_text.length(); ++idx) {
- if (colour_it != colour_instructions.end() && idx == colour_it->first) {
- if (section_start < idx) {
- add_text(display_text.substr(section_start, idx - section_start));
- section_start = idx;
- }
- if (colour_it->second == RESET_COLOUR_CHAR) {
- if (has_colour) {
- pop();
- has_colour = false;
- }
- } else {
- const GFX::Font::colour_codes_t::const_iterator it = colour_codes->find(colour_it->second);
- if (it != colour_codes->end()) {
- if (has_colour) {
- pop();
- } else {
- has_colour = true;
+ while ((marker_pos = string.find(CURRENCY_MARKER, start_pos)) != -1) {
+ if (start_pos < marker_pos) {
+ push_string_segment(start_pos, marker_pos);
+ }
+
+ line.segments.push_back(currency_segment_t {});
+ line.width += currency_width;
+
+ start_pos = marker_pos + CURRENCY_MARKER.length();
+ }
+
+ if (start_pos < string.length()) {
+ push_string_segment(start_pos, string.length());
+ }
+}
+
+std::vector<GUITextLabel::line_t> GUITextLabel::wrap_lines(std::vector<line_t>& unwrapped_lines) const {
+ std::vector<line_t> wrapped_lines;
+
+ const Size2 size = get_content_max_size();
+
+ for (line_t& line : unwrapped_lines) {
+ if (line.width <= size.x) {
+ wrapped_lines.push_back(std::move(line));
+ } else {
+ line_t* current_line = &wrapped_lines.emplace_back();
+
+ for (segment_t& segment : line.segments) {
+ const real_t segment_width = get_segment_width(segment);
+
+ if (current_line->width + segment_width <= size.x) {
+ // Segement on current line
+ current_line->segments.emplace_back(std::move(segment));
+ current_line->width += segment_width;
+ } else if (string_segment_t const* string_segment = std::get_if<string_segment_t>(&segment)) {
+ // String segement wrapped onto new line
+ static const String SPACE_MARKER = " ";
+
+ String const& string = string_segment->text;
+
+ int64_t start_pos = 0;
+
+ while (start_pos < string.length()) {
+ String whole_segment_string = string.substr(start_pos);
+ real_t whole_segment_width = get_string_width(whole_segment_string);
+
+ if (current_line->width + whole_segment_width > size.x) {
+ String new_segment_string;
+ real_t new_segment_width = 0.0_real;
+
+ int64_t last_marker_pos = 0;
+ int64_t marker_pos;
+
+ while ((marker_pos = whole_segment_string.find(SPACE_MARKER, last_marker_pos)) != -1) {
+ String substring = whole_segment_string.substr(0, marker_pos);
+ const real_t width = get_string_width(substring);
+ if (current_line->width + width <= size.x) {
+ new_segment_string = std::move(substring);
+ new_segment_width = width;
+ last_marker_pos = marker_pos + SPACE_MARKER.length();
+ } else {
+ break;
+ }
+ }
+
+ if (last_marker_pos != 0 || !current_line->segments.empty()) {
+ if (!new_segment_string.is_empty()) {
+ current_line->segments.emplace_back(string_segment_t {
+ std::move(new_segment_string), string_segment->colour, new_segment_width
+ });
+ current_line->width += new_segment_width;
+ }
+
+ current_line = &wrapped_lines.emplace_back();
+
+ start_pos += last_marker_pos;
+
+ continue;
+ }
}
- push_color(Utilities::to_godot_color(it->second));
+ current_line->segments.emplace_back(string_segment_t {
+ std::move(whole_segment_string), string_segment->colour, whole_segment_width
+ });
+ current_line->width += whole_segment_width;
+ break;
}
+
+ } else {
+ // Currency segement on new line
+ line_t* current_line = &wrapped_lines.emplace_back();
+ current_line->segments.push_back(std::move(segment));
+ current_line->width = segment_width;
}
- ++colour_it;
}
- if (static_cast<char>(display_text[idx]) == CURRENCY_CHAR) {
- if (section_start < idx) {
- add_text(display_text.substr(section_start, idx - section_start));
- }
- if (currency_texture.is_valid()) {
- add_image(currency_texture);
+ }
+ }
+
+ if (wrapped_lines.size() > 1 && wrapped_lines.size() * font->get_height(font->get_fixed_size()) > size.y) {
+ do {
+ wrapped_lines.pop_back();
+ } while (wrapped_lines.size() > 1 && wrapped_lines.size() * font->get_height(font->get_fixed_size()) > size.y);
+
+ static const String ELLIPSIS = "...";
+ const real_t ellipsis_width = get_string_width(ELLIPSIS);
+
+ line_t& last_line = wrapped_lines.back();
+ Color last_colour = default_colour;
+
+ while (last_line.segments.size() > 0 && last_line.width + ellipsis_width > size.x) {
+ if (string_segment_t* string_segment = std::get_if<string_segment_t>(&last_line.segments.back())) {
+ last_colour = string_segment->colour;
+
+ String& last_string = string_segment->text;
+ if (last_string.length() > 1) {
+ last_string = last_string.substr(0, last_string.length() - 1);
+
+ last_line.width -= string_segment->width;
+ string_segment->width = get_string_width(last_string);
+ last_line.width += string_segment->width;
} else {
- static const String currency_fallback = "£";
- add_text(currency_fallback);
+ last_line.width -= string_segment->width;
+ last_line.segments.pop_back();
}
- section_start = idx + 1;
+ } else {
+ last_line.width -= currency_texture->get_width();
+ last_line.segments.pop_back();
}
}
- if (section_start < display_text.length()) {
- add_text(display_text.substr(section_start));
- }
+
+ last_line.segments.push_back(string_segment_t { ELLIPSIS, last_colour, ellipsis_width });
+ last_line.width += ellipsis_width;
}
+
+ return wrapped_lines;
}
diff --git a/extension/src/openvic-extension/classes/GUITextLabel.hpp b/extension/src/openvic-extension/classes/GUITextLabel.hpp
index b29870e..aadb76f 100644
--- a/extension/src/openvic-extension/classes/GUITextLabel.hpp
+++ b/extension/src/openvic-extension/classes/GUITextLabel.hpp
@@ -1,12 +1,16 @@
#pragma once
-#include <godot_cpp/classes/rich_text_label.hpp>
+#include <godot_cpp/classes/control.hpp>
+#include <godot_cpp/classes/font_file.hpp>
+#include <godot_cpp/classes/style_box_texture.hpp>
#include <openvic-simulation/interface/GUI.hpp>
+#include "openvic-extension/classes/GFXSpriteTexture.hpp"
+
namespace OpenVic {
- class GUITextLabel : public godot::RichTextLabel {
- GDCLASS(GUITextLabel, godot::RichTextLabel)
+ class GUITextLabel : public godot::Control {
+ GDCLASS(GUITextLabel, godot::Control)
using colour_instructions_t = std::vector<std::pair<int64_t, char>>;
@@ -15,11 +19,30 @@ namespace OpenVic {
godot::String PROPERTY(text);
godot::Dictionary PROPERTY(substitution_dict);
godot::HorizontalAlignment PROPERTY(alignment);
- real_t font_height;
+ godot::Vector2 border_size;
+
+ godot::Ref<godot::FontFile> font;
+ godot::Color default_colour;
GFX::Font::colour_codes_t const* colour_codes;
- int32_t PROPERTY(max_lines);
+ godot::Ref<GFXSpriteTexture> currency_texture;
+
+ godot::Ref<godot::StyleBoxTexture> PROPERTY(background);
+
+ struct string_segment_t {
+ godot::String text;
+ godot::Color colour;
+ real_t width;
+ };
+ using currency_segment_t = std::monostate;
+ using segment_t = std::variant<string_segment_t, currency_segment_t>;
+ struct line_t {
+ std::vector<segment_t> segments;
+ real_t width {};
+ };
- bool update_queued;
+ std::vector<line_t> lines;
+
+ bool line_update_queued;
protected:
static void _bind_methods();
@@ -33,7 +56,9 @@ namespace OpenVic {
void clear();
/* Set the GUI::Text. */
- godot::Error set_gui_text(GUI::Text const* new_gui_text);
+ godot::Error set_gui_text(
+ GUI::Text const* new_gui_text, GFX::Font::colour_codes_t const* override_colour_codes = nullptr
+ );
/* Return the name of the GUI::Text, or an empty String if it's null. */
godot::String get_gui_text_name() const;
@@ -43,15 +68,29 @@ namespace OpenVic {
void set_substitution_dict(godot::Dictionary const& new_substitution_dict);
void clear_substitutions();
- /* Any text going over this number of lines will be trimmed and replaced with an ellipsis.
- * Values less than 1 indicate no limit. Default value: 1. */
- void set_max_lines(int32_t new_max_lines);
-
private:
- godot::Error _update_font();
-
- void _queue_update();
- void _update_text();
- void _generate_text(godot::String const& display_text, colour_instructions_t const& colour_instructions);
+ godot::Vector2 get_content_max_size() const;
+
+ godot::Error _update_font(GFX::Font::colour_codes_t const* override_colour_codes);
+ real_t get_string_width(godot::String const& string) const;
+ real_t get_segment_width(segment_t const& segment) const;
+
+ void _queue_line_update();
+ void _update_lines();
+
+ godot::String generate_substituted_text(godot::String const& base_text) const;
+ std::pair<godot::String, colour_instructions_t> generate_display_text_and_colour_instructions(
+ godot::String const& substituted_text
+ ) const;
+ std::vector<line_t> generate_lines_and_segments(
+ godot::String const& display_text, colour_instructions_t const& colour_instructions
+ ) const;
+ void separate_lines(
+ godot::String const& string, godot::Color const& colour, std::vector<line_t>& lines
+ ) const;
+ void separate_currency_segments(
+ godot::String const& string, godot::Color const& colour, line_t& line
+ ) const;
+ std::vector<line_t> wrap_lines(std::vector<line_t>& unwrapped_lines) const;
};
}
diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp
index f972681..3fcc837 100644
--- a/extension/src/openvic-extension/utility/UITools.cpp
+++ b/extension/src/openvic-extension/utility/UITools.cpp
@@ -510,7 +510,11 @@ static bool generate_text(generate_gui_args_t&& args) {
text_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
- if (text_label->set_gui_text(&text) != OK) {
+ GameSingleton const* game_singleton = GameSingleton::get_singleton();
+ GFX::Font::colour_codes_t const* override_colour_codes = game_singleton != nullptr
+ ? &game_singleton->get_definition_manager().get_ui_manager().get_universal_colour_codes() : nullptr;
+
+ if (text_label->set_gui_text(&text, override_colour_codes) != OK) {
UtilityFunctions::push_error("Error initialising GUITextLabel for GUI text ", text_name);
ret = false;
}