From 7c85ab11e840c281a2499dcc6dd3219c33e7d37f Mon Sep 17 00:00:00 2001 From: hop311 Date: Thu, 15 Aug 2024 00:13:54 +0100 Subject: Add GUITextLabel (colour code + currency icon support) --- .../src/openvic-extension/classes/GUINode.cpp | 7 +- .../src/openvic-extension/classes/GUINode.hpp | 8 +- .../src/openvic-extension/classes/GUITextLabel.cpp | 334 +++++++++++++++++++++ .../src/openvic-extension/classes/GUITextLabel.hpp | 57 ++++ extension/src/openvic-extension/register_types.cpp | 10 +- .../openvic-extension/singletons/AssetManager.cpp | 42 ++- .../openvic-extension/singletons/AssetManager.hpp | 19 +- .../openvic-extension/singletons/GameSingleton.cpp | 8 +- .../src/openvic-extension/utility/UITools.cpp | 58 +--- .../src/openvic-extension/utility/Utilities.cpp | 5 + .../src/openvic-extension/utility/Utilities.hpp | 3 + 11 files changed, 495 insertions(+), 56 deletions(-) create mode 100644 extension/src/openvic-extension/classes/GUITextLabel.cpp create mode 100644 extension/src/openvic-extension/classes/GUITextLabel.hpp (limited to 'extension/src/openvic-extension') diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index bd8197b..4d7d33e 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -40,7 +40,7 @@ using namespace OpenVic; #define APPLY_TO_CHILD_TYPES(F) \ F(Button, button) \ - F(Label, label) \ + F(GUITextLabel, gui_text_label) \ F(Panel, panel) \ F(TextureProgressBar, progress_bar) \ F(TextureRect, texture_rect) \ @@ -90,6 +90,7 @@ void GUINode::_bind_methods() { OV_BIND_SMETHOD(int_to_string_suffixed, { "val" }); OV_BIND_SMETHOD(float_to_string_suffixed, { "val" }); OV_BIND_SMETHOD(float_to_string_dp, { "val", "decimal_places" }); + OV_BIND_SMETHOD(float_to_string_dp_dynamic, { "val" }); OV_BIND_SMETHOD(format_province_name, { "province_identifier" }); } @@ -266,6 +267,10 @@ String GUINode::float_to_string_dp(float val, int32_t decimal_places) { return Utilities::float_to_string_dp(val, decimal_places); } +String GUINode::float_to_string_dp_dynamic(float val) { + return Utilities::float_to_string_dp_dynamic(val); +} + String GUINode::format_province_name(String const& province_identifier) { if (!province_identifier.is_empty()) { static const String province_prefix = "PROV"; diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index f8eb62c..6168e7e 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -25,6 +24,7 @@ #include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" +#include "openvic-extension/classes/GUITextLabel.hpp" namespace OpenVic { class GUINode : public godot::Control { @@ -52,7 +52,7 @@ namespace OpenVic { static godot::Vector2 get_gui_position(godot::String const& gui_scene, godot::String const& gui_position); static godot::Button* get_button_from_node(godot::Node* node); - static godot::Label* get_label_from_node(godot::Node* node); + static GUITextLabel* get_gui_text_label_from_node(godot::Node* node); static godot::Panel* get_panel_from_node(godot::Node* node); static godot::TextureProgressBar* get_progress_bar_from_node(godot::Node* node); static godot::TextureRect* get_texture_rect_from_node(godot::Node* node); @@ -62,7 +62,7 @@ namespace OpenVic { static godot::LineEdit* get_line_edit_from_node(godot::Node* node); godot::Button* get_button_from_nodepath(godot::NodePath const& path) const; - godot::Label* get_label_from_nodepath(godot::NodePath const& path) const; + GUITextLabel* get_gui_text_label_from_nodepath(godot::NodePath const& path) const; godot::Panel* get_panel_from_nodepath(godot::NodePath const& path) const; godot::TextureProgressBar* get_progress_bar_from_nodepath(godot::NodePath const& path) const; godot::TextureRect* get_texture_rect_from_nodepath(godot::NodePath const& path) const; @@ -91,6 +91,8 @@ namespace OpenVic { static godot::String int_to_string_suffixed(int64_t val); static godot::String float_to_string_suffixed(float val); static godot::String float_to_string_dp(float val, int32_t decimal_places); + // 3dp if abs(val) < 2 else 2dp if abs(val) < 10 else 1dp + static godot::String float_to_string_dp_dynamic(float val); static godot::String format_province_name(godot::String const& province_identifier); godot::Ref get_click_mask() const; diff --git a/extension/src/openvic-extension/classes/GUITextLabel.cpp b/extension/src/openvic-extension/classes/GUITextLabel.cpp new file mode 100644 index 0000000..5baba70 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUITextLabel.cpp @@ -0,0 +1,334 @@ +#include "GUITextLabel.hpp" + +#include +#include + +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace OpenVic; +using namespace godot; +using namespace OpenVic::Utilities::literals; + +void GUITextLabel::_bind_methods() { + OV_BIND_METHOD(GUITextLabel::clear); + + OV_BIND_METHOD(GUITextLabel::get_text); + OV_BIND_METHOD(GUITextLabel::set_text, { "new_text" }); + + OV_BIND_METHOD(GUITextLabel::get_substitution_dict); + OV_BIND_METHOD(GUITextLabel::add_substitution, { "key", "value" }); + 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); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "substitution_dict"), "set_substitution_dict", "get_substitution_dict"); +} + +void GUITextLabel::_notification(int what) { + switch (what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + _queue_update(); + } 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); +} + +void GUITextLabel::clear() { + gui_text = nullptr; + + text = String {}; + substitution_dict.clear(); + alignment = HORIZONTAL_ALIGNMENT_LEFT; + max_lines = 1; + + static const StringName normal_theme = "normal"; + remove_theme_stylebox_override(normal_theme); + + _update_font(); + + update_queued = false; +} + +Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text) { + if (gui_text == new_gui_text) { + return OK; + } + + if (new_gui_text == nullptr) { + clear(); + return OK; + } + + gui_text = new_gui_text; + + text = Utilities::std_to_godot_string(gui_text->get_text()); + + using enum GUI::AlignedElement::format_t; + static const ordered_map format_map { + { left, HORIZONTAL_ALIGNMENT_LEFT }, + { centre, HORIZONTAL_ALIGNMENT_CENTER }, + { right, HORIZONTAL_ALIGNMENT_RIGHT } + }; + 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; + + 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_update(); + + Error err = _update_font(); + + if (!gui_text->get_texture_file().empty()) { + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V(asset_manager, FAILED); + + const StringName texture_path = Utilities::std_to_godot_string(gui_text->get_texture_file()); + Ref texture = asset_manager->get_texture(texture_path); + ERR_FAIL_NULL_V_MSG( + 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); + + static const StringName normal_theme = "normal"; + add_theme_stylebox_override(normal_theme, stylebox); + } + + return err; +} + +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) { + if (text != new_text) { + text = new_text; + _queue_update(); + } +} + +void GUITextLabel::add_substitution(String const& key, godot::String const& value) { + Variant& existing_value = substitution_dict[key]; + if (existing_value != value) { + existing_value = value; + _queue_update(); + } +} + +void GUITextLabel::set_substitution_dict(godot::Dictionary const& new_substitution_dict) { + substitution_dict = new_substitution_dict; + _queue_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(); + } +} + +Error GUITextLabel::_update_font() { + static const StringName font_theme = "normal_font"; + static const StringName font_color_theme = "default_color"; + + 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; + colour_codes = nullptr; + + 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(); + + 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); + + 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(); + + return OK; +} + +void GUITextLabel::_queue_update() { + if (!update_queued) { + update_queued = true; + + callable_mp(this, &GUITextLabel::_update_text).call_deferred(); + } +} + +void GUITextLabel::_update_text() { + static constexpr char SUBSTITUTION_CHAR = '$'; + static constexpr char COLOUR_CHAR = '\xA7'; // § + + 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); + } + } + + // Separate out colour codes from displayed test + String display_text; + 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); + } + if (++idx < substituted_text.length() && colour_codes != nullptr) { + colour_instructions.emplace_back(display_text.length(), static_cast(substituted_text[idx])); + } + section_start = idx + 1; + } + } + if (section_start < substituted_text.length()) { + display_text += substituted_text.substr(section_start); + } + } + + _generate_text(display_text, colour_instructions); + + // 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 + ); + } + } + + update_queued = false; +} + +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'; // ¤ + + AssetManager const* asset_manager = AssetManager::get_singleton(); + Ref const& currency_texture = + asset_manager != nullptr ? asset_manager->get_currency_texture(font_height) : Ref {}; + + RichTextLabel::clear(); + + push_paragraph(alignment); + + // 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; + } + push_color(Utilities::to_godot_color(it->second)); + } + } + ++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); + } else { + static const String currency_fallback = "£"; + add_text(currency_fallback); + } + section_start = idx + 1; + } + } + if (section_start < display_text.length()) { + add_text(display_text.substr(section_start)); + } + } +} diff --git a/extension/src/openvic-extension/classes/GUITextLabel.hpp b/extension/src/openvic-extension/classes/GUITextLabel.hpp new file mode 100644 index 0000000..b29870e --- /dev/null +++ b/extension/src/openvic-extension/classes/GUITextLabel.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include + +namespace OpenVic { + class GUITextLabel : public godot::RichTextLabel { + GDCLASS(GUITextLabel, godot::RichTextLabel) + + using colour_instructions_t = std::vector>; + + GUI::Text const* PROPERTY(gui_text); + + godot::String PROPERTY(text); + godot::Dictionary PROPERTY(substitution_dict); + godot::HorizontalAlignment PROPERTY(alignment); + real_t font_height; + GFX::Font::colour_codes_t const* colour_codes; + int32_t PROPERTY(max_lines); + + bool update_queued; + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUITextLabel(); + + /* Reset gui_text to nullptr and reset current text. */ + void clear(); + + /* Set the GUI::Text. */ + godot::Error set_gui_text(GUI::Text const* new_gui_text); + + /* Return the name of the GUI::Text, or an empty String if it's null. */ + godot::String get_gui_text_name() const; + + void set_text(godot::String const& new_text); + void add_substitution(godot::String const& key, godot::String const& value); + 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); + }; +} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index 0b9d779..eb58cb0 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -10,6 +10,7 @@ #include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" +#include "openvic-extension/classes/GUITextLabel.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -43,10 +44,6 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { _load_localisation = memnew(LoadLocalisation); Engine::get_singleton()->register_singleton("LoadLocalisation", LoadLocalisation::get_singleton()); - ClassDB::register_class(); - _sound_singleton = memnew(SoundSingleton); - Engine::get_singleton()->register_singleton("SoundSingleton", SoundSingleton::get_singleton()); - ClassDB::register_class(); _game_singleton = memnew(GameSingleton); Engine::get_singleton()->register_singleton("GameSingleton", GameSingleton::get_singleton()); @@ -63,6 +60,10 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { _asset_manager_singleton = memnew(AssetManager); Engine::get_singleton()->register_singleton("AssetManager", AssetManager::get_singleton()); + ClassDB::register_class(); + _sound_singleton = memnew(SoundSingleton); + Engine::get_singleton()->register_singleton("SoundSingleton", SoundSingleton::get_singleton()); + ClassDB::register_class(); ClassDB::register_abstract_class(); @@ -79,6 +80,7 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/singletons/AssetManager.cpp b/extension/src/openvic-extension/singletons/AssetManager.cpp index eec1ada..d17edd0 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.cpp +++ b/extension/src/openvic-extension/singletons/AssetManager.cpp @@ -4,6 +4,7 @@ #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/UITools.hpp" #include "openvic-extension/utility/Utilities.hpp" using namespace godot; @@ -119,7 +120,7 @@ Ref AssetManager::get_texture(StringName const& path, LoadFlags lo } } -Ref AssetManager::get_font(StringName const& name) { +Ref AssetManager::get_font(StringName const& name) { const font_map_t::const_iterator it = fonts.find(name); if (it != fonts.end()) { ERR_FAIL_NULL_V_MSG(it->second, nullptr, vformat("Failed to load font previously: %s", name)); @@ -152,7 +153,7 @@ Ref AssetManager::get_font(StringName const& name) { ERR_FAIL_V_MSG(nullptr, vformat("Failed to look up font: %s", font_path)); } - const Ref font = Utilities::load_godot_font(lookedup_font_path, image); + const Ref font = Utilities::load_godot_font(lookedup_font_path, image); if (font.is_null()) { fonts.emplace(name, nullptr); @@ -165,3 +166,40 @@ Ref AssetManager::get_font(StringName const& name) { fonts.emplace(name, font); return font; } + +Error AssetManager::preload_textures() { + static const String currency_sprite_big = "GFX_tooltip_money_big"; + static const String currency_sprite_medium = "GFX_tooltip_money_small"; + static const String currency_sprite_small = "GFX_tooltip_money"; + + constexpr auto load = [](String const& sprite_name, Ref& texture) -> bool { + GFX::Sprite const* sprite = UITools::get_gfx_sprite(sprite_name); + ERR_FAIL_NULL_V(sprite, false); + + GFX::IconTextureSprite const* icon_sprite = sprite->cast_to(); + ERR_FAIL_NULL_V(icon_sprite, false); + + texture = GFXSpriteTexture::make_gfx_sprite_texture(icon_sprite); + ERR_FAIL_NULL_V(texture, false); + + return true; + }; + + bool ret = true; + + ret &= load(currency_sprite_big, currency_texture_big); + ret &= load(currency_sprite_medium, currency_texture_medium); + ret &= load(currency_sprite_small, currency_texture_small); + + return ERR(ret); +} + +Ref const& AssetManager::get_currency_texture(real_t height) const { + if (height > currency_texture_big->get_height()) { + return currency_texture_big; + } else if (height > currency_texture_medium->get_height()) { + return currency_texture_medium; + } else { + return currency_texture_small; + } +} diff --git a/extension/src/openvic-extension/singletons/AssetManager.hpp b/extension/src/openvic-extension/singletons/AssetManager.hpp index 0856d05..deca309 100644 --- a/extension/src/openvic-extension/singletons/AssetManager.hpp +++ b/extension/src/openvic-extension/singletons/AssetManager.hpp @@ -1,12 +1,14 @@ #pragma once #include -#include +#include #include #include #include +#include "openvic-extension/classes/GFXSpriteTexture.hpp" + namespace OpenVic { class AssetManager : public godot::Object { GDCLASS(AssetManager, godot::Object) @@ -32,7 +34,7 @@ namespace OpenVic { }; /* deque_ordered_map to avoid the need to reallocate. */ using image_asset_map_t = deque_ordered_map; - using font_map_t = deque_ordered_map>; + using font_map_t = deque_ordered_map>; image_asset_map_t image_assets; font_map_t fonts; @@ -68,7 +70,18 @@ namespace OpenVic { /* Search for and load a font with the specified name from the game defines' font directory, first checking the * AssetManager's font cache in case it has already been loaded, and returning nullptr if font loading fails. */ - godot::Ref get_font(godot::StringName const& name); + godot::Ref get_font(godot::StringName const& name); + + private: + godot::Ref PROPERTY(currency_texture_big); // 32x32 + godot::Ref PROPERTY(currency_texture_medium); // 24x24 + godot::Ref PROPERTY(currency_texture_small); // 16x16 + + public: + godot::Error preload_textures(); + + /* Get the largest currency texture with height less than the specified font height. */ + godot::Ref const& get_currency_texture(real_t height) const; }; } diff --git a/extension/src/openvic-extension/singletons/GameSingleton.cpp b/extension/src/openvic-extension/singletons/GameSingleton.cpp index 5268789..13324d0 100644 --- a/extension/src/openvic-extension/singletons/GameSingleton.cpp +++ b/extension/src/openvic-extension/singletons/GameSingleton.cpp @@ -597,7 +597,7 @@ Error GameSingleton::set_compatibility_mode_roots(PackedStringArray const& file_ Error GameSingleton::load_defines_compatibility_mode() { Error err = OK; auto add_message = std::bind_front(&LoadLocalisation::add_message, LoadLocalisation::get_singleton()); - + if (!game_manager.load_definitions(add_message)) { UtilityFunctions::push_error("Failed to load defines!"); err = FAILED; @@ -616,6 +616,12 @@ Error GameSingleton::load_defines_compatibility_mode() { err = FAILED; } + AssetManager* asset_manager = AssetManager::get_singleton(); + if (asset_manager == nullptr || asset_manager->preload_textures() != OK) { + UtilityFunctions::push_error("Failed to preload assets!"); + err = FAILED; + } + return err; } diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index cffab22..f972681 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -19,6 +18,7 @@ #include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" +#include "openvic-extension/classes/GUITextLabel.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -500,55 +500,22 @@ static bool generate_checkbox(generate_gui_args_t&& args) { } static bool generate_text(generate_gui_args_t&& args) { - using namespace OpenVic::Utilities::literals; - GUI::Text const& text = static_cast(args.element); const String text_name = Utilities::std_to_godot_string(text.get_name()); - Label* godot_label = nullptr; - bool ret = new_control(godot_label, text, args.name); - ERR_FAIL_NULL_V_MSG(godot_label, false, vformat("Failed to create Label for GUI text %s", text_name)); - - godot_label->set_text(Utilities::std_to_godot_string(text.get_text())); + GUITextLabel* text_label = nullptr; + bool ret = new_control(text_label, text, args.name); + ERR_FAIL_NULL_V_MSG(text_label, false, vformat("Failed to create GUITextLabel for GUI text %s", text_name)); - static const Vector2 default_padding { 1.0_real, 0.0_real }; - const Vector2 border_size = Utilities::to_godot_fvec2(text.get_border_size()) + default_padding; - const Vector2 max_size = Utilities::to_godot_fvec2(text.get_max_size()); - godot_label->set_position(godot_label->get_position() + border_size); - godot_label->set_custom_minimum_size(max_size - 2 * border_size); + text_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - using enum GUI::AlignedElement::format_t; - static const ordered_map format_map { - { left, HORIZONTAL_ALIGNMENT_LEFT }, - { centre, HORIZONTAL_ALIGNMENT_CENTER }, - { right, HORIZONTAL_ALIGNMENT_RIGHT } - }; - - const decltype(format_map)::const_iterator it = format_map.find(text.get_format()); - if (it != format_map.end()) { - godot_label->set_horizontal_alignment(it->second); - } else { - UtilityFunctions::push_error("Invalid text format (horizontal alignment) for GUI text ", text_name); + if (text_label->set_gui_text(&text) != OK) { + UtilityFunctions::push_error("Error initialising GUITextLabel for GUI text ", text_name); ret = false; } - if (text.get_font() != nullptr) { - const StringName font_file = Utilities::std_to_godot_string(text.get_font()->get_fontname()); - const Ref font = args.asset_manager.get_font(font_file); - if (font.is_valid()) { - static const StringName font_theme = "font"; - godot_label->add_theme_font_override(font_theme, font); - } else { - UtilityFunctions::push_error("Failed to load font \"", font_file, "\" for GUI text ", text_name); - ret = false; - } - const Color colour = Utilities::to_godot_color(text.get_font()->get_colour()); - static const StringName font_color_theme = "font_color"; - godot_label->add_theme_color_override(font_color_theme, colour); - } - - args.result = godot_label; + args.result = text_label; return ret; } @@ -564,7 +531,14 @@ static bool generate_overlapping_elements(generate_gui_args_t&& args) { vformat("Failed to create GUIOverlappingElementsBox for GUI overlapping elements %s", overlapping_elements_name) ); box->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - ret &= box->set_gui_overlapping_elements_box(&overlapping_elements) == OK; + + if (box->set_gui_overlapping_elements_box(&overlapping_elements) != OK) { + UtilityFunctions::push_error( + "Error initialising GUIOverlappingElementsBox for GUI overlapping elements ", overlapping_elements_name + ); + ret = false; + } + args.result = box; return ret; } diff --git a/extension/src/openvic-extension/utility/Utilities.cpp b/extension/src/openvic-extension/utility/Utilities.cpp index 4a774a7..1fcdea8 100644 --- a/extension/src/openvic-extension/utility/Utilities.cpp +++ b/extension/src/openvic-extension/utility/Utilities.cpp @@ -63,6 +63,11 @@ String Utilities::float_to_string_dp(float val, int32_t decimal_places) { return String::num(val, decimal_places).pad_decimals(decimal_places); } +String Utilities::float_to_string_dp_dynamic(float val) { + const float abs_val = std::abs(val); + return float_to_string_dp(val, abs_val < 2.0f ? 3 : abs_val < 10.0f ? 2 : 1); +} + /* Date formatted like this: "January 1, 1836" (with the month localised, if possible). */ String Utilities::date_to_formatted_string(Date date) { const String month_name = Utilities::std_to_godot_string(date.get_month_name()); diff --git a/extension/src/openvic-extension/utility/Utilities.hpp b/extension/src/openvic-extension/utility/Utilities.hpp index 49314ca..48be1e0 100644 --- a/extension/src/openvic-extension/utility/Utilities.hpp +++ b/extension/src/openvic-extension/utility/Utilities.hpp @@ -27,6 +27,9 @@ namespace OpenVic::Utilities { godot::String float_to_string_dp(float val, int32_t decimal_places); + // 3dp if abs(val) < 2 else 2dp if abs(val) < 10 else 1dp + godot::String float_to_string_dp_dynamic(float val); + constexpr real_t to_real_t(std::floating_point auto val) { return static_cast(val); } -- cgit v1.2.3-56-ga3b1 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(-) (limited to 'extension/src/openvic-extension') 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 From 344651044bfee007f59e76d48661b34edee4bd5a Mon Sep 17 00:00:00 2001 From: hop311 Date: Fri, 23 Aug 2024 19:38:04 +0100 Subject: Expand GUITextLabel getters/setters + add auto adjust to content size option --- .../src/openvic-extension/classes/GUITextLabel.cpp | 358 ++++++++++++++++----- .../src/openvic-extension/classes/GUITextLabel.hpp | 46 ++- 2 files changed, 317 insertions(+), 87 deletions(-) (limited to 'extension/src/openvic-extension') diff --git a/extension/src/openvic-extension/classes/GUITextLabel.cpp b/extension/src/openvic-extension/classes/GUITextLabel.cpp index 508dbc8..ec8f158 100644 --- a/extension/src/openvic-extension/classes/GUITextLabel.cpp +++ b/extension/src/openvic-extension/classes/GUITextLabel.cpp @@ -1,5 +1,6 @@ #include "GUITextLabel.hpp" +#include #include #include @@ -11,8 +12,11 @@ using namespace OpenVic; using namespace godot; using namespace OpenVic::Utilities::literals; +static constexpr int32_t DEFAULT_FONT_SIZE = 16; + void GUITextLabel::_bind_methods() { OV_BIND_METHOD(GUITextLabel::clear); + OV_BIND_METHOD(GUITextLabel::get_gui_text_name); OV_BIND_METHOD(GUITextLabel::get_text); OV_BIND_METHOD(GUITextLabel::set_text, { "new_text" }); @@ -22,11 +26,57 @@ 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_alignment); - OV_BIND_METHOD(GUITextLabel::get_gui_text_name); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text"); + OV_BIND_METHOD(GUITextLabel::get_horizontal_alignment); + OV_BIND_METHOD(GUITextLabel::set_horizontal_alignment, { "new_horizontal_alignment" }); + OV_BIND_METHOD(GUITextLabel::get_max_size); + OV_BIND_METHOD(GUITextLabel::set_max_size, { "new_max_size" }); + OV_BIND_METHOD(GUITextLabel::get_border_size); + OV_BIND_METHOD(GUITextLabel::set_border_size, { "new_border_size" }); + OV_BIND_METHOD(GUITextLabel::get_adjusted_rect); + OV_BIND_METHOD(GUITextLabel::will_auto_adjust_to_content_size); + OV_BIND_METHOD(GUITextLabel::set_auto_adjust_to_content_size, { "new_auto_adjust_to_content_size" }); + + OV_BIND_METHOD(GUITextLabel::get_font); + OV_BIND_METHOD(GUITextLabel::set_font, { "new_font" }); + OV_BIND_METHOD(GUITextLabel::set_font_file, { "new_font_file" }); + OV_BIND_METHOD(GUITextLabel::get_font_size); + OV_BIND_METHOD(GUITextLabel::set_font_size, { "new_font_size" }); + OV_BIND_METHOD(GUITextLabel::get_default_colour); + OV_BIND_METHOD(GUITextLabel::set_default_colour, { "new_default_colour" }); + OV_BIND_METHOD(GUITextLabel::get_currency_texture); + + OV_BIND_METHOD(GUITextLabel::get_background); + OV_BIND_METHOD(GUITextLabel::set_background_texture, { "new_texture" }); + OV_BIND_METHOD(GUITextLabel::set_background_stylebox, { "new_stylebox_texture" }); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "substitution_dict"), "set_substitution_dict", "get_substitution_dict"); + ADD_PROPERTY( + PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Centre,Right,Fill"), + "set_horizontal_alignment", "get_horizontal_alignment" + ); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size"); + ADD_PROPERTY( + PropertyInfo(Variant::VECTOR2, "border_size", PROPERTY_HINT_NONE, "suffix:px"), "set_border_size", "get_border_size" + ); + ADD_PROPERTY( + PropertyInfo(Variant::RECT2, "adjusted_rect", PROPERTY_HINT_NONE, "suffix:px"), "", "get_adjusted_rect" + ); + ADD_PROPERTY( + PropertyInfo(Variant::BOOL, "auto_adjust_to_content_size"), "set_auto_adjust_to_content_size", + "will_auto_adjust_to_content_size" + ); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_NONE, "suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_colour"), "set_default_colour", "get_default_colour"); + ADD_PROPERTY( + PropertyInfo(Variant::OBJECT, "currency_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "", "get_currency_texture" + ); + ADD_PROPERTY( + PropertyInfo(Variant::OBJECT, "background", PROPERTY_HINT_RESOURCE_TYPE, "StyleBoxTexture"), "set_background_stylebox", + "get_background" + ); } void GUITextLabel::_notification(int what) { @@ -37,38 +87,35 @@ void GUITextLabel::_notification(int what) { } 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 }); + draw_style_box(background, adjusted_rect); } 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; + const Vector2 offset = base_offset + adjusted_rect.position + border_size; Vector2 position = offset; for (line_t const& line : lines) { position.x = offset.x; - switch (alignment) { + switch (horizontal_alignment) { case HORIZONTAL_ALIGNMENT_CENTER: { - position.x += (content_max_size.width - line.width + 1.0_real) / 2.0_real; + position.x += (adjusted_rect.size.width - 2 * border_size.width - line.width + 1.0_real) / 2.0_real; } break; case HORIZONTAL_ALIGNMENT_RIGHT: { - position.x += content_max_size.width - line.width; + position.x += adjusted_rect.size.width - 2 * border_size.width - line.width; } break; case HORIZONTAL_ALIGNMENT_LEFT: default: break; } - position.y += font->get_ascent(font->get_fixed_size()); + position.y += font->get_ascent(font_size); for (segment_t const& segment : line.segments) { string_segment_t const* string_segment = std::get_if(&segment); @@ -84,14 +131,14 @@ void GUITextLabel::_notification(int what) { } } else { font->draw_string( - ci, position, string_segment->text, HORIZONTAL_ALIGNMENT_LEFT, -1, font->get_fixed_size(), + ci, position, string_segment->text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, string_segment->colour ); position.x += string_segment->width; } } - position.y += font->get_descent(font->get_fixed_size()); + position.y += font->get_descent(font_size); } } break; @@ -99,17 +146,39 @@ void GUITextLabel::_notification(int what) { } GUITextLabel::GUITextLabel() - : gui_text { nullptr }, alignment { HORIZONTAL_ALIGNMENT_LEFT }, colour_codes { nullptr }, line_update_queued { false } {} + : gui_text { nullptr }, + text {}, + substitution_dict {}, + horizontal_alignment { HORIZONTAL_ALIGNMENT_LEFT }, + max_size {}, + border_size {}, + adjusted_rect {}, + auto_adjust_to_content_size { false }, + font {}, + font_size { DEFAULT_FONT_SIZE }, + default_colour {}, + colour_codes { nullptr }, + currency_texture {}, + background {}, + lines {}, + line_update_queued { false } {} void GUITextLabel::clear() { gui_text = nullptr; text = String {}; substitution_dict.clear(); - alignment = HORIZONTAL_ALIGNMENT_LEFT; + horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; + max_size = {}; border_size = {}; + adjusted_rect = {}; + auto_adjust_to_content_size = false; - _update_font(nullptr); + font.unref(); + font_size = DEFAULT_FONT_SIZE; + default_colour = {}; + colour_codes = nullptr; + currency_texture.unref(); background.unref(); lines.clear(); @@ -119,6 +188,10 @@ void GUITextLabel::clear() { queue_redraw(); } +String GUITextLabel::get_gui_text_name() const { + return gui_text != nullptr ? Utilities::std_to_godot_string(gui_text->get_name()) : String {}; +} + 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; @@ -131,7 +204,7 @@ Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text, GFX::Font::colou gui_text = new_gui_text; - text = Utilities::std_to_godot_string(gui_text->get_text()); + set_text(Utilities::std_to_godot_string(gui_text->get_text())); using enum GUI::AlignedElement::format_t; static const ordered_map format_map { @@ -140,47 +213,58 @@ Error GUITextLabel::set_gui_text(GUI::Text const* new_gui_text, GFX::Font::colou { right, HORIZONTAL_ALIGNMENT_RIGHT } }; const decltype(format_map)::const_iterator it = format_map.find(gui_text->get_format()); - alignment = it != format_map.end() ? it->second : HORIZONTAL_ALIGNMENT_LEFT; - - set_custom_minimum_size(Utilities::to_godot_fvec2(gui_text->get_max_size())); - border_size = Utilities::to_godot_fvec2(gui_text->get_border_size()); + set_horizontal_alignment(it != format_map.end() ? it->second : HORIZONTAL_ALIGNMENT_LEFT); - _queue_line_update(); + set_max_size(Utilities::to_godot_fvec2(gui_text->get_max_size())); + set_border_size(Utilities::to_godot_fvec2(gui_text->get_border_size())); - Error err = _update_font(override_colour_codes); + colour_codes = override_colour_codes != nullptr ? override_colour_codes : &gui_text->get_font()->get_colour_codes(); + set_default_colour(Utilities::to_godot_color(gui_text->get_font()->get_colour())); + font.unref(); + font_size = DEFAULT_FONT_SIZE; + currency_texture.unref(); background.unref(); - if (!gui_text->get_texture_file().empty()) { - AssetManager* asset_manager = AssetManager::get_singleton(); - ERR_FAIL_NULL_V(asset_manager, FAILED); + Error err = OK; - const StringName texture_path = Utilities::std_to_godot_string(gui_text->get_texture_file()); - Ref texture = asset_manager->get_texture(texture_path); - ERR_FAIL_NULL_V_MSG( - texture, FAILED, vformat("Failed to load texture \"%s\" for GUITextLabel %s", texture_path, get_name()) - ); - - background.instantiate(); - ERR_FAIL_NULL_V(background, FAILED); - background->set_texture(texture); + AssetManager* asset_manager = AssetManager::get_singleton(); + if (asset_manager != nullptr) { + const StringName font_filepath = Utilities::std_to_godot_string(gui_text->get_font()->get_fontname()); + Ref font_file = asset_manager->get_font(font_filepath); + if (font_file.is_valid()) { + if (set_font_file(font_file) != OK) { + err = FAILED; + } + } else { + UtilityFunctions::push_error("Failed to load font \"", font_filepath, "\" for GUITextLabel"); + err = FAILED; + } - 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); + if (!gui_text->get_texture_file().empty()) { + const StringName texture_path = Utilities::std_to_godot_string(gui_text->get_texture_file()); + Ref texture = asset_manager->get_texture(texture_path); + if (texture.is_valid()) { + set_background_texture(texture); + } else { + UtilityFunctions::push_error("Failed to load texture \"", texture_path, "\" for GUITextLabel ", get_name()); + err = FAILED; + } + } + } else { + UtilityFunctions::push_error("Failed to get AssetManager singleton for GUITextLabel"); + err = FAILED; } - return err; -} + _queue_line_update(); -String GUITextLabel::get_gui_text_name() const { - return gui_text != nullptr ? Utilities::std_to_godot_string(gui_text->get_name()) : String {}; + return err; } void GUITextLabel::set_text(String const& new_text) { if (text != new_text) { text = new_text; + _queue_line_update(); } } @@ -189,6 +273,7 @@ void GUITextLabel::add_substitution(String const& key, String const& value) { Variant& existing_value = substitution_dict[key]; if (existing_value != value) { existing_value = value; + _queue_line_update(); } } @@ -201,42 +286,129 @@ void GUITextLabel::set_substitution_dict(Dictionary const& new_substitution_dict void GUITextLabel::clear_substitutions() { if (!substitution_dict.is_empty()) { substitution_dict.clear(); + _queue_line_update(); } } -Vector2 GUITextLabel::get_content_max_size() const { - return get_size() - 2 * border_size; +void GUITextLabel::set_horizontal_alignment(HorizontalAlignment new_horizontal_alignment) { + if (horizontal_alignment != new_horizontal_alignment) { + horizontal_alignment = new_horizontal_alignment; + + _queue_line_update(); + } } -Error GUITextLabel::_update_font(GFX::Font::colour_codes_t const* override_colour_codes) { - if (gui_text == nullptr || gui_text->get_font() == nullptr) { - font.unref(); - default_colour = {}; - colour_codes = nullptr; - currency_texture.unref(); +void GUITextLabel::set_max_size(Size2 new_max_size) { + if (max_size != new_max_size) { + max_size = new_max_size; - return OK; + set_custom_minimum_size(max_size); + set_size(max_size); + + _queue_line_update(); } +} - 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(); +void GUITextLabel::set_border_size(Size2 new_border_size) { + if (border_size != new_border_size) { + border_size = new_border_size; + + update_stylebox_border_size(); + + _queue_line_update(); + } +} + +void GUITextLabel::set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size) { + if (auto_adjust_to_content_size != new_auto_adjust_to_content_size) { + auto_adjust_to_content_size = new_auto_adjust_to_content_size; + + adjust_to_content_size(); + + queue_redraw(); + } +} + +Ref GUITextLabel::get_font() const { + return font; +} + +void GUITextLabel::set_font(Ref const& new_font) { + font = new_font; + + _queue_line_update(); +} + +Error GUITextLabel::set_font_file(Ref const& new_font_file) { + ERR_FAIL_NULL_V(new_font_file, FAILED); + + set_font(new_font_file); + + return set_font_size(new_font_file->get_fixed_size()); +} + +Error GUITextLabel::set_font_size(int32_t new_font_size) { + font_size = new_font_size; + + _queue_line_update(); 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()); - font = asset_manager->get_font(font_file); - ERR_FAIL_NULL_V_MSG(font, FAILED, vformat("Failed to load font \"%s\" for GUITextLabel", font_file)); - - currency_texture = asset_manager->get_currency_texture(font->get_fixed_size()); + currency_texture = asset_manager->get_currency_texture(font_size); ERR_FAIL_NULL_V(currency_texture, FAILED); return OK; } +void GUITextLabel::set_default_colour(Color const& new_default_colour) { + if (default_colour != new_default_colour) { + default_colour = new_default_colour; + _queue_line_update(); + } +} + +Ref GUITextLabel::get_currency_texture() const { + return currency_texture; +} + +Ref GUITextLabel::get_background() const { + return background; +} + +void GUITextLabel::set_background_texture(Ref const& new_texture) { + Ref new_background; + + if (new_texture.is_valid()) { + new_background.instantiate(); + ERR_FAIL_NULL(new_background); + + new_background->set_texture(new_texture); + } + + set_background_stylebox(new_background); +} + +void GUITextLabel::set_background_stylebox(Ref const& new_stylebox_texture) { + if (background != new_stylebox_texture) { + background = new_stylebox_texture; + update_stylebox_border_size(); + queue_redraw(); + } +} + +void GUITextLabel::update_stylebox_border_size() { + if (background.is_valid()) { + background->set_texture_margin(SIDE_LEFT, border_size.width); + background->set_texture_margin(SIDE_RIGHT, border_size.width); + background->set_texture_margin(SIDE_TOP, border_size.height); + background->set_texture_margin(SIDE_BOTTOM, border_size.height); + } +} + 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; + return font->get_string_size(string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; } real_t GUITextLabel::get_segment_width(segment_t const& segment) const { @@ -276,7 +448,7 @@ void GUITextLabel::_update_lines() { lines = wrap_lines(unwrapped_lines); - // TODO - trim and add ellipsis if necessary + adjust_to_content_size(); queue_redraw(); } @@ -293,7 +465,7 @@ String GUITextLabel::generate_substituted_text(String const& base_text) const { int64_t marker_end_pos = base_text.find(SUBSTITUTION_MARKER, marker_start_pos + SUBSTITUTION_MARKER.length()); if (marker_end_pos == -1) { - return result; + marker_end_pos = base_text.length(); } String key = base_text.substr( @@ -301,12 +473,15 @@ String GUITextLabel::generate_substituted_text(String const& base_text) const { ); String value = substitution_dict.get(key, String {}); - result += value; + // Use the un-substituted key if no value is found or the value is empty + result += value.is_empty() ? key : is_auto_translating() ? tr(value) : value; start_pos = marker_end_pos + SUBSTITUTION_MARKER.length(); } - result += base_text.substr(start_pos); + if (start_pos < base_text.length()) { + result += base_text.substr(start_pos); + } return result; } @@ -446,10 +621,10 @@ void GUITextLabel::separate_currency_segments( std::vector GUITextLabel::wrap_lines(std::vector& unwrapped_lines) const { std::vector wrapped_lines; - const Size2 size = get_content_max_size(); + const Size2 max_content_size = max_size - 2 * border_size; for (line_t& line : unwrapped_lines) { - if (line.width <= size.x) { + if (line.width <= max_content_size.width) { wrapped_lines.push_back(std::move(line)); } else { line_t* current_line = &wrapped_lines.emplace_back(); @@ -457,7 +632,7 @@ std::vector GUITextLabel::wrap_lines(std::vector& for (segment_t& segment : line.segments) { const real_t segment_width = get_segment_width(segment); - if (current_line->width + segment_width <= size.x) { + if (current_line->width + segment_width <= max_content_size.width) { // Segement on current line current_line->segments.emplace_back(std::move(segment)); current_line->width += segment_width; @@ -473,7 +648,7 @@ std::vector GUITextLabel::wrap_lines(std::vector& 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) { + if (current_line->width + whole_segment_width > max_content_size.width) { String new_segment_string; real_t new_segment_width = 0.0_real; @@ -483,7 +658,7 @@ std::vector GUITextLabel::wrap_lines(std::vector& 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) { + if (current_line->width + width <= max_content_size.width) { new_segment_string = std::move(substring); new_segment_width = width; last_marker_pos = marker_pos + SPACE_MARKER.length(); @@ -524,10 +699,15 @@ std::vector GUITextLabel::wrap_lines(std::vector& } } - if (wrapped_lines.size() > 1 && wrapped_lines.size() * font->get_height(font->get_fixed_size()) > size.y) { + const auto is_over_max_height = [this, &wrapped_lines, &max_content_size]() -> bool { + return wrapped_lines.size() > 1 + && wrapped_lines.size() * font->get_height(font_size) > max_content_size.height; + }; + + if (is_over_max_height()) { do { wrapped_lines.pop_back(); - } while (wrapped_lines.size() > 1 && wrapped_lines.size() * font->get_height(font->get_fixed_size()) > size.y); + } while (is_over_max_height()); static const String ELLIPSIS = "..."; const real_t ellipsis_width = get_string_width(ELLIPSIS); @@ -535,7 +715,7 @@ std::vector GUITextLabel::wrap_lines(std::vector& 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) { + while (last_line.segments.size() > 0 && last_line.width + ellipsis_width > max_content_size.width) { if (string_segment_t* string_segment = std::get_if(&last_line.segments.back())) { last_colour = string_segment->colour; @@ -562,3 +742,33 @@ std::vector GUITextLabel::wrap_lines(std::vector& return wrapped_lines; } + +void GUITextLabel::adjust_to_content_size() { + if (auto_adjust_to_content_size) { + adjusted_rect = {}; + + for (line_t const& line : lines) { + if (adjusted_rect.size.width < line.width) { + adjusted_rect.size.width = line.width; + } + } + + adjusted_rect.size.height = lines.size() * font->get_height(font_size); + + adjusted_rect.size += 2 * border_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_CENTER: { + adjusted_rect.position.x = (max_size.width - adjusted_rect.size.width + 1.0_real) / 2.0_real; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + adjusted_rect.position.x = max_size.width - adjusted_rect.size.width; + } break; + case HORIZONTAL_ALIGNMENT_LEFT: + default: + break; + } + } else { + adjusted_rect = { {}, max_size }; + } +} diff --git a/extension/src/openvic-extension/classes/GUITextLabel.hpp b/extension/src/openvic-extension/classes/GUITextLabel.hpp index aadb76f..7a1491c 100644 --- a/extension/src/openvic-extension/classes/GUITextLabel.hpp +++ b/extension/src/openvic-extension/classes/GUITextLabel.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -18,15 +19,19 @@ namespace OpenVic { godot::String PROPERTY(text); godot::Dictionary PROPERTY(substitution_dict); - godot::HorizontalAlignment PROPERTY(alignment); - godot::Vector2 border_size; - - godot::Ref font; - godot::Color default_colour; + godot::HorizontalAlignment PROPERTY(horizontal_alignment); + godot::Size2 PROPERTY(max_size); // Actual max size is max_size - 2 * border_size + godot::Size2 PROPERTY(border_size); // The padding between the Nodes bounding box and the text within it + godot::Rect2 PROPERTY(adjusted_rect); // Offset + size after adjustment to fit content size + bool PROPERTY_CUSTOM_PREFIX(auto_adjust_to_content_size, will); + + godot::Ref font; + int32_t PROPERTY(font_size); + godot::Color PROPERTY(default_colour); GFX::Font::colour_codes_t const* colour_codes; godot::Ref currency_texture; - godot::Ref PROPERTY(background); + godot::Ref background; struct string_segment_t { godot::String text; @@ -54,24 +59,38 @@ namespace OpenVic { /* Reset gui_text to nullptr and reset current text. */ void clear(); - + /* Return the name of the GUI::Text, or an empty String if it's null. */ + godot::String get_gui_text_name() const; /* Set the 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; - void set_text(godot::String const& new_text); + void add_substitution(godot::String const& key, godot::String const& value); void set_substitution_dict(godot::Dictionary const& new_substitution_dict); void clear_substitutions(); - private: - godot::Vector2 get_content_max_size() const; + void set_horizontal_alignment(godot::HorizontalAlignment new_horizontal_alignment); + void set_max_size(godot::Size2 new_max_size); + void set_border_size(godot::Size2 new_border_size); + void set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size); + + godot::Ref get_font() const; + void set_font(godot::Ref const& new_font); + godot::Error set_font_file(godot::Ref const& new_font_file); + godot::Error set_font_size(int32_t new_font_size); + void set_default_colour(godot::Color const& new_default_colour); - godot::Error _update_font(GFX::Font::colour_codes_t const* override_colour_codes); + godot::Ref get_currency_texture() const; + + godot::Ref get_background() const; + void set_background_texture(godot::Ref const& new_texture); + void set_background_stylebox(godot::Ref const& new_stylebox_texture); + + private: + void update_stylebox_border_size(); real_t get_string_width(godot::String const& string) const; real_t get_segment_width(segment_t const& segment) const; @@ -92,5 +111,6 @@ namespace OpenVic { godot::String const& string, godot::Color const& colour, line_t& line ) const; std::vector wrap_lines(std::vector& unwrapped_lines) const; + void adjust_to_content_size(); }; } -- cgit v1.2.3-56-ga3b1 From d7672f406406eea46625bc725690651f28211e19 Mon Sep 17 00:00:00 2001 From: hop311 Date: Mon, 26 Aug 2024 23:54:19 +0100 Subject: Rename GUITextLabel to GUILabel --- .../src/openvic-extension/classes/GUILabel.cpp | 774 +++++++++++++++++++++ .../src/openvic-extension/classes/GUILabel.hpp | 116 +++ .../src/openvic-extension/classes/GUINode.cpp | 2 +- .../src/openvic-extension/classes/GUINode.hpp | 6 +- .../src/openvic-extension/classes/GUITextLabel.cpp | 774 --------------------- .../src/openvic-extension/classes/GUITextLabel.hpp | 116 --- extension/src/openvic-extension/register_types.cpp | 4 +- .../src/openvic-extension/utility/UITools.cpp | 16 +- .../NationManagementScreen/BudgetMenu.gd | 96 +-- .../NationManagementScreen/PopulationMenu.gd | 42 +- game/src/Game/GameSession/ProvinceOverviewPanel.gd | 62 +- game/src/Game/GameSession/Topbar.gd | 108 +-- 12 files changed, 1058 insertions(+), 1058 deletions(-) create mode 100644 extension/src/openvic-extension/classes/GUILabel.cpp create mode 100644 extension/src/openvic-extension/classes/GUILabel.hpp delete mode 100644 extension/src/openvic-extension/classes/GUITextLabel.cpp delete mode 100644 extension/src/openvic-extension/classes/GUITextLabel.hpp (limited to 'extension/src/openvic-extension') diff --git a/extension/src/openvic-extension/classes/GUILabel.cpp b/extension/src/openvic-extension/classes/GUILabel.cpp new file mode 100644 index 0000000..9fd6b60 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUILabel.cpp @@ -0,0 +1,774 @@ +#include "GUILabel.hpp" + +#include +#include +#include + +#include "openvic-extension/singletons/AssetManager.hpp" +#include "openvic-extension/utility/ClassBindings.hpp" +#include "openvic-extension/utility/Utilities.hpp" + +using namespace OpenVic; +using namespace godot; +using namespace OpenVic::Utilities::literals; + +static constexpr int32_t DEFAULT_FONT_SIZE = 16; + +void GUILabel::_bind_methods() { + OV_BIND_METHOD(GUILabel::clear); + OV_BIND_METHOD(GUILabel::get_gui_text_name); + + OV_BIND_METHOD(GUILabel::get_text); + OV_BIND_METHOD(GUILabel::set_text, { "new_text" }); + + OV_BIND_METHOD(GUILabel::get_substitution_dict); + OV_BIND_METHOD(GUILabel::add_substitution, { "key", "value" }); + OV_BIND_METHOD(GUILabel::set_substitution_dict, { "new_substitution_dict" }); + OV_BIND_METHOD(GUILabel::clear_substitutions); + + OV_BIND_METHOD(GUILabel::get_horizontal_alignment); + OV_BIND_METHOD(GUILabel::set_horizontal_alignment, { "new_horizontal_alignment" }); + OV_BIND_METHOD(GUILabel::get_max_size); + OV_BIND_METHOD(GUILabel::set_max_size, { "new_max_size" }); + OV_BIND_METHOD(GUILabel::get_border_size); + OV_BIND_METHOD(GUILabel::set_border_size, { "new_border_size" }); + OV_BIND_METHOD(GUILabel::get_adjusted_rect); + OV_BIND_METHOD(GUILabel::will_auto_adjust_to_content_size); + OV_BIND_METHOD(GUILabel::set_auto_adjust_to_content_size, { "new_auto_adjust_to_content_size" }); + + OV_BIND_METHOD(GUILabel::get_font); + OV_BIND_METHOD(GUILabel::set_font, { "new_font" }); + OV_BIND_METHOD(GUILabel::set_font_file, { "new_font_file" }); + OV_BIND_METHOD(GUILabel::get_font_size); + OV_BIND_METHOD(GUILabel::set_font_size, { "new_font_size" }); + OV_BIND_METHOD(GUILabel::get_default_colour); + OV_BIND_METHOD(GUILabel::set_default_colour, { "new_default_colour" }); + OV_BIND_METHOD(GUILabel::get_currency_texture); + + OV_BIND_METHOD(GUILabel::get_background); + OV_BIND_METHOD(GUILabel::set_background_texture, { "new_texture" }); + OV_BIND_METHOD(GUILabel::set_background_stylebox, { "new_stylebox_texture" }); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "substitution_dict"), "set_substitution_dict", "get_substitution_dict"); + ADD_PROPERTY( + PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Centre,Right,Fill"), + "set_horizontal_alignment", "get_horizontal_alignment" + ); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size"); + ADD_PROPERTY( + PropertyInfo(Variant::VECTOR2, "border_size", PROPERTY_HINT_NONE, "suffix:px"), "set_border_size", "get_border_size" + ); + ADD_PROPERTY( + PropertyInfo(Variant::RECT2, "adjusted_rect", PROPERTY_HINT_NONE, "suffix:px"), "", "get_adjusted_rect" + ); + ADD_PROPERTY( + PropertyInfo(Variant::BOOL, "auto_adjust_to_content_size"), "set_auto_adjust_to_content_size", + "will_auto_adjust_to_content_size" + ); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_NONE, "suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_colour"), "set_default_colour", "get_default_colour"); + ADD_PROPERTY( + PropertyInfo(Variant::OBJECT, "currency_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "", "get_currency_texture" + ); + ADD_PROPERTY( + PropertyInfo(Variant::OBJECT, "background", PROPERTY_HINT_RESOURCE_TYPE, "StyleBoxTexture"), "set_background_stylebox", + "get_background" + ); +} + +void GUILabel::_notification(int what) { + switch (what) { + case NOTIFICATION_RESIZED: + case NOTIFICATION_TRANSLATION_CHANGED: { + _queue_line_update(); + } break; + case NOTIFICATION_DRAW: { + const RID ci = get_canvas_item(); + + if (background.is_valid()) { + draw_style_box(background, adjusted_rect); + } + + if (font.is_null()) { + return; + } + + // Starting offset needed + static const Vector2 base_offset { 1.0_real, -1.0_real }; + const Vector2 offset = base_offset + adjusted_rect.position + border_size; + Vector2 position = offset; + + for (line_t const& line : lines) { + position.x = offset.x; + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_CENTER: { + position.x += (adjusted_rect.size.width - 2 * border_size.width - line.width + 1.0_real) / 2.0_real; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + position.x += adjusted_rect.size.width - 2 * border_size.width - line.width; + } break; + case HORIZONTAL_ALIGNMENT_LEFT: + default: + break; + } + + position.y += font->get_ascent(font_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_size, + string_segment->colour + ); + position.x += string_segment->width; + } + } + + position.y += font->get_descent(font_size); + } + + } break; + } +} + +GUILabel::GUILabel() + : gui_text { nullptr }, + text {}, + substitution_dict {}, + horizontal_alignment { HORIZONTAL_ALIGNMENT_LEFT }, + max_size {}, + border_size {}, + adjusted_rect {}, + auto_adjust_to_content_size { false }, + font {}, + font_size { DEFAULT_FONT_SIZE }, + default_colour {}, + colour_codes { nullptr }, + currency_texture {}, + background {}, + lines {}, + line_update_queued { false } {} + +void GUILabel::clear() { + gui_text = nullptr; + + text = String {}; + substitution_dict.clear(); + horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; + max_size = {}; + border_size = {}; + adjusted_rect = {}; + auto_adjust_to_content_size = false; + + font.unref(); + font_size = DEFAULT_FONT_SIZE; + default_colour = {}; + colour_codes = nullptr; + currency_texture.unref(); + + background.unref(); + lines.clear(); + + line_update_queued = false; + + queue_redraw(); +} + +String GUILabel::get_gui_text_name() const { + return gui_text != nullptr ? Utilities::std_to_godot_string(gui_text->get_name()) : String {}; +} + +Error GUILabel::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; + } + + if (new_gui_text == nullptr) { + clear(); + return OK; + } + + gui_text = new_gui_text; + + set_text(Utilities::std_to_godot_string(gui_text->get_text())); + + using enum GUI::AlignedElement::format_t; + static const ordered_map format_map { + { left, HORIZONTAL_ALIGNMENT_LEFT }, + { centre, HORIZONTAL_ALIGNMENT_CENTER }, + { right, HORIZONTAL_ALIGNMENT_RIGHT } + }; + const decltype(format_map)::const_iterator it = format_map.find(gui_text->get_format()); + set_horizontal_alignment(it != format_map.end() ? it->second : HORIZONTAL_ALIGNMENT_LEFT); + + set_max_size(Utilities::to_godot_fvec2(gui_text->get_max_size())); + set_border_size(Utilities::to_godot_fvec2(gui_text->get_border_size())); + + colour_codes = override_colour_codes != nullptr ? override_colour_codes : &gui_text->get_font()->get_colour_codes(); + set_default_colour(Utilities::to_godot_color(gui_text->get_font()->get_colour())); + + font.unref(); + font_size = DEFAULT_FONT_SIZE; + currency_texture.unref(); + background.unref(); + + Error err = OK; + + AssetManager* asset_manager = AssetManager::get_singleton(); + if (asset_manager != nullptr) { + const StringName font_filepath = Utilities::std_to_godot_string(gui_text->get_font()->get_fontname()); + Ref font_file = asset_manager->get_font(font_filepath); + if (font_file.is_valid()) { + if (set_font_file(font_file) != OK) { + err = FAILED; + } + } else { + UtilityFunctions::push_error("Failed to load font \"", font_filepath, "\" for GUILabel"); + err = FAILED; + } + + if (!gui_text->get_texture_file().empty()) { + const StringName texture_path = Utilities::std_to_godot_string(gui_text->get_texture_file()); + Ref texture = asset_manager->get_texture(texture_path); + if (texture.is_valid()) { + set_background_texture(texture); + } else { + UtilityFunctions::push_error("Failed to load texture \"", texture_path, "\" for GUILabel ", get_name()); + err = FAILED; + } + } + } else { + UtilityFunctions::push_error("Failed to get AssetManager singleton for GUILabel"); + err = FAILED; + } + + _queue_line_update(); + + return err; +} + +void GUILabel::set_text(String const& new_text) { + if (text != new_text) { + text = new_text; + + _queue_line_update(); + } +} + +void GUILabel::add_substitution(String const& key, String const& value) { + Variant& existing_value = substitution_dict[key]; + if (existing_value != value) { + existing_value = value; + + _queue_line_update(); + } +} + +void GUILabel::set_substitution_dict(Dictionary const& new_substitution_dict) { + substitution_dict = new_substitution_dict; + _queue_line_update(); +} + +void GUILabel::clear_substitutions() { + if (!substitution_dict.is_empty()) { + substitution_dict.clear(); + + _queue_line_update(); + } +} + +void GUILabel::set_horizontal_alignment(HorizontalAlignment new_horizontal_alignment) { + if (horizontal_alignment != new_horizontal_alignment) { + horizontal_alignment = new_horizontal_alignment; + + _queue_line_update(); + } +} + +void GUILabel::set_max_size(Size2 new_max_size) { + if (max_size != new_max_size) { + max_size = new_max_size; + + set_custom_minimum_size(max_size); + set_size(max_size); + + _queue_line_update(); + } +} + +void GUILabel::set_border_size(Size2 new_border_size) { + if (border_size != new_border_size) { + border_size = new_border_size; + + update_stylebox_border_size(); + + _queue_line_update(); + } +} + +void GUILabel::set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size) { + if (auto_adjust_to_content_size != new_auto_adjust_to_content_size) { + auto_adjust_to_content_size = new_auto_adjust_to_content_size; + + adjust_to_content_size(); + + queue_redraw(); + } +} + +Ref GUILabel::get_font() const { + return font; +} + +void GUILabel::set_font(Ref const& new_font) { + font = new_font; + + _queue_line_update(); +} + +Error GUILabel::set_font_file(Ref const& new_font_file) { + ERR_FAIL_NULL_V(new_font_file, FAILED); + + set_font(new_font_file); + + return set_font_size(new_font_file->get_fixed_size()); +} + +Error GUILabel::set_font_size(int32_t new_font_size) { + font_size = new_font_size; + + _queue_line_update(); + + AssetManager* asset_manager = AssetManager::get_singleton(); + ERR_FAIL_NULL_V_MSG(asset_manager, FAILED, "Failed to get AssetManager singleton for GUILabel"); + + currency_texture = asset_manager->get_currency_texture(font_size); + ERR_FAIL_NULL_V(currency_texture, FAILED); + + return OK; +} + +void GUILabel::set_default_colour(Color const& new_default_colour) { + if (default_colour != new_default_colour) { + default_colour = new_default_colour; + _queue_line_update(); + } +} + +Ref GUILabel::get_currency_texture() const { + return currency_texture; +} + +Ref GUILabel::get_background() const { + return background; +} + +void GUILabel::set_background_texture(Ref const& new_texture) { + Ref new_background; + + if (new_texture.is_valid()) { + new_background.instantiate(); + ERR_FAIL_NULL(new_background); + + new_background->set_texture(new_texture); + } + + set_background_stylebox(new_background); +} + +void GUILabel::set_background_stylebox(Ref const& new_stylebox_texture) { + if (background != new_stylebox_texture) { + background = new_stylebox_texture; + update_stylebox_border_size(); + queue_redraw(); + } +} + +void GUILabel::update_stylebox_border_size() { + if (background.is_valid()) { + background->set_texture_margin(SIDE_LEFT, border_size.width); + background->set_texture_margin(SIDE_RIGHT, border_size.width); + background->set_texture_margin(SIDE_TOP, border_size.height); + background->set_texture_margin(SIDE_BOTTOM, border_size.height); + } +} + +real_t GUILabel::get_string_width(String const& string) const { + return font->get_string_size(string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; +} + +real_t GUILabel::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 GUILabel::_queue_line_update() { + if (!line_update_queued) { + line_update_queued = true; + + callable_mp(this, &GUILabel::_update_lines).call_deferred(); + } +} + +void GUILabel::_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; + + 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); + + adjust_to_content_size(); + + queue_redraw(); +} + +String GUILabel::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) { + marker_end_pos = base_text.length(); + } + + 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 {}); + + // Use the un-substituted key if no value is found or the value is empty + result += value.is_empty() ? key : is_auto_translating() ? tr(value) : value; + + start_pos = marker_end_pos + SUBSTITUTION_MARKER.length(); + } + + if (start_pos < base_text.length()) { + result += base_text.substr(start_pos); + } + + return result; +} + +std::pair GUILabel::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 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 GUILabel::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); + } + } + ++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; + } + current_colour = new_colour; + } + } + } + + if (section_start < display_text.length()) { + separate_lines(display_text.substr(section_start), current_colour, unwrapped_lines); + } + + return unwrapped_lines; +} + +void GUILabel::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(); + } + + if (start_pos < string.length()) { + separate_currency_segments(string.substr(start_pos), colour, unwrapped_lines.back()); + } +} + +void GUILabel::separate_currency_segments( + String const& string, Color const& colour, line_t& line +) const { + static const String CURRENCY_MARKER = String::chr(0xA4); // ¤ + + 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; + }; + + int64_t start_pos = 0; + int64_t marker_pos; + + const real_t currency_width = currency_texture.is_valid() ? currency_texture->get_width() : 0.0_real; + + 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 GUILabel::wrap_lines(std::vector& unwrapped_lines) const { + std::vector wrapped_lines; + + const Size2 max_content_size = max_size - 2 * border_size; + + for (line_t& line : unwrapped_lines) { + if (line.width <= max_content_size.width) { + 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 <= max_content_size.width) { + // 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 > max_content_size.width) { + 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 <= max_content_size.width) { + 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; + } + } + 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; + } + } + } + } + + const auto is_over_max_height = [this, &wrapped_lines, &max_content_size]() -> bool { + return wrapped_lines.size() > 1 + && wrapped_lines.size() * font->get_height(font_size) > max_content_size.height; + }; + + if (is_over_max_height()) { + do { + wrapped_lines.pop_back(); + } while (is_over_max_height()); + + 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 > max_content_size.width) { + 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 { + last_line.width -= string_segment->width; + last_line.segments.pop_back(); + } + } else { + last_line.width -= currency_texture->get_width(); + last_line.segments.pop_back(); + } + } + + last_line.segments.push_back(string_segment_t { ELLIPSIS, last_colour, ellipsis_width }); + last_line.width += ellipsis_width; + } + + return wrapped_lines; +} + +void GUILabel::adjust_to_content_size() { + if (auto_adjust_to_content_size) { + adjusted_rect = {}; + + for (line_t const& line : lines) { + if (adjusted_rect.size.width < line.width) { + adjusted_rect.size.width = line.width; + } + } + + adjusted_rect.size.height = lines.size() * font->get_height(font_size); + + adjusted_rect.size += 2 * border_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_CENTER: { + adjusted_rect.position.x = (max_size.width - adjusted_rect.size.width + 1.0_real) / 2.0_real; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + adjusted_rect.position.x = max_size.width - adjusted_rect.size.width; + } break; + case HORIZONTAL_ALIGNMENT_LEFT: + default: + break; + } + } else { + adjusted_rect = { {}, max_size }; + } +} diff --git a/extension/src/openvic-extension/classes/GUILabel.hpp b/extension/src/openvic-extension/classes/GUILabel.hpp new file mode 100644 index 0000000..e0982b2 --- /dev/null +++ b/extension/src/openvic-extension/classes/GUILabel.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "openvic-extension/classes/GFXSpriteTexture.hpp" + +namespace OpenVic { + class GUILabel : public godot::Control { + GDCLASS(GUILabel, godot::Control) + + using colour_instructions_t = std::vector>; + + GUI::Text const* PROPERTY(gui_text); + + godot::String PROPERTY(text); + godot::Dictionary PROPERTY(substitution_dict); + godot::HorizontalAlignment PROPERTY(horizontal_alignment); + godot::Size2 PROPERTY(max_size); // Actual max size is max_size - 2 * border_size + godot::Size2 PROPERTY(border_size); // The padding between the Nodes bounding box and the text within it + godot::Rect2 PROPERTY(adjusted_rect); // Offset + size after adjustment to fit content size + bool PROPERTY_CUSTOM_PREFIX(auto_adjust_to_content_size, will); + + godot::Ref font; + int32_t PROPERTY(font_size); + godot::Color PROPERTY(default_colour); + GFX::Font::colour_codes_t const* colour_codes; + godot::Ref currency_texture; + + godot::Ref 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 {}; + }; + + std::vector lines; + + bool line_update_queued; + + protected: + static void _bind_methods(); + + void _notification(int what); + + public: + GUILabel(); + + /* Reset gui_text to nullptr and reset current text. */ + void clear(); + /* Return the name of the GUI::Text, or an empty String if it's null. */ + godot::String get_gui_text_name() const; + /* Set the GUI::Text. */ + godot::Error set_gui_text( + GUI::Text const* new_gui_text, GFX::Font::colour_codes_t const* override_colour_codes = nullptr + ); + + void set_text(godot::String const& new_text); + + void add_substitution(godot::String const& key, godot::String const& value); + void set_substitution_dict(godot::Dictionary const& new_substitution_dict); + void clear_substitutions(); + + void set_horizontal_alignment(godot::HorizontalAlignment new_horizontal_alignment); + void set_max_size(godot::Size2 new_max_size); + void set_border_size(godot::Size2 new_border_size); + void set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size); + + godot::Ref get_font() const; + void set_font(godot::Ref const& new_font); + godot::Error set_font_file(godot::Ref const& new_font_file); + godot::Error set_font_size(int32_t new_font_size); + void set_default_colour(godot::Color const& new_default_colour); + + godot::Ref get_currency_texture() const; + + godot::Ref get_background() const; + void set_background_texture(godot::Ref const& new_texture); + void set_background_stylebox(godot::Ref const& new_stylebox_texture); + + private: + void update_stylebox_border_size(); + 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; + void adjust_to_content_size(); + }; +} diff --git a/extension/src/openvic-extension/classes/GUINode.cpp b/extension/src/openvic-extension/classes/GUINode.cpp index 4d7d33e..25ef821 100644 --- a/extension/src/openvic-extension/classes/GUINode.cpp +++ b/extension/src/openvic-extension/classes/GUINode.cpp @@ -40,7 +40,7 @@ using namespace OpenVic; #define APPLY_TO_CHILD_TYPES(F) \ F(Button, button) \ - F(GUITextLabel, gui_text_label) \ + F(GUILabel, gui_label) \ F(Panel, panel) \ F(TextureProgressBar, progress_bar) \ F(TextureRect, texture_rect) \ diff --git a/extension/src/openvic-extension/classes/GUINode.hpp b/extension/src/openvic-extension/classes/GUINode.hpp index 6168e7e..73ca92b 100644 --- a/extension/src/openvic-extension/classes/GUINode.hpp +++ b/extension/src/openvic-extension/classes/GUINode.hpp @@ -21,10 +21,10 @@ #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" #include "openvic-extension/classes/GFXSpriteTexture.hpp" +#include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" -#include "openvic-extension/classes/GUITextLabel.hpp" namespace OpenVic { class GUINode : public godot::Control { @@ -52,7 +52,7 @@ namespace OpenVic { static godot::Vector2 get_gui_position(godot::String const& gui_scene, godot::String const& gui_position); static godot::Button* get_button_from_node(godot::Node* node); - static GUITextLabel* get_gui_text_label_from_node(godot::Node* node); + static GUILabel* get_gui_label_from_node(godot::Node* node); static godot::Panel* get_panel_from_node(godot::Node* node); static godot::TextureProgressBar* get_progress_bar_from_node(godot::Node* node); static godot::TextureRect* get_texture_rect_from_node(godot::Node* node); @@ -62,7 +62,7 @@ namespace OpenVic { static godot::LineEdit* get_line_edit_from_node(godot::Node* node); godot::Button* get_button_from_nodepath(godot::NodePath const& path) const; - GUITextLabel* get_gui_text_label_from_nodepath(godot::NodePath const& path) const; + GUILabel* get_gui_label_from_nodepath(godot::NodePath const& path) const; godot::Panel* get_panel_from_nodepath(godot::NodePath const& path) const; godot::TextureProgressBar* get_progress_bar_from_nodepath(godot::NodePath const& path) const; godot::TextureRect* get_texture_rect_from_nodepath(godot::NodePath const& path) const; diff --git a/extension/src/openvic-extension/classes/GUITextLabel.cpp b/extension/src/openvic-extension/classes/GUITextLabel.cpp deleted file mode 100644 index ec8f158..0000000 --- a/extension/src/openvic-extension/classes/GUITextLabel.cpp +++ /dev/null @@ -1,774 +0,0 @@ -#include "GUITextLabel.hpp" - -#include -#include -#include - -#include "openvic-extension/singletons/AssetManager.hpp" -#include "openvic-extension/utility/ClassBindings.hpp" -#include "openvic-extension/utility/Utilities.hpp" - -using namespace OpenVic; -using namespace godot; -using namespace OpenVic::Utilities::literals; - -static constexpr int32_t DEFAULT_FONT_SIZE = 16; - -void GUITextLabel::_bind_methods() { - OV_BIND_METHOD(GUITextLabel::clear); - OV_BIND_METHOD(GUITextLabel::get_gui_text_name); - - OV_BIND_METHOD(GUITextLabel::get_text); - OV_BIND_METHOD(GUITextLabel::set_text, { "new_text" }); - - OV_BIND_METHOD(GUITextLabel::get_substitution_dict); - OV_BIND_METHOD(GUITextLabel::add_substitution, { "key", "value" }); - OV_BIND_METHOD(GUITextLabel::set_substitution_dict, { "new_substitution_dict" }); - OV_BIND_METHOD(GUITextLabel::clear_substitutions); - - OV_BIND_METHOD(GUITextLabel::get_horizontal_alignment); - OV_BIND_METHOD(GUITextLabel::set_horizontal_alignment, { "new_horizontal_alignment" }); - OV_BIND_METHOD(GUITextLabel::get_max_size); - OV_BIND_METHOD(GUITextLabel::set_max_size, { "new_max_size" }); - OV_BIND_METHOD(GUITextLabel::get_border_size); - OV_BIND_METHOD(GUITextLabel::set_border_size, { "new_border_size" }); - OV_BIND_METHOD(GUITextLabel::get_adjusted_rect); - OV_BIND_METHOD(GUITextLabel::will_auto_adjust_to_content_size); - OV_BIND_METHOD(GUITextLabel::set_auto_adjust_to_content_size, { "new_auto_adjust_to_content_size" }); - - OV_BIND_METHOD(GUITextLabel::get_font); - OV_BIND_METHOD(GUITextLabel::set_font, { "new_font" }); - OV_BIND_METHOD(GUITextLabel::set_font_file, { "new_font_file" }); - OV_BIND_METHOD(GUITextLabel::get_font_size); - OV_BIND_METHOD(GUITextLabel::set_font_size, { "new_font_size" }); - OV_BIND_METHOD(GUITextLabel::get_default_colour); - OV_BIND_METHOD(GUITextLabel::set_default_colour, { "new_default_colour" }); - OV_BIND_METHOD(GUITextLabel::get_currency_texture); - - OV_BIND_METHOD(GUITextLabel::get_background); - OV_BIND_METHOD(GUITextLabel::set_background_texture, { "new_texture" }); - OV_BIND_METHOD(GUITextLabel::set_background_stylebox, { "new_stylebox_texture" }); - - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "substitution_dict"), "set_substitution_dict", "get_substitution_dict"); - ADD_PROPERTY( - PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Centre,Right,Fill"), - "set_horizontal_alignment", "get_horizontal_alignment" - ); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size"); - ADD_PROPERTY( - PropertyInfo(Variant::VECTOR2, "border_size", PROPERTY_HINT_NONE, "suffix:px"), "set_border_size", "get_border_size" - ); - ADD_PROPERTY( - PropertyInfo(Variant::RECT2, "adjusted_rect", PROPERTY_HINT_NONE, "suffix:px"), "", "get_adjusted_rect" - ); - ADD_PROPERTY( - PropertyInfo(Variant::BOOL, "auto_adjust_to_content_size"), "set_auto_adjust_to_content_size", - "will_auto_adjust_to_content_size" - ); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_NONE, "suffix:px"), "set_font_size", "get_font_size"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "default_colour"), "set_default_colour", "get_default_colour"); - ADD_PROPERTY( - PropertyInfo(Variant::OBJECT, "currency_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "", "get_currency_texture" - ); - ADD_PROPERTY( - PropertyInfo(Variant::OBJECT, "background", PROPERTY_HINT_RESOURCE_TYPE, "StyleBoxTexture"), "set_background_stylebox", - "get_background" - ); -} - -void GUITextLabel::_notification(int what) { - switch (what) { - case NOTIFICATION_RESIZED: - case NOTIFICATION_TRANSLATION_CHANGED: { - _queue_line_update(); - } break; - case NOTIFICATION_DRAW: { - const RID ci = get_canvas_item(); - - if (background.is_valid()) { - draw_style_box(background, adjusted_rect); - } - - if (font.is_null()) { - return; - } - - // Starting offset needed - static const Vector2 base_offset { 1.0_real, -1.0_real }; - const Vector2 offset = base_offset + adjusted_rect.position + border_size; - Vector2 position = offset; - - for (line_t const& line : lines) { - position.x = offset.x; - switch (horizontal_alignment) { - case HORIZONTAL_ALIGNMENT_CENTER: { - position.x += (adjusted_rect.size.width - 2 * border_size.width - line.width + 1.0_real) / 2.0_real; - } break; - case HORIZONTAL_ALIGNMENT_RIGHT: { - position.x += adjusted_rect.size.width - 2 * border_size.width - line.width; - } break; - case HORIZONTAL_ALIGNMENT_LEFT: - default: - break; - } - - position.y += font->get_ascent(font_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_size, - string_segment->colour - ); - position.x += string_segment->width; - } - } - - position.y += font->get_descent(font_size); - } - - } break; - } -} - -GUITextLabel::GUITextLabel() - : gui_text { nullptr }, - text {}, - substitution_dict {}, - horizontal_alignment { HORIZONTAL_ALIGNMENT_LEFT }, - max_size {}, - border_size {}, - adjusted_rect {}, - auto_adjust_to_content_size { false }, - font {}, - font_size { DEFAULT_FONT_SIZE }, - default_colour {}, - colour_codes { nullptr }, - currency_texture {}, - background {}, - lines {}, - line_update_queued { false } {} - -void GUITextLabel::clear() { - gui_text = nullptr; - - text = String {}; - substitution_dict.clear(); - horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; - max_size = {}; - border_size = {}; - adjusted_rect = {}; - auto_adjust_to_content_size = false; - - font.unref(); - font_size = DEFAULT_FONT_SIZE; - default_colour = {}; - colour_codes = nullptr; - currency_texture.unref(); - - background.unref(); - lines.clear(); - - line_update_queued = false; - - queue_redraw(); -} - -String GUITextLabel::get_gui_text_name() const { - return gui_text != nullptr ? Utilities::std_to_godot_string(gui_text->get_name()) : String {}; -} - -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; - } - - if (new_gui_text == nullptr) { - clear(); - return OK; - } - - gui_text = new_gui_text; - - set_text(Utilities::std_to_godot_string(gui_text->get_text())); - - using enum GUI::AlignedElement::format_t; - static const ordered_map format_map { - { left, HORIZONTAL_ALIGNMENT_LEFT }, - { centre, HORIZONTAL_ALIGNMENT_CENTER }, - { right, HORIZONTAL_ALIGNMENT_RIGHT } - }; - const decltype(format_map)::const_iterator it = format_map.find(gui_text->get_format()); - set_horizontal_alignment(it != format_map.end() ? it->second : HORIZONTAL_ALIGNMENT_LEFT); - - set_max_size(Utilities::to_godot_fvec2(gui_text->get_max_size())); - set_border_size(Utilities::to_godot_fvec2(gui_text->get_border_size())); - - colour_codes = override_colour_codes != nullptr ? override_colour_codes : &gui_text->get_font()->get_colour_codes(); - set_default_colour(Utilities::to_godot_color(gui_text->get_font()->get_colour())); - - font.unref(); - font_size = DEFAULT_FONT_SIZE; - currency_texture.unref(); - background.unref(); - - Error err = OK; - - AssetManager* asset_manager = AssetManager::get_singleton(); - if (asset_manager != nullptr) { - const StringName font_filepath = Utilities::std_to_godot_string(gui_text->get_font()->get_fontname()); - Ref font_file = asset_manager->get_font(font_filepath); - if (font_file.is_valid()) { - if (set_font_file(font_file) != OK) { - err = FAILED; - } - } else { - UtilityFunctions::push_error("Failed to load font \"", font_filepath, "\" for GUITextLabel"); - err = FAILED; - } - - if (!gui_text->get_texture_file().empty()) { - const StringName texture_path = Utilities::std_to_godot_string(gui_text->get_texture_file()); - Ref texture = asset_manager->get_texture(texture_path); - if (texture.is_valid()) { - set_background_texture(texture); - } else { - UtilityFunctions::push_error("Failed to load texture \"", texture_path, "\" for GUITextLabel ", get_name()); - err = FAILED; - } - } - } else { - UtilityFunctions::push_error("Failed to get AssetManager singleton for GUITextLabel"); - err = FAILED; - } - - _queue_line_update(); - - return err; -} - -void GUITextLabel::set_text(String const& new_text) { - if (text != new_text) { - text = new_text; - - _queue_line_update(); - } -} - -void GUITextLabel::add_substitution(String const& key, String const& value) { - Variant& existing_value = substitution_dict[key]; - if (existing_value != value) { - existing_value = value; - - _queue_line_update(); - } -} - -void GUITextLabel::set_substitution_dict(Dictionary const& new_substitution_dict) { - substitution_dict = new_substitution_dict; - _queue_line_update(); -} - -void GUITextLabel::clear_substitutions() { - if (!substitution_dict.is_empty()) { - substitution_dict.clear(); - - _queue_line_update(); - } -} - -void GUITextLabel::set_horizontal_alignment(HorizontalAlignment new_horizontal_alignment) { - if (horizontal_alignment != new_horizontal_alignment) { - horizontal_alignment = new_horizontal_alignment; - - _queue_line_update(); - } -} - -void GUITextLabel::set_max_size(Size2 new_max_size) { - if (max_size != new_max_size) { - max_size = new_max_size; - - set_custom_minimum_size(max_size); - set_size(max_size); - - _queue_line_update(); - } -} - -void GUITextLabel::set_border_size(Size2 new_border_size) { - if (border_size != new_border_size) { - border_size = new_border_size; - - update_stylebox_border_size(); - - _queue_line_update(); - } -} - -void GUITextLabel::set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size) { - if (auto_adjust_to_content_size != new_auto_adjust_to_content_size) { - auto_adjust_to_content_size = new_auto_adjust_to_content_size; - - adjust_to_content_size(); - - queue_redraw(); - } -} - -Ref GUITextLabel::get_font() const { - return font; -} - -void GUITextLabel::set_font(Ref const& new_font) { - font = new_font; - - _queue_line_update(); -} - -Error GUITextLabel::set_font_file(Ref const& new_font_file) { - ERR_FAIL_NULL_V(new_font_file, FAILED); - - set_font(new_font_file); - - return set_font_size(new_font_file->get_fixed_size()); -} - -Error GUITextLabel::set_font_size(int32_t new_font_size) { - font_size = new_font_size; - - _queue_line_update(); - - AssetManager* asset_manager = AssetManager::get_singleton(); - ERR_FAIL_NULL_V_MSG(asset_manager, FAILED, "Failed to get AssetManager singleton for GUITextLabel"); - - currency_texture = asset_manager->get_currency_texture(font_size); - ERR_FAIL_NULL_V(currency_texture, FAILED); - - return OK; -} - -void GUITextLabel::set_default_colour(Color const& new_default_colour) { - if (default_colour != new_default_colour) { - default_colour = new_default_colour; - _queue_line_update(); - } -} - -Ref GUITextLabel::get_currency_texture() const { - return currency_texture; -} - -Ref GUITextLabel::get_background() const { - return background; -} - -void GUITextLabel::set_background_texture(Ref const& new_texture) { - Ref new_background; - - if (new_texture.is_valid()) { - new_background.instantiate(); - ERR_FAIL_NULL(new_background); - - new_background->set_texture(new_texture); - } - - set_background_stylebox(new_background); -} - -void GUITextLabel::set_background_stylebox(Ref const& new_stylebox_texture) { - if (background != new_stylebox_texture) { - background = new_stylebox_texture; - update_stylebox_border_size(); - queue_redraw(); - } -} - -void GUITextLabel::update_stylebox_border_size() { - if (background.is_valid()) { - background->set_texture_margin(SIDE_LEFT, border_size.width); - background->set_texture_margin(SIDE_RIGHT, border_size.width); - background->set_texture_margin(SIDE_TOP, border_size.height); - background->set_texture_margin(SIDE_BOTTOM, border_size.height); - } -} - -real_t GUITextLabel::get_string_width(String const& string) const { - return font->get_string_size(string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; -} - -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::_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; - - 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); - - adjust_to_content_size(); - - 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) { - marker_end_pos = base_text.length(); - } - - 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 {}); - - // Use the un-substituted key if no value is found or the value is empty - result += value.is_empty() ? key : is_auto_translating() ? tr(value) : value; - - start_pos = marker_end_pos + SUBSTITUTION_MARKER.length(); - } - - if (start_pos < base_text.length()) { - 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 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); - } - } - ++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; - } - current_colour = new_colour; - } - } - } - - if (section_start < display_text.length()) { - separate_lines(display_text.substr(section_start), current_colour, unwrapped_lines); - } - - 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(); - } - - if (start_pos < string.length()) { - separate_currency_segments(string.substr(start_pos), colour, unwrapped_lines.back()); - } -} - -void GUITextLabel::separate_currency_segments( - String const& string, Color const& colour, line_t& line -) const { - static const String CURRENCY_MARKER = String::chr(0xA4); // ¤ - - 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; - }; - - int64_t start_pos = 0; - int64_t marker_pos; - - const real_t currency_width = currency_texture.is_valid() ? currency_texture->get_width() : 0.0_real; - - 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 max_content_size = max_size - 2 * border_size; - - for (line_t& line : unwrapped_lines) { - if (line.width <= max_content_size.width) { - 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 <= max_content_size.width) { - // 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 > max_content_size.width) { - 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 <= max_content_size.width) { - 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; - } - } - 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; - } - } - } - } - - const auto is_over_max_height = [this, &wrapped_lines, &max_content_size]() -> bool { - return wrapped_lines.size() > 1 - && wrapped_lines.size() * font->get_height(font_size) > max_content_size.height; - }; - - if (is_over_max_height()) { - do { - wrapped_lines.pop_back(); - } while (is_over_max_height()); - - 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 > max_content_size.width) { - 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 { - last_line.width -= string_segment->width; - last_line.segments.pop_back(); - } - } else { - last_line.width -= currency_texture->get_width(); - last_line.segments.pop_back(); - } - } - - last_line.segments.push_back(string_segment_t { ELLIPSIS, last_colour, ellipsis_width }); - last_line.width += ellipsis_width; - } - - return wrapped_lines; -} - -void GUITextLabel::adjust_to_content_size() { - if (auto_adjust_to_content_size) { - adjusted_rect = {}; - - for (line_t const& line : lines) { - if (adjusted_rect.size.width < line.width) { - adjusted_rect.size.width = line.width; - } - } - - adjusted_rect.size.height = lines.size() * font->get_height(font_size); - - adjusted_rect.size += 2 * border_size; - - switch (horizontal_alignment) { - case HORIZONTAL_ALIGNMENT_CENTER: { - adjusted_rect.position.x = (max_size.width - adjusted_rect.size.width + 1.0_real) / 2.0_real; - } break; - case HORIZONTAL_ALIGNMENT_RIGHT: { - adjusted_rect.position.x = max_size.width - adjusted_rect.size.width; - } break; - case HORIZONTAL_ALIGNMENT_LEFT: - default: - break; - } - } else { - adjusted_rect = { {}, max_size }; - } -} diff --git a/extension/src/openvic-extension/classes/GUITextLabel.hpp b/extension/src/openvic-extension/classes/GUITextLabel.hpp deleted file mode 100644 index 7a1491c..0000000 --- a/extension/src/openvic-extension/classes/GUITextLabel.hpp +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -#include "openvic-extension/classes/GFXSpriteTexture.hpp" - -namespace OpenVic { - class GUITextLabel : public godot::Control { - GDCLASS(GUITextLabel, godot::Control) - - using colour_instructions_t = std::vector>; - - GUI::Text const* PROPERTY(gui_text); - - godot::String PROPERTY(text); - godot::Dictionary PROPERTY(substitution_dict); - godot::HorizontalAlignment PROPERTY(horizontal_alignment); - godot::Size2 PROPERTY(max_size); // Actual max size is max_size - 2 * border_size - godot::Size2 PROPERTY(border_size); // The padding between the Nodes bounding box and the text within it - godot::Rect2 PROPERTY(adjusted_rect); // Offset + size after adjustment to fit content size - bool PROPERTY_CUSTOM_PREFIX(auto_adjust_to_content_size, will); - - godot::Ref font; - int32_t PROPERTY(font_size); - godot::Color PROPERTY(default_colour); - GFX::Font::colour_codes_t const* colour_codes; - godot::Ref currency_texture; - - godot::Ref 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 {}; - }; - - std::vector lines; - - bool line_update_queued; - - protected: - static void _bind_methods(); - - void _notification(int what); - - public: - GUITextLabel(); - - /* Reset gui_text to nullptr and reset current text. */ - void clear(); - /* Return the name of the GUI::Text, or an empty String if it's null. */ - godot::String get_gui_text_name() const; - /* Set the GUI::Text. */ - godot::Error set_gui_text( - GUI::Text const* new_gui_text, GFX::Font::colour_codes_t const* override_colour_codes = nullptr - ); - - void set_text(godot::String const& new_text); - - void add_substitution(godot::String const& key, godot::String const& value); - void set_substitution_dict(godot::Dictionary const& new_substitution_dict); - void clear_substitutions(); - - void set_horizontal_alignment(godot::HorizontalAlignment new_horizontal_alignment); - void set_max_size(godot::Size2 new_max_size); - void set_border_size(godot::Size2 new_border_size); - void set_auto_adjust_to_content_size(bool new_auto_adjust_to_content_size); - - godot::Ref get_font() const; - void set_font(godot::Ref const& new_font); - godot::Error set_font_file(godot::Ref const& new_font_file); - godot::Error set_font_size(int32_t new_font_size); - void set_default_colour(godot::Color const& new_default_colour); - - godot::Ref get_currency_texture() const; - - godot::Ref get_background() const; - void set_background_texture(godot::Ref const& new_texture); - void set_background_stylebox(godot::Ref const& new_stylebox_texture); - - private: - void update_stylebox_border_size(); - 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; - void adjust_to_content_size(); - }; -} diff --git a/extension/src/openvic-extension/register_types.cpp b/extension/src/openvic-extension/register_types.cpp index eb58cb0..bd50e34 100644 --- a/extension/src/openvic-extension/register_types.cpp +++ b/extension/src/openvic-extension/register_types.cpp @@ -6,11 +6,11 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUINode.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" -#include "openvic-extension/classes/GUITextLabel.hpp" #include "openvic-extension/classes/MapMesh.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/Checksum.hpp" @@ -76,11 +76,11 @@ void initialize_openvic_types(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); - ClassDB::register_class(); } void uninitialize_openvic_types(ModuleInitializationLevel p_level) { diff --git a/extension/src/openvic-extension/utility/UITools.cpp b/extension/src/openvic-extension/utility/UITools.cpp index 3fcc837..723fb24 100644 --- a/extension/src/openvic-extension/utility/UITools.cpp +++ b/extension/src/openvic-extension/utility/UITools.cpp @@ -15,10 +15,10 @@ #include "openvic-extension/classes/GFXSpriteTexture.hpp" #include "openvic-extension/classes/GFXMaskedFlagTexture.hpp" #include "openvic-extension/classes/GFXPieChartTexture.hpp" +#include "openvic-extension/classes/GUILabel.hpp" #include "openvic-extension/classes/GUIListBox.hpp" #include "openvic-extension/classes/GUIOverlappingElementsBox.hpp" #include "openvic-extension/classes/GUIScrollbar.hpp" -#include "openvic-extension/classes/GUITextLabel.hpp" #include "openvic-extension/singletons/AssetManager.hpp" #include "openvic-extension/singletons/GameSingleton.hpp" #include "openvic-extension/utility/Utilities.hpp" @@ -504,22 +504,22 @@ static bool generate_text(generate_gui_args_t&& args) { const String text_name = Utilities::std_to_godot_string(text.get_name()); - GUITextLabel* text_label = nullptr; - bool ret = new_control(text_label, text, args.name); - ERR_FAIL_NULL_V_MSG(text_label, false, vformat("Failed to create GUITextLabel for GUI text %s", text_name)); + GUILabel* gui_label = nullptr; + bool ret = new_control(gui_label, text, args.name); + ERR_FAIL_NULL_V_MSG(gui_label, false, vformat("Failed to create GUILabel for GUI text %s", text_name)); - text_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + gui_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); 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); + if (gui_label->set_gui_text(&text, override_colour_codes) != OK) { + UtilityFunctions::push_error("Error initialising GUILabel for GUI text ", text_name); ret = false; } - args.result = text_label; + args.result = gui_label; return ret; } diff --git a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd index 90df650..20eb198 100644 --- a/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/BudgetMenu.gd @@ -4,36 +4,36 @@ var _active : bool = false var _incVal : int = 0 # incremental value to see the UI update, replace later by real values # income -var _lower_class_label : GUITextLabel -var _middle_class_label : GUITextLabel -var _upper_class_label : GUITextLabel -var _gold_label : GUITextLabel -var _total_inc_label : GUITextLabel +var _lower_class_label : GUILabel +var _middle_class_label : GUILabel +var _upper_class_label : GUILabel +var _gold_label : GUILabel +var _total_inc_label : GUILabel # debt -var _national_bank_label : GUITextLabel -var _total_funds_label : GUITextLabel -var _debt_val_label : GUITextLabel -var _interest_val_label : GUITextLabel +var _national_bank_label : GUILabel +var _total_funds_label : GUILabel +var _debt_val_label : GUILabel +var _interest_val_label : GUILabel # costs -var _nat_stock_val_label : GUITextLabel -var _nat_stock_exp_label : GUITextLabel -var _mil_cost_val_label : GUITextLabel -var _overseas_cost_val_label : GUITextLabel -var _ind_sub_val_label : GUITextLabel -var _admin_efficiency_label : GUITextLabel -var _education_exp_label : GUITextLabel -var _administration_exp_label : GUITextLabel -var _social_exp_label : GUITextLabel -var _military_exp_label : GUITextLabel -var _total_exp_label : GUITextLabel +var _nat_stock_val_label : GUILabel +var _nat_stock_exp_label : GUILabel +var _mil_cost_val_label : GUILabel +var _overseas_cost_val_label : GUILabel +var _ind_sub_val_label : GUILabel +var _admin_efficiency_label : GUILabel +var _education_exp_label : GUILabel +var _administration_exp_label : GUILabel +var _social_exp_label : GUILabel +var _military_exp_label : GUILabel +var _total_exp_label : GUILabel # others -var _tariffs_percent_label : GUITextLabel -var _tariff_val_label : GUITextLabel -var _diplomatic_balance_label : GUITextLabel -var _balance_label : GUITextLabel +var _tariffs_percent_label : GUILabel +var _tariff_val_label : GUILabel +var _diplomatic_balance_label : GUILabel +var _balance_label : GUILabel var _lower_class_chart : GFXPieChartTexture var _middle_class_chart : GFXPieChartTexture @@ -55,33 +55,33 @@ func _ready() -> void: # labels # income - _lower_class_label = get_gui_text_label_from_nodepath(^"./country_budget/tax_0_inc") - _middle_class_label = get_gui_text_label_from_nodepath(^"./country_budget/tax_1_inc") - _upper_class_label = get_gui_text_label_from_nodepath(^"./country_budget/tax_2_inc") - _gold_label = get_gui_text_label_from_nodepath(^"./country_budget/gold_inc") - _total_inc_label = get_gui_text_label_from_nodepath(^"./country_budget/total_inc") + _lower_class_label = get_gui_label_from_nodepath(^"./country_budget/tax_0_inc") + _middle_class_label = get_gui_label_from_nodepath(^"./country_budget/tax_1_inc") + _upper_class_label = get_gui_label_from_nodepath(^"./country_budget/tax_2_inc") + _gold_label = get_gui_label_from_nodepath(^"./country_budget/gold_inc") + _total_inc_label = get_gui_label_from_nodepath(^"./country_budget/total_inc") # debt - _national_bank_label = get_gui_text_label_from_nodepath(^"./country_budget/national_bank_val") - _total_funds_label = get_gui_text_label_from_nodepath(^"./country_budget/total_funds_val") - _debt_val_label = get_gui_text_label_from_nodepath(^"./country_budget/debt_val") - _interest_val_label = get_gui_text_label_from_nodepath(^"./country_budget/interest_val") + _national_bank_label = get_gui_label_from_nodepath(^"./country_budget/national_bank_val") + _total_funds_label = get_gui_label_from_nodepath(^"./country_budget/total_funds_val") + _debt_val_label = get_gui_label_from_nodepath(^"./country_budget/debt_val") + _interest_val_label = get_gui_label_from_nodepath(^"./country_budget/interest_val") # costs - _nat_stock_val_label = get_gui_text_label_from_nodepath(^"./country_budget/nat_stock_val") - _nat_stock_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/nat_stock_est") - _mil_cost_val_label = get_gui_text_label_from_nodepath(^"./country_budget/mil_cost_val") - _overseas_cost_val_label = get_gui_text_label_from_nodepath(^"./country_budget/overseas_cost_val") - _ind_sub_val_label = get_gui_text_label_from_nodepath(^"./country_budget/ind_sub_val") - _admin_efficiency_label = get_gui_text_label_from_nodepath(^"./country_budget/admin_efficiency") - _education_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/exp_val_0") - _administration_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/exp_val_1") - _social_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/exp_val_2") - _military_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/exp_val_3") - _total_exp_label = get_gui_text_label_from_nodepath(^"./country_budget/total_exp") + _nat_stock_val_label = get_gui_label_from_nodepath(^"./country_budget/nat_stock_val") + _nat_stock_exp_label = get_gui_label_from_nodepath(^"./country_budget/nat_stock_est") + _mil_cost_val_label = get_gui_label_from_nodepath(^"./country_budget/mil_cost_val") + _overseas_cost_val_label = get_gui_label_from_nodepath(^"./country_budget/overseas_cost_val") + _ind_sub_val_label = get_gui_label_from_nodepath(^"./country_budget/ind_sub_val") + _admin_efficiency_label = get_gui_label_from_nodepath(^"./country_budget/admin_efficiency") + _education_exp_label = get_gui_label_from_nodepath(^"./country_budget/exp_val_0") + _administration_exp_label = get_gui_label_from_nodepath(^"./country_budget/exp_val_1") + _social_exp_label = get_gui_label_from_nodepath(^"./country_budget/exp_val_2") + _military_exp_label = get_gui_label_from_nodepath(^"./country_budget/exp_val_3") + _total_exp_label = get_gui_label_from_nodepath(^"./country_budget/total_exp") # others - _tariffs_percent_label = get_gui_text_label_from_nodepath(^"./country_budget/tariffs_percent") - _tariff_val_label = get_gui_text_label_from_nodepath(^"./country_budget/tariff_val") - _diplomatic_balance_label = get_gui_text_label_from_nodepath(^"./country_budget/diplomatic_balance") - _balance_label = get_gui_text_label_from_nodepath(^"./country_budget/balance") + _tariffs_percent_label = get_gui_label_from_nodepath(^"./country_budget/tariffs_percent") + _tariff_val_label = get_gui_label_from_nodepath(^"./country_budget/tariff_val") + _diplomatic_balance_label = get_gui_label_from_nodepath(^"./country_budget/diplomatic_balance") + _balance_label = get_gui_label_from_nodepath(^"./country_budget/balance") # sliders # income diff --git a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd index 92ae6e2..e078934 100644 --- a/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd +++ b/game/src/Game/GameSession/NationManagementScreen/PopulationMenu.gd @@ -14,8 +14,8 @@ var _province_list_types : Array[MenuSingleton.ProvinceListEntry] var _province_list_indices : PackedInt32Array var _province_list_panels : Array[Panel] var _province_list_button_icons : Array[GFXSpriteTexture] -var _province_list_name_labels : Array[GUITextLabel] -var _province_list_size_labels : Array[GUITextLabel] +var _province_list_name_labels : Array[GUILabel] +var _province_list_size_labels : Array[GUILabel] var _province_list_growth_icons : Array[GFXSpriteTexture] var _province_list_colony_buttons : Array[Button] var _province_list_national_focus_icons : Array[GFXSpriteTexture] @@ -33,19 +33,19 @@ var _pop_list_scrollbar : GUIScrollbar var _pop_list_scroll_index : int = 0 var _pop_list_rows : Array[Panel] -var _pop_list_size_labels : Array[GUITextLabel] +var _pop_list_size_labels : Array[GUILabel] var _pop_list_type_buttons : Array[Button] var _pop_list_type_icons : Array[GFXSpriteTexture] var _pop_list_producing_icons : Array[GFXSpriteTexture] -var _pop_list_culture_labels : Array[GUITextLabel] +var _pop_list_culture_labels : Array[GUILabel] var _pop_list_religion_icons : Array[GFXSpriteTexture] -var _pop_list_location_labels : Array[GUITextLabel] -var _pop_list_militancy_labels : Array[GUITextLabel] -var _pop_list_consciousness_labels : Array[GUITextLabel] +var _pop_list_location_labels : Array[GUILabel] +var _pop_list_militancy_labels : Array[GUILabel] +var _pop_list_consciousness_labels : Array[GUILabel] var _pop_list_ideology_charts : Array[GFXPieChartTexture] var _pop_list_issues_charts : Array[GFXPieChartTexture] var _pop_list_unemployment_progressbars : Array[TextureProgressBar] -var _pop_list_cash_labels : Array[GUITextLabel] +var _pop_list_cash_labels : Array[GUILabel] var _pop_list_life_needs_progressbars : Array[TextureProgressBar] var _pop_list_everyday_needs_progressbars : Array[TextureProgressBar] var _pop_list_luxury_needs_progressbars : Array[TextureProgressBar] @@ -58,7 +58,7 @@ var _pop_list_political_movement_icons : Array[GFXSpriteTexture] var _pop_list_national_movement_texture_rects : Array[TextureRect] var _pop_list_national_movement_flags : Array[GFXMaskedFlagTexture] var _pop_list_size_change_icons : Array[GFXSpriteTexture] -var _pop_list_literacy_labels : Array[GUITextLabel] +var _pop_list_literacy_labels : Array[GUILabel] func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -142,9 +142,9 @@ func _generate_province_list_row(index : int, type : MenuSingleton.ProvinceListE ) _province_list_button_icons[index] = GUINode.get_gfx_sprite_texture_from_node(base_button) - _province_list_name_labels[index] = GUINode.get_gui_text_label_from_node(entry_panel.get_node(^"./poplist_name")) + _province_list_name_labels[index] = GUINode.get_gui_label_from_node(entry_panel.get_node(^"./poplist_name")) - _province_list_size_labels[index] = GUINode.get_gui_text_label_from_node(entry_panel.get_node(^"./poplist_numpops")) + _province_list_size_labels[index] = GUINode.get_gui_label_from_node(entry_panel.get_node(^"./poplist_numpops")) _province_list_growth_icons[index] = GUINode.get_gfx_sprite_texture_from_node(entry_panel.get_node(^"./growth_indicator")) @@ -273,7 +273,7 @@ func _setup_distribution_windows() -> void: _pop_screen_panel.add_child(distribution_panel) distribution_panel.set_position(distribution_start + distribution_step * Vector2(index % columns, index / columns)) - var name_label : GUITextLabel = GUINode.get_gui_text_label_from_node(distribution_panel.get_node(^"./item_name")) + var name_label : GUILabel = GUINode.get_gui_label_from_node(distribution_panel.get_node(^"./item_name")) if name_label: name_label.set_text(distribution_names[index]) @@ -318,7 +318,7 @@ func _setup_pop_list() -> void: height += pop_row_panel.size.y _pop_list_rows.push_back(pop_row_panel) - _pop_list_size_labels.push_back(GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_size"))) + _pop_list_size_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_size"))) var pop_type_button : Button = GUINode.get_button_from_node(pop_row_panel.get_node(^"./pop_type")) # TODO - open pop details menu on pop type button press @@ -328,17 +328,17 @@ func _setup_pop_list() -> void: _pop_list_producing_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_producing_icon"))) - var culture_label : GUITextLabel = GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_nation")) + var culture_label : GUILabel = GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_nation")) _pop_list_culture_labels.push_back(culture_label) _pop_list_religion_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./pop_religion"))) - var location_label : GUITextLabel = GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_location")) + var location_label : GUILabel = GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_location")) _pop_list_location_labels.push_back(location_label) - _pop_list_militancy_labels.push_back(GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_mil"))) + _pop_list_militancy_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_mil"))) - _pop_list_consciousness_labels.push_back(GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_con"))) + _pop_list_consciousness_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_con"))) _pop_list_ideology_charts.push_back(GUINode.get_gfx_pie_chart_texture_from_node(pop_row_panel.get_node(^"./pop_ideology"))) @@ -346,7 +346,7 @@ func _setup_pop_list() -> void: _pop_list_unemployment_progressbars.push_back(GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./pop_unemployment_bar"))) - _pop_list_cash_labels.push_back(GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_cash"))) + _pop_list_cash_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_cash"))) var pop_list_life_needs_progressbar : TextureProgressBar = GUINode.get_progress_bar_from_node(pop_row_panel.get_node(^"./lifeneed_progress")) if pop_list_life_needs_progressbar: @@ -390,7 +390,7 @@ func _setup_pop_list() -> void: _pop_list_size_change_icons.push_back(GUINode.get_gfx_sprite_texture_from_node(pop_row_panel.get_node(^"./growth_indicator"))) - _pop_list_literacy_labels.push_back(GUINode.get_gui_text_label_from_node(pop_row_panel.get_node(^"./pop_literacy"))) + _pop_list_literacy_labels.push_back(GUINode.get_gui_label_from_node(pop_row_panel.get_node(^"./pop_literacy"))) func _notification(what : int) -> void: match what: @@ -546,11 +546,11 @@ func _update_distributions(): if colour_icon_rect: colour_icon_rect.set_modulate(distribution_row[slice_colour_key]) - var identifier_label : GUITextLabel = GUINode.get_gui_text_label_from_node(child.get_node(^"./legend_title")) + var identifier_label : GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_title")) if identifier_label: identifier_label.set_text(distribution_row[slice_identifier_key]) - var weight_label : GUITextLabel = GUINode.get_gui_text_label_from_node(child.get_node(^"./legend_value")) + var weight_label : GUILabel = GUINode.get_gui_label_from_node(child.get_node(^"./legend_value")) if weight_label: weight_label.set_text("%s%%" % GUINode.float_to_string_dp(distribution_row[slice_weight_key] * 100.0, 1)) diff --git a/game/src/Game/GameSession/ProvinceOverviewPanel.gd b/game/src/Game/GameSession/ProvinceOverviewPanel.gd index f953c56..f2669c0 100644 --- a/game/src/Game/GameSession/ProvinceOverviewPanel.gd +++ b/game/src/Game/GameSession/ProvinceOverviewPanel.gd @@ -1,13 +1,13 @@ extends GUINode # Header -var _province_name_label : GUITextLabel -var _state_name_label : GUITextLabel +var _province_name_label : GUILabel +var _state_name_label : GUILabel var _slave_status_icon : TextureRect var _colony_status_button : Button var _colony_status_button_texture : GFXSpriteTexture -var _administrative_percentage_label : GUITextLabel -var _owner_percentage_label : GUITextLabel +var _administrative_percentage_label : GUILabel +var _owner_percentage_label : GUILabel var _province_modifiers_overlapping_elements_box : GUIOverlappingElementsBox var _terrain_type_texture : GFXSpriteTexture var _life_rating_bar : TextureProgressBar @@ -15,21 +15,21 @@ var _controller_flag_texture : GFXMaskedFlagTexture # Statistics var _rgo_icon_texture : GFXSpriteTexture -var _rgo_produced_label : GUITextLabel -var _rgo_income_label : GUITextLabel +var _rgo_produced_label : GUILabel +var _rgo_income_label : GUILabel var _rgo_employment_percentage_texture : GFXSpriteTexture -var _rgo_employment_population_label : GUITextLabel -var _rgo_employment_percentage_label : GUITextLabel -var _crime_name_label : GUITextLabel +var _rgo_employment_population_label : GUILabel +var _rgo_employment_percentage_label : GUILabel +var _crime_name_label : GUILabel var _crime_icon_texture : GFXSpriteTexture -var _crime_fighting_label : GUITextLabel -var _total_population_label : GUITextLabel -var _migration_label : GUITextLabel -var _population_growth_label : GUITextLabel +var _crime_fighting_label : GUILabel +var _total_population_label : GUILabel +var _migration_label : GUILabel +var _population_growth_label : GUILabel var _pop_types_piechart : GFXPieChartTexture var _pop_ideologies_piechart : GFXPieChartTexture var _pop_cultures_piechart : GFXPieChartTexture -var _supply_limit_label : GUITextLabel +var _supply_limit_label : GUILabel var _cores_overlapping_elements_box : GUIOverlappingElementsBox # Buildings @@ -48,7 +48,7 @@ class BuildingSlot: var _expand_button : Button var _expanding_icon : TextureRect var _expanding_progress_bar : TextureProgressBar - var _expanding_label : GUITextLabel + var _expanding_label : GUILabel func _init(new_slot_index : int, new_slot_node : Control) -> void: if new_slot_index < 0: @@ -68,7 +68,7 @@ class BuildingSlot: else: icon.hide() - var building_name := GUINode.get_gui_text_label_from_node(_slot_node.get_node(^"./description")) + var building_name := GUINode.get_gui_label_from_node(_slot_node.get_node(^"./description")) if building_name: building_name.text = MenuSingleton.get_province_building_identifier(_slot_index) _expand_button = GUINode.get_button_from_node(_slot_node.get_node(^"./expand")) @@ -79,7 +79,7 @@ class BuildingSlot: if _expanding_progress_bar: _expanding_progress_bar.max_value = 1.0 _expanding_progress_bar.step = _expanding_progress_bar.max_value / 100 - _expanding_label = GUINode.get_gui_text_label_from_node(_slot_node.get_node(^"./expand_text")) + _expanding_label = GUINode.get_gui_label_from_node(_slot_node.get_node(^"./expand_text")) enum ExpansionState { CannotExpand, CanExpand, Preparing, Expanding } @@ -136,8 +136,8 @@ func _ready() -> void: close_button.pressed.connect(_on_close_button_pressed) # Header - _province_name_label = get_gui_text_label_from_nodepath(^"./province_view/province_view_header/province_name") - _state_name_label = get_gui_text_label_from_nodepath(^"./province_view/province_view_header/state_name") + _province_name_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/province_name") + _state_name_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/state_name") if _state_name_label: # State names are already translated in the MenuSingleton _state_name_label.auto_translate = false @@ -150,8 +150,8 @@ func _ready() -> void: var admin_icon_texture : GFXSpriteTexture = get_gfx_sprite_texture_from_nodepath(^"./province_view/province_view_header/admin_icon") if admin_icon_texture: admin_icon_texture.set_icon_index(MenuSingleton.get_administrative_pop_icon_index()) - _administrative_percentage_label = get_gui_text_label_from_nodepath(^"./province_view/province_view_header/admin_efficiency") - _owner_percentage_label = get_gui_text_label_from_nodepath(^"./province_view/province_view_header/owner_presence") + _administrative_percentage_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/admin_efficiency") + _owner_percentage_label = get_gui_label_from_nodepath(^"./province_view/province_view_header/owner_presence") _province_modifiers_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./province_view/province_view_header/province_modifiers") if _province_modifiers_overlapping_elements_box and _province_modifiers_overlapping_elements_box.set_gui_child_element_name("province_interface", "prov_state_modifier") != OK: _province_modifiers_overlapping_elements_box = null # hide province modifiers box since we can't do anything with it @@ -161,17 +161,17 @@ func _ready() -> void: # Statistics _rgo_icon_texture = get_gfx_sprite_texture_from_nodepath(^"./province_view/province_statistics/goods_type") - _rgo_produced_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/produced") - _rgo_income_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/income") + _rgo_produced_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/produced") + _rgo_income_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/income") _rgo_employment_percentage_texture = get_gfx_sprite_texture_from_nodepath(^"./province_view/province_statistics/employment_ratio") - _rgo_employment_population_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/rgo_population") - _rgo_employment_percentage_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/rgo_percent") - _crime_name_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/crime_name") + _rgo_employment_population_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/rgo_population") + _rgo_employment_percentage_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/rgo_percent") + _crime_name_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/crime_name") _crime_icon_texture = get_gfx_sprite_texture_from_nodepath(^"./province_view/province_statistics/crime_icon") - _crime_fighting_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/crimefight_percent") - _total_population_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/total_population") - _migration_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/migration") - _population_growth_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/growth") + _crime_fighting_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/crimefight_percent") + _total_population_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/total_population") + _migration_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/migration") + _population_growth_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/growth") _pop_types_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/workforce_chart") _pop_ideologies_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/ideology_chart") _pop_cultures_piechart = get_gfx_pie_chart_texture_from_nodepath(^"./province_view/province_statistics/culture_chart") @@ -183,7 +183,7 @@ func _ready() -> void: _on_close_button_pressed() Events.NationManagementScreens.open_nation_management_screen(NationManagement.Screen.POPULATION) ) - _supply_limit_label = get_gui_text_label_from_nodepath(^"./province_view/province_statistics/supply_limit_label") + _supply_limit_label = get_gui_label_from_nodepath(^"./province_view/province_statistics/supply_limit_label") _cores_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./province_view/province_statistics/core_icons") if _cores_overlapping_elements_box and _cores_overlapping_elements_box.set_gui_child_element_name("province_interface", "province_core") != OK: _cores_overlapping_elements_box = null # hide cores box since we can't do anything with it diff --git a/game/src/Game/GameSession/Topbar.gd b/game/src/Game/GameSession/Topbar.gd index a173ccd..d6cc358 100644 --- a/game/src/Game/GameSession/Topbar.gd +++ b/game/src/Game/GameSession/Topbar.gd @@ -3,21 +3,21 @@ extends GUINode # Country info var _country_flag_texture : GFXMaskedFlagTexture var _country_flag_overlay_texture : GFXSpriteTexture -var _country_name_label : GUITextLabel -var _country_rank_label : GUITextLabel -var _country_prestige_label : GUITextLabel -var _country_prestige_rank_label : GUITextLabel -var _country_industrial_power_label : GUITextLabel -var _country_industrial_power_rank_label : GUITextLabel -var _country_military_power_label : GUITextLabel -var _country_military_power_rank_label : GUITextLabel -var _country_colonial_power_label : GUITextLabel +var _country_name_label : GUILabel +var _country_rank_label : GUILabel +var _country_prestige_label : GUILabel +var _country_prestige_rank_label : GUILabel +var _country_industrial_power_label : GUILabel +var _country_industrial_power_rank_label : GUILabel +var _country_military_power_label : GUILabel +var _country_military_power_rank_label : GUILabel +var _country_colonial_power_label : GUILabel # Time controls var _speed_up_button : Button var _speed_down_button : Button var _speed_indicator_texture : GFXSpriteTexture -var _date_label : GUITextLabel +var _date_label : GUILabel # NationManagement.Screen-Button var _nation_management_buttons : Dictionary @@ -32,48 +32,48 @@ var _production_alert_unemployment_texture : GFXSpriteTexture # Budget # TODO - line chart -var _budget_funds_label : GUITextLabel +var _budget_funds_label : GUILabel # Technology var _technology_progress_bar : TextureProgressBar -var _technology_current_research_label : GUITextLabel -var _technology_literacy_label : GUITextLabel -var _technology_research_points_label : GUITextLabel +var _technology_current_research_label : GUILabel +var _technology_literacy_label : GUILabel +var _technology_research_points_label : GUILabel # Politics var _politics_party_icon : TextureRect -var _politics_party_label : GUITextLabel -var _politics_suppression_points_label : GUITextLabel -var _politics_infamy_label : GUITextLabel +var _politics_party_label : GUILabel +var _politics_suppression_points_label : GUILabel +var _politics_infamy_label : GUILabel var _politics_reforms_texture : GFXSpriteTexture var _politics_decisions_texture : GFXSpriteTexture var _politics_election_texture : GFXSpriteTexture var _politics_rebels_texture : GFXSpriteTexture # Population -var _population_total_size_label : GUITextLabel -var _population_national_foci_label : GUITextLabel -var _population_militancy_label : GUITextLabel -var _population_consciousness_label : GUITextLabel +var _population_total_size_label : GUILabel +var _population_national_foci_label : GUILabel +var _population_militancy_label : GUILabel +var _population_consciousness_label : GUILabel # Trade var _trade_imported_textures : Array[GFXSpriteTexture] var _trade_exported_textures : Array[GFXSpriteTexture] # Diplomacy -var _diplomacy_peace_label : GUITextLabel +var _diplomacy_peace_label : GUILabel var _diplomacy_war_enemies_overlapping_elements_box : GUIOverlappingElementsBox -var _diplomacy_diplomatic_points_label : GUITextLabel +var _diplomacy_diplomatic_points_label : GUILabel var _diplomacy_alert_colony_texture : GFXSpriteTexture var _diplomacy_alert_crisis_texture : GFXSpriteTexture var _diplomacy_alert_sphere_texture : GFXSpriteTexture var _diplomacy_alert_great_power_texture : GFXSpriteTexture # Military -var _military_army_size_label : GUITextLabel -var _military_navy_size_label : GUITextLabel -var _military_mobilisation_size_label : GUITextLabel -var _military_leadership_points_label : GUITextLabel +var _military_army_size_label : GUILabel +var _military_navy_size_label : GUILabel +var _military_mobilisation_size_label : GUILabel +var _military_leadership_points_label : GUILabel func _ready() -> void: GameSingleton.gamestate_updated.connect(_update_info) @@ -102,15 +102,15 @@ func _ready() -> void: ) _country_flag_texture = GUINode.get_gfx_masked_flag_texture_from_node(country_flag_button) _country_flag_overlay_texture = get_gfx_sprite_texture_from_nodepath(^"./topbar/topbar_flag_overlay") - _country_name_label = get_gui_text_label_from_nodepath(^"./topbar/CountryName") - _country_rank_label = get_gui_text_label_from_nodepath(^"./topbar/nation_totalrank") - _country_prestige_label = get_gui_text_label_from_nodepath(^"./topbar/country_prestige") - _country_prestige_rank_label = get_gui_text_label_from_nodepath(^"./topbar/selected_prestige_rank") - _country_industrial_power_label = get_gui_text_label_from_nodepath(^"./topbar/country_economic") - _country_industrial_power_rank_label = get_gui_text_label_from_nodepath(^"./topbar/selected_industry_rank") - _country_military_power_label = get_gui_text_label_from_nodepath(^"./topbar/country_military") - _country_military_power_rank_label = get_gui_text_label_from_nodepath(^"./topbar/selected_military_rank") - _country_colonial_power_label = get_gui_text_label_from_nodepath(^"./topbar/country_colonial_power") + _country_name_label = get_gui_label_from_nodepath(^"./topbar/CountryName") + _country_rank_label = get_gui_label_from_nodepath(^"./topbar/nation_totalrank") + _country_prestige_label = get_gui_label_from_nodepath(^"./topbar/country_prestige") + _country_prestige_rank_label = get_gui_label_from_nodepath(^"./topbar/selected_prestige_rank") + _country_industrial_power_label = get_gui_label_from_nodepath(^"./topbar/country_economic") + _country_industrial_power_rank_label = get_gui_label_from_nodepath(^"./topbar/selected_industry_rank") + _country_military_power_label = get_gui_label_from_nodepath(^"./topbar/country_military") + _country_military_power_rank_label = get_gui_label_from_nodepath(^"./topbar/selected_military_rank") + _country_colonial_power_label = get_gui_label_from_nodepath(^"./topbar/country_colonial_power") # Time controls _speed_up_button = get_button_from_nodepath(^"./topbar/button_speedup") @@ -126,7 +126,7 @@ func _ready() -> void: if speed_indicator_button: speed_indicator_button.pressed.connect(_on_play_pause_button_pressed) _speed_indicator_texture = GUINode.get_gfx_sprite_texture_from_node(speed_indicator_button) - _date_label = get_gui_text_label_from_nodepath(^"./topbar/DateText") + _date_label = get_gui_label_from_nodepath(^"./topbar/DateText") # Nation management screens const screen_nodepaths : Dictionary = { @@ -162,17 +162,17 @@ func _ready() -> void: _production_alert_unemployment_texture = get_gfx_sprite_texture_from_nodepath(^"./topbar/alert_unemployed_workers") # Budget - _budget_funds_label = get_gui_text_label_from_nodepath(^"./topbar/budget_funds") + _budget_funds_label = get_gui_label_from_nodepath(^"./topbar/budget_funds") # Technology _technology_progress_bar = get_progress_bar_from_nodepath(^"./topbar/topbar_tech_progress") - _technology_current_research_label = get_gui_text_label_from_nodepath(^"./topbar/tech_current_research") - _technology_literacy_label = get_gui_text_label_from_nodepath(^"./topbar/tech_literacy_value") - _technology_research_points_label = get_gui_text_label_from_nodepath(^"./topbar/topbar_researchpoints_value") + _technology_current_research_label = get_gui_label_from_nodepath(^"./topbar/tech_current_research") + _technology_literacy_label = get_gui_label_from_nodepath(^"./topbar/tech_literacy_value") + _technology_research_points_label = get_gui_label_from_nodepath(^"./topbar/topbar_researchpoints_value") # Politics _politics_party_icon = get_texture_rect_from_nodepath(^"./topbar/politics_party_icon") - _politics_party_label = get_gui_text_label_from_nodepath(^"./topbar/politics_ruling_party") + _politics_party_label = get_gui_label_from_nodepath(^"./topbar/politics_ruling_party") var politics_suppression_button : Button = get_button_from_nodepath(^"./topbar/topbar_supression_icon") if politics_suppression_button: politics_suppression_button.pressed.connect( @@ -180,8 +180,8 @@ func _ready() -> void: # TODO - open the politics menu on the Movements tab Events.NationManagementScreens.toggle_nation_management_screen(NationManagement.Screen.POLITICS) ) - _politics_suppression_points_label = get_gui_text_label_from_nodepath(^"./topbar/politics_supressionpoints_value") - _politics_infamy_label = get_gui_text_label_from_nodepath(^"./topbar/politics_infamy_value") + _politics_suppression_points_label = get_gui_label_from_nodepath(^"./topbar/politics_supressionpoints_value") + _politics_infamy_label = get_gui_label_from_nodepath(^"./topbar/politics_infamy_value") var politics_reforms_button : Button = get_button_from_nodepath(^"./topbar/alert_can_do_reforms") if politics_reforms_button: politics_reforms_button.pressed.connect( @@ -209,10 +209,10 @@ func _ready() -> void: _politics_rebels_texture = GUINode.get_gfx_sprite_texture_from_node(politics_rebels_button) # Population - _population_total_size_label = get_gui_text_label_from_nodepath(^"./topbar/population_total_value") - _population_national_foci_label = get_gui_text_label_from_nodepath(^"./topbar/topbar_focus_value") - _population_militancy_label = get_gui_text_label_from_nodepath(^"./topbar/population_avg_mil_value") - _population_consciousness_label = get_gui_text_label_from_nodepath(^"./topbar/population_avg_con_value") + _population_total_size_label = get_gui_label_from_nodepath(^"./topbar/population_total_value") + _population_national_foci_label = get_gui_label_from_nodepath(^"./topbar/topbar_focus_value") + _population_militancy_label = get_gui_label_from_nodepath(^"./topbar/population_avg_mil_value") + _population_consciousness_label = get_gui_label_from_nodepath(^"./topbar/population_avg_con_value") # Trade const TRADE_GOOD_COUNT : int = 3 @@ -221,9 +221,9 @@ func _ready() -> void: _trade_exported_textures.push_back(get_gfx_sprite_texture_from_nodepath("./topbar/topbar_export%d" % idx)) # Diplomacy - _diplomacy_peace_label = get_gui_text_label_from_nodepath(^"./topbar/diplomacy_status") + _diplomacy_peace_label = get_gui_label_from_nodepath(^"./topbar/diplomacy_status") _diplomacy_war_enemies_overlapping_elements_box = get_gui_overlapping_elements_box_from_nodepath(^"./topbar/diplomacy_at_war") - _diplomacy_diplomatic_points_label = get_gui_text_label_from_nodepath(^"./topbar/diplomacy_diplopoints_value") + _diplomacy_diplomatic_points_label = get_gui_label_from_nodepath(^"./topbar/diplomacy_diplopoints_value") var diplomacy_alert_colony_button : Button = get_button_from_nodepath(^"./topbar/alert_colony") if diplomacy_alert_colony_button: diplomacy_alert_colony_button.pressed.connect( @@ -237,10 +237,10 @@ func _ready() -> void: _diplomacy_alert_great_power_texture = get_gfx_sprite_texture_from_nodepath(^"./topbar/alert_loosing_gp") # Military - _military_army_size_label = get_gui_text_label_from_nodepath(^"./topbar/military_army_value") - _military_navy_size_label = get_gui_text_label_from_nodepath(^"./topbar/military_navy_value") - _military_mobilisation_size_label = get_gui_text_label_from_nodepath(^"./topbar/military_manpower_value") - _military_leadership_points_label = get_gui_text_label_from_nodepath(^"./topbar/military_leadership_value") + _military_army_size_label = get_gui_label_from_nodepath(^"./topbar/military_army_value") + _military_navy_size_label = get_gui_label_from_nodepath(^"./topbar/military_navy_value") + _military_mobilisation_size_label = get_gui_label_from_nodepath(^"./topbar/military_manpower_value") + _military_leadership_points_label = get_gui_label_from_nodepath(^"./topbar/military_leadership_value") _update_info() _update_speed_controls() -- cgit v1.2.3-56-ga3b1