aboutsummaryrefslogtreecommitdiff
path: root/extension/src/openvic-extension/classes/GFXPieChartTexture.hpp
blob: 3610efb055cfe72839feb61599b69935ae8abe22 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#pragma once

#include <godot_cpp/classes/image_texture.hpp>

#include <openvic-simulation/interface/GFXSprite.hpp>

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

namespace OpenVic {
   class GFXPieChartTexture : public godot::ImageTexture {
      GDCLASS(GFXPieChartTexture, godot::ImageTexture)

   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 PROPERTY(total_weight);
      godot::Ref<godot::Image> pie_chart_image;

      static godot::StringName const& _slice_identifier_key();
      static godot::StringName const& _slice_colour_key();
      static godot::StringName const& _slice_weight_key();

      godot::Error _generate_pie_chart_image();

   protected:
      static void _bind_methods();

   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:
       *  - colour: Color
       *  - weight: float */
      godot::Error set_slices_array(godot_pie_chart_data_t const& new_slices);

      /* Generate slice data from a distribution of HasIdentifierAndColour derived objects, sorted by their weight.
       * The resulting Array of Dictionaries can be used as an argument for set_slices_array. */
      template<typename Container>
      static godot_pie_chart_data_t distribution_to_slices_array(Container const& dist)
      requires(
         (
            /* fixed_point_map_t<T const*>, T derived from HasIdentifierAndColour */
            utility::is_specialization_of_v<Container, tsl::ordered_map> &&
            std::derived_from<std::remove_pointer_t<typename Container::key_type>, HasIdentifierAndColour> &&
            std::is_same_v<typename Container::mapped_type, fixed_point_t>
         ) || (
            /* IndexedMap<T, fixed_point_t>, T derived from HasIdentifierAndColour */
            utility::is_specialization_of_v<Container, IndexedMap> &&
            std::derived_from<typename Container::key_t, HasIdentifierAndColour> &&
            std::is_same_v<typename Container::value_t, fixed_point_t>
         )
      ) {
         using namespace godot;
         using entry_t = std::pair<HasIdentifierAndColour const*, fixed_point_t>;
         std::vector<entry_t> sorted_dist;

         if constexpr (utility::is_specialization_of_v<Container, tsl::ordered_map>) {
            sorted_dist.reserve(dist.size());
            for (entry_t const& entry : dist) {
               ERR_CONTINUE_MSG(
                  entry.first == nullptr, vformat("Null distribution key with value %f", entry.second.to_float())
               );
               sorted_dist.push_back(entry);
            }
         } else {
            for (size_t index = 0; index < dist.size(); ++index) {
               fixed_point_t const& value = dist[index];
               if (value != 0) {
                  HasIdentifierAndColour const* key = &dist(index);
                  sorted_dist.emplace_back(key, value);
               }
            }
         }

         std::sort(sorted_dist.begin(), sorted_dist.end(), [](entry_t const& lhs, entry_t const& rhs) -> bool {
            return lhs.first < rhs.first;
         });

         godot_pie_chart_data_t array;
         ERR_FAIL_COND_V(array.resize(sorted_dist.size()) != OK, {});

         for (size_t idx = 0; idx < array.size(); ++idx) {
            auto const& [key, val] = sorted_dist[idx];
            Dictionary sub_dict;
            sub_dict[_slice_identifier_key()] = Utilities::std_to_godot_string(key->get_identifier());
            sub_dict[_slice_colour_key()] = Utilities::to_godot_color(key->get_colour());
            sub_dict[_slice_weight_key()] = val.to_float();
            array[idx] = std::move(sub_dict);
         }
         return array;
      }

      /* Create a GFXPieChartTexture using the specified GFX::PieChart. Returns nullptr if gfx_pie_chart fails. */
      static godot::Ref<GFXPieChartTexture> make_gfx_pie_chart_texture(GFX::PieChart const* gfx_pie_chart);

      /* Reset gfx_pie_chart, flag_country and flag_type to nullptr/an empty string, and unreference all images.
       * This does not affect the godot::ImageTexture, which cannot be reset to a null or empty image. */
      void clear();

      /* Set the GFX::PieChart and regenerate the pie chart image. */
      godot::Error set_gfx_pie_chart(GFX::PieChart const* new_gfx_pie_chart);

      /* Search for a GFX::PieChart with the specfied name and, if successful, set it using set_gfx_pie_chart. */
      godot::Error set_gfx_pie_chart_name(godot::String const& gfx_pie_chart_name);

      /* Return the name of the GFX::PieChart, or an empty String if it's null. */
      godot::String get_gfx_pie_chart_name() const;
   };
}