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 // Very slow button repeat frequency (in seconds)
35 #define SLOW_BUTTON_REPEAT (1.0f)
37 using namespace irr::core;
39 extern Settings *g_settings;
41 const char** touchgui_button_imagenames = (const char*[]) {
59 static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
110 return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
113 TouchScreenGUI *g_touchscreengui;
115 TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver):
117 m_guienv(device->getGUIEnvironment()),
124 for (unsigned int i=0; i < after_last_element_id; i++) {
125 m_buttons[i].guibutton = 0;
126 m_buttons[i].repeatcounter = -1;
127 m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY;
130 m_screensize = m_device->getVideoDriver()->getScreenSize();
133 void TouchScreenGUI::loadButtonTexture(button_info* btn, const char* path)
136 video::ITexture *texture = m_texturesource->getTexture(path,&tid);
138 btn->guibutton->setUseAlphaChannel(true);
139 btn->guibutton->setImage(texture);
140 btn->guibutton->setPressedImage(texture);
141 btn->guibutton->setScaleImage(true);
142 btn->guibutton->setDrawBorder(false);
143 btn->guibutton->setText(L"");
147 void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
148 std::wstring caption, bool immediate_release, float repeat_delay)
151 button_info* btn = &m_buttons[id];
152 btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
153 btn->guibutton->grab();
154 btn->repeatcounter = -1;
155 btn->repeatdelay = repeat_delay;
156 btn->keycode = id2keycode(id);
157 btn->immediate_release = immediate_release;
160 loadButtonTexture(btn,touchgui_button_imagenames[id]);
163 static int getMaxControlPadSize(float density) {
164 return 200 * density * g_settings->getFloat("hud_scaling");
167 void TouchScreenGUI::init(ISimpleTextureSource* tsrc, float density)
171 u32 control_pad_size =
172 MYMIN((2 * m_screensize.Y) / 3,getMaxControlPadSize(density));
174 u32 button_size = control_pad_size / 3;
176 m_texturesource = tsrc;
177 m_control_pad_rect = rect<s32>(0, m_screensize.Y - 3 * button_size,
178 3 * button_size, m_screensize.Y);
183 for now only 0, 1, 2, and 4 are used
186 for (int y = 0; y < 2; ++y)
187 for (int x = 0; x < 3; ++x, ++number) {
188 rect<s32> button_rect(
189 x * button_size, m_screensize.Y - button_size * (2 - y),
190 (x + 1) * button_size, m_screensize.Y - button_size * (1 - y)
192 touch_gui_button_id id = after_last_element_id;
193 std::wstring caption;
212 if (id != after_last_element_id) {
213 initButton(id, button_rect, caption, false);
217 /* init inventory button */
218 initButton(inventory_id,
219 rect<s32>(0, m_screensize.Y - (button_size/2),
220 (button_size/2), m_screensize.Y), L"inv", true);
222 /* init drop button */
224 rect<s32>(2.5*button_size, m_screensize.Y - (button_size/2),
225 3*button_size, m_screensize.Y), L"drop", true);
227 /* init jump button */
229 rect<s32>(m_screensize.X-(1.75*button_size),
230 m_screensize.Y - (0.5*button_size),
231 m_screensize.X-(0.25*button_size),
235 /* init crunch button */
236 initButton(crunch_id,
237 rect<s32>(m_screensize.X-(3.25*button_size),
238 m_screensize.Y - (0.5*button_size),
239 m_screensize.X-(1.75*button_size),
243 /* init fly button */
245 rect<s32>(m_screensize.X - (0.75*button_size),
246 m_screensize.Y - (2.25*button_size),
247 m_screensize.X, m_screensize.Y - (button_size*1.5)),
248 L"fly", false, SLOW_BUTTON_REPEAT);
250 /* init noclip button */
251 initButton(noclip_id,
252 rect<s32>(m_screensize.X - (0.75*button_size), 2.25*button_size,
253 m_screensize.X, 3*button_size),
254 L"clip", false, SLOW_BUTTON_REPEAT);
256 /* init fast button */
258 rect<s32>(m_screensize.X - (0.75*button_size), 1.5*button_size,
259 m_screensize.X, 2.25*button_size),
260 L"fast", false, SLOW_BUTTON_REPEAT);
262 /* init debug button */
264 rect<s32>(m_screensize.X - (0.75*button_size), 0.75*button_size,
265 m_screensize.X, 1.5*button_size),
266 L"dbg", false, SLOW_BUTTON_REPEAT);
268 /* init chat button */
270 rect<s32>(m_screensize.X - (0.75*button_size), 0,
271 m_screensize.X, 0.75*button_size),
274 /* init camera button */
275 initButton(camera_id,
276 rect<s32>(m_screensize.X - (1.5*button_size), 0,
277 m_screensize.X - (0.75*button_size), 0.75*button_size),
278 L"cam", false, SLOW_BUTTON_REPEAT);
280 /* init rangeselect button */
282 rect<s32>(m_screensize.X - (2.25*button_size), 0,
283 m_screensize.X - (1.5*button_size), 0.75*button_size),
284 L"far", false, SLOW_BUTTON_REPEAT);
287 touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
289 IGUIElement* rootguielement = m_guienv->getRootGUIElement();
291 if (rootguielement != NULL) {
292 gui::IGUIElement *element =
293 rootguielement->getElementFromPoint(core::position2d<s32>(x,y));
296 for (unsigned int i=0; i < after_last_element_id; i++) {
297 if (element == m_buttons[i].guibutton) {
298 return (touch_gui_button_id) i;
303 return after_last_element_id;
306 touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
308 for (unsigned int i=0; i < after_last_element_id; i++) {
309 button_info* btn = &m_buttons[i];
311 std::vector<int>::iterator id =
312 std::find(btn->ids.begin(),btn->ids.end(), eventID);
314 if (id != btn->ids.end())
315 return (touch_gui_button_id) i;
318 return after_last_element_id;
321 bool TouchScreenGUI::isHUDButton(const SEvent &event)
323 // check if hud item is pressed
324 for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin();
325 iter != m_hud_rects.end(); iter++) {
326 if (iter->second.isPointInside(
327 v2s32(event.TouchInput.X,
330 if ( iter->first < 8) {
331 SEvent* translated = new SEvent();
332 memset(translated,0,sizeof(SEvent));
333 translated->EventType = irr::EET_KEY_INPUT_EVENT;
334 translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
335 translated->KeyInput.Control = false;
336 translated->KeyInput.Shift = false;
337 translated->KeyInput.PressedDown = true;
338 m_receiver->OnEvent(*translated);
339 m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
348 bool TouchScreenGUI::isReleaseHUDButton(int eventID)
350 std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
352 if (iter != m_hud_ids.end()) {
353 SEvent* translated = new SEvent();
354 memset(translated,0,sizeof(SEvent));
355 translated->EventType = irr::EET_KEY_INPUT_EVENT;
356 translated->KeyInput.Key = iter->second;
357 translated->KeyInput.PressedDown = false;
358 translated->KeyInput.Control = false;
359 translated->KeyInput.Shift = false;
360 m_receiver->OnEvent(*translated);
361 m_hud_ids.erase(iter);
368 void TouchScreenGUI::ButtonEvent(touch_gui_button_id button,
369 int eventID, bool action)
371 button_info* btn = &m_buttons[button];
372 SEvent* translated = new SEvent();
373 memset(translated,0,sizeof(SEvent));
374 translated->EventType = irr::EET_KEY_INPUT_EVENT;
375 translated->KeyInput.Key = btn->keycode;
376 translated->KeyInput.Control = false;
377 translated->KeyInput.Shift = false;
378 translated->KeyInput.Char = 0;
382 assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end());
384 btn->ids.push_back(eventID);
386 if (btn->ids.size() > 1) return;
388 btn->repeatcounter = 0;
389 translated->KeyInput.PressedDown = true;
390 translated->KeyInput.Key = btn->keycode;
391 m_receiver->OnEvent(*translated);
394 if ((!action) || (btn->immediate_release)) {
396 std::vector<int>::iterator pos =
397 std::find(btn->ids.begin(),btn->ids.end(), eventID);
398 /* has to be in touch list */
399 assert(pos != btn->ids.end());
402 if (btn->ids.size() > 0) { return; }
404 translated->KeyInput.PressedDown = false;
405 btn->repeatcounter = -1;
406 m_receiver->OnEvent(*translated);
411 void TouchScreenGUI::translateEvent(const SEvent &event)
414 infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
418 if (event.EventType != EET_TOUCH_INPUT_EVENT) {
422 if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
424 /* add to own copy of eventlist ...
425 * android would provide this information but irrlicht guys don't
426 * wanna design a efficient interface
429 toadd.id = event.TouchInput.ID;
430 toadd.X = event.TouchInput.X;
431 toadd.Y = event.TouchInput.Y;
432 m_known_ids.push_back(toadd);
434 int eventID = event.TouchInput.ID;
436 touch_gui_button_id button =
437 getButtonID(event.TouchInput.X, event.TouchInput.Y);
439 /* handle button events */
440 if (button != after_last_element_id) {
441 ButtonEvent(button,eventID,true);
443 else if (isHUDButton(event))
445 /* already handled in isHUDButton() */
447 /* handle non button events */
449 /* if we don't already have a moving point make this the moving one */
450 if (m_move_id == -1) {
451 m_move_id = event.TouchInput.ID;
452 m_move_has_really_moved = false;
453 m_move_downtime = getTimeMs();
454 m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y);
455 m_move_sent_as_mouse_event = false;
459 m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
461 else if (event.TouchInput.Event == ETIE_LEFT_UP) {
462 verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
464 touch_gui_button_id button = getButtonID(event.TouchInput.ID);
466 /* handle button events */
467 if (button != after_last_element_id) {
468 ButtonEvent(button,event.TouchInput.ID,false);
470 /* handle hud button events */
471 else if (isReleaseHUDButton(event.TouchInput.ID)) {
472 /* nothing to do here */
474 /* handle the point used for moving view */
475 else if (event.TouchInput.ID == m_move_id) {
478 /* if this pointer issued a mouse event issue symmetric release here */
479 if (m_move_sent_as_mouse_event) {
480 SEvent* translated = new SEvent;
481 memset(translated,0,sizeof(SEvent));
482 translated->EventType = EET_MOUSE_INPUT_EVENT;
483 translated->MouseInput.X = m_move_downlocation.X;
484 translated->MouseInput.Y = m_move_downlocation.Y;
485 translated->MouseInput.Shift = false;
486 translated->MouseInput.Control = false;
487 translated->MouseInput.ButtonStates = 0;
488 translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
489 m_receiver->OnEvent(*translated);
493 /* do double tap detection */
494 doubleTapDetection();
499 << "TouchScreenGUI::translateEvent released unknown button: "
500 << event.TouchInput.ID << std::endl;
503 for (std::vector<id_status>::iterator iter = m_known_ids.begin();
504 iter != m_known_ids.end(); iter++) {
505 if (iter->id == event.TouchInput.ID) {
506 m_known_ids.erase(iter);
512 assert(event.TouchInput.Event == ETIE_MOVED);
513 int move_idx = event.TouchInput.ID;
515 if (m_pointerpos[event.TouchInput.ID] ==
516 v2s32(event.TouchInput.X, event.TouchInput.Y)) {
520 if (m_move_id != -1) {
521 if ((event.TouchInput.ID == m_move_id) &&
522 (!m_move_sent_as_mouse_event)) {
524 double distance = sqrt(
525 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
526 (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
527 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
528 (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
530 if ((distance > g_settings->getU16("touchscreen_threshold")) ||
531 (m_move_has_really_moved)) {
532 m_move_has_really_moved = true;
533 s32 X = event.TouchInput.X;
534 s32 Y = event.TouchInput.Y;
536 // update camera_yaw and camera_pitch
537 s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
538 s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
540 /* adapt to similar behaviour as pc screen */
541 double d = g_settings->getFloat("mouse_sensitivity") *4;
542 double old_yaw = m_camera_yaw;
543 double old_pitch = m_camera_pitch;
545 m_camera_yaw -= dx * d;
546 m_camera_pitch = MYMIN(MYMAX( m_camera_pitch + (dy * d),-180),180);
548 while (m_camera_yaw < 0)
551 while (m_camera_yaw > 360)
555 m_shootline = m_device
557 ->getSceneCollisionManager()
558 ->getRayFromScreenCoordinates(v2s32(X, Y));
559 m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
562 else if ((event.TouchInput.ID == m_move_id) &&
563 (m_move_sent_as_mouse_event)) {
564 m_shootline = m_device
566 ->getSceneCollisionManager()
567 ->getRayFromScreenCoordinates(
568 v2s32(event.TouchInput.X,event.TouchInput.Y));
572 handleChangedButton(event);
577 void TouchScreenGUI::handleChangedButton(const SEvent &event)
579 for (unsigned int i = 0; i < after_last_element_id; i++) {
581 if (m_buttons[i].ids.empty()) {
584 for(std::vector<int>::iterator iter = m_buttons[i].ids.begin();
585 iter != m_buttons[i].ids.end(); iter++) {
587 if (event.TouchInput.ID == *iter) {
589 int current_button_id =
590 getButtonID(event.TouchInput.X, event.TouchInput.Y);
592 if (current_button_id == i) {
596 /* remove old button */
597 ButtonEvent((touch_gui_button_id) i,*iter,false);
599 if (current_button_id == after_last_element_id) {
602 ButtonEvent((touch_gui_button_id) current_button_id,*iter,true);
609 int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
611 if (current_button_id == after_last_element_id) {
615 button_info* btn = &m_buttons[current_button_id];
616 if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID) == btn->ids.end()) {
617 ButtonEvent((touch_gui_button_id) current_button_id,event.TouchInput.ID,true);
622 bool TouchScreenGUI::doubleTapDetection()
624 m_key_events[0].down_time = m_key_events[1].down_time;
625 m_key_events[0].x = m_key_events[1].x;
626 m_key_events[0].y = m_key_events[1].y;
627 m_key_events[1].down_time = m_move_downtime;
628 m_key_events[1].x = m_move_downlocation.X;
629 m_key_events[1].y = m_move_downlocation.Y;
631 u32 delta = porting::getDeltaMs(m_key_events[0].down_time,getTimeMs());
635 double distance = sqrt(
636 (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) +
637 (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y));
640 if (distance >(20 + g_settings->getU16("touchscreen_threshold")))
643 SEvent* translated = new SEvent();
644 memset(translated,0,sizeof(SEvent));
645 translated->EventType = EET_MOUSE_INPUT_EVENT;
646 translated->MouseInput.X = m_key_events[0].x;
647 translated->MouseInput.Y = m_key_events[0].y;
648 translated->MouseInput.Shift = false;
649 translated->MouseInput.Control = false;
650 translated->MouseInput.ButtonStates = EMBSM_RIGHT;
653 m_shootline = m_device
655 ->getSceneCollisionManager()
656 ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
658 translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
659 verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
660 m_receiver->OnEvent(*translated);
662 translated->MouseInput.ButtonStates = 0;
663 translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
664 verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
665 m_receiver->OnEvent(*translated);
671 TouchScreenGUI::~TouchScreenGUI()
673 for (unsigned int i=0; i < after_last_element_id; i++) {
674 button_info* btn = &m_buttons[i];
675 if (btn->guibutton != 0) {
676 btn->guibutton->drop();
677 btn->guibutton = NULL;
682 void TouchScreenGUI::step(float dtime)
684 /* simulate keyboard repeats */
685 for (unsigned int i=0; i < after_last_element_id; i++) {
686 button_info* btn = &m_buttons[i];
688 if (btn->ids.size() > 0) {
689 btn->repeatcounter += dtime;
691 /* in case we're moving around digging does not happen */
693 m_move_has_really_moved = true;
695 if (btn->repeatcounter < btn->repeatdelay) continue;
697 btn->repeatcounter = 0;
699 memset(&translated,0,sizeof(SEvent));
700 translated.EventType = irr::EET_KEY_INPUT_EVENT;
701 translated.KeyInput.Key = btn->keycode;
702 translated.KeyInput.PressedDown = false;
703 m_receiver->OnEvent(translated);
705 translated.KeyInput.PressedDown = true;
706 m_receiver->OnEvent(translated);
710 /* if a new placed pointer isn't moved for some time start digging */
711 if ((m_move_id != -1) &&
712 (!m_move_has_really_moved) &&
713 (!m_move_sent_as_mouse_event)) {
715 u32 delta = porting::getDeltaMs(m_move_downtime,getTimeMs());
717 if (delta > MIN_DIG_TIME_MS) {
718 m_shootline = m_device
720 ->getSceneCollisionManager()
721 ->getRayFromScreenCoordinates(
722 v2s32(m_move_downlocation.X,m_move_downlocation.Y));
725 memset(&translated,0,sizeof(SEvent));
726 translated.EventType = EET_MOUSE_INPUT_EVENT;
727 translated.MouseInput.X = m_move_downlocation.X;
728 translated.MouseInput.Y = m_move_downlocation.Y;
729 translated.MouseInput.Shift = false;
730 translated.MouseInput.Control = false;
731 translated.MouseInput.ButtonStates = EMBSM_LEFT;
732 translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
733 verbosestream << "TouchScreenGUI::step left click press" << std::endl;
734 m_receiver->OnEvent(translated);
735 m_move_sent_as_mouse_event = true;
740 void TouchScreenGUI::resetHud()
745 void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
747 m_hud_rects[index] = rect;
750 void TouchScreenGUI::Toggle(bool visible)
753 for (unsigned int i=0; i < after_last_element_id; i++) {
754 button_info* btn = &m_buttons[i];
755 if (btn->guibutton != 0) {
756 btn->guibutton->setVisible(visible);
761 void TouchScreenGUI::Hide()
766 void TouchScreenGUI::Show()