Support both mouse and touch input in GUIs in a single binary (#14146)

This commit is contained in:
grorp 2023-12-27 22:37:36 +01:00 committed by GitHub
parent 4f1dbb127a
commit 32e492837c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 83 deletions

@ -3656,22 +3656,18 @@ void GUIFormSpecMenu::drawMenu()
NULL, m_client, IT_ROT_HOVERED); NULL, m_client, IT_ROT_HOVERED);
} }
// On touchscreens, m_pointer is set by GUIModalMenu::preprocessEvent instead.
#ifndef HAVE_TOUCHSCREENGUI
m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
#endif
/* /*
Draw fields/buttons tooltips and update the mouse cursor Draw fields/buttons tooltips and update the mouse cursor
*/ */
gui::IGUIElement *hovered = gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint(m_pointer); Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
#ifndef HAVE_TOUCHSCREENGUI
gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()-> gui::ICursorControl *cursor_control = RenderingEngine::get_raw_device()->
getCursorControl(); getCursorControl();
gui::ECURSOR_ICON current_cursor_icon = cursor_control->getActiveIcon(); gui::ECURSOR_ICON current_cursor_icon = gui::ECI_NORMAL;
#endif if (cursor_control)
current_cursor_icon = cursor_control->getActiveIcon();
bool hovered_element_found = false; bool hovered_element_found = false;
if (hovered) { if (hovered) {
@ -3715,11 +3711,10 @@ void GUIFormSpecMenu::drawMenu()
m_tooltips[field.fname].bgcolor); m_tooltips[field.fname].bgcolor);
} }
#ifndef HAVE_TOUCHSCREENGUI if (cursor_control &&
if (field.ftype != f_HyperText && // Handled directly in guiHyperText field.ftype != f_HyperText && // Handled directly in guiHyperText
current_cursor_icon != field.fcursor_icon) current_cursor_icon != field.fcursor_icon)
cursor_control->setActiveIcon(field.fcursor_icon); cursor_control->setActiveIcon(field.fcursor_icon);
#endif
hovered_element_found = true; hovered_element_found = true;
@ -3730,10 +3725,8 @@ void GUIFormSpecMenu::drawMenu()
if (!hovered_element_found) { if (!hovered_element_found) {
// no element is hovered // no element is hovered
#ifndef HAVE_TOUCHSCREENGUI if (cursor_control && current_cursor_icon != ECI_NORMAL)
if (current_cursor_icon != ECI_NORMAL)
cursor_control->setActiveIcon(ECI_NORMAL); cursor_control->setActiveIcon(ECI_NORMAL);
#endif
} }
m_tooltip_element->draw(); m_tooltip_element->draw();
@ -3764,16 +3757,13 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
v2u32 screenSize = Environment->getVideoDriver()->getScreenSize(); v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
int tooltip_offset_x = m_btn_height; int tooltip_offset_x = m_btn_height;
int tooltip_offset_y = m_btn_height; int tooltip_offset_y = m_btn_height;
#ifdef HAVE_TOUCHSCREENGUI
if (m_pointer_type == PointerType::Touch) {
tooltip_offset_x *= 3; tooltip_offset_x *= 3;
tooltip_offset_y = 0; tooltip_offset_y = 0;
if (m_pointer.X > (s32)screenSize.X / 2) if (m_pointer.X > (s32)screenSize.X / 2)
tooltip_offset_x = -(tooltip_offset_x + tooltip_width); tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
}
// Hide tooltip after ETIE_LEFT_UP
if (m_pointer.X == 0)
return;
#endif
// Calculate and set the tooltip position // Calculate and set the tooltip position
s32 tooltip_x = m_pointer.X + tooltip_offset_x; s32 tooltip_x = m_pointer.X + tooltip_offset_x;
@ -4070,6 +4060,11 @@ void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode)
bool GUIFormSpecMenu::preprocessEvent(const SEvent& event) bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
{ {
// This must be done first so that GUIModalMenu can set m_pointer_type
// correctly.
if (GUIModalMenu::preprocessEvent(event))
return true;
// The IGUITabControl renders visually using the skin's selected // The IGUITabControl renders visually using the skin's selected
// font, which we override for the duration of form drawing, // font, which we override for the duration of form drawing,
// but computes tab hotspots based on how it would have rendered // but computes tab hotspots based on how it would have rendered
@ -4147,7 +4142,7 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
return handled; return handled;
} }
return GUIModalMenu::preprocessEvent(event); return false;
} }
void GUIFormSpecMenu::tryClose() void GUIFormSpecMenu::tryClose()
@ -4326,14 +4321,12 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
#ifdef HAVE_TOUCHSCREENGUI
// The second touch (see GUIModalMenu::preprocessEvent() function) // The second touch (see GUIModalMenu::preprocessEvent() function)
ButtonEventType touch = BET_OTHER; ButtonEventType touch = BET_OTHER;
if (event.EventType == EET_TOUCH_INPUT_EVENT) { if (event.EventType == EET_TOUCH_INPUT_EVENT) {
if (event.TouchInput.Event == ETIE_LEFT_UP) if (event.TouchInput.Event == ETIE_LEFT_UP)
touch = BET_RIGHT; touch = BET_RIGHT;
} }
#endif
// Set this number to a positive value to generate a move action // Set this number to a positive value to generate a move action
// from m_selected_item to s. // from m_selected_item to s.
@ -4678,7 +4671,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
break; break;
} }
#ifdef HAVE_TOUCHSCREENGUI
if (touch == BET_RIGHT && m_selected_item && !m_left_dragging) { if (touch == BET_RIGHT && m_selected_item && !m_left_dragging) {
if (!s.isValid()) { if (!s.isValid()) {
// Not a valid slot // Not a valid slot
@ -4698,7 +4690,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
} }
#endif
// Update left-dragged slots // Update left-dragged slots
if (m_left_dragging && m_left_drag_stacks.size() > 1) { if (m_left_dragging && m_left_drag_stacks.size() > 1) {
@ -5067,10 +5058,8 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
} }
} }
#ifdef HAVE_TOUCHSCREENGUI
if (m_second_touch) if (m_second_touch)
return true; // Stop propagating the event return true; // Stop propagating the event
#endif
return Parent ? Parent->OnEvent(event) : false; return Parent ? Parent->OnEvent(event) : false;
} }

