1 //  SuperTux
2 //  Copyright (C) 2020 A. Semphris <semphris@protonmail.com>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 #ifndef HEADER_SUPERTUX_INTERFACE_CONTROL_ENUM_HPP
18 #define HEADER_SUPERTUX_INTERFACE_CONTROL_ENUM_HPP
19 
20 #include <vector>
21 
22 #include "interface/control.hpp"
23 
24 template<class T>
25 class ControlEnum : public InterfaceControl
26 {
27 public:
28   ControlEnum();
29 
30   virtual void draw(DrawingContext& context) override;
31   virtual bool on_mouse_button_up(const SDL_MouseButtonEvent& button) override;
32   virtual bool on_mouse_button_down(const SDL_MouseButtonEvent& button) override;
33   virtual bool on_mouse_motion(const SDL_MouseMotionEvent& motion) override;
34   virtual bool on_key_up(const SDL_KeyboardEvent& key) override;
35   virtual bool on_key_down(const SDL_KeyboardEvent& key) override;
36 
get_value() const37   T get_value() const { return *m_value; }
set_value(T value)38   void set_value(T value) { *m_value = value; }
bind_value(T * value)39   void bind_value(T* value) { m_value = value; }
40 
add_option(T key,std::string label)41   void add_option(T key, std::string label) { m_options.push_back(std::make_pair(key, label)); }
42 
43 private:
44   T* m_value;
45   bool m_open_list;
46 
47   std::vector<std::pair<T, std::string>> m_options;
48   Vector m_mouse_pos;
49 
50 private:
51   ControlEnum(const ControlEnum&) = delete;
52   ControlEnum& operator=(const ControlEnum&) = delete;
53 };
54 
55 
56 
57 
58 
59 // ============================================================================
60 // ============================================================================
61 // ==============================   SOURCE   ==================================
62 // ============================================================================
63 // ============================================================================
64 
65 #include <math.h>
66 
67 #include "math/easing.hpp"
68 #include "math/vector.hpp"
69 #include "object/custom_particle_system.hpp"
70 #include "supertux/resources.hpp"
71 #include "video/video_system.hpp"
72 #include "video/viewport.hpp"
73 
74 template<class T>
ControlEnum()75 ControlEnum<T>::ControlEnum() :
76   m_value(),
77   m_open_list(false),
78   m_options(),
79   m_mouse_pos(0.0f, 0.0f)
80 {
81 }
82 
83 template<class T>
84 void
draw(DrawingContext & context)85 ControlEnum<T>::draw(DrawingContext& context)
86 {
87   InterfaceControl::draw(context);
88 
89   context.color().draw_filled_rect(m_rect,
90                                    m_has_focus ? Color(0.75f, 0.75f, 0.7f, 1.f)
91                                                : Color(0.5f, 0.5f, 0.5f, 1.f),
92                                    LAYER_GUI);
93 
94   std::string label;
95   auto it = std::find_if(m_options.begin(), m_options.end(), [this](const auto &a) { return a.first == *m_value; });
96   if (it != m_options.end()) {
97     label = it->second;
98   } else {
99     label = "<invalid>";
100   }
101 
102   context.color().draw_text(Resources::control_font,
103                             label,
104                             Vector(m_rect.get_left() + 5.f,
105                                    (m_rect.get_top() + m_rect.get_bottom()) / 2 -
106                                     Resources::control_font->get_height() / 2),
107                             FontAlignment::ALIGN_LEFT,
108                             LAYER_GUI + 1,
109                             Color::BLACK);
110   int i = 0;
111   if (m_open_list) {
112     for (const auto& option : m_options) {
113       i++;
114       Rectf box = m_rect.moved(Vector(0.f, m_rect.get_height() * float(i)));
115       context.color().draw_filled_rect(box.grown(2.f).moved(Vector(0,4.f)), Color(0.f, 0.f, 0.f, 0.1f), 2.f, LAYER_GUI + 4);
116       context.color().draw_filled_rect(box.grown(4.f).moved(Vector(0,4.f)), Color(0.f, 0.f, 0.f, 0.1f), 2.f, LAYER_GUI + 4);
117       context.color().draw_filled_rect(box.grown(6.f).moved(Vector(0,4.f)), Color(0.f, 0.f, 0.f, 0.1f), 2.f, LAYER_GUI + 4);
118       context.color().draw_filled_rect(box,
119                                        (box.contains(m_mouse_pos)
120                                          || option.first == *m_value)
121                                            ? Color(0.75f, 0.75f, 0.7f, 1.f)
122                                            : Color(0.5f, 0.5f, 0.5f, 1.f),
123                                        LAYER_GUI + 5);
124 
125       std::string label2 = option.second;
126 
127       context.color().draw_text(Resources::control_font,
128                                 label2,
129                                 Vector(m_rect.get_left() + 5.f,
130                                        (m_rect.get_top() + m_rect.get_bottom()) / 2 -
131                                         Resources::control_font->get_height() / 2 +
132                                         m_rect.get_height() * float(i)),
133                                 FontAlignment::ALIGN_LEFT,
134                                 LAYER_GUI + 6,
135                                 Color::BLACK);
136     }
137   }
138 }
139 
140 template<class T>
141 bool
on_mouse_button_up(const SDL_MouseButtonEvent & button)142 ControlEnum<T>::on_mouse_button_up(const SDL_MouseButtonEvent& button)
143 {
144   if (button.button != SDL_BUTTON_LEFT)
145     return false;
146 
147   Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y);
148   if (m_rect.contains(mouse_pos)) {
149     m_open_list = !m_open_list;
150     m_has_focus = true;
151     return true;
152   } else if (Rectf(m_rect.get_left(),
153              m_rect.get_top(),
154              m_rect.get_right(),
155              m_rect.get_bottom() + m_rect.get_height() * float(m_options.size())
156             ).contains(mouse_pos) && m_open_list) {
157     return true;
158   } else {
159     return false;
160   }
161 }
162 
163 template<class T>
164 bool
on_mouse_button_down(const SDL_MouseButtonEvent & button)165 ControlEnum<T>::on_mouse_button_down(const SDL_MouseButtonEvent& button)
166 {
167   Vector mouse_pos = VideoSystem::current()->get_viewport().to_logical(button.x, button.y);
168   if (m_open_list) {
169     if (!Rectf(m_rect.get_left(),
170                m_rect.get_top(),
171                m_rect.get_right(),
172                m_rect.get_bottom() + m_rect.get_height() * float(m_options.size())
173               ).contains(mouse_pos)) {
174       m_has_focus = false;
175       m_open_list = false;
176     } else {
177       int pos = int(floor((mouse_pos.y - m_rect.get_bottom()) / m_rect.get_height()));
178       if (pos != -1) {
179         // This verification shouldn't be needed but I don't trust myself
180         if (pos >= 0 && pos < int(m_options.size())) {
181           // Yes. We need this. Because I can't acced by numerical index.
182           // There's probably a way, but I'm too bored to investigate.
183           for (const auto& option : m_options) {
184             if (--pos != -1) continue;
185             *m_value = option.first;
186 
187             if (m_on_change)
188               m_on_change();
189 
190             break;
191           }
192         } else {
193           log_warning << "Clicked on control enum inside dropdown but at invalid position ("
194                       << pos << " for a size of " << m_options.size() << ")" << std::endl;
195         }
196       }
197       return true;
198     }
199   } else {
200     if (!m_rect.contains(mouse_pos)) {
201       m_has_focus = false;
202       m_open_list = false;
203     }
204   }
205   return false;
206 }
207 
208 template<class T>
209 bool
on_mouse_motion(const SDL_MouseMotionEvent & motion)210 ControlEnum<T>::on_mouse_motion(const SDL_MouseMotionEvent& motion)
211 {
212   InterfaceControl::on_mouse_motion(motion);
213 
214   m_mouse_pos = VideoSystem::current()->get_viewport().to_logical(motion.x, motion.y);
215   return false;
216 }
217 
218 template<class T>
219 bool
on_key_up(const SDL_KeyboardEvent & key)220 ControlEnum<T>::on_key_up(const SDL_KeyboardEvent& key)
221 {
222   if ((key.keysym.sym == SDLK_SPACE
223     || key.keysym.sym == SDLK_RETURN
224     || key.keysym.sym == SDLK_RETURN2) && m_has_focus) {
225     m_open_list = !m_open_list;
226     return true;
227   } else {
228     return false;
229   }
230 }
231 
232 template<class T>
233 bool
on_key_down(const SDL_KeyboardEvent & key)234 ControlEnum<T>::on_key_down(const SDL_KeyboardEvent& key)
235 {
236   if (!m_has_focus)
237     return false;
238 
239   if (key.keysym.sym == SDLK_DOWN) {
240     bool is_next = false;
241     // Hacky way to get the next one in the list
242     for (const auto& option : m_options) {
243       if (is_next) {
244         *m_value = option.first;
245         is_next = false;
246         break;
247       } else if (option.first == *m_value) {
248         is_next = true;
249       }
250     }
251 
252     // if we're at the last index, loop back to the beginning
253     if (is_next && !m_options.empty())
254       *m_value = m_options.begin()->first;
255 
256     if (m_on_change)
257       m_on_change();
258 
259     return true;
260   } else if (key.keysym.sym == SDLK_UP) {
261 
262     bool is_last = false;
263     bool currently_on_first = true;
264     T last_value = *m_value; // must assign a value else clang will complain
265 
266     // Hacky way to get the preceeding one in the list
267     for (const auto& option : m_options) {
268       if (option.first == *m_value) {
269         if (currently_on_first) {
270           is_last = true;
271         } else {
272           *m_value = last_value;
273         }
274       }
275       last_value = option.first;
276       currently_on_first = false;
277     }
278 
279     if (is_last)
280       *m_value = last_value;
281 
282     if (m_on_change)
283       m_on_change();
284 
285     return true;
286   }
287 
288   return false;
289 }
290 
291 #endif
292 
293 /* EOF */
294