2 Copyright (C) 2014 sapier
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 2.1 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "touchscreengui.h"
20 #include "irrlichttypes.h"
26 #include "util/numeric.h"
32 #include <ISceneCollisionManager.h>
34 using namespace irr::core;
36 extern Settings *g_settings;
38 const char** touchgui_button_imagenames = (const char*[]) {
56 static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
107 return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
110 TouchScreenGUI *g_touchscreengui;
112 TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver):
114 m_guienv(device->getGUIEnvironment()),
121 for (unsigned int i=0; i < after_last_element_id; i++) {
122 m_buttons[i].guibutton = 0;
123 m_buttons[i].repeatcounter = -1;
126 m_screensize = m_device->getVideoDriver()->getScreenSize();
129 void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path)
132 video::ITexture *texture = m_texturesource->getTexture(path,&tid);
134 btn->guibutton->setUseAlphaChannel(true);
135 btn->guibutton->setImage(texture);
136 btn->guibutton->setPressedImage(texture);
137 btn->guibutton->setScaleImage(true);
138 btn->guibutton->setDrawBorder(false);
139 btn->guibutton->setText(L"");
143 void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
144 std::wstring caption, bool immediate_release )
147 button_info* btn = &m_buttons[id];
148 btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
149 btn->guibutton->grab();
150 btn->repeatcounter = -1;
151 btn->keycode = id2keycode(id);
152 btn->immediate_release = immediate_release;
155 loadButtonTexture(btn,touchgui_button_imagenames[id]);
158 static int getMaxControlPadSize(float density) {
159 return 200 * density * g_settings->getFloat("hud_scaling");
162 void TouchScreenGUI::init(ISimpleTextureSource* tsrc, float density)
166 u32 control_pad_size =
167 MYMIN((2 * m_screensize.Y) / 3,getMaxControlPadSize(density));
169 u32 button_size = control_pad_size / 3;
171 m_texturesource = tsrc;
172 m_control_pad_rect = rect<s32>(0, m_screensize.Y - 3 * button_size,
173 3 * button_size, m_screensize.Y);
178 for now only 0, 1, 2, and 4 are used
181 for (int y = 0; y < 2; ++y)
182 for (int x = 0; x < 3; ++x, ++number) {
183 rect<s32> button_rect(
184 x * button_size, m_screensize.Y - button_size * (2 - y),
185 (x + 1) * button_size, m_screensize.Y - button_size * (1 - y)
187 touch_gui_button_id id = after_last_element_id;
188 std::wstring caption;
207 if (id != after_last_element_id) {
208 initButton(id, button_rect, caption, false);
212 /* init inventory button */
213 initButton(inventory_id,
214 rect<s32>(0, m_screensize.Y - (button_size/2),
215 (button_size/2), m_screensize.Y), L"inv", true);
217 /* init drop button */
219 rect<s32>(2.5*button_size, m_screensize.Y - (button_size/2),
220 3*button_size, m_screensize.Y), L"drop", true);
222 /* init jump button */
224 rect<s32>(m_screensize.X-(1.75*button_size),
225 m_screensize.Y - (0.5*button_size),
226 m_screensize.X-(0.25*button_size),
230 /* init crunch button */
231 initButton(crunch_id,
232 rect<s32>(m_screensize.X-(3.25*button_size),
233 m_screensize.Y - (0.5*button_size),
234 m_screensize.X-(1.75*button_size),
238 /* init fly button */
240 rect<s32>(m_screensize.X - (0.75*button_size),
241 m_screensize.Y - (2.25*button_size),
242 m_screensize.X, m_screensize.Y - (button_size*1.5)),
245 /* init noclip button */
246 initButton(noclip_id,
247 rect<s32>(m_screensize.X - (0.75*button_size), 2.25*button_size,
248 m_screensize.X, 3*button_size),
251 /* init fast button */
253 rect<s32>(m_screensize.X - (0.75*button_size), 1.5*button_size,
254 m_screensize.X, 2.25*button_size),
257 /* init debug button */
259 rect<s32>(m_screensize.X - (0.75*button_size), 0.75*button_size,
260 m_screensize.X, 1.5*button_size),
263 /* init chat button */
265 rect<s32>(m_screensize.X - (0.75*button_size), 0,
266 m_screensize.X, 0.75*button_size),
269 /* init camera button */
270 initButton(camera_id,
271 rect<s32>(m_screensize.X - (1.5*button_size), 0,
272 m_screensize.X - (0.75*button_size), 0.75*button_size),
275 /* init rangeselect button */
277 rect<s32>(m_screensize.X - (2.25*button_size), 0,
278 m_screensize.X - (1.5*button_size), 0.75*button_size),
282 touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
284 IGUIElement* rootguielement = m_guienv->getRootGUIElement();
286 if (rootguielement != NULL) {
287 gui::IGUIElement *element =
288 rootguielement->getElementFromPoint(core::position2d<s32>(x,y));
291 for (unsigned int i=0; i < after_last_element_id; i++) {
292 if (element == m_buttons[i].guibutton) {
293 return (touch_gui_button_id) i;
298 return after_last_element_id;
301 touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
303 for (unsigned int i=0; i < after_last_element_id; i++) {
304 button_info* btn = &m_buttons[i];
306 std::vector<int>::iterator id =
307 std::find(btn->ids.begin(),btn->ids.end(), eventID);
309 if (id != btn->ids.end())
310 return (touch_gui_button_id) i;
313 return after_last_element_id;
316 bool TouchScreenGUI::isHUDButton(const SEvent &event)
318 // check if hud item is pressed
319 for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin();
320 iter != m_hud_rects.end(); iter++) {
321 if (iter->second.isPointInside(
322 v2s32(event.TouchInput.X,
325 if ( iter->first < 8) {
326 SEvent* translated = new SEvent();
327 memset(translated,0,sizeof(SEvent));
328 translated->EventType = irr::EET_KEY_INPUT_EVENT;
329 translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
330 translated->KeyInput.Control = false;
331 translated->KeyInput.Shift = false;
332 translated->KeyInput.PressedDown = true;
333 m_receiver->OnEvent(*translated);
334 m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
343 bool TouchScreenGUI::isReleaseHUDButton(int eventID)
345 std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
347 if (iter != m_hud_ids.end()) {
348 SEvent* translated = new SEvent();
349 memset(translated,0,sizeof(SEvent));
350 translated->EventType = irr::EET_KEY_INPUT_EVENT;
351 translated->KeyInput.Key = iter->second;
352 translated->KeyInput.PressedDown = false;
353 translated->KeyInput.Control = false;
354 translated->KeyInput.Shift = false;
355 m_receiver->OnEvent(*translated);
356 m_hud_ids.erase(iter);
363 void TouchScreenGUI::ButtonEvent(touch_gui_button_id button,
364 int eventID, bool action)
366 button_info* btn = &m_buttons[button];
367 SEvent* translated = new SEvent();
368 memset(translated,0,sizeof(SEvent));
369 translated->EventType = irr::EET_KEY_INPUT_EVENT;
370 translated->KeyInput.Key = btn->keycode;
371 translated->KeyInput.Control = false;
372 translated->KeyInput.Shift = false;
373 translated->KeyInput.Char = 0;
377 assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end());
379 btn->ids.push_back(eventID);
381 if (btn->ids.size() > 1) return;
383 btn->repeatcounter = 0;
384 translated->KeyInput.PressedDown = true;
385 translated->KeyInput.Key = btn->keycode;
386 m_receiver->OnEvent(*translated);
389 if ((!action) || (btn->immediate_release)) {
391 std::vector<int>::iterator pos =
392 std::find(btn->ids.begin(),btn->ids.end(), eventID);
393 /* has to be in touch list */
394 assert(pos != btn->ids.end());
397 if (btn->ids.size() > 0) { return; }
399 translated->KeyInput.PressedDown = false;
400 btn->repeatcounter = -1;
401 m_receiver->OnEvent(*translated);
406 void TouchScreenGUI::translateEvent(const SEvent &event)
409 infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
413 if (event.EventType != EET_TOUCH_INPUT_EVENT) {
417 if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
419 /* add to own copy of eventlist ...
420 * android would provide this information but irrlicht guys don't
421 * wanna design a efficient interface
424 toadd.id = event.TouchInput.ID;
425 toadd.X = event.TouchInput.X;
426 toadd.Y = event.TouchInput.Y;
427 m_known_ids.push_back(toadd);
429 int eventID = event.TouchInput.ID;
431 touch_gui_button_id button =
432 getButtonID(event.TouchInput.X, event.TouchInput.Y);
434 /* handle button events */
435 if (button != after_last_element_id) {
436 ButtonEvent(button,eventID,true);
438 else if (isHUDButton(event))
440 /* already handled in isHUDButton() */
442 /* handle non button events */
444 /* if we don't already have a moving point make this the moving one */
445 if (m_move_id == -1) {
446 m_move_id = event.TouchInput.ID;
447 m_move_has_really_moved = false;
448 m_move_downtime = getTimeMs();
449 m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y);
450 m_move_sent_as_mouse_event = false;
454 m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
456 else if (event.TouchInput.Event == ETIE_LEFT_UP) {
457 verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
459 touch_gui_button_id button = getButtonID(event.TouchInput.ID);
461 /* handle button events */
462 if (button != after_last_element_id) {
463 ButtonEvent(button,event.TouchInput.ID,false);
465 /* handle hud button events */
466 else if (isReleaseHUDButton(event.TouchInput.ID)) {
467 /* nothing to do here */
469 /* handle the point used for moving view */
470 else if (event.TouchInput.ID == m_move_id) {
473 /* if this pointer issued a mouse event issue symmetric release here */
474 if (m_move_sent_as_mouse_event) {
475 SEvent* translated = new SEvent;
476 memset(translated,0,sizeof(SEvent));
477 translated->EventType = EET_MOUSE_INPUT_EVENT;
478 translated->MouseInput.X = m_move_downlocation.X;
479 translated->MouseInput.Y = m_move_downlocation.Y;
480 translated->MouseInput.Shift = false;
481 translated->MouseInput.Control = false;
482 translated->MouseInput.ButtonStates = 0;
483 translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
484 m_receiver->OnEvent(*translated);
488 /* do double tap detection */
489 doubleTapDetection();
494 << "TouchScreenGUI::translateEvent released unknown button: "
495 << event.TouchInput.ID << std::endl;
498 for (std::vector<id_status>::iterator iter = m_known_ids.begin();
499 iter != m_known_ids.end(); iter++) {
500 if (iter->id == event.TouchInput.ID) {
501 m_known_ids.erase(iter);
507 assert(event.TouchInput.Event == ETIE_MOVED);
508 int move_idx = event.TouchInput.ID;
510 if (m_pointerpos[event.TouchInput.ID] ==
511 v2s32(event.TouchInput.X, event.TouchInput.Y)) {
515 if (m_move_id != -1) {
516 if ((event.TouchInput.ID == m_move_id) &&
517 (!m_move_sent_as_mouse_event)) {
519 double distance = sqrt(
520 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
521 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
522 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
523 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
525 if ((distance > g_settings->getU16("touchscreen_threshold")) ||
526 (m_move_has_really_moved)) {
527 m_move_has_really_moved = true;
528 s32 X = event.TouchInput.X;
529 s32 Y = event.TouchInput.Y;
531 // update camera_yaw and camera_pitch
532 s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
533 s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
535 /* adapt to similar behaviour as pc screen */
536 double d = g_settings->getFloat("mouse_sensitivity") *4;
537 double old_yaw = m_camera_yaw;
538 double old_pitch = m_camera_pitch;
540 m_camera_yaw -= dx * d;
541 m_camera_pitch = MYMIN(MYMAX( m_camera_pitch + (dy * d),-180),180);
543 while (m_camera_yaw < 0)
546 while (m_camera_yaw > 360)
550 m_shootline = m_device
552 ->getSceneCollisionManager()
553 ->getRayFromScreenCoordinates(v2s32(X, Y));
554 m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
557 else if ((event.TouchInput.ID == m_move_id) &&
558 (m_move_sent_as_mouse_event)) {
559 m_shootline = m_device
561 ->getSceneCollisionManager()
562 ->getRayFromScreenCoordinates(
563 v2s32(event.TouchInput.X,event.TouchInput.Y));
567 handleChangedButton(event);
572 void TouchScreenGUI::handleChangedButton(const SEvent &event)
574 for (unsigned int i = 0; i < after_last_element_id; i++) {
576 if (m_buttons[i].ids.empty()) {
579 for(std::vector<int>::iterator iter = m_buttons[i].ids.begin();
580 iter != m_buttons[i].ids.end(); iter++) {
582 if (event.TouchInput.ID == *iter) {
584 int current_button_id =
585 getButtonID(event.TouchInput.X, event.TouchInput.Y);
587 if (current_button_id == i) {
591 /* remove old button */
592 ButtonEvent((touch_gui_button_id) i,*iter,false);
594 if (current_button_id == after_last_element_id) {
597 ButtonEvent((touch_gui_button_id) current_button_id,*iter,true);
604 int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
606 if (current_button_id == after_last_element_id) {
610 button_info* btn = &m_buttons[current_button_id];
611 if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID) == btn->ids.end()) {
612 ButtonEvent((touch_gui_button_id) current_button_id,event.TouchInput.ID,true);
617 bool TouchScreenGUI::doubleTapDetection()
619 m_key_events[0].down_time = m_key_events[1].down_time;
620 m_key_events[0].x = m_key_events[1].x;
621 m_key_events[0].y = m_key_events[1].y;
622 m_key_events[1].down_time = m_move_downtime;
623 m_key_events[1].x = m_move_downlocation.X;
624 m_key_events[1].y = m_move_downlocation.Y;
626 u32 delta = porting::getDeltaMs(m_key_events[0].down_time,getTimeMs());
630 double distance = sqrt(
631 (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) +
632 (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y));
635 if (distance >(20 + g_settings->getU16("touchscreen_threshold")))
638 SEvent* translated = new SEvent();
639 memset(translated,0,sizeof(SEvent));
640 translated->EventType = EET_MOUSE_INPUT_EVENT;
641 translated->MouseInput.X = m_key_events[0].x;
642 translated->MouseInput.Y = m_key_events[0].y;
643 translated->MouseInput.Shift = false;
644 translated->MouseInput.Control = false;
645 translated->MouseInput.ButtonStates = EMBSM_RIGHT;
648 m_shootline = m_device
650 ->getSceneCollisionManager()
651 ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
653 translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
654 verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
655 m_receiver->OnEvent(*translated);
657 translated->MouseInput.ButtonStates = 0;
658 translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
659 verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
660 m_receiver->OnEvent(*translated);
666 TouchScreenGUI::~TouchScreenGUI()
668 for (unsigned int i=0; i < after_last_element_id; i++) {
669 button_info* btn = &m_buttons[i];
670 if (btn->guibutton != 0) {
671 btn->guibutton->drop();
672 btn->guibutton = NULL;
677 void TouchScreenGUI::step(float dtime)
679 /* simulate keyboard repeats */
680 for (unsigned int i=0; i < after_last_element_id; i++) {
681 button_info* btn = &m_buttons[i];
683 if (btn->ids.size() > 0) {
684 btn->repeatcounter += dtime;
686 /* in case we're moving around digging does not happen */
688 m_move_has_really_moved = true;
690 if (btn->repeatcounter < 0.2) continue;
692 btn->repeatcounter = 0;
694 memset(&translated,0,sizeof(SEvent));
695 translated.EventType = irr::EET_KEY_INPUT_EVENT;
696 translated.KeyInput.Key = btn->keycode;
697 translated.KeyInput.PressedDown = false;
698 m_receiver->OnEvent(translated);
700 translated.KeyInput.PressedDown = true;
701 m_receiver->OnEvent(translated);
705 /* if a new placed pointer isn't moved for some time start digging */
706 if ((m_move_id != -1) &&
707 (!m_move_has_really_moved) &&
708 (!m_move_sent_as_mouse_event)) {
710 u32 delta = porting::getDeltaMs(m_move_downtime,getTimeMs());
712 if (delta > MIN_DIG_TIME_MS) {
713 m_shootline = m_device
715 ->getSceneCollisionManager()
716 ->getRayFromScreenCoordinates(
717 v2s32(m_move_downlocation.X,m_move_downlocation.Y));
720 memset(&translated,0,sizeof(SEvent));
721 translated.EventType = EET_MOUSE_INPUT_EVENT;
722 translated.MouseInput.X = m_move_downlocation.X;
723 translated.MouseInput.Y = m_move_downlocation.Y;
724 translated.MouseInput.Shift = false;
725 translated.MouseInput.Control = false;
726 translated.MouseInput.ButtonStates = EMBSM_LEFT;
727 translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
728 verbosestream << "TouchScreenGUI::step left click press" << std::endl;
729 m_receiver->OnEvent(translated);
730 m_move_sent_as_mouse_event = true;
735 void TouchScreenGUI::resetHud()
740 void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
742 m_hud_rects[index] = rect;
745 void TouchScreenGUI::Toggle(bool visible)
748 for (unsigned int i=0; i < after_last_element_id; i++) {
749 button_info* btn = &m_buttons[i];
750 if (btn->guibutton != 0) {
751 btn->guibutton->setVisible(visible);
756 void TouchScreenGUI::Hide()
761 void TouchScreenGUI::Show()