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