@ -1052,14 +1052,10 @@ void GUIHyperText::checkHover(s32 X, s32 Y)
} }
} }
#ifndef HAVE_TOUCHSCREENGUI ICursorControl *cursor_control = RenderingEngine::get_raw_device()->getCursorControl();
if (m_drawer.m_hovertag)
RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon( if (cursor_control)
gui::ECI_HAND); cursor_control->setActiveIcon(m_drawer.m_hovertag ? gui::ECI_HAND : gui::ECI_NORMAL);
else
RenderingEngine::get_raw_device()->getCursorControl()->setActiveIcon(
gui::ECI_NORMAL);
#endif
} }
bool GUIHyperText::OnEvent(const SEvent &event) bool GUIHyperText::OnEvent(const SEvent &event)
@ -1075,12 +1071,11 @@ bool GUIHyperText::OnEvent(const SEvent &event)
if (event.EventType == EET_GUI_EVENT && if (event.EventType == EET_GUI_EVENT &&
event.GUIEvent.EventType == EGET_ELEMENT_LEFT) { event.GUIEvent.EventType == EGET_ELEMENT_LEFT) {
m_drawer.m_hovertag = nullptr; m_drawer.m_hovertag = nullptr;
#ifndef HAVE_TOUCHSCREENGUI
gui::ICursorControl *cursor_control = ICursorControl *cursor_control = RenderingEngine::get_raw_device()->getCursorControl();
RenderingEngine::get_raw_device()->getCursorControl();
if (cursor_control->isVisible()) if (cursor_control && cursor_control->isVisible())
cursor_control->setActiveIcon(gui::ECI_NORMAL); cursor_control->setActiveIcon(gui::ECI_NORMAL);
#endif
} }
if (event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.EventType == EET_MOUSE_INPUT_EVENT) {

@ -152,10 +152,10 @@ void GUIInventoryList::draw()
// Add hovering tooltip // Add hovering tooltip
bool show_tooltip = !item.empty() && hovering && !selected_item; bool show_tooltip = !item.empty() && hovering && !selected_item;
#ifdef HAVE_TOUCHSCREENGUI
// Make it possible to see item tooltips on touchscreens // Make it possible to see item tooltips on touchscreens
if (m_fs_menu->getPointerType() == PointerType::Touch) {
show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0; show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0;
#endif }
if (show_tooltip) { if (show_tooltip) {
std::string tooltip = orig_item.getDescription(client->idef()); std::string tooltip = orig_item.getDescription(client->idef());
if (m_fs_menu->doTooltipAppendItemname()) if (m_fs_menu->doTooltipAppendItemname())

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/ */
#include <cstdlib> #include <cstdlib>
#include <IEventReceiver.h>
#include "client/renderingengine.h" #include "client/renderingengine.h"
#include "modalMenu.h" #include "modalMenu.h"
#include "gettext.h" #include "gettext.h"
@ -103,7 +104,7 @@ void GUIModalMenu::quitMenu()
m_menumgr->deletingMenu(this); m_menumgr->deletingMenu(this);
this->remove(); this->remove();
#ifdef HAVE_TOUCHSCREENGUI #ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui && m_touchscreen_visible) if (g_touchscreengui)
g_touchscreengui->show(); g_touchscreengui->show();
#endif #endif
} }
@ -169,8 +170,6 @@ static bool isChild(gui::IGUIElement *tocheck, gui::IGUIElement *parent)
return false; return false;
} }
#ifdef HAVE_TOUCHSCREENGUI
bool GUIModalMenu::simulateMouseEvent( bool GUIModalMenu::simulateMouseEvent(
gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event) gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event)
{ {
@ -194,41 +193,51 @@ bool GUIModalMenu::simulateMouseEvent(
default: default:
return false; return false;
} }
if (preprocessEvent(mouse_event))
return true; bool retval;
if (!target) m_simulated_mouse = true;
return false; do {
return target->OnEvent(mouse_event); if (preprocessEvent(mouse_event)) {
retval = true;
break;
}
if (!target) {
retval = false;
break;
}
retval = target->OnEvent(mouse_event);
} while (false);
m_simulated_mouse = false;
return retval;
} }
void GUIModalMenu::enter(gui::IGUIElement *hovered) void GUIModalMenu::enter(gui::IGUIElement *hovered)
{ {
if (!hovered) if (!hovered)
return; return;
sanity_check(!m_hovered); sanity_check(!m_touch_hovered);
m_hovered.grab(hovered); m_touch_hovered.grab(hovered);
SEvent gui_event{}; SEvent gui_event{};
gui_event.EventType = EET_GUI_EVENT; gui_event.EventType = EET_GUI_EVENT;
gui_event.GUIEvent.Caller = m_hovered.get(); gui_event.GUIEvent.Caller = m_touch_hovered.get();
gui_event.GUIEvent.EventType = EGET_ELEMENT_HOVERED; gui_event.GUIEvent.EventType = gui::EGET_ELEMENT_HOVERED;
gui_event.GUIEvent.Element = gui_event.GUIEvent.Caller; gui_event.GUIEvent.Element = gui_event.GUIEvent.Caller;
m_hovered->OnEvent(gui_event); m_touch_hovered->OnEvent(gui_event);
} }
void GUIModalMenu::leave() void GUIModalMenu::leave()
{ {
if (!m_hovered) if (!m_touch_hovered)
return; return;
SEvent gui_event{}; SEvent gui_event{};
gui_event.EventType = EET_GUI_EVENT; gui_event.EventType = EET_GUI_EVENT;
gui_event.GUIEvent.Caller = m_hovered.get(); gui_event.GUIEvent.Caller = m_touch_hovered.get();
gui_event.GUIEvent.EventType = EGET_ELEMENT_LEFT; gui_event.GUIEvent.EventType = gui::EGET_ELEMENT_LEFT;
m_hovered->OnEvent(gui_event); m_touch_hovered->OnEvent(gui_event);
m_hovered.reset(); m_touch_hovered.reset();
} }
#endif
bool GUIModalMenu::preprocessEvent(const SEvent &event) bool GUIModalMenu::preprocessEvent(const SEvent &event)
{ {
#ifdef __ANDROID__ #ifdef __ANDROID__
@ -268,25 +277,26 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
} }
#endif #endif
#ifdef HAVE_TOUCHSCREENGUI // Convert touch events into mouse events.
if (event.EventType == EET_TOUCH_INPUT_EVENT) { if (event.EventType == EET_TOUCH_INPUT_EVENT) {
irr_ptr<GUIModalMenu> holder; irr_ptr<GUIModalMenu> holder;
holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) holder.grab(this); // keep this alive until return (it might be dropped downstream [?])
if (event.TouchInput.touchedCount == 1) { if (event.TouchInput.touchedCount == 1) {
if (event.TouchInput.Event == ETIE_PRESSED_DOWN || event.TouchInput.Event == ETIE_MOVED) m_pointer_type = PointerType::Touch;
m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y);
gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer)); gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d<s32>(m_pointer));
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) if (event.TouchInput.Event == ETIE_PRESSED_DOWN)
Environment->setFocus(hovered); Environment->setFocus(hovered);
if (m_hovered != hovered) { if (m_touch_hovered != hovered) {
leave(); leave();
enter(hovered); enter(hovered);
} }
gui::IGUIElement *focused = Environment->getFocus(); gui::IGUIElement *focused = Environment->getFocus();
bool ret = simulateMouseEvent(focused, event.TouchInput.Event); bool ret = simulateMouseEvent(focused, event.TouchInput.Event);
if (!ret && m_hovered != focused) if (!ret && m_touch_hovered != focused)
ret = simulateMouseEvent(m_hovered.get(), event.TouchInput.Event); ret = simulateMouseEvent(m_touch_hovered.get(), event.TouchInput.Event);
if (event.TouchInput.Event == ETIE_LEFT_UP) if (event.TouchInput.Event == ETIE_LEFT_UP)
leave(); leave();
return ret; return ret;
@ -306,20 +316,24 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event)
return true; return true;
} }
} }
#endif
if (event.EventType == EET_MOUSE_INPUT_EVENT) { if (event.EventType == EET_MOUSE_INPUT_EVENT) {
s32 x = event.MouseInput.X; if (!m_simulated_mouse) {
s32 y = event.MouseInput.Y; // Only set the pointer type to mouse if this is a real mouse event.
m_pointer_type = PointerType::Mouse;
m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y);
m_touch_hovered.reset();
}
gui::IGUIElement *hovered = gui::IGUIElement *hovered =
Environment->getRootGUIElement()->getElementFromPoint( Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
core::position2d<s32>(x, y));
if (!isChild(hovered, this)) { if (!isChild(hovered, this)) {
if (DoubleClickDetection(event)) { if (DoubleClickDetection(event)) {
return true; return true;
} }
} }
} }
return false; return false;
} }

