From 95a6b95a5df63fa1bcb1d4680b3fdd7091a9c38e Mon Sep 17 00:00:00 2001 From: hop311 Date: Fri, 23 Aug 2024 00:03:02 +0100 Subject: Rework GUITextLabel to use custom text handling instead of RichTextLabel --- extension/deps/openvic-simulation | 2 +- .../src/openvic-extension/classes/GUITextLabel.cpp | 558 +++++++++++++++------ .../src/openvic-extension/classes/GUITextLabel.hpp | 71 ++- .../src/openvic-extension/utility/UITools.cpp | 6 +- .../NationManagementScreen/BudgetMenu.gd | 10 +- game/src/Game/GameSession/Topbar.gd | 7 +- 6 files changed, 463 insertions(+), 191 deletions(-) diff --git a/extension/deps/openvic-simulation b/extension/deps/openvic-simulation index 53bf0ac..5813948 160000 --- a/extension/deps/openvic-simulation +++ b/extension/deps/openvic-simulation @@ -1 +1 @@ -Subproject commit 53bf0ac14be0fd7a049306d6e20c01030916fdc6 +Subproject commit 5813948cd3ed6432de374664650d68afbff71915 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(&segment); + + if (string_segment == nullptr) { + if (currency_texture.is_valid()) { + currency_texture->draw( + ci, position - Vector2 { + 1.0_real, static_cast(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 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 = 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(&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(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 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 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(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(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::generate_lines_and_segments( + String const& display_text, colour_instructions_t const& colour_instructions +) const { + static constexpr char RESET_COLOUR_CODE = '!'; + + std::vector 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(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& 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 const& currency_texture = - asset_manager != nullptr ? asset_manager->get_currency_texture(font_height) : Ref {}; + 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::wrap_lines(std::vector& unwrapped_lines) const { + std::vector 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(&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(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(&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 +#include +#include +#include #include +#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>; @@ -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 font; + godot::Color default_colour; GFX::Font::colour_codes_t const* colour_codes; - int32_t PROPERTY(max_lines); + godot::Ref currency_texture; + + godot::Ref 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; + struct line_t { + std::vector segments; + real_t width {}; + }; - bool update_queued; + std::vector 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 generate_display_text_and_colour_instructions( + godot::String const& substituted_text + ) const; + std::vector 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& lines + ) const; + void separate_currency_segments( + godot::String const& string, godot::Color const& colour, line_t& line + ) const; + std::vector wrap_lines(std::vector& 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; } diff --git a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd index 3c2fb3b..90df650 100644 --- a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd @@ -87,15 +87,15 @@ func _ready() -> void: # income var _lower_class_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_0_slider") if _lower_class_slider and _lower_class_label: - _lower_class_slider.value_changed.connect(func(value : int) -> void: _lower_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3)) + _lower_class_slider.value_changed.connect(func(value : int) -> void: _lower_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3 if abs(value) < 1000 else 1)) _lower_class_slider.emit_value_changed() var _middle_class_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_1_slider") if _middle_class_slider and _middle_class_label: - _middle_class_slider.value_changed.connect(func(value : int) -> void: _middle_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3)) + _middle_class_slider.value_changed.connect(func(value : int) -> void: _middle_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3 if abs(value) < 1000 else 1)) _middle_class_slider.emit_value_changed() var _upper_class_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/tax_2_slider") if _upper_class_slider and _upper_class_label: - _upper_class_slider.value_changed.connect(func(value : int) -> void: _upper_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3)) + _upper_class_slider.value_changed.connect(func(value : int) -> void: _upper_class_label.text = "%s¤" % GUINode.float_to_string_dp(value, 3 if abs(value) < 1000 else 1)) _upper_class_slider.emit_value_changed() # costs @@ -123,7 +123,7 @@ func _ready() -> void: if _administration_exp_label: _exp_1_slider.value_changed.connect(func(value : int) -> void: _administration_exp_label.text = "%s¤" % GUINode.float_to_string_dp_dynamic(value)) if _admin_efficiency_label: - _exp_1_slider.value_changed.connect(func(value : int) -> void: _admin_efficiency_label.text = "%s%%" % value) + _exp_1_slider.value_changed.connect(func(value : int) -> void: _admin_efficiency_label.text = "%s%%" % GUINode.float_to_string_dp(value, 1)) _exp_1_slider.emit_value_changed() var _exp_2_slider : GUIScrollbar = get_gui_scrollbar_from_nodepath(^"./country_budget/exp_2_slider") if _exp_2_slider and _social_exp_label: @@ -140,7 +140,7 @@ func _ready() -> void: if _tariff_val_label: _tariff_slider.value_changed.connect(func(value : int) -> void: _tariff_val_label.text = "%s¤" % GUINode.float_to_string_dp_dynamic(value)) if _tariffs_percent_label: - _tariff_slider.value_changed.connect(func(value : int) -> void: _tariffs_percent_label.text = "%s%%" % value) + _tariff_slider.value_changed.connect(func(value : int) -> void: _tariffs_percent_label.text = "%s%%" % GUINode.float_to_string_dp(value, 1)) _tariff_slider.emit_value_changed() # debt buttons diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd index 5f5640f..a173ccd 100644 --- a/game/src/Game/GameSession/Topbar.gd +++ b/game/src/Game/GameSession/Topbar.gd @@ -269,8 +269,7 @@ func _update_info() -> void: _country_name_label.set_text(player_country) if _country_rank_label: - # TODO - fix label alignment - _country_rank_label.set_text(" %d" % 1) + _country_rank_label.set_text(str(1)) if _country_prestige_label: _country_prestige_label.set_text(str(11)) @@ -294,7 +293,7 @@ func _update_info() -> void: var available_colonial_power : int = 123 var total_colonial_power : int = 456 _country_colonial_power_label.set_text( - ("%s/%s" if available_colonial_power > 0 else "§R%s§!/%s") % [available_colonial_power, total_colonial_power] + "§%s%s§!/%s" % ["W" if available_colonial_power > 0 else "R", available_colonial_power, total_colonial_power] ) ## Time control @@ -334,7 +333,7 @@ func _update_info() -> void: _technology_current_research_label.set_text("TB_TECH_NO_CURRENT") if _technology_literacy_label: - _technology_literacy_label.set_text("§Y%s%%" % GUINode.float_to_string_dp(80.0, 1)) + _technology_literacy_label.set_text("§Y%s§W%%" % GUINode.float_to_string_dp(80.0, 1)) if _technology_research_points_label: _technology_research_points_label.set_text("§Y%s" % GUINode.float_to_string_dp(10.0, 2)) -- cgit v1.2.3-56-ga3b1