aboutsummaryrefslogtreecommitdiff
path: root/extension
diff options
context:
space:
mode:
author hop311 <hop3114@gmail.com>2024-08-29 00:16:24 +0200
committer hop311 <hop3114@gmail.com>2024-08-29 23:04:30 +0200
commitbdc2ba527bc02e7cdf977f6040f2ca85aa4f9a94 (patch)
tree4627ad955ac60f5c66b94dfc3106bd8442b58302 /extension
parent88acb31bd43f0e163522837bb1d0dd7da2977c4a (diff)
Add tooltips for buttons, labels, icons, pie charts, sliders, and progress barstooltip
Diffstat (limited to 'extension')
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.cpp97
-rw-r--r--extension/src/openvic-extension/classes/GFXPieChartTexture.hpp13
-rw-r--r--extension/src/openvic-extension/classes/GUIButton.cpp12
-rw-r--r--extension/src/openvic-extension/classes/GUIButton.hpp7
-rw-r--r--extension/src/openvic-extension/classes/GUIHasTooltip.hpp121
-rw-r--r--extension/src/openvic-extension/classes/GUILabel.cpp57
-rw-r--r--extension/src/openvic-extension/classes/GUILabel.hpp7
-rw-r--r--extension/src/openvic-extension/classes/GUIPieChart.cpp80
-rw-r--r--extension/src/openvic-extension/classes/GUIPieChart.hpp13
-rw-r--r--extension/src/openvic-extension/classes/GUIProgressBar.cpp12
-rw-r--r--extension/src/openvic-extension/classes/GUIProgressBar.hpp7
-rw-r--r--extension/src/openvic-extension/classes/GUIScrollbar.cpp15
-rw-r--r--extension/src/openvic-extension/classes/GUIScrollbar.hpp4
-rw-r--r--extension/src/openvic-extension/classes/GUITextureRect.cpp12
-rw-r--r--extension/src/openvic-extension/classes/GUITextureRect.hpp9
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.cpp33
-rw-r--r--extension/src/openvic-extension/singletons/MenuSingleton.hpp14
17 files changed, 443 insertions, 70 deletions
diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp
index 417566d..5d955a3 100644
--- a/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp
+++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.cpp
@@ -7,6 +7,7 @@
using namespace godot;
using namespace OpenVic;
+using namespace OpenVic::Utilities::literals;
StringName const& GFXPieChartTexture::_slice_identifier_key() {
static const StringName slice_identifier_key = "identifier";
@@ -21,7 +22,35 @@ StringName const& GFXPieChartTexture::_slice_weight_key() {
return slice_weight_key;
}
-static constexpr float TWO_PI = 2.0f * std::numbers::pi_v<float>;
+GFXPieChartTexture::slice_t const* GFXPieChartTexture::get_slice(Vector2 const& position) const {
+ if (slices.empty() || position.length_squared() > 1.0_real) {
+ return nullptr;
+ }
+
+ static constexpr float TWO_PI = 2.0f * std::numbers::pi_v<float>;
+
+ /* Calculate the anti-clockwise angle between the point and the centre of the image.
+ * The y coordinate is negated as the image coordinate system's y increases downwards. */
+ float theta = atan2(-position.y, position.x);
+ if (theta < 0.0f) {
+ theta += TWO_PI;
+ }
+
+ /* Rescale angle so that total_weight is a full rotation. */
+ theta *= total_weight / TWO_PI;
+
+ /* Find the slice theta lies in. */
+ for (slice_t const& slice : slices) {
+ theta -= slice.weight;
+
+ if (theta <= 0.0f) {
+ return &slice;
+ }
+ }
+
+ /* Default to the first slice in case theta never reaches 0 due to floating point inaccuracy. */
+ return &slices.front();
+}
Error GFXPieChartTexture::_generate_pie_chart_image() {
ERR_FAIL_NULL_V(gfx_pie_chart, FAILED);
@@ -30,60 +59,37 @@ Error GFXPieChartTexture::_generate_pie_chart_image() {
vformat("Invalid GFX::PieChart size for GFXPieChartTexture - %d", gfx_pie_chart->get_size())
);
- const int32_t pie_chart_size = 2 * gfx_pie_chart->get_size();
+ const int32_t pie_chart_radius = gfx_pie_chart->get_size();
+ const int32_t pie_chart_diameter = 2 * pie_chart_radius;
/* Whether we've already set the ImageTexture to an image of the right dimensions,
* and so can update it without creating and setting a new image, or not. */
- const bool can_update = pie_chart_image.is_valid() && pie_chart_image->get_width() == pie_chart_size
- && pie_chart_image->get_height() == pie_chart_size;
+ const bool can_update = pie_chart_image.is_valid() && pie_chart_image->get_width() == pie_chart_diameter
+ && pie_chart_image->get_height() == pie_chart_diameter;
if (!can_update) {
- pie_chart_image = Image::create(pie_chart_size, pie_chart_size, false, Image::FORMAT_RGBA8);
+ pie_chart_image = Image::create(pie_chart_diameter, pie_chart_diameter, false, Image::FORMAT_RGBA8);
ERR_FAIL_NULL_V(pie_chart_image, FAILED);
}
static const Color background_colour { 0.0f, 0.0f, 0.0f, 0.0f };
if (!slices.empty()) {
- const float pie_chart_radius = gfx_pie_chart->get_size();
-
- const Vector2 centre_translation = Vector2 { 0.5f, 0.5f } - static_cast<Vector2>(pie_chart_image->get_size()) * 0.5f;
-
- for (Vector2i point { 0, 0 }; point.y < pie_chart_image->get_height(); ++point.y) {
-
- for (point.x = 0; point.x < pie_chart_image->get_width(); ++point.x) {
-
- const Vector2 offset = centre_translation + point;
-
- if (offset.length() <= pie_chart_radius) {
-
- /* Calculate the anti-clockwise angle between the point and the centre of the image.
- * The y coordinate is negated as the image coordinate system's y increases downwards. */
- float theta = atan2(-offset.y, offset.x);
- if (theta < 0.0f) {
- theta += TWO_PI;
- }
-
- /* Rescale angle so that total_weight is a full rotation. */
- theta *= total_weight / TWO_PI;
+ for (Vector2i point { 0, 0 }; point.y < pie_chart_diameter; ++point.y) {
- /* Default to the first colour in case theta never reaches 0 due to floating point inaccuracy. */
- Color colour = slices.front().first;
+ for (point.x = 0; point.x < pie_chart_diameter; ++point.x) {
- /* Find the slice theta lies in. */
- for (slice_t const& slice : slices) {
- theta -= slice.second;
+ Vector2 offset = point;
+ // Move to the centre of the pixel
+ offset += Vector2 { 0.5_real, 0.5_real };
+ // Normalise to [0, 2]
+ offset /= pie_chart_radius;
+ // Translate to [-1, 1]
+ offset -= Vector2 { 1.0_real, 1.0_real };
- if (theta <= 0.0f) {
- colour = slice.first;
- break;
- }
- }
+ slice_t const* slice = get_slice(offset);
- pie_chart_image->set_pixelv(point, colour);
- } else {
- pie_chart_image->set_pixelv(point, background_colour);
- }
+ pie_chart_image->set_pixelv(point, slice != nullptr ? slice->colour : background_colour);
}
}
} else {
@@ -107,12 +113,15 @@ Error GFXPieChartTexture::set_slices_array(godot_pie_chart_data_t const& new_sli
for (int32_t i = 0; i < new_slices.size(); ++i) {
Dictionary const& slice_dict = new_slices[i];
ERR_CONTINUE_MSG(
- !slice_dict.has(_slice_colour_key()) || !slice_dict.has(_slice_weight_key()),
+ !slice_dict.has(_slice_identifier_key()) || !slice_dict.has(_slice_colour_key())
+ || !slice_dict.has(_slice_weight_key()),
vformat("Invalid slice keys at index %d", i)
);
- const slice_t slice = std::make_pair(slice_dict[_slice_colour_key()], slice_dict[_slice_weight_key()]);
- if (slice.second > 0.0f) {
- total_weight += slice.second;
+ const slice_t slice {
+ slice_dict[_slice_identifier_key()], slice_dict[_slice_colour_key()], slice_dict[_slice_weight_key()]
+ };
+ if (slice.weight > 0.0f) {
+ total_weight += slice.weight;
slices.push_back(slice);
}
}
diff --git a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
index 9642f4e..3610efb 100644
--- a/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
+++ b/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
@@ -10,11 +10,17 @@ namespace OpenVic {
class GFXPieChartTexture : public godot::ImageTexture {
GDCLASS(GFXPieChartTexture, godot::ImageTexture)
- using slice_t = std::pair<godot::Color, float>;
+ public:
+ struct slice_t {
+ godot::String name;
+ godot::Color colour;
+ float weight;
+ };
+ private:
GFX::PieChart const* PROPERTY(gfx_pie_chart);
std::vector<slice_t> slices;
- float total_weight;
+ float PROPERTY(total_weight);
godot::Ref<godot::Image> pie_chart_image;
static godot::StringName const& _slice_identifier_key();
@@ -29,6 +35,9 @@ namespace OpenVic {
public:
GFXPieChartTexture();
+ // Position must be centred and normalised so that coords are in [-1, 1].
+ slice_t const* get_slice(godot::Vector2 const& position) const;
+
using godot_pie_chart_data_t = godot::TypedArray<godot::Dictionary>;
/* Set slices given an Array of Dictionaries, each with the following key-value entries:
diff --git a/extension/src/openvic-extension/classes/GUIButton.cpp b/extension/src/openvic-extension/classes/GUIButton.cpp
index 323b03c..e35d67a 100644
--- a/extension/src/openvic-extension/classes/GUIButton.cpp
+++ b/extension/src/openvic-extension/classes/GUIButton.cpp
@@ -10,7 +10,17 @@
using namespace godot;
using namespace OpenVic;
-void GUIButton::_bind_methods() {}
+GUI_TOOLTIP_IMPLEMENTATIONS(GUIButton)
+
+void GUIButton::_bind_methods() {
+ GUI_TOOLTIP_BIND_METHODS(GUIButton)
+}
+
+void GUIButton::_notification(int what) {
+ _tooltip_notification(what);
+}
+
+GUIButton::GUIButton() : tooltip_active { false } {}
Error GUIButton::set_gfx_button_state_having_texture(Ref<GFXButtonStateHavingTexture> const& texture) {
ERR_FAIL_NULL_V(texture, FAILED);
diff --git a/extension/src/openvic-extension/classes/GUIButton.hpp b/extension/src/openvic-extension/classes/GUIButton.hpp
index 4e53125..3873a4d 100644
--- a/extension/src/openvic-extension/classes/GUIButton.hpp
+++ b/extension/src/openvic-extension/classes/GUIButton.hpp
@@ -5,17 +5,24 @@
#include <openvic-simulation/interface/GFXSprite.hpp>
#include "openvic-extension/classes/GFXButtonStateTexture.hpp"
+#include "openvic-extension/classes/GUIHasTooltip.hpp"
namespace OpenVic {
class GUIButton : public godot::Button {
GDCLASS(GUIButton, godot::Button)
+ GUI_TOOLTIP_DEFINITIONS
+
protected:
static void _bind_methods();
+ void _notification(int what);
+
godot::Error set_gfx_button_state_having_texture(godot::Ref<GFXButtonStateHavingTexture> const& texture);
public:
+ GUIButton();
+
godot::Error set_gfx_font(GFX::Font const* gfx_font);
};
}
diff --git a/extension/src/openvic-extension/classes/GUIHasTooltip.hpp b/extension/src/openvic-extension/classes/GUIHasTooltip.hpp
new file mode 100644
index 0000000..22413ec
--- /dev/null
+++ b/extension/src/openvic-extension/classes/GUIHasTooltip.hpp
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <godot_cpp/classes/control.hpp>
+#include <godot_cpp/variant/string.hpp>
+#include <godot_cpp/variant/utility_functions.hpp>
+#include <godot_cpp/variant/vector2.hpp>
+
+#include <openvic-simulation/utility/Getters.hpp>
+
+#include "openvic-extension/singletons/MenuSingleton.hpp"
+#include "openvic-extension/utility/ClassBindings.hpp"
+#include "openvic-extension/utility/Utilities.hpp"
+
+/* To add tooltip functionality to a class:
+ * - the class must be derived from Control.
+ * - add GUI_TOOLTIP_DEFINITIONS to the class definition, bearing in mind that it leaves visibility as private.
+ * - add GUI_TOOLTIP_IMPLEMENTATIONS(CLASS) to the class' source file.
+ * - add GUI_TOOLTIP_BIND_METHODS(CLASS) to the class' _bind_methods implementation.
+ * - call _tooltip_notification from the class' _notification method.
+ * - initialise tooltip_active to false in the class' constructor. */
+
+#define GUI_TOOLTIP_DEFINITIONS \
+ public: \
+ void set_tooltip_string_and_substitution_dict( \
+ godot::String const& new_tooltip_string, godot::Dictionary const& new_tooltip_substitution_dict \
+ ); \
+ void set_tooltip_string(godot::String const& new_tooltip_string); \
+ void set_tooltip_substitution_dict(godot::Dictionary const& new_tooltip_substitution_dict); \
+ void clear_tooltip(); \
+ private: \
+ godot::String PROPERTY(tooltip_string); \
+ godot::Dictionary PROPERTY(tooltip_substitution_dict); \
+ bool PROPERTY_CUSTOM_PREFIX(tooltip_active, is); \
+ void _tooltip_notification(int what); \
+ void _set_tooltip_active(bool new_tooltip_active); \
+ void _set_tooltip_visibility(bool visible);
+
+#define GUI_TOOLTIP_IMPLEMENTATIONS(CLASS) \
+ void CLASS::set_tooltip_string_and_substitution_dict( \
+ String const& new_tooltip_string, Dictionary const& new_tooltip_substitution_dict \
+ ) { \
+ if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \
+ UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \
+ } \
+ if (tooltip_string != new_tooltip_string || tooltip_substitution_dict != new_tooltip_substitution_dict) { \
+ tooltip_string = new_tooltip_string; \
+ tooltip_substitution_dict = new_tooltip_substitution_dict; \
+ if (tooltip_active) { \
+ _set_tooltip_visibility(!tooltip_string.is_empty()); \
+ } \
+ } \
+ } \
+ void CLASS::set_tooltip_string(String const& new_tooltip_string) { \
+ if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \
+ UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \
+ } \
+ if (tooltip_string != new_tooltip_string) { \
+ tooltip_string = new_tooltip_string; \
+ if (tooltip_active) { \
+ _set_tooltip_visibility(!tooltip_string.is_empty()); \
+ } \
+ } \
+ } \
+ void CLASS::set_tooltip_substitution_dict(Dictionary const& new_tooltip_substitution_dict) { \
+ if (get_mouse_filter() == MOUSE_FILTER_IGNORE) { \
+ UtilityFunctions::push_error("Tooltips won't work for \"", get_name(), "\" as it has MOUSE_FILTER_IGNORE"); \
+ } \
+ if (tooltip_substitution_dict != new_tooltip_substitution_dict) { \
+ tooltip_substitution_dict = new_tooltip_substitution_dict; \
+ if (tooltip_active) { \
+ _set_tooltip_visibility(!tooltip_string.is_empty()); \
+ } \
+ } \
+ } \
+ void CLASS::clear_tooltip() { \
+ set_tooltip_string_and_substitution_dict({}, {}); \
+ } \
+ void CLASS::_tooltip_notification(int what) { \
+ if (what == NOTIFICATION_MOUSE_ENTER_SELF) { \
+ _set_tooltip_active(true); \
+ } else if (what == NOTIFICATION_MOUSE_EXIT_SELF) { \
+ _set_tooltip_active(false); \
+ } \
+ } \
+ void CLASS::_set_tooltip_active(bool new_tooltip_active) { \
+ if (tooltip_active != new_tooltip_active) { \
+ tooltip_active = new_tooltip_active; \
+ if (!tooltip_string.is_empty()) { \
+ _set_tooltip_visibility(tooltip_active); \
+ } \
+ } \
+ } \
+ void CLASS::_set_tooltip_visibility(bool visible) { \
+ MenuSingleton* menu_singleton = MenuSingleton::get_singleton(); \
+ ERR_FAIL_NULL(menu_singleton); \
+ if (visible) { \
+ menu_singleton->show_control_tooltip(tooltip_string, tooltip_substitution_dict, this); \
+ } else { \
+ menu_singleton->hide_tooltip(); \
+ } \
+ }
+
+#define GUI_TOOLTIP_BIND_METHODS(CLASS) \
+ OV_BIND_METHOD(CLASS::get_tooltip_string); \
+ OV_BIND_METHOD(CLASS::set_tooltip_string, { "new_tooltip_string" }); \
+ OV_BIND_METHOD(CLASS::get_tooltip_substitution_dict); \
+ OV_BIND_METHOD(CLASS::set_tooltip_substitution_dict, { "new_tooltip_substitution_dict" }); \
+ OV_BIND_METHOD( \
+ CLASS::set_tooltip_string_and_substitution_dict, { "new_tooltip_string", "new_tooltip_substitution_dict" } \
+ ); \
+ OV_BIND_METHOD(CLASS::clear_tooltip); \
+ OV_BIND_METHOD(CLASS::is_tooltip_active); \
+ ADD_PROPERTY( \
+ PropertyInfo(Variant::STRING, "tooltip_string", PROPERTY_HINT_MULTILINE_TEXT), \
+ "set_tooltip_string", "get_tooltip_string" \
+ ); \
+ ADD_PROPERTY( \
+ PropertyInfo(Variant::DICTIONARY, "tooltip_substitution_dict"), \
+ "set_tooltip_substitution_dict", "get_tooltip_substitution_dict" \
+ ); \
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tooltip_active"), "", "is_tooltip_active");
diff --git a/extension/src/openvic-extension/classes/GUILabel.cpp b/extension/src/openvic-extension/classes/GUILabel.cpp
index 9fd6b60..732dec2 100644
--- a/extension/src/openvic-extension/classes/GUILabel.cpp
+++ b/extension/src/openvic-extension/classes/GUILabel.cpp
@@ -14,7 +14,30 @@ using namespace OpenVic::Utilities::literals;
static constexpr int32_t DEFAULT_FONT_SIZE = 16;
+GUI_TOOLTIP_IMPLEMENTATIONS(GUILabel)
+
+String const& GUILabel::get_colour_marker() {
+ static const String COLOUR_MARKER = String::chr(0xA7); // §
+ return COLOUR_MARKER;
+}
+
+String const& GUILabel::get_currency_marker() {
+ static const String CURRENCY_MARKER = String::chr(0xA4); // ¤
+ return CURRENCY_MARKER;
+}
+
+String const& GUILabel::get_substitution_marker() {
+ static const String SUBSTITUTION_MARKER = String::chr(0x24); // $
+ return SUBSTITUTION_MARKER;
+}
+
void GUILabel::_bind_methods() {
+ GUI_TOOLTIP_BIND_METHODS(GUILabel)
+
+ OV_BIND_SMETHOD(get_colour_marker);
+ OV_BIND_SMETHOD(get_currency_marker);
+ OV_BIND_SMETHOD(get_substitution_marker);
+
OV_BIND_METHOD(GUILabel::clear);
OV_BIND_METHOD(GUILabel::get_gui_text_name);
@@ -80,6 +103,8 @@ void GUILabel::_bind_methods() {
}
void GUILabel::_notification(int what) {
+ _tooltip_notification(what);
+
switch (what) {
case NOTIFICATION_RESIZED:
case NOTIFICATION_TRANSLATION_CHANGED: {
@@ -146,7 +171,8 @@ void GUILabel::_notification(int what) {
}
GUILabel::GUILabel()
- : gui_text { nullptr },
+ : tooltip_active { false },
+ gui_text { nullptr },
text {},
substitution_dict {},
horizontal_alignment { HORIZONTAL_ALIGNMENT_LEFT },
@@ -454,29 +480,30 @@ void GUILabel::_update_lines() {
}
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) {
+ while ((marker_start_pos = base_text.find(get_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());
+ int64_t marker_end_pos = base_text.find(
+ get_substitution_marker(), marker_start_pos + get_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()
+ marker_start_pos + get_substitution_marker().length(),
+ marker_end_pos - marker_start_pos - get_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();
+ start_pos = marker_end_pos + get_substitution_marker().length();
}
if (start_pos < base_text.length()) {
@@ -489,25 +516,23 @@ String GUILabel::generate_substituted_text(String const& base_text) const {
std::pair<String, GUILabel::colour_instructions_t> 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) {
+ while ((marker_pos = substituted_text.find(get_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()];
+ if (marker_pos + get_colour_marker().length() < substituted_text.length()) {
+ const char32_t colour_code = substituted_text[marker_pos + get_colour_marker().length()];
// Check that the colour code can be safely cast to a char
if (colour_code >> sizeof(char) * CHAR_BIT == 0) {
colour_instructions.emplace_back(result.length(), static_cast<char>(colour_code));
}
- start_pos = marker_pos + COLOUR_MARKER.length() + 1;
+ start_pos = marker_pos + get_colour_marker().length() + 1;
} else {
return { std::move(result), std::move(colour_instructions) };
}
@@ -588,8 +613,6 @@ void GUILabel::separate_lines(
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);
@@ -602,7 +625,7 @@ void GUILabel::separate_currency_segments(
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) {
+ while ((marker_pos = string.find(get_currency_marker(), start_pos)) != -1) {
if (start_pos < marker_pos) {
push_string_segment(start_pos, marker_pos);
}
@@ -610,7 +633,7 @@ void GUILabel::separate_currency_segments(
line.segments.push_back(currency_segment_t {});
line.width += currency_width;
- start_pos = marker_pos + CURRENCY_MARKER.length();
+ start_pos = marker_pos + get_currency_marker().length();
}
if (start_pos < string.length()) {
diff --git a/extension/src/openvic-extension/classes/GUILabel.hpp b/extension/src/openvic-extension/classes/GUILabel.hpp
index e0982b2..102ad94 100644
--- a/extension/src/openvic-extension/classes/GUILabel.hpp
+++ b/extension/src/openvic-extension/classes/GUILabel.hpp
@@ -8,11 +8,14 @@
#include <openvic-simulation/interface/GUI.hpp>
#include "openvic-extension/classes/GFXSpriteTexture.hpp"
+#include "openvic-extension/classes/GUIHasTooltip.hpp"
namespace OpenVic {
class GUILabel : public godot::Control {
GDCLASS(GUILabel, godot::Control)
+ GUI_TOOLTIP_DEFINITIONS
+
using colour_instructions_t = std::vector<std::pair<int64_t, char>>;
GUI::Text const* PROPERTY(gui_text);
@@ -55,6 +58,10 @@ namespace OpenVic {
void _notification(int what);
public:
+ static godot::String const& get_colour_marker();
+ static godot::String const& get_currency_marker();
+ static godot::String const& get_substitution_marker();
+
GUILabel();
/* Reset gui_text to nullptr and reset current text. */
diff --git a/extension/src/openvic-extension/classes/GUIPieChart.cpp b/extension/src/openvic-extension/classes/GUIPieChart.cpp
index a8ed087..0688dd3 100644
--- a/extension/src/openvic-extension/classes/GUIPieChart.cpp
+++ b/extension/src/openvic-extension/classes/GUIPieChart.cpp
@@ -1,11 +1,51 @@
#include "GUIPieChart.hpp"
+#include <godot_cpp/classes/input_event_mouse_motion.hpp>
+
+#include "openvic-extension/classes/GUILabel.hpp"
+#include "openvic-extension/singletons/MenuSingleton.hpp"
#include "openvic-extension/utility/ClassBindings.hpp"
using namespace godot;
using namespace OpenVic;
using namespace OpenVic::Utilities::literals;
+void GUIPieChart::_update_tooltip() {
+ MenuSingleton* menu_singleton = MenuSingleton::get_singleton();
+ ERR_FAIL_NULL(menu_singleton);
+
+ if (gfx_pie_chart_texture.is_valid()) {
+ GFXPieChartTexture::slice_t const* slice = gfx_pie_chart_texture->get_slice(tooltip_position);
+
+ if (slice != nullptr) {
+ static const String tooltip_identifier_key = "ID";
+ static const String tooltip_percent_key = "PC";
+ // "§Y$ID$§!: $PC$%"
+ static const String tooltip_string =
+ GUILabel::get_colour_marker() + String { "Y" } + GUILabel::get_substitution_marker() + tooltip_identifier_key
+ + GUILabel::get_substitution_marker() + GUILabel::get_colour_marker() + "!: "
+ + GUILabel::get_substitution_marker() + tooltip_percent_key + GUILabel::get_substitution_marker() + "%";
+
+ Dictionary substitution_dict;
+ substitution_dict[tooltip_identifier_key] = slice->name;
+
+ float percent = slice->weight * 100.0f;
+ if (gfx_pie_chart_texture->get_total_weight() > 0.0f) {
+ percent /= gfx_pie_chart_texture->get_total_weight();
+ }
+ substitution_dict[tooltip_percent_key] = Utilities::float_to_string_dp(percent, 2);
+
+ menu_singleton->show_control_tooltip(tooltip_string, substitution_dict, this);
+
+ tooltip_active = true;
+ return;
+ }
+ }
+
+ menu_singleton->hide_tooltip();
+ tooltip_active = false;
+}
+
void GUIPieChart::_bind_methods() {
OV_BIND_METHOD(GUIPieChart::get_gfx_pie_chart_texture);
OV_BIND_METHOD(GUIPieChart::set_gfx_pie_chart_name, { "gfx_pie_chart_name" });
@@ -13,6 +53,28 @@ void GUIPieChart::_bind_methods() {
OV_BIND_METHOD(GUIPieChart::set_slices_array, { "new_slices" });
}
+static const Vector2 disabled_tooltip_position { -1.0_real, -1.0_real };
+
+void GUIPieChart::_notification(int what) {
+ if (what == NOTIFICATION_MOUSE_EXIT_SELF) {
+ tooltip_position = disabled_tooltip_position;
+
+ _update_tooltip();
+ }
+}
+
+void GUIPieChart::_gui_input(Ref<InputEvent> const& event) {
+ Ref<InputEventMouseMotion> mm = event;
+
+ if (mm.is_valid()) {
+ tooltip_position = mm->get_position() * 2.0_real / get_size() - Vector2 { 1.0_real, 1.0_real };
+
+ _update_tooltip();
+ }
+}
+
+GUIPieChart::GUIPieChart() : tooltip_active { false }, tooltip_position { disabled_tooltip_position } {}
+
Error GUIPieChart::set_gfx_pie_chart(GFX::PieChart const* gfx_pie_chart) {
const bool needs_setting = gfx_pie_chart_texture.is_null();
@@ -27,6 +89,10 @@ Error GUIPieChart::set_gfx_pie_chart(GFX::PieChart const* gfx_pie_chart) {
set_texture(gfx_pie_chart_texture);
}
+ if (tooltip_active) {
+ _update_tooltip();
+ }
+
return err;
}
@@ -50,6 +116,10 @@ Error GUIPieChart::set_gfx_pie_chart_name(String const& gfx_pie_chart_name) {
set_texture(gfx_pie_chart_texture);
}
+ if (tooltip_active) {
+ _update_tooltip();
+ }
+
return err;
}
@@ -59,8 +129,14 @@ String GUIPieChart::get_gfx_pie_chart_name() const {
return gfx_pie_chart_texture->get_gfx_pie_chart_name();
}
-Error GUIPieChart::set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices) const {
+Error GUIPieChart::set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices) {
ERR_FAIL_NULL_V(gfx_pie_chart_texture, FAILED);
- return gfx_pie_chart_texture->set_slices_array(new_slices);
+ const Error err = gfx_pie_chart_texture->set_slices_array(new_slices);
+
+ if (tooltip_active) {
+ _update_tooltip();
+ }
+
+ return err;
}
diff --git a/extension/src/openvic-extension/classes/GUIPieChart.hpp b/extension/src/openvic-extension/classes/GUIPieChart.hpp
index 6b4ac87..3356dba 100644
--- a/extension/src/openvic-extension/classes/GUIPieChart.hpp
+++ b/extension/src/openvic-extension/classes/GUIPieChart.hpp
@@ -12,10 +12,21 @@ namespace OpenVic {
godot::Ref<GFXPieChartTexture> gfx_pie_chart_texture;
+ bool tooltip_active;
+ godot::Vector2 tooltip_position;
+
+ void _update_tooltip();
+
protected:
static void _bind_methods();
+ void _notification(int what);
+
public:
+ void _gui_input(godot::Ref<godot::InputEvent> const& event) override;
+
+ GUIPieChart();
+
godot::Error set_gfx_pie_chart(GFX::PieChart const* gfx_pie_chart);
godot::Ref<GFXPieChartTexture> get_gfx_pie_chart_texture() const;
@@ -24,6 +35,6 @@ namespace OpenVic {
godot::String get_gfx_pie_chart_name() const;
- godot::Error set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices) const;
+ godot::Error set_slices_array(GFXPieChartTexture::godot_pie_chart_data_t const& new_slices);
};
}
diff --git a/extension/src/openvic-extension/classes/GUIProgressBar.cpp b/extension/src/openvic-extension/classes/GUIProgressBar.cpp
index 6021746..d13f455 100644
--- a/extension/src/openvic-extension/classes/GUIProgressBar.cpp
+++ b/extension/src/openvic-extension/classes/GUIProgressBar.cpp
@@ -8,7 +8,17 @@
using namespace godot;
using namespace OpenVic;
-void GUIProgressBar::_bind_methods() {}
+GUI_TOOLTIP_IMPLEMENTATIONS(GUIProgressBar)
+
+void GUIProgressBar::_bind_methods() {
+ GUI_TOOLTIP_BIND_METHODS(GUIProgressBar)
+}
+
+void GUIProgressBar::_notification(int what) {
+ _tooltip_notification(what);
+}
+
+GUIProgressBar::GUIProgressBar() : tooltip_active { false } {}
Error GUIProgressBar::set_gfx_progress_bar(GFX::ProgressBar const* progress_bar) {
ERR_FAIL_NULL_V(progress_bar, FAILED);
diff --git a/extension/src/openvic-extension/classes/GUIProgressBar.hpp b/extension/src/openvic-extension/classes/GUIProgressBar.hpp
index 130ac91..11b677a 100644
--- a/extension/src/openvic-extension/classes/GUIProgressBar.hpp
+++ b/extension/src/openvic-extension/classes/GUIProgressBar.hpp
@@ -3,15 +3,22 @@
#include <godot_cpp/classes/texture_progress_bar.hpp>
#include "openvic-simulation/interface/GFXSprite.hpp"
+#include "openvic-extension/classes/GUIHasTooltip.hpp"
namespace OpenVic {
class GUIProgressBar : public godot::TextureProgressBar {
GDCLASS(GUIProgressBar, godot::TextureProgressBar)
+ GUI_TOOLTIP_DEFINITIONS
+
protected:
static void _bind_methods();
+ void _notification(int what);
+
public:
+ GUIProgressBar();
+
godot::Error set_gfx_progress_bar(GFX::ProgressBar const* progress_bar);
};
}
diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.cpp b/extension/src/openvic-extension/classes/GUIScrollbar.cpp
index ddcba7c..6e310d7 100644
--- a/extension/src/openvic-extension/classes/GUIScrollbar.cpp
+++ b/extension/src/openvic-extension/classes/GUIScrollbar.cpp
@@ -18,7 +18,11 @@ StringName const& GUIScrollbar::signal_value_changed() {
return signal_value_changed;
}
+GUI_TOOLTIP_IMPLEMENTATIONS(GUIScrollbar)
+
void GUIScrollbar::_bind_methods() {
+ GUI_TOOLTIP_BIND_METHODS(GUIScrollbar)
+
OV_BIND_METHOD(GUIScrollbar::emit_value_changed);
OV_BIND_METHOD(GUIScrollbar::reset);
OV_BIND_METHOD(GUIScrollbar::clear);
@@ -49,7 +53,7 @@ void GUIScrollbar::_bind_methods() {
ADD_SIGNAL(MethodInfo(signal_value_changed(), PropertyInfo(Variant::INT, "value")));
}
-GUIScrollbar::GUIScrollbar() {
+GUIScrollbar::GUIScrollbar() : tooltip_active { false } {
/* Anything which the constructor might not have default initialised will be set by clear(). */
clear();
}
@@ -587,11 +591,16 @@ void GUIScrollbar::_gui_input(Ref<InputEvent> const& event) {
hover_more = !hover_more;
queue_redraw();
}
+
+ _set_tooltip_active(hover_slider || hover_track || hover_less || hover_more);
+
return;
}
}
void GUIScrollbar::_notification(int what) {
+ // GUIScrollbar doesn't use _tooltip_notification, as we don't want to show tooltips when hovering over transparent parts.
+
switch (what) {
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_MOUSE_EXIT: {
@@ -602,6 +611,10 @@ void GUIScrollbar::_notification(int what) {
queue_redraw();
} break;
+ case NOTIFICATION_MOUSE_EXIT_SELF: {
+ _set_tooltip_active(false);
+ } break;
+
/* Pressing (and holding) less and more buttons. */
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
const double delta = get_physics_process_delta_time();
diff --git a/extension/src/openvic-extension/classes/GUIScrollbar.hpp b/extension/src/openvic-extension/classes/GUIScrollbar.hpp
index 16b2e00..d9b22f1 100644
--- a/extension/src/openvic-extension/classes/GUIScrollbar.hpp
+++ b/extension/src/openvic-extension/classes/GUIScrollbar.hpp
@@ -6,11 +6,14 @@
#include <openvic-simulation/interface/GUI.hpp>
#include "openvic-extension/classes/GFXSpriteTexture.hpp"
+#include "openvic-extension/classes/GUIHasTooltip.hpp"
namespace OpenVic {
class GUIScrollbar : public godot::Control {
GDCLASS(GUIScrollbar, godot::Control)
+ GUI_TOOLTIP_DEFINITIONS
+
GUI::Scrollbar const* PROPERTY(gui_scrollbar);
godot::Ref<GFXSpriteTexture> slider_texture;
@@ -74,6 +77,7 @@ namespace OpenVic {
protected:
static void _bind_methods();
+
void _notification(int what);
public:
diff --git a/extension/src/openvic-extension/classes/GUITextureRect.cpp b/extension/src/openvic-extension/classes/GUITextureRect.cpp
index 13fd3bb..fba9b19 100644
--- a/extension/src/openvic-extension/classes/GUITextureRect.cpp
+++ b/extension/src/openvic-extension/classes/GUITextureRect.cpp
@@ -3,4 +3,14 @@
using namespace godot;
using namespace OpenVic;
-void GUITextureRect::_bind_methods() {}
+GUI_TOOLTIP_IMPLEMENTATIONS(GUITextureRect)
+
+void GUITextureRect::_bind_methods() {
+ GUI_TOOLTIP_BIND_METHODS(GUITextureRect)
+}
+
+void GUITextureRect::_notification(int what) {
+ _tooltip_notification(what);
+}
+
+GUITextureRect::GUITextureRect() : tooltip_active { false } {}
diff --git a/extension/src/openvic-extension/classes/GUITextureRect.hpp b/extension/src/openvic-extension/classes/GUITextureRect.hpp
index afcf4da..6fc8123 100644
--- a/extension/src/openvic-extension/classes/GUITextureRect.hpp
+++ b/extension/src/openvic-extension/classes/GUITextureRect.hpp
@@ -2,11 +2,20 @@
#include <godot_cpp/classes/texture_rect.hpp>
+#include "openvic-extension/classes/GUIHasTooltip.hpp"
+
namespace OpenVic {
class GUITextureRect : public godot::TextureRect {
GDCLASS(GUITextureRect, godot::TextureRect)
+ GUI_TOOLTIP_DEFINITIONS
+
protected:
static void _bind_methods();
+
+ void _notification(int what);
+
+ public:
+ GUITextureRect();
};
}
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.cpp b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
index b95bc15..367462b 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.cpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.cpp
@@ -30,6 +30,10 @@ StringName const& MenuSingleton::_signal_search_cache_changed() {
static const StringName signal_search_cache_changed = "search_cache_changed";
return signal_search_cache_changed;
}
+StringName const& MenuSingleton::_signal_update_tooltip() {
+ static const StringName signal_update_tooltip = "update_tooltip";
+ return signal_update_tooltip;
+}
String MenuSingleton::get_state_name(State const& state) const {
StateSet const& state_set = state.get_state_set();
@@ -103,6 +107,16 @@ String MenuSingleton::get_country_adjective(CountryInstance const& country) cons
}
void MenuSingleton::_bind_methods() {
+ /* TOOLTIP */
+ OV_BIND_METHOD(MenuSingleton::show_tooltip, { "text", "substitution_dict", "position" });
+ OV_BIND_METHOD(MenuSingleton::show_control_tooltip, { "text", "substitution_dict", "control" });
+ OV_BIND_METHOD(MenuSingleton::hide_tooltip);
+
+ ADD_SIGNAL(MethodInfo(
+ _signal_update_tooltip(), PropertyInfo(Variant::STRING, "text"),
+ PropertyInfo(Variant::DICTIONARY, "substitution_dict"), PropertyInfo(Variant::VECTOR2, "position")
+ ));
+
/* PROVINCE OVERVIEW PANEL */
OV_BIND_METHOD(MenuSingleton::get_province_info_from_index, { "index" });
OV_BIND_METHOD(MenuSingleton::get_province_building_count);
@@ -205,6 +219,25 @@ MenuSingleton::~MenuSingleton() {
singleton = nullptr;
}
+/* TOOLTIP */
+
+void MenuSingleton::show_tooltip(String const& text, Dictionary const& substitution_dict, Vector2 const& position) {
+ emit_signal(_signal_update_tooltip(), text, substitution_dict, position);
+}
+
+void MenuSingleton::show_control_tooltip(String const& text, Dictionary const& substitution_dict, Control const* control) {
+ ERR_FAIL_NULL(control);
+
+ using namespace OpenVic::Utilities::literals;
+ static const Vector2 offset { 0.0_real, 64.0_real };
+
+ show_tooltip(text, substitution_dict, control->get_global_position() + offset);
+}
+
+void MenuSingleton::hide_tooltip() {
+ show_tooltip({}, {}, {});
+}
+
/* PROVINCE OVERVIEW PANEL */
static TypedArray<Dictionary> _make_buildings_dict_array(
diff --git a/extension/src/openvic-extension/singletons/MenuSingleton.hpp b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
index 190e3ea..022bce5 100644
--- a/extension/src/openvic-extension/singletons/MenuSingleton.hpp
+++ b/extension/src/openvic-extension/singletons/MenuSingleton.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include <godot_cpp/classes/control.hpp>
#include <godot_cpp/classes/image.hpp>
#include <openvic-simulation/pop/Pop.hpp>
@@ -95,6 +96,10 @@ namespace OpenVic {
static godot::StringName const& _signal_population_menu_pops_changed();
/* Emitted when the collection of possible search results changes. */
static godot::StringName const& _signal_search_cache_changed();
+ /* Emitted when the current tooltip changes. Arguments: text (godot::String), substitution_dict (godot::Dictionary),
+ * position (godot::Vector2). If text is empty then the tooltip will be hidden, otherwise the text will be shown at
+ * the given position. */
+ static godot::StringName const& _signal_update_tooltip();
godot::String get_state_name(State const& state) const;
godot::String get_country_name(CountryInstance const& country) const;
@@ -110,6 +115,15 @@ namespace OpenVic {
MenuSingleton();
~MenuSingleton();
+ /* TOOLTIP */
+ void show_tooltip(
+ godot::String const& text, godot::Dictionary const& substitution_dict, godot::Vector2 const& position
+ );
+ void show_control_tooltip(
+ godot::String const& text, godot::Dictionary const& substitution_dict, godot::Control const* control
+ );
+ void hide_tooltip();
+
/* PROVINCE OVERVIEW PANEL */
/* Get info to display in Province Overview Panel, packaged in a Dictionary using StringName constants as keys. */
godot::Dictionary get_province_info_from_index(int32_t index) const;