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