diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 182319be0..d668fba50 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -237,6 +237,12 @@ local function load() zh_CN = "中文 (简体) [zh_CN]", zh_TW = "正體中文 (繁體) [zh_TW]", } + + get_setting_info("touch_controls").option_labels = { + ["auto"] = fgettext_ne("Auto"), + ["true"] = fgettext_ne("Enabled"), + ["false"] = fgettext_ne("Disabled"), + } end @@ -336,9 +342,14 @@ local function check_requirements(name, requires) local video_driver = core.get_active_driver() local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2" + local touch_controls = core.settings:get("touch_controls") local special = { android = PLATFORM == "Android", desktop = PLATFORM ~= "Android", + -- When touch_controls is "auto", we don't which input method will be used, + -- so we show settings for both. + touchscreen = touch_controls == "auto" or core.is_yes(touch_controls), + keyboard_mouse = touch_controls == "auto" or not core.is_yes(touch_controls), shaders_support = shaders_support, shaders = core.settings:get_bool("enable_shaders") and shaders_support, opengl = video_driver == "opengl", @@ -624,6 +635,18 @@ function write_settings_early() end end +local function regenerate_page_list(dialogdata) + local suggested_page_id = update_filtered_pages(dialogdata.query) + + dialogdata.components = nil + + if not filtered_page_by_id[dialogdata.page_id] then + dialogdata.leftscroll = 0 + dialogdata.rightscroll = 0 + + dialogdata.page_id = suggested_page_id + end +end local function buttonhandler(this, fields) local dialogdata = this.data @@ -648,27 +671,7 @@ local function buttonhandler(this, fields) local value = core.is_yes(fields.show_advanced) core.settings:set_bool("show_advanced", value) write_settings_early() - end - - -- touch_controls is a checkbox in a setting component. We handle this - -- setting differently so we can hide/show pages using the next if-statement - if fields.touch_controls ~= nil then - local value = core.is_yes(fields.touch_controls) - core.settings:set_bool("touch_controls", value) - write_settings_early() - end - - if fields.show_advanced ~= nil or fields.touch_controls ~= nil then - local suggested_page_id = update_filtered_pages(dialogdata.query) - - dialogdata.components = nil - - if not filtered_page_by_id[dialogdata.page_id] then - dialogdata.leftscroll = 0 - dialogdata.rightscroll = 0 - - dialogdata.page_id = suggested_page_id - end + regenerate_page_list(dialogdata) return true end @@ -701,20 +704,26 @@ local function buttonhandler(this, fields) end end - for i, comp in ipairs(dialogdata.components) do - if comp.on_submit and comp:on_submit(fields, this) then - write_settings_early() - + local function after_setting_change(comp) + write_settings_early() + if comp.setting.name == "touch_controls" then + -- Changing the "touch_controls" setting may result in a different + -- page list. + regenerate_page_list(dialogdata) + else -- Clear components so they regenerate dialogdata.components = nil + end + end + + for i, comp in ipairs(dialogdata.components) do + if comp.on_submit and comp:on_submit(fields, this) then + after_setting_change(comp) return true end if comp.setting and fields["reset_" .. i] then core.settings:remove(comp.setting.name) - write_settings_early() - - -- Clear components so they regenerate - dialogdata.components = nil + after_setting_change(comp) return true end end diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 2f7af3fae..1813a6cdf 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -61,7 +61,7 @@ # # # This is a comment # # -# # Requires: shaders, enable_dynamic_shadows, !touch_controls +# # Requires: shaders, enable_dynamic_shadows, !enable_waving_leaves # name (Readable name) type type_args # # A requirement can be the name of a boolean setting or an engine-defined value. @@ -72,6 +72,7 @@ # * shaders_support (a video driver that supports shaders, may not be enabled) # * shaders (both enable_shaders and shaders_support) # * desktop / android +# * touchscreen / keyboard_mouse # * opengl / gles # * You can negate any requirement by prepending with ! # @@ -91,7 +92,7 @@ camera_smoothing (Camera smoothing) float 0.0 0.0 0.99 # Smooths rotation of camera when in cinematic mode, 0 to disable. Enter cinematic mode by using the key set in Controls. # -# Requires: !touch_controls +# Requires: keyboard_mouse cinematic_camera_smoothing (Camera smoothing in cinematic mode) float 0.7 0.0 0.99 # If enabled, you can place nodes at the position (feet + eye level) where you stand. @@ -112,8 +113,8 @@ always_fly_fast (Always fly fast) bool true # The time in seconds it takes between repeated node placements when holding # the place button. # -# Requires: !touch_controls -repeat_place_time (Place repetition interval) float 0.25 0.16 2.0 +# Requires: keyboard_mouse +repeat_place_time (Place repetition interval) float 0.25 0.15 2.0 # The minimum time in seconds it takes between digging nodes when holding # the dig button. @@ -131,60 +132,62 @@ safe_dig_and_place (Safe digging and placing) bool false # Invert vertical mouse movement. # -# Requires: !touch_controls +# Requires: keyboard_mouse invert_mouse (Invert mouse) bool false # Mouse sensitivity multiplier. # -# Requires: !touch_controls +# Requires: keyboard_mouse mouse_sensitivity (Mouse sensitivity) float 0.2 0.001 10.0 # Enable mouse wheel (scroll) for item selection in hotbar. # -# Requires: !touch_controls +# Requires: keyboard_mouse enable_hotbar_mouse_wheel (Hotbar: Enable mouse wheel for selection) bool true # Invert mouse wheel (scroll) direction for item selection in hotbar. # -# Requires: !touch_controls +# Requires: keyboard_mouse invert_hotbar_mouse_wheel (Hotbar: Invert mouse wheel direction) bool false [*Touchscreen] # Enables the touchscreen controls, allowing you to play the game with a touchscreen. -touch_controls (Enable touchscreen controls) bool true +# "auto" means that the touchscreen controls will be enabled and disabled +# automatically depending on the last used input method. +touch_controls (Touchscreen controls) enum auto auto,true,false # Touchscreen sensitivity multiplier. # -# Requires: touch_controls +# Requires: touchscreen touchscreen_sensitivity (Touchscreen sensitivity) float 0.2 0.001 10.0 # The length in pixels after which a touch interaction is considered movement. # -# Requires: touch_controls +# Requires: touchscreen touchscreen_threshold (Movement threshold) int 20 0 100 # The delay in milliseconds after which a touch interaction is considered a long tap. # -# Requires: touch_controls +# Requires: touchscreen touch_long_tap_delay (Threshold for long taps) int 400 100 1000 # Use crosshair to select object instead of whole screen. # If enabled, a crosshair will be shown and will be used for selecting object. # -# Requires: touch_controls +# Requires: touchscreen touch_use_crosshair (Use crosshair for touch screen) bool false # Fixes the position of virtual joystick. # If disabled, virtual joystick will center to first-touch's position. # -# Requires: touch_controls +# Requires: touchscreen fixed_virtual_joystick (Fixed virtual joystick) bool false # Use virtual joystick to trigger "Aux1" button. # If enabled, virtual joystick will also tap "Aux1" button when out of main circle. # -# Requires: touch_controls +# Requires: touchscreen virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool false # The gesture for punching players/entities. @@ -197,7 +200,7 @@ virtual_joystick_triggers_aux1 (Virtual joystick triggers Aux1 button) bool fals # Known from the classic Minetest mobile controls. # Combat is more or less impossible. # -# Requires: touch_controls +# Requires: touchscreen touch_punch_gesture (Punch gesture) enum short_tap short_tap,long_tap diff --git a/irr/include/IEventReceiver.h b/irr/include/IEventReceiver.h index a484bfb84..7fb9e5f4e 100644 --- a/irr/include/IEventReceiver.h +++ b/irr/include/IEventReceiver.h @@ -347,6 +347,9 @@ struct SEvent //! Type of mouse event EMOUSE_INPUT_EVENT Event; + + //! Is this a simulated mouse event generated by Minetest itself? + bool Simulated; }; //! Any kind of keyboard event. @@ -538,6 +541,11 @@ struct SEvent struct SUserEvent UserEvent; struct SApplicationEvent ApplicationEvent; }; + + SEvent() { + // would be left uninitialized in many places otherwise + MouseInput.Simulated = false; + } }; //! Interface of an object which can receive events. diff --git a/src/client/game.cpp b/src/client/game.cpp index 76e9194ec..090af05c9 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -723,6 +723,7 @@ protected: void processUserInput(f32 dtime); void processKeyInput(); void processItemSelection(u16 *new_playeritem); + bool shouldShowTouchControls(); void dropSelectedItem(bool single_item = false); void openInventory(); @@ -1565,6 +1566,14 @@ bool Game::createClient(const GameStartData &start_data) return true; } +bool Game::shouldShowTouchControls() +{ + const std::string &touch_controls = g_settings->get("touch_controls"); + if (touch_controls == "auto") + return RenderingEngine::getLastPointerType() == PointerType::Touch; + return is_yes(touch_controls); +} + bool Game::initGui() { m_game_ui->init(); @@ -1579,7 +1588,7 @@ bool Game::initGui() gui_chat_console = make_irr(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); - if (g_settings->getBool("touch_controls")) { + if (shouldShowTouchControls()) { g_touchcontrols = new TouchControls(device, texture_src); g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); } @@ -2031,6 +2040,15 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, void Game::processUserInput(f32 dtime) { + bool desired = shouldShowTouchControls(); + if (desired && !g_touchcontrols) { + g_touchcontrols = new TouchControls(device, texture_src); + + } else if (!desired && g_touchcontrols) { + delete g_touchcontrols; + g_touchcontrols = nullptr; + } + // Reset input if window not active or some menu is active if (!device->isWindowActive() || isMenuActive() || guienv->hasFocus(gui_chat_console.get())) { if (m_game_focused) { diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 6517ad582..2ce058ff4 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gui/touchcontrols.h" #include "hud.h" #include "log_internal.h" +#include "client/renderingengine.h" void KeyCache::populate_nonchanging() { @@ -142,6 +143,11 @@ bool MyEventReceiver::OnEvent(const SEvent &event) } } + if (event.EventType == EET_MOUSE_INPUT_EVENT && !event.MouseInput.Simulated) + last_pointer_type = PointerType::Mouse; + else if (event.EventType == EET_TOUCH_INPUT_EVENT) + last_pointer_type = PointerType::Touch; + // Let the menu handle events, if one is active. if (isMenuActive()) { if (g_touchcontrols) @@ -237,6 +243,26 @@ float RealInputHandler::getJoystickDirection() return joystick.getMovementDirection(); } +v2s32 RealInputHandler::getMousePos() +{ + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + return control->getPosition(); + } + + return m_mousepos; +} + +void RealInputHandler::setMousePos(s32 x, s32 y) +{ + auto control = RenderingEngine::get_raw_device()->getCursorControl(); + if (control) { + control->setPosition(x, y); + } else { + m_mousepos = v2s32(x, y); + } +} + /* * RandomInputHandler */ diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 8efefce5b..ee76151f4 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -23,10 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "joystick_controller.h" #include #include "keycode.h" -#include "renderingengine.h" class InputHandler; +enum class PointerType { + Mouse, + Touch, +}; + /**************************************************************************** Fast key cache for main game loop ****************************************************************************/ @@ -199,6 +203,8 @@ public: JoystickController *joystick = nullptr; + PointerType getLastPointerType() { return last_pointer_type; } + private: s32 mouse_wheel = 0; @@ -223,6 +229,8 @@ private: // Intentionally not reset by clearInput/releaseAllKeys. bool fullscreen_is_down = false; + + PointerType last_pointer_type = PointerType::Mouse; }; class InputHandler @@ -331,25 +339,8 @@ public: m_receiver->dontListenForKeys(); } - virtual v2s32 getMousePos() - { - auto control = RenderingEngine::get_raw_device()->getCursorControl(); - if (control) { - return control->getPosition(); - } - - return m_mousepos; - } - - virtual void setMousePos(s32 x, s32 y) - { - auto control = RenderingEngine::get_raw_device()->getCursorControl(); - if (control) { - control->setPosition(x, y); - } else { - m_mousepos = v2s32(x, y); - } - } + virtual v2s32 getMousePos(); + virtual void setMousePos(s32 x, s32 y); virtual s32 getMouseWheel() { diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index b709fc7bf..f0d2abddb 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -172,7 +172,7 @@ static irr::IrrlichtDevice *createDevice(SIrrlichtCreationParameters params, std /* RenderingEngine class */ -RenderingEngine::RenderingEngine(IEventReceiver *receiver) +RenderingEngine::RenderingEngine(MyEventReceiver *receiver) { sanity_check(!s_singleton); @@ -225,6 +225,8 @@ RenderingEngine::RenderingEngine(IEventReceiver *receiver) // This changes the minimum allowed number of vertices in a VBO. Default is 500. driver->setMinHardwareBufferVertexCount(4); + m_receiver = receiver; + s_singleton = this; g_settings->registerChangedCallback("fullscreen", settingChangedCallback, this); diff --git a/src/client/renderingengine.h b/src/client/renderingengine.h index 5f6890c8b..ffdda636c 100644 --- a/src/client/renderingengine.h +++ b/src/client/renderingengine.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "client/inputhandler.h" #include "irrlichttypes_extrabloated.h" #include "debug.h" #include "client/shader.h" @@ -82,7 +83,7 @@ class RenderingEngine public: static const video::SColor MENU_SKY_COLOR; - RenderingEngine(IEventReceiver *eventReceiver); + RenderingEngine(MyEventReceiver *eventReceiver); ~RenderingEngine(); void setResizable(bool resize); @@ -167,6 +168,12 @@ public: const irr::core::dimension2d initial_screen_size, const bool initial_window_maximized); + static PointerType getLastPointerType() + { + sanity_check(s_singleton && s_singleton->m_receiver); + return s_singleton->m_receiver->getLastPointerType(); + } + private: static void settingChangedCallback(const std::string &name, void *data); v2u32 _getWindowSize() const; @@ -174,5 +181,6 @@ private: std::unique_ptr core; irr::IrrlichtDevice *m_device = nullptr; irr::video::IVideoDriver *driver; + MyEventReceiver *m_receiver = nullptr; static RenderingEngine *s_singleton; }; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index ae9180e72..f1756bd8e 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -97,7 +97,10 @@ void set_default_settings() // Client settings->setDefault("address", ""); settings->setDefault("enable_sound", "true"); - settings->setDefault("touch_controls", bool_to_cstr(has_touch)); + settings->setDefault("touch_controls", "auto"); + // Since GUI scaling shouldn't suddenly change during a session, we use + // hardware detection for "touch_gui" instead of switching based on the last + // input method used. settings->setDefault("touch_gui", bool_to_cstr(has_touch)); settings->setDefault("sound_volume", "0.8"); settings->setDefault("sound_volume_unfocused", "0.3"); diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp index c9366f83f..efd7b7e8c 100644 --- a/src/gui/guiFormSpecMenu.cpp +++ b/src/gui/guiFormSpecMenu.cpp @@ -3615,7 +3615,7 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text, int tooltip_offset_x = m_btn_height; int tooltip_offset_y = m_btn_height; - if (m_pointer_type == PointerType::Touch) { + if (RenderingEngine::getLastPointerType() == PointerType::Touch) { tooltip_offset_x *= 3; tooltip_offset_y = 0; if (m_pointer.X > (s32)screenSize.X / 2) diff --git a/src/gui/guiInventoryList.cpp b/src/gui/guiInventoryList.cpp index e5ed6e6ef..1dd36bfc9 100644 --- a/src/gui/guiInventoryList.cpp +++ b/src/gui/guiInventoryList.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiFormSpecMenu.h" #include "client/hud.h" #include "client/client.h" +#include "client/renderingengine.h" #include GUIInventoryList::GUIInventoryList(gui::IGUIEnvironment *env, @@ -154,7 +155,7 @@ void GUIInventoryList::draw() // Add hovering tooltip bool show_tooltip = !item.empty() && hovering && !selected_item; // Make it possible to see item tooltips on touchscreens - if (m_fs_menu->getPointerType() == PointerType::Touch) { + if (RenderingEngine::getLastPointerType() == PointerType::Touch) { show_tooltip |= hovering && selected && m_fs_menu->getSelectedAmount() != 0; } if (show_tooltip) { diff --git a/src/gui/modalMenu.cpp b/src/gui/modalMenu.cpp index fd60d08c2..ad4839170 100644 --- a/src/gui/modalMenu.cpp +++ b/src/gui/modalMenu.cpp @@ -187,6 +187,7 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon mouse_event.EventType = EET_MOUSE_INPUT_EVENT; mouse_event.MouseInput.X = m_pointer.X; mouse_event.MouseInput.Y = m_pointer.Y; + mouse_event.MouseInput.Simulated = true; switch (touch_event) { case ETIE_PRESSED_DOWN: mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; @@ -210,7 +211,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon } bool retval; - m_simulated_mouse = true; do { if (preprocessEvent(mouse_event)) { retval = true; @@ -222,7 +222,6 @@ bool GUIModalMenu::simulateMouseEvent(ETOUCH_INPUT_EVENT touch_event, bool secon } retval = target->OnEvent(mouse_event); } while (false); - m_simulated_mouse = false; if (!retval && !second_try) return simulateMouseEvent(touch_event, true); @@ -330,7 +329,6 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) holder.grab(this); // keep this alive until return (it might be dropped downstream [?]) if (event.TouchInput.touchedCount == 1) { - m_pointer_type = PointerType::Touch; m_pointer = v2s32(event.TouchInput.X, event.TouchInput.Y); gui::IGUIElement *hovered = Environment->getRootGUIElement()->getElementFromPoint(core::position2d(m_pointer)); @@ -373,9 +371,8 @@ bool GUIModalMenu::preprocessEvent(const SEvent &event) } if (event.EventType == EET_MOUSE_INPUT_EVENT) { - if (!m_simulated_mouse) { - // Only set the pointer type to mouse if this is a real mouse event. - m_pointer_type = PointerType::Mouse; + if (!event.MouseInput.Simulated) { + // Only process if this is a real mouse event. m_pointer = v2s32(event.MouseInput.X, event.MouseInput.Y); m_touch_hovered.reset(); } diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h index 071024120..2f770f9f5 100644 --- a/src/gui/modalMenu.h +++ b/src/gui/modalMenu.h @@ -26,11 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #endif -enum class PointerType { - Mouse, - Touch, -}; - struct PointerAction { v2s32 pos; u64 time; // ms @@ -74,14 +69,10 @@ public: porting::AndroidDialogState getAndroidUIInputState(); #endif - PointerType getPointerType() { return m_pointer_type; }; - protected: virtual std::wstring getLabelByID(s32 id) = 0; virtual std::string getNameByID(s32 id) = 0; - // 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. @@ -102,9 +93,6 @@ protected: // This is set to true if the menu is currently processing a second-touch event. bool m_second_touch = false; - // This is set to true if the menu is currently processing a mouse event - // that was synthesized by the menu itself from a touch event. - bool m_simulated_mouse = false; private: IMenuManager *m_menumgr; diff --git a/src/gui/touchcontrols.cpp b/src/gui/touchcontrols.cpp index 4a673ccf3..f3301a64d 100644 --- a/src/gui/touchcontrols.cpp +++ b/src/gui/touchcontrols.cpp @@ -418,6 +418,11 @@ TouchControls::TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc) m_status_text->setVisible(false); } +TouchControls::~TouchControls() +{ + releaseAll(); +} + void TouchControls::addButton(std::vector &buttons, touch_gui_button_id id, const std::string &image, const recti &rect, bool visible) { @@ -843,6 +848,7 @@ void TouchControls::emitMouseEvent(EMOUSE_INPUT_EVENT type) event.MouseInput.Control = false; event.MouseInput.ButtonStates = 0; event.MouseInput.Event = type; + event.MouseInput.Simulated = true; m_receiver->OnEvent(event); } diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 1787f6a5d..98ec806bd 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemdef.h" #include "client/game.h" +#include "util/basic_macros.h" namespace irr { @@ -136,6 +137,8 @@ class TouchControls { public: TouchControls(IrrlichtDevice *device, ISimpleTextureSource *tsrc); + ~TouchControls(); + DISABLE_CLASS_COPY(TouchControls); void translateEvent(const SEvent &event); void applyContextControls(const TouchInteractionMode &mode);