Skip to content

Commit

Permalink
Merge pull request #253 from OpenVicProject/tooltip
Browse files Browse the repository at this point in the history
Tooltips
  • Loading branch information
Hop311 authored Aug 30, 2024
2 parents f54e454 + bdc2ba5 commit 2e0bc5b
Show file tree
Hide file tree
Showing 45 changed files with 1,952 additions and 750 deletions.
97 changes: 53 additions & 44 deletions extension/src/openvic-extension/classes/GFXPieChartTexture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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);
}
}
Expand Down
13 changes: 11 additions & 2 deletions extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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:
Expand Down
109 changes: 109 additions & 0 deletions extension/src/openvic-extension/classes/GUIButton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#include "GUIButton.hpp"

#include <array>

#include <godot_cpp/variant/utility_functions.hpp>

#include "openvic-extension/singletons/AssetManager.hpp"
#include "openvic-extension/utility/Utilities.hpp"

using namespace godot;
using namespace OpenVic;

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);

Error err = OK;

set_custom_minimum_size(texture->get_size());

{
Ref<StyleBoxTexture> stylebox = AssetManager::make_stylebox_texture(texture);

if (stylebox.is_valid()) {
static const StringName normal_theme = "normal";

add_theme_stylebox_override(normal_theme, stylebox);
} else {
UtilityFunctions::push_error("Failed to make StyleBoxTexture for GUIButton ", get_name());

err = FAILED;
}
}

using enum GFXButtonStateTexture::ButtonState;

for (GFXButtonStateTexture::ButtonState button_state : { HOVER, PRESSED, DISABLED }) {
Ref<GFXButtonStateTexture> button_state_texture = texture->get_button_state_texture(button_state);

if (button_state_texture.is_valid()) {
Ref<StyleBoxTexture> stylebox = AssetManager::make_stylebox_texture(button_state_texture);

if (stylebox.is_valid()) {
add_theme_stylebox_override(button_state_texture->get_button_state_name(), stylebox);
} else {
UtilityFunctions::push_error(
"Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state),
" StyleBoxTexture for GUIButton ", get_name()
);

err = FAILED;
}
} else {
UtilityFunctions::push_error(
"Failed to make ", GFXButtonStateTexture::button_state_to_name(button_state),
" GFXButtonStateTexture for GUIButton ", get_name()
);

err = FAILED;
}
}

return err;
}

Error GUIButton::set_gfx_font(GFX::Font const* gfx_font) {
ERR_FAIL_NULL_V(gfx_font, FAILED);

AssetManager* asset_manager = AssetManager::get_singleton();
ERR_FAIL_NULL_V(asset_manager, FAILED);

Error err = OK;

const StringName font_file = Utilities::std_to_godot_string(gfx_font->get_fontname());
const Ref<Font> font = asset_manager->get_font(font_file);

if (font.is_valid()) {
static const StringName font_theme = "font";

add_theme_font_override(font_theme, font);
} else {
UtilityFunctions::push_error("Failed to load font \"", font_file, "\" for GUIButton ", get_name());

err = FAILED;
}

static const std::array<StringName, 5> button_font_themes {
"font_color", "font_hover_color", "font_hover_pressed_color", "font_pressed_color", "font_disabled_color"
};

const Color colour = Utilities::to_godot_color(gfx_font->get_colour());

for (StringName const& theme_name : button_font_themes) {
add_theme_color_override(theme_name, colour);
}

return err;
}
28 changes: 28 additions & 0 deletions extension/src/openvic-extension/classes/GUIButton.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <godot_cpp/classes/button.hpp>

#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);
};
}
Loading

0 comments on commit 2e0bc5b

Please sign in to comment.