Add warning when disabling secure.enable_security (#9943)
[oweals/minetest.git] / src / gui / touchscreengui.cpp
1 /*
2 Copyright (C) 2014 sapier
3 Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto
4                 <muhammadrifqipriyosusanto@gmail.com>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "touchscreengui.h"
22 #include "irrlichttypes.h"
23 #include "irr_v2d.h"
24 #include "log.h"
25 #include "client/keycode.h"
26 #include "settings.h"
27 #include "gettime.h"
28 #include "util/numeric.h"
29 #include "porting.h"
30 #include "client/guiscalingfilter.h"
31
32 #include <iostream>
33 #include <algorithm>
34
35 #include <ISceneCollisionManager.h>
36
37 using namespace irr::core;
38
39 const char **button_imagenames = (const char *[]) {
40         "jump_btn.png",
41         "down.png",
42         "zoom.png",
43         "aux_btn.png"
44 };
45
46 const char **joystick_imagenames = (const char *[]) {
47         "joystick_off.png",
48         "joystick_bg.png",
49         "joystick_center.png"
50 };
51
52 static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
53 {
54         std::string key = "";
55         switch (id) {
56                 case forward_id:
57                         key = "forward";
58                         break;
59                 case left_id:
60                         key = "left";
61                         break;
62                 case right_id:
63                         key = "right";
64                         break;
65                 case backward_id:
66                         key = "backward";
67                         break;
68                 case inventory_id:
69                         key = "inventory";
70                         break;
71                 case drop_id:
72                         key = "drop";
73                         break;
74                 case jump_id:
75                         key = "jump";
76                         break;
77                 case crunch_id:
78                         key = "sneak";
79                         break;
80                 case zoom_id:
81                         key = "zoom";
82                         break;
83                 case special1_id:
84                         key = "special1";
85                         break;
86                 case fly_id:
87                         key = "freemove";
88                         break;
89                 case noclip_id:
90                         key = "noclip";
91                         break;
92                 case fast_id:
93                         key = "fastmove";
94                         break;
95                 case debug_id:
96                         key = "toggle_debug";
97                         break;
98                 case toggle_chat_id:
99                         key = "toggle_chat";
100                         break;
101                 case minimap_id:
102                         key = "minimap";
103                         break;
104                 case chat_id:
105                         key = "chat";
106                         break;
107                 case camera_id:
108                         key = "camera_mode";
109                         break;
110                 case range_id:
111                         key = "rangeselect";
112                         break;
113                 default:
114                         break;
115         }
116         assert(!key.empty());
117         return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
118 }
119
120 TouchScreenGUI *g_touchscreengui;
121
122 static void load_button_texture(button_info *btn, const char *path,
123                 const rect<s32> &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver)
124 {
125         unsigned int tid;
126         video::ITexture *texture = guiScalingImageButton(driver,
127                         tsrc->getTexture(path, &tid), button_rect.getWidth(),
128                         button_rect.getHeight());
129         if (texture) {
130                 btn->guibutton->setUseAlphaChannel(true);
131                 if (g_settings->getBool("gui_scaling_filter")) {
132                         rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
133                         btn->guibutton->setImage(texture, txr_rect);
134                         btn->guibutton->setPressedImage(texture, txr_rect);
135                         btn->guibutton->setScaleImage(false);
136                 } else {
137                         btn->guibutton->setImage(texture);
138                         btn->guibutton->setPressedImage(texture);
139                         btn->guibutton->setScaleImage(true);
140                 }
141                 btn->guibutton->setDrawBorder(false);
142                 btn->guibutton->setText(L"");
143         }
144 }
145
146 AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
147                 IEventReceiver *receiver) :
148                         m_driver(device->getVideoDriver()),
149                         m_guienv(device->getGUIEnvironment()),
150                         m_receiver(receiver)
151 {
152 }
153
154 void AutoHideButtonBar::init(ISimpleTextureSource *tsrc,
155                 const char *starter_img, int button_id, const v2s32 &UpperLeft,
156                 const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout)
157 {
158         m_texturesource = tsrc;
159
160         m_upper_left = UpperLeft;
161         m_lower_right = LowerRight;
162
163         // init settings bar
164
165         irr::core::rect<int> current_button = rect<s32>(UpperLeft.X, UpperLeft.Y,
166                         LowerRight.X, LowerRight.Y);
167
168         m_starter.guibutton         = m_guienv->addButton(current_button, nullptr, button_id, L"", nullptr);
169         m_starter.guibutton->grab();
170         m_starter.repeatcounter     = -1;
171         m_starter.keycode           = KEY_OEM_8; // use invalid keycode as it's not relevant
172         m_starter.immediate_release = true;
173         m_starter.ids.clear();
174
175         load_button_texture(&m_starter, starter_img, current_button,
176                         m_texturesource, m_driver);
177
178         m_dir = dir;
179         m_timeout_value = timeout;
180
181         m_initialized = true;
182 }
183
184 AutoHideButtonBar::~AutoHideButtonBar()
185 {
186         if (m_starter.guibutton) {
187                 m_starter.guibutton->setVisible(false);
188                 m_starter.guibutton->drop();
189         }
190 }
191
192 void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
193                 const wchar_t *caption, const char *btn_image)
194 {
195
196         if (!m_initialized) {
197                 errorstream << "AutoHideButtonBar::addButton not yet initialized!"
198                                 << std::endl;
199                 return;
200         }
201         int button_size = 0;
202
203         if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top))
204                 button_size = m_lower_right.X - m_upper_left.X;
205         else
206                 button_size = m_lower_right.Y - m_upper_left.Y;
207
208         irr::core::rect<int> current_button;
209
210         if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) {
211                 int x_start = 0;
212                 int x_end = 0;
213
214                 if (m_dir == AHBB_Dir_Left_Right) {
215                         x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
216                                         + (button_size * 0.25);
217                         x_end = x_start + button_size;
218                 } else {
219                         x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
220                                         - (button_size * 0.25);
221                         x_start = x_end - button_size;
222                 }
223
224                 current_button = rect<s32>(x_start, m_upper_left.Y, x_end,
225                                 m_lower_right.Y);
226         } else {
227                 double y_start = 0;
228                 double y_end = 0;
229
230                 if (m_dir == AHBB_Dir_Top_Bottom) {
231                         y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
232                                         + (button_size * 0.25);
233                         y_end = y_start + button_size;
234                 } else {
235                         y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
236                                         - (button_size * 0.25);
237                         y_start = y_end - button_size;
238                 }
239
240                 current_button = rect<s32>(m_upper_left.X, y_start,
241                                 m_lower_right.Y, y_end);
242         }
243
244         auto *btn              = new button_info();
245         btn->guibutton         = m_guienv->addButton(current_button,
246                                         nullptr, button_id, caption, nullptr);
247         btn->guibutton->grab();
248         btn->guibutton->setVisible(false);
249         btn->guibutton->setEnabled(false);
250         btn->repeatcounter     = -1;
251         btn->keycode           = id2keycode(button_id);
252         btn->immediate_release = true;
253         btn->ids.clear();
254
255         load_button_texture(btn, btn_image, current_button, m_texturesource,
256                         m_driver);
257
258         m_buttons.push_back(btn);
259 }
260
261 void AutoHideButtonBar::addToggleButton(touch_gui_button_id button_id,
262                 const wchar_t *caption, const char *btn_image_1,
263                 const char *btn_image_2)
264 {
265         addButton(button_id, caption, btn_image_1);
266         button_info *btn = m_buttons.back();
267         btn->togglable = 1;
268         btn->textures.push_back(btn_image_1);
269         btn->textures.push_back(btn_image_2);
270 }
271
272 bool AutoHideButtonBar::isButton(const SEvent &event)
273 {
274         IGUIElement *rootguielement = m_guienv->getRootGUIElement();
275
276         if (rootguielement == nullptr)
277                 return false;
278
279         gui::IGUIElement *element = rootguielement->getElementFromPoint(
280                         core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
281
282         if (element == nullptr)
283                 return false;
284
285         if (m_active) {
286                 // check for all buttons in vector
287                 auto iter = m_buttons.begin();
288
289                 while (iter != m_buttons.end()) {
290                         if ((*iter)->guibutton == element) {
291
292                                 auto *translated = new SEvent();
293                                 memset(translated, 0, sizeof(SEvent));
294                                 translated->EventType            = irr::EET_KEY_INPUT_EVENT;
295                                 translated->KeyInput.Key         = (*iter)->keycode;
296                                 translated->KeyInput.Control     = false;
297                                 translated->KeyInput.Shift       = false;
298                                 translated->KeyInput.Char        = 0;
299
300                                 // add this event
301                                 translated->KeyInput.PressedDown = true;
302                                 m_receiver->OnEvent(*translated);
303
304                                 // remove this event
305                                 translated->KeyInput.PressedDown = false;
306                                 m_receiver->OnEvent(*translated);
307
308                                 delete translated;
309
310                                 (*iter)->ids.push_back(event.TouchInput.ID);
311
312                                 m_timeout = 0;
313
314                                 if ((*iter)->togglable == 1) {
315                                         (*iter)->togglable = 2;
316                                         load_button_texture(*iter, (*iter)->textures[1],
317                                                         (*iter)->guibutton->getRelativePosition(),
318                                                         m_texturesource, m_driver);
319                                 } else if ((*iter)->togglable == 2) {
320                                         (*iter)->togglable = 1;
321                                         load_button_texture(*iter, (*iter)->textures[0],
322                                                         (*iter)->guibutton->getRelativePosition(),
323                                                         m_texturesource, m_driver);
324                                 }
325
326                                 return true;
327                         }
328                         ++iter;
329                 }
330         } else {
331                 // check for starter button only
332                 if (element == m_starter.guibutton) {
333                         m_starter.ids.push_back(event.TouchInput.ID);
334                         m_starter.guibutton->setVisible(false);
335                         m_starter.guibutton->setEnabled(false);
336                         m_active = true;
337                         m_timeout = 0;
338
339                         auto iter = m_buttons.begin();
340
341                         while (iter != m_buttons.end()) {
342                                 (*iter)->guibutton->setVisible(true);
343                                 (*iter)->guibutton->setEnabled(true);
344                                 ++iter;
345                         }
346
347                         return true;
348                 }
349         }
350         return false;
351 }
352
353 void AutoHideButtonBar::step(float dtime)
354 {
355         if (m_active) {
356                 m_timeout += dtime;
357
358                 if (m_timeout > m_timeout_value)
359                         deactivate();
360         }
361 }
362
363 void AutoHideButtonBar::deactivate()
364 {
365         if (m_visible) {
366                 m_starter.guibutton->setVisible(true);
367                 m_starter.guibutton->setEnabled(true);
368         }
369         m_active = false;
370
371         auto iter = m_buttons.begin();
372
373         while (iter != m_buttons.end()) {
374                 (*iter)->guibutton->setVisible(false);
375                 (*iter)->guibutton->setEnabled(false);
376                 ++iter;
377         }
378 }
379
380 void AutoHideButtonBar::hide()
381 {
382         m_visible = false;
383         m_starter.guibutton->setVisible(false);
384         m_starter.guibutton->setEnabled(false);
385
386         auto iter = m_buttons.begin();
387
388         while (iter != m_buttons.end()) {
389                 (*iter)->guibutton->setVisible(false);
390                 (*iter)->guibutton->setEnabled(false);
391                 ++iter;
392         }
393 }
394
395 void AutoHideButtonBar::show()
396 {
397         m_visible = true;
398
399         if (m_active) {
400                 auto iter = m_buttons.begin();
401
402                 while (iter != m_buttons.end()) {
403                         (*iter)->guibutton->setVisible(true);
404                         (*iter)->guibutton->setEnabled(true);
405                         ++iter;
406                 }
407         } else {
408                 m_starter.guibutton->setVisible(true);
409                 m_starter.guibutton->setEnabled(true);
410         }
411 }
412
413 TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver):
414         m_device(device),
415         m_guienv(device->getGUIEnvironment()),
416         m_receiver(receiver),
417         m_settingsbar(device, receiver),
418         m_rarecontrolsbar(device, receiver)
419 {
420         for (auto &button : m_buttons) {
421                 button.guibutton     = nullptr;
422                 button.repeatcounter = -1;
423                 button.repeatdelay   = BUTTON_REPEAT_DELAY;
424         }
425
426         m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold");
427         m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick");
428         m_joystick_triggers_special1 = g_settings->getBool("virtual_joystick_triggers_aux");
429         m_screensize = m_device->getVideoDriver()->getScreenSize();
430         button_size = MYMIN(m_screensize.Y / 4.5f,
431                         porting::getDisplayDensity() *
432                         g_settings->getFloat("hud_scaling") * 65.0f);
433 }
434
435 void TouchScreenGUI::initButton(touch_gui_button_id id, const rect<s32> &button_rect,
436                 const std::wstring &caption, bool immediate_release, float repeat_delay)
437 {
438         button_info *btn       = &m_buttons[id];
439         btn->guibutton         = m_guienv->addButton(button_rect, nullptr, id, caption.c_str());
440         btn->guibutton->grab();
441         btn->repeatcounter     = -1;
442         btn->repeatdelay       = repeat_delay;
443         btn->keycode           = id2keycode(id);
444         btn->immediate_release = immediate_release;
445         btn->ids.clear();
446
447         load_button_texture(btn, button_imagenames[id], button_rect,
448                         m_texturesource, m_device->getVideoDriver());
449 }
450
451 button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id,
452                 const rect<s32> &button_rect, int texture_id, bool visible)
453 {
454         auto *btn = new button_info();
455         btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, L"O");
456         btn->guibutton->setVisible(visible);
457         btn->guibutton->grab();
458         btn->ids.clear();
459
460         load_button_texture(btn, joystick_imagenames[texture_id],
461                         button_rect, m_texturesource, m_device->getVideoDriver());
462
463         return btn;
464 }
465
466 void TouchScreenGUI::init(ISimpleTextureSource *tsrc)
467 {
468         assert(tsrc);
469
470         m_visible       = true;
471         m_texturesource = tsrc;
472
473         /* Init joystick display "button"
474          * Joystick is placed on bottom left of screen.
475          */
476         if (m_fixed_joystick) {
477                 m_joystick_btn_off = initJoystickButton(joystick_off_id,
478                                 rect<s32>(button_size,
479                                                 m_screensize.Y - button_size * 4,
480                                                 button_size * 4,
481                                                 m_screensize.Y - button_size), 0);
482         } else {
483                 m_joystick_btn_off = initJoystickButton(joystick_off_id,
484                                 rect<s32>(button_size,
485                                                 m_screensize.Y - button_size * 3,
486                                                 button_size * 3,
487                                                 m_screensize.Y - button_size), 0);
488         }
489
490         m_joystick_btn_bg = initJoystickButton(joystick_bg_id,
491                         rect<s32>(button_size,
492                                         m_screensize.Y - button_size * 4,
493                                         button_size * 4,
494                                         m_screensize.Y - button_size),
495                         1, false);
496
497         m_joystick_btn_center = initJoystickButton(joystick_center_id,
498                         rect<s32>(0, 0, button_size, button_size), 2, false);
499
500         // init jump button
501         initButton(jump_id,
502                         rect<s32>(m_screensize.X - (1.75 * button_size),
503                                         m_screensize.Y - button_size,
504                                         m_screensize.X - (0.25 * button_size),
505                                         m_screensize.Y),
506                         L"x", false);
507
508         // init crunch button
509         initButton(crunch_id,
510                         rect<s32>(m_screensize.X - (3.25 * button_size),
511                                         m_screensize.Y - button_size,
512                                         m_screensize.X - (1.75 * button_size),
513                                         m_screensize.Y),
514                         L"H", false);
515
516         // init zoom button
517         initButton(zoom_id,
518                         rect<s32>(m_screensize.X - (1.25 * button_size),
519                                         m_screensize.Y - (4 * button_size),
520                                         m_screensize.X - (0.25 * button_size),
521                                         m_screensize.Y - (3 * button_size)),
522                         L"z", false);
523
524         // init special1/aux button
525         if (!m_joystick_triggers_special1)
526                 initButton(special1_id,
527                                 rect<s32>(m_screensize.X - (1.25 * button_size),
528                                                 m_screensize.Y - (2.5 * button_size),
529                                                 m_screensize.X - (0.25 * button_size),
530                                                 m_screensize.Y - (1.5 * button_size)),
531                                 L"spc1", false);
532
533         m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id,
534                 v2s32(m_screensize.X - (1.25 * button_size),
535                         m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1.0) * button_size)
536                                 + (0.5 * button_size)),
537                 v2s32(m_screensize.X - (0.25 * button_size),
538                         m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size)
539                                 + (0.5 * button_size)),
540                 AHBB_Dir_Right_Left, 3.0);
541
542         m_settingsbar.addButton(fly_id,     L"fly",       "fly_btn.png");
543         m_settingsbar.addButton(noclip_id,  L"noclip",    "noclip_btn.png");
544         m_settingsbar.addButton(fast_id,    L"fast",      "fast_btn.png");
545         m_settingsbar.addButton(debug_id,   L"debug",     "debug_btn.png");
546         m_settingsbar.addButton(camera_id,  L"camera",    "camera_btn.png");
547         m_settingsbar.addButton(range_id,   L"rangeview", "rangeview_btn.png");
548         m_settingsbar.addButton(minimap_id, L"minimap",   "minimap_btn.png");
549
550         // Chat is shown by default, so chat_hide_btn.png is shown first.
551         m_settingsbar.addToggleButton(toggle_chat_id, L"togglechat",
552                         "chat_hide_btn.png", "chat_show_btn.png");
553
554         m_rarecontrolsbar.init(m_texturesource, "rare_controls.png",
555                 rare_controls_starter_id,
556                 v2s32(0.25 * button_size,
557                         m_screensize.Y - ((RARE_CONTROLS_BAR_Y_OFFSET + 1.0) * button_size)
558                                 + (0.5 * button_size)),
559                 v2s32(0.75 * button_size,
560                         m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size)
561                                 + (0.5 * button_size)),
562                 AHBB_Dir_Left_Right, 2.0);
563
564         m_rarecontrolsbar.addButton(chat_id,      L"Chat", "chat_btn.png");
565         m_rarecontrolsbar.addButton(inventory_id, L"inv",  "inventory_btn.png");
566         m_rarecontrolsbar.addButton(drop_id,      L"drop", "drop_btn.png");
567 }
568
569 touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
570 {
571         IGUIElement *rootguielement = m_guienv->getRootGUIElement();
572
573         if (rootguielement != nullptr) {
574                 gui::IGUIElement *element =
575                                 rootguielement->getElementFromPoint(core::position2d<s32>(x, y));
576
577                 if (element)
578                         for (unsigned int i = 0; i < after_last_element_id; i++)
579                                 if (element == m_buttons[i].guibutton)
580                                         return (touch_gui_button_id) i;
581         }
582
583         return after_last_element_id;
584 }
585
586 touch_gui_button_id TouchScreenGUI::getButtonID(size_t eventID)
587 {
588         for (unsigned int i = 0; i < after_last_element_id; i++) {
589                 button_info *btn = &m_buttons[i];
590
591                 auto id = std::find(btn->ids.begin(), btn->ids.end(), eventID);
592
593                 if (id != btn->ids.end())
594                         return (touch_gui_button_id) i;
595         }
596
597         return after_last_element_id;
598 }
599
600 bool TouchScreenGUI::isHUDButton(const SEvent &event)
601 {
602         // check if hud item is pressed
603         for (auto &hud_rect : m_hud_rects) {
604                 if (hud_rect.second.isPointInside(v2s32(event.TouchInput.X,
605                                 event.TouchInput.Y))) {
606                         auto *translated = new SEvent();
607                         memset(translated, 0, sizeof(SEvent));
608                         translated->EventType = irr::EET_KEY_INPUT_EVENT;
609                         translated->KeyInput.Key         = (irr::EKEY_CODE) (KEY_KEY_1 + hud_rect.first);
610                         translated->KeyInput.Control     = false;
611                         translated->KeyInput.Shift       = false;
612                         translated->KeyInput.PressedDown = true;
613                         m_receiver->OnEvent(*translated);
614                         m_hud_ids[event.TouchInput.ID]   = translated->KeyInput.Key;
615                         delete translated;
616                         return true;
617                 }
618         }
619         return false;
620 }
621
622 void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
623                 size_t eventID, bool action)
624 {
625         button_info *btn = &m_buttons[button];
626         auto *translated = new SEvent();
627         memset(translated, 0, sizeof(SEvent));
628         translated->EventType            = irr::EET_KEY_INPUT_EVENT;
629         translated->KeyInput.Key         = btn->keycode;
630         translated->KeyInput.Control     = false;
631         translated->KeyInput.Shift       = false;
632         translated->KeyInput.Char        = 0;
633
634         // add this event
635         if (action) {
636                 assert(std::find(btn->ids.begin(), btn->ids.end(), eventID) == btn->ids.end());
637
638                 btn->ids.push_back(eventID);
639
640                 if (btn->ids.size() > 1) return;
641
642                 btn->repeatcounter = 0;
643                 translated->KeyInput.PressedDown = true;
644                 translated->KeyInput.Key = btn->keycode;
645                 m_receiver->OnEvent(*translated);
646         }
647
648         // remove event
649         if ((!action) || (btn->immediate_release)) {
650                 auto pos = std::find(btn->ids.begin(), btn->ids.end(), eventID);
651                 // has to be in touch list
652                 assert(pos != btn->ids.end());
653                 btn->ids.erase(pos);
654
655                 if (!btn->ids.empty())
656                         return;
657
658                 translated->KeyInput.PressedDown = false;
659                 btn->repeatcounter               = -1;
660                 m_receiver->OnEvent(*translated);
661         }
662         delete translated;
663 }
664
665 void TouchScreenGUI::handleReleaseEvent(size_t evt_id)
666 {
667         touch_gui_button_id button = getButtonID(evt_id);
668
669
670         if (button != after_last_element_id) {
671                 // handle button events
672                 handleButtonEvent(button, evt_id, false);
673         } else if (evt_id == m_move_id) {
674                 // handle the point used for moving view
675                 m_move_id = -1;
676
677                 // if this pointer issued a mouse event issue symmetric release here
678                 if (m_move_sent_as_mouse_event) {
679                         auto *translated = new SEvent;
680                         memset(translated, 0, sizeof(SEvent));
681                         translated->EventType               = EET_MOUSE_INPUT_EVENT;
682                         translated->MouseInput.X            = m_move_downlocation.X;
683                         translated->MouseInput.Y            = m_move_downlocation.Y;
684                         translated->MouseInput.Shift        = false;
685                         translated->MouseInput.Control      = false;
686                         translated->MouseInput.ButtonStates = 0;
687                         translated->MouseInput.Event        = EMIE_LMOUSE_LEFT_UP;
688                         m_receiver->OnEvent(*translated);
689                         delete translated;
690                 } else {
691                         // do double tap detection
692                         doubleTapDetection();
693                 }
694         }
695
696         // handle joystick
697         else if (evt_id == m_joystick_id) {
698                 m_joystick_id = -1;
699
700                 // reset joystick
701                 for (unsigned int i = 0; i < 4; i++)
702                         m_joystick_status[i] = false;
703                 applyJoystickStatus();
704
705                 m_joystick_btn_off->guibutton->setVisible(true);
706                 m_joystick_btn_bg->guibutton->setVisible(false);
707                 m_joystick_btn_center->guibutton->setVisible(false);
708         } else {
709                 infostream
710                         << "TouchScreenGUI::translateEvent released unknown button: "
711                         << evt_id << std::endl;
712         }
713
714         for (auto iter = m_known_ids.begin();
715                         iter != m_known_ids.end(); ++iter) {
716                 if (iter->id == evt_id) {
717                         m_known_ids.erase(iter);
718                         break;
719                 }
720         }
721 }
722
723 void TouchScreenGUI::translateEvent(const SEvent &event)
724 {
725         if (!m_visible) {
726                 infostream
727                         << "TouchScreenGUI::translateEvent got event but not visible!"
728                         << std::endl;
729                 return;
730         }
731
732         if (event.EventType != EET_TOUCH_INPUT_EVENT)
733                 return;
734
735         if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
736                 /*
737                  * Add to own copy of event list...
738                  * android would provide this information but Irrlicht guys don't
739                  * wanna design a efficient interface
740                  */
741                 id_status toadd{};
742                 toadd.id = event.TouchInput.ID;
743                 toadd.X  = event.TouchInput.X;
744                 toadd.Y  = event.TouchInput.Y;
745                 m_known_ids.push_back(toadd);
746
747                 size_t eventID = event.TouchInput.ID;
748
749                 touch_gui_button_id button =
750                                 getButtonID(event.TouchInput.X, event.TouchInput.Y);
751
752                 // handle button events
753                 if (button != after_last_element_id) {
754                         handleButtonEvent(button, eventID, true);
755                         m_settingsbar.deactivate();
756                         m_rarecontrolsbar.deactivate();
757                 } else if (isHUDButton(event)) {
758                         m_settingsbar.deactivate();
759                         m_rarecontrolsbar.deactivate();
760                         // already handled in isHUDButton()
761                 } else if (m_settingsbar.isButton(event)) {
762                         m_rarecontrolsbar.deactivate();
763                         // already handled in isSettingsBarButton()
764                 } else if (m_rarecontrolsbar.isButton(event)) {
765                         m_settingsbar.deactivate();
766                         // already handled in isSettingsBarButton()
767                 } else {
768                         // handle non button events
769                         m_settingsbar.deactivate();
770                         m_rarecontrolsbar.deactivate();
771
772                         s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f;
773                         s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f;
774
775                         /* Select joystick when left 1/3 of screen dragged or
776                          * when joystick tapped (fixed joystick position)
777                          */
778                         if ((m_fixed_joystick && dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5) ||
779                                         (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3.0f)) {
780                                 // If we don't already have a starting point for joystick make this the one.
781                                 if (m_joystick_id == -1) {
782                                         m_joystick_id               = event.TouchInput.ID;
783                                         m_joystick_has_really_moved = false;
784
785                                         m_joystick_btn_off->guibutton->setVisible(false);
786                                         m_joystick_btn_bg->guibutton->setVisible(true);
787                                         m_joystick_btn_center->guibutton->setVisible(true);
788
789                                         // If it's a fixed joystick, don't move the joystick "button".
790                                         if (!m_fixed_joystick)
791                                                 m_joystick_btn_bg->guibutton->setRelativePosition(v2s32(
792                                                                 event.TouchInput.X - button_size * 3.0f / 2.0f,
793                                                                 event.TouchInput.Y - button_size * 3.0f / 2.0f));
794
795                                         m_joystick_btn_center->guibutton->setRelativePosition(v2s32(
796                                                         event.TouchInput.X - button_size / 2.0f,
797                                                         event.TouchInput.Y - button_size / 2.0f));
798                                 }
799                         } else {
800                                 // If we don't already have a moving point make this the moving one.
801                                 if (m_move_id == -1) {
802                                         m_move_id                  = event.TouchInput.ID;
803                                         m_move_has_really_moved    = false;
804                                         m_move_downtime            = porting::getTimeMs();
805                                         m_move_downlocation        = v2s32(event.TouchInput.X, event.TouchInput.Y);
806                                         m_move_sent_as_mouse_event = false;
807                                 }
808                         }
809                 }
810
811                 m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
812         }
813         else if (event.TouchInput.Event == ETIE_LEFT_UP) {
814                 verbosestream
815                         << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
816                 handleReleaseEvent(event.TouchInput.ID);
817         } else {
818                 assert(event.TouchInput.Event == ETIE_MOVED);
819
820                 if (m_pointerpos[event.TouchInput.ID] ==
821                                 v2s32(event.TouchInput.X, event.TouchInput.Y))
822                         return;
823
824                 if (m_move_id != -1) {
825                         if ((event.TouchInput.ID == m_move_id) &&
826                                 (!m_move_sent_as_mouse_event)) {
827
828                                 double distance = sqrt(
829                                                 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
830                                                 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
831                                                 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
832                                                 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
833
834                                 if ((distance > m_touchscreen_threshold) ||
835                                                 (m_move_has_really_moved)) {
836                                         m_move_has_really_moved = true;
837                                         s32 X = event.TouchInput.X;
838                                         s32 Y = event.TouchInput.Y;
839
840                                         // update camera_yaw and camera_pitch
841                                         s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
842                                         s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
843
844                                         // adapt to similar behaviour as pc screen
845                                         double d = g_settings->getFloat("mouse_sensitivity") * 3.0f;
846
847                                         m_camera_yaw_change -= dx * d;
848                                         m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180);
849
850                                         // update shootline
851                                         m_shootline = m_device
852                                                         ->getSceneManager()
853                                                         ->getSceneCollisionManager()
854                                                         ->getRayFromScreenCoordinates(v2s32(X, Y));
855                                         m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
856                                 }
857                         } else if ((event.TouchInput.ID == m_move_id) &&
858                                         (m_move_sent_as_mouse_event)) {
859                                 m_shootline = m_device
860                                                 ->getSceneManager()
861                                                 ->getSceneCollisionManager()
862                                                 ->getRayFromScreenCoordinates(
863                                                                 v2s32(event.TouchInput.X, event.TouchInput.Y));
864                         }
865                 }
866
867                 if (m_joystick_id != -1 && event.TouchInput.ID == m_joystick_id) {
868                         s32 X = event.TouchInput.X;
869                         s32 Y = event.TouchInput.Y;
870
871                         s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
872                         s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
873                         if (m_fixed_joystick) {
874                                 dx = X - button_size * 5 / 2;
875                                 dy = Y - m_screensize.Y + button_size * 5 / 2;
876                         }
877
878                         double distance_sq = dx * dx + dy * dy;
879
880                         s32 dxj = event.TouchInput.X - button_size * 5.0f / 2.0f;
881                         s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5.0f / 2.0f;
882                         bool inside_joystick = (dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5);
883
884                         if (m_joystick_has_really_moved ||
885                                         (!m_joystick_has_really_moved && inside_joystick) ||
886                                         (!m_fixed_joystick &&
887                                         distance_sq > m_touchscreen_threshold * m_touchscreen_threshold)) {
888                                 m_joystick_has_really_moved = true;
889                                 double distance = sqrt(distance_sq);
890
891                                 // angle in degrees
892                                 double angle = acos(dx / distance) * 180 / M_PI;
893                                 if (dy < 0)
894                                         angle *= -1;
895                                 // rotate to make comparing easier
896                                 angle = fmod(angle + 180 + 22.5, 360);
897
898                                 // reset state before applying
899                                 for (bool & joystick_status : m_joystick_status)
900                                         joystick_status = false;
901
902                                 if (distance <= m_touchscreen_threshold) {
903                                         // do nothing
904                                 } else if (angle < 45)
905                                         m_joystick_status[j_left] = true;
906                                 else if (angle < 90) {
907                                         m_joystick_status[j_forward] = true;
908                                         m_joystick_status[j_left] = true;
909                                 } else if (angle < 135)
910                                         m_joystick_status[j_forward] = true;
911                                 else if (angle < 180) {
912                                         m_joystick_status[j_forward] = true;
913                                         m_joystick_status[j_right] = true;
914                                 } else if (angle < 225)
915                                         m_joystick_status[j_right] = true;
916                                 else if (angle < 270) {
917                                         m_joystick_status[j_backward] = true;
918                                         m_joystick_status[j_right] = true;
919                                 } else if (angle < 315)
920                                         m_joystick_status[j_backward] = true;
921                                 else if (angle <= 360) {
922                                         m_joystick_status[j_backward] = true;
923                                         m_joystick_status[j_left] = true;
924                                 }
925
926                                 if (distance > button_size) {
927                                         m_joystick_status[j_special1] = true;
928                                         // move joystick "button"
929                                         s32 ndx = button_size * dx / distance - button_size / 2.0f;
930                                         s32 ndy = button_size * dy / distance - button_size / 2.0f;
931                                         if (m_fixed_joystick) {
932                                                 m_joystick_btn_center->guibutton->setRelativePosition(v2s32(
933                                                         button_size * 5 / 2 + ndx,
934                                                         m_screensize.Y - button_size * 5 / 2 + ndy));
935                                         } else {
936                                                 m_joystick_btn_center->guibutton->setRelativePosition(v2s32(
937                                                         m_pointerpos[event.TouchInput.ID].X + ndx,
938                                                         m_pointerpos[event.TouchInput.ID].Y + ndy));
939                                         }
940                                 } else {
941                                         m_joystick_btn_center->guibutton->setRelativePosition(
942                                                         v2s32(X - button_size / 2, Y - button_size / 2));
943                                 }
944                         }
945                 }
946
947                 if (m_move_id == -1 && m_joystick_id == -1)
948                         handleChangedButton(event);
949         }
950 }
951
952 void TouchScreenGUI::handleChangedButton(const SEvent &event)
953 {
954         for (unsigned int i = 0; i < after_last_element_id; i++) {
955                 if (m_buttons[i].ids.empty())
956                         continue;
957
958                 for (auto iter = m_buttons[i].ids.begin();
959                                 iter != m_buttons[i].ids.end(); ++iter) {
960                         if (event.TouchInput.ID == *iter) {
961                                 int current_button_id =
962                                                 getButtonID(event.TouchInput.X, event.TouchInput.Y);
963
964                                 if (current_button_id == i)
965                                         continue;
966
967                                 // remove old button
968                                 handleButtonEvent((touch_gui_button_id) i, *iter, false);
969
970                                 if (current_button_id == after_last_element_id)
971                                         return;
972
973                                 handleButtonEvent((touch_gui_button_id) current_button_id, *iter, true);
974                                 return;
975                         }
976                 }
977         }
978
979         int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
980
981         if (current_button_id == after_last_element_id)
982                 return;
983
984         button_info *btn = &m_buttons[current_button_id];
985         if (std::find(btn->ids.begin(), btn->ids.end(), event.TouchInput.ID)
986                         == btn->ids.end())
987                 handleButtonEvent((touch_gui_button_id) current_button_id,
988                                 event.TouchInput.ID, true);
989 }
990
991 bool TouchScreenGUI::doubleTapDetection()
992 {
993         m_key_events[0].down_time = m_key_events[1].down_time;
994         m_key_events[0].x         = m_key_events[1].x;
995         m_key_events[0].y         = m_key_events[1].y;
996         m_key_events[1].down_time = m_move_downtime;
997         m_key_events[1].x         = m_move_downlocation.X;
998         m_key_events[1].y         = m_move_downlocation.Y;
999
1000         u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs());
1001         if (delta > 400)
1002                 return false;
1003
1004         double distance = sqrt(
1005                         (m_key_events[0].x - m_key_events[1].x) *
1006                         (m_key_events[0].x - m_key_events[1].x) +
1007                         (m_key_events[0].y - m_key_events[1].y) *
1008                         (m_key_events[0].y - m_key_events[1].y));
1009
1010         if (distance > (20 + m_touchscreen_threshold))
1011                 return false;
1012
1013         auto *translated = new SEvent();
1014         memset(translated, 0, sizeof(SEvent));
1015         translated->EventType               = EET_MOUSE_INPUT_EVENT;
1016         translated->MouseInput.X            = m_key_events[0].x;
1017         translated->MouseInput.Y            = m_key_events[0].y;
1018         translated->MouseInput.Shift        = false;
1019         translated->MouseInput.Control      = false;
1020         translated->MouseInput.ButtonStates = EMBSM_RIGHT;
1021
1022         // update shootline
1023         m_shootline = m_device
1024                         ->getSceneManager()
1025                         ->getSceneCollisionManager()
1026                         ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
1027
1028         translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
1029         verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
1030         m_receiver->OnEvent(*translated);
1031
1032         translated->MouseInput.ButtonStates = 0;
1033         translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
1034         verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
1035         m_receiver->OnEvent(*translated);
1036         delete translated;
1037         return true;
1038 }
1039
1040 void TouchScreenGUI::applyJoystickStatus()
1041 {
1042         for (unsigned int i = 0; i < 5; i++) {
1043                 if (i == 4 && !m_joystick_triggers_special1)
1044                         continue;
1045
1046                 SEvent translated{};
1047                 translated.EventType            = irr::EET_KEY_INPUT_EVENT;
1048                 translated.KeyInput.Key         = id2keycode(m_joystick_names[i]);
1049                 translated.KeyInput.PressedDown = false;
1050                 m_receiver->OnEvent(translated);
1051
1052                 if (m_joystick_status[i]) {
1053                         translated.KeyInput.PressedDown = true;
1054                         m_receiver->OnEvent(translated);
1055                 }
1056         }
1057 }
1058
1059 TouchScreenGUI::~TouchScreenGUI()
1060 {
1061         for (auto &button : m_buttons) {
1062                 if (button.guibutton) {
1063                         button.guibutton->drop();
1064                         button.guibutton = nullptr;
1065                 }
1066         }
1067
1068         if (m_joystick_btn_off->guibutton) {
1069                 m_joystick_btn_off->guibutton->drop();
1070                 m_joystick_btn_off->guibutton = nullptr;
1071         }
1072
1073         if (m_joystick_btn_bg->guibutton) {
1074                 m_joystick_btn_bg->guibutton->drop();
1075                 m_joystick_btn_bg->guibutton = nullptr;
1076         }
1077
1078         if (m_joystick_btn_center->guibutton) {
1079                 m_joystick_btn_center->guibutton->drop();
1080                 m_joystick_btn_center->guibutton = nullptr;
1081         }
1082 }
1083
1084 void TouchScreenGUI::step(float dtime)
1085 {
1086         // simulate keyboard repeats
1087         for (auto &button : m_buttons) {
1088                 if (!button.ids.empty()) {
1089                         button.repeatcounter += dtime;
1090
1091                         // in case we're moving around digging does not happen
1092                         if (m_move_id != -1)
1093                                 m_move_has_really_moved = true;
1094
1095                         if (button.repeatcounter < button.repeatdelay)
1096                                 continue;
1097
1098                         button.repeatcounter            = 0;
1099                         SEvent translated;
1100                         memset(&translated, 0, sizeof(SEvent));
1101                         translated.EventType            = irr::EET_KEY_INPUT_EVENT;
1102                         translated.KeyInput.Key         = button.keycode;
1103                         translated.KeyInput.PressedDown = false;
1104                         m_receiver->OnEvent(translated);
1105
1106                         translated.KeyInput.PressedDown = true;
1107                         m_receiver->OnEvent(translated);
1108                 }
1109         }
1110
1111         // joystick
1112         for (unsigned int i = 0; i < 4; i++) {
1113                 if (m_joystick_status[i]) {
1114                         applyJoystickStatus();
1115                         break;
1116                 }
1117         }
1118
1119         // if a new placed pointer isn't moved for some time start digging
1120         if ((m_move_id != -1) &&
1121                         (!m_move_has_really_moved) &&
1122                         (!m_move_sent_as_mouse_event)) {
1123
1124                 u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
1125
1126                 if (delta > MIN_DIG_TIME_MS) {
1127                         m_shootline = m_device
1128                                         ->getSceneManager()
1129                                         ->getSceneCollisionManager()
1130                                         ->getRayFromScreenCoordinates(
1131                                                         v2s32(m_move_downlocation.X,m_move_downlocation.Y));
1132
1133                         SEvent translated;
1134                         memset(&translated, 0, sizeof(SEvent));
1135                         translated.EventType               = EET_MOUSE_INPUT_EVENT;
1136                         translated.MouseInput.X            = m_move_downlocation.X;
1137                         translated.MouseInput.Y            = m_move_downlocation.Y;
1138                         translated.MouseInput.Shift        = false;
1139                         translated.MouseInput.Control      = false;
1140                         translated.MouseInput.ButtonStates = EMBSM_LEFT;
1141                         translated.MouseInput.Event        = EMIE_LMOUSE_PRESSED_DOWN;
1142                         verbosestream << "TouchScreenGUI::step left click press" << std::endl;
1143                         m_receiver->OnEvent(translated);
1144                         m_move_sent_as_mouse_event         = true;
1145                 }
1146         }
1147
1148         m_settingsbar.step(dtime);
1149         m_rarecontrolsbar.step(dtime);
1150 }
1151
1152 void TouchScreenGUI::resetHud()
1153 {
1154         m_hud_rects.clear();
1155 }
1156
1157 void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
1158 {
1159         m_hud_rects[index] = rect;
1160 }
1161
1162 void TouchScreenGUI::Toggle(bool visible)
1163 {
1164         m_visible = visible;
1165         for (auto &button : m_buttons) {
1166                 if (button.guibutton)
1167                         button.guibutton->setVisible(visible);
1168         }
1169
1170         if (m_joystick_btn_off->guibutton)
1171                 m_joystick_btn_off->guibutton->setVisible(visible);
1172
1173         // clear all active buttons
1174         if (!visible) {
1175                 while (!m_known_ids.empty())
1176                         handleReleaseEvent(m_known_ids.begin()->id);
1177
1178                 m_settingsbar.hide();
1179                 m_rarecontrolsbar.hide();
1180         } else {
1181                 m_settingsbar.show();
1182                 m_rarecontrolsbar.show();
1183         }
1184 }
1185
1186 void TouchScreenGUI::hide()
1187 {
1188         if (!m_visible)
1189                 return;
1190
1191         Toggle(false);
1192 }
1193
1194 void TouchScreenGUI::show()
1195 {
1196         if (m_visible)
1197                 return;
1198
1199         Toggle(true);
1200 }