From 05a53cd330017d97112bb1b054652f80b9ee28a2 Mon Sep 17 00:00:00 2001 From: grorp Date: Fri, 5 Jan 2024 00:39:40 +0100 Subject: [PATCH] Touchscreen: Recognize double-taps as double-clicks (#14187) --- src/gui/modalMenu.cpp | 127 ++++++++++++++++++++++-------------------- src/gui/modalMenu.h | 24 +++----- 2 files changed, 77 insertions(+), 74 deletions(-) diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index 5d4e03baf..8d46c70f3 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -51,11 +51,8 @@ GUIModalMenu::GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, setVisible(true); m_menumgr->createdMenu(this); - m_doubleclickdetect[0].time = 0; - m_doubleclickdetect[1].time = 0; - - m_doubleclickdetect[0].pos = v2s32(0, 0); - m_doubleclickdetect[1].pos = v2s32(0, 0); + m_last_touch.time = 0; + m_last_touch.pos = v2s32(0, 0); } GUIModalMenu::~GUIModalMenu() @@ -109,7 +106,18 @@ void GUIModalMenu::quitMenu() #endif } -bool GUIModalMenu::DoubleClickDetection(const SEvent &event) +static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) +{ + while (tocheck) { + if (tocheck == parent) { + return true; + } + tocheck = tocheck->getParent(); + } + return false; +} + +bool GUIModalMenu::remapDoubleClick(const SEvent &event) { /* The following code is for capturing double-clicks of the mouse button * and translating the double-click into an EET_KEY_INPUT_EVENT event @@ -124,55 +132,37 @@ bool GUIModalMenu::DoubleClickDetection(const SEvent &event) if (!m_remap_dbl_click) return false; - if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) { - m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos; - m_doubleclickdetect[0].time = m_doubleclickdetect[1].time; + if (event.EventType != EET_MOUSE_INPUT_EVENT || + event.MouseInput.Event != EMIE_LMOUSE_DOUBLE_CLICK) + return false; - m_doubleclickdetect[1].pos = m_pointer; - m_doubleclickdetect[1].time = porting::getTimeMs(); - } else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) { - u64 delta = porting::getDeltaMs( - m_doubleclickdetect[0].time, porting::getTimeMs()); - if (delta > 400) - return false; + // Only exit if the double-click happened outside the menu. + gui::IGUIElement *hovered = + Environment->getRootGUIElement()->getElementFromPoint(m_pointer); + if (isChild(hovered, this)) + return false; - double squaredistance = m_doubleclickdetect[0].pos. - getDistanceFromSQ(m_doubleclickdetect[1].pos); + // Translate double-click to escape. + SEvent translated{}; + translated.EventType = EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = KEY_ESCAPE; + translated.KeyInput.Control = false; + translated.KeyInput.Shift = false; + translated.KeyInput.PressedDown = true; + translated.KeyInput.Char = 0; + OnEvent(translated); - if (squaredistance > (30 * 30)) { - return false; - } - - SEvent translated{}; - // translate doubleclick to escape - translated.EventType = EET_KEY_INPUT_EVENT; - translated.KeyInput.Key = KEY_ESCAPE; - translated.KeyInput.Control = false; - translated.KeyInput.Shift = false; - translated.KeyInput.PressedDown = true; - translated.KeyInput.Char = 0; - OnEvent(translated); - - return true; - } - - return false; + return true; } -static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent) +bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try) { - while (tocheck) { - if (tocheck == parent) { - return true; - } - tocheck = tocheck->getParent(); - } - return false; -} + IGUIElement *target; + if (!second_try) + target = Environment->getFocus(); + else + target = m_touch_hovered.get(); -bool GUIModalMenu::simulateMouseEvent( - gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event) -{ SEvent mouse_event{}; // value-initialized, not unitialized mouse_event.EventType = EET_MOUSE_INPUT_EVENT; mouse_event.MouseInput.X = m_pointer.X; @@ -190,6 +180,11 @@ bool GUIModalMenu::simulateMouseEvent( mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; mouse_event.MouseInput.ButtonStates = 0; break; + case ETIE_COUNT: + // ETIE_COUNT is used for double-tap events. + mouse_event.MouseInput.Event = EMIE_LMOUSE_DOUBLE_CLICK; + mouse_event.MouseInput.ButtonStates = EMBSM_LEFT; + break; default: return false; } @@ -209,6 +204,9 @@ bool GUIModalMenu::simulateMouseEvent( } while (false); m_simulated_mouse = false; + if (!retval && !second_try) + return simulateMouseEvent(touch_event, true); + return retval; } @@ -293,12 +291,28 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) leave(); enter(hovered); } - gui::IGUIElement *focused = Environment->getFocus(); - bool ret = simulateMouseEvent(focused, event.TouchInput.Event); - if (!ret && m_touch_hovered != focused) - ret = simulateMouseEvent(m_touch_hovered.get(), event.TouchInput.Event); + bool ret = simulateMouseEvent(event.TouchInput.Event); if (event.TouchInput.Event == ETIE_LEFT_UP) leave(); + + // Detect double-taps and convert them into double-click events. + if (event.TouchInput.Event == ETIE_PRESSED_DOWN) { + u64 time_now = porting::getTimeMs(); + u64 time_delta = porting::getDeltaMs(m_last_touch.time, time_now); + + v2s32 pos_delta = m_pointer - m_last_touch.pos; + f32 distance_sq = (f32)pos_delta.X * pos_delta.X + + (f32)pos_delta.Y * pos_delta.Y; + + if (time_delta < 400 && distance_sq < (30 * 30)) { + // ETIE_COUNT is used for double-tap events. + simulateMouseEvent(ETIE_COUNT); + } + + m_last_touch.time = time_now; + m_last_touch.pos = m_pointer; + } + return ret; } else if (event.TouchInput.touchedCount == 2) { if (event.TouchInput.Event != ETIE_LEFT_UP) @@ -325,13 +339,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) m_touch_hovered.reset(); } - gui::IGUIElement *hovered = - Environment->getRootGUIElement()->getElementFromPoint(m_pointer); - if (!isChild(hovered, this)) { - if (DoubleClickDetection(event)) { - return true; - } - } + if (remapDoubleClick(event)) + return true; } return false; diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 30eda8bc9..bfa5d4ac8 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -69,13 +69,6 @@ protected: virtual std::wstring getLabelByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0; - /** - * check if event is part of a double click - * @param event event to evaluate - * @return true/false if a doubleclick was detected - */ - bool DoubleClickDetection(const SEvent &event); - // Stores the last known pointer type. PointerType m_pointer_type = PointerType::Mouse; // Stores the last known pointer position. @@ -97,13 +90,6 @@ protected: bool m_simulated_mouse = false; private: - struct clickpos - { - v2s32 pos; - s64 time; - }; - clickpos m_doubleclickdetect[2]; - IMenuManager *m_menumgr; /* If true, remap a double-click (or double-tap) action to ESC. This is so * that, for example, Android users can double-tap to close a formspec. @@ -112,6 +98,8 @@ private: * and the default value for the setting is true. */ bool m_remap_dbl_click; + bool remapDoubleClick(const SEvent &event); + // This might be necessary to expose to the implementation if it // wants to launch other menus bool m_allow_focus_removal = false; @@ -120,7 +108,13 @@ private: irr_ptr m_touch_hovered; - bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); + bool simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool second_try=false); void enter(gui::IGUIElement *element); void leave(); + + // Used to detect double-taps and convert them into double-click events. + struct { + v2s32 pos; + s64 time; + } m_last_touch; };