@ -23,6 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "irr_ptr.h" #include "irr_ptr.h"
#include "util/string.h" #include "util/string.h"
enum class PointerType {
Mouse,
Touch,
};
class GUIModalMenu; class GUIModalMenu;
class IMenuManager class IMenuManager
@ -58,6 +63,8 @@ public:
bool hasAndroidUIInput(); bool hasAndroidUIInput();
#endif #endif
PointerType getPointerType() { return m_pointer_type; };
protected: protected:
virtual std::wstring getLabelByID(s32 id) = 0; virtual std::wstring getLabelByID(s32 id) = 0;
virtual std::string getNameByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0;
@ -69,18 +76,25 @@ protected:
*/ */
bool DoubleClickDetection(const SEvent &event); bool DoubleClickDetection(const SEvent &event);
// Stores the last known pointer type.
PointerType m_pointer_type = PointerType::Mouse;
// Stores the last known pointer position.
// If the last input event was a mouse event, it's the cursor position.
// If the last input event was a touch event, it's the finger position.
v2s32 m_pointer; v2s32 m_pointer;
v2s32 m_old_pointer; // Mouse position after previous mouse event v2s32 m_old_pointer; // Mouse position after previous mouse event
v2u32 m_screensize_old; v2u32 m_screensize_old;
float m_gui_scale; float m_gui_scale;
#ifdef __ANDROID__ #ifdef __ANDROID__
std::string m_jni_field_name; std::string m_jni_field_name;
#endif #endif
#ifdef HAVE_TOUCHSCREENGUI
// This is set to true if the menu is currently processing a second-touch event. // This is set to true if the menu is currently processing a second-touch event.
bool m_second_touch = false; bool m_second_touch = false;
bool m_touchscreen_visible = true; // This is set to true if the menu is currently processing a mouse event
#endif // that was synthesized by the menu itself from a touch event.
bool m_simulated_mouse = false;
private: private:
struct clickpos struct clickpos
@ -102,11 +116,11 @@ private:
// wants to launch other menus // wants to launch other menus
bool m_allow_focus_removal = false; bool m_allow_focus_removal = false;
#ifdef HAVE_TOUCHSCREENGUI // Stuff related to touchscreen input
irr_ptr<gui::IGUIElement> m_hovered;
irr_ptr<gui::IGUIElement> m_touch_hovered;
bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event); bool simulateMouseEvent(gui::IGUIElement *target, ETOUCH_INPUT_EVENT touch_event);
void enter(gui::IGUIElement *element); void enter(gui::IGUIElement *element);
void leave(); void leave();
#endif
}; };