Skip to content

Commit

Permalink
Touchscreen: Allow mods to swap the meaning of short and long taps
Browse files Browse the repository at this point in the history
  • Loading branch information
grorp committed Jan 19, 2024
1 parent 699d1bf commit 5bd2bf9
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 104 deletions.
14 changes: 14 additions & 0 deletions doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -8787,6 +8787,20 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- Otherwise should be name of node which the client immediately places
-- upon digging. Server will always update with actual result shortly.

touch_interaction = {
-- Only affects touchscreen clients.
-- Defines the meaning of short and long taps with the item in hand.
-- The fields in this table have two valid values:
-- * "long_dig_short_place" (long tap = dig, short tap = place)
-- * "short_dig_long_place" (short tap = dig, long tap = place)
-- The field to be used is selected according to the current
-- `pointed_thing`.

pointed_nothing = "long_dig_short_place",
pointed_node = "long_dig_short_place",
pointed_object = "short_dig_long_place",
},

sound = {
-- Definition of item sounds to be played at various events.
-- All fields in this table are optional.
Expand Down
5 changes: 5 additions & 0 deletions src/client/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3349,6 +3349,11 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud)
if (pointed != runData.pointed_old)
infostream << "Pointing at " << pointed.dump() << std::endl;

#ifdef HAVE_TOUCHSCREENGUI
if (g_touchscreengui)
g_touchscreengui->applyContextControls(selected_def.touch_interaction.getMode(pointed));
#endif

// Note that updating the selection mesh every frame is not particularly efficient,
// but the halo rendering code is already inefficient so there's no point in optimizing it here
hud->updateSelectionMesh(camera_offset);
Expand Down
224 changes: 125 additions & 99 deletions src/gui/touchscreengui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Copyright (C) 2014 sapier
Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto
<[email protected]>
Copyright (C) 2024 grorp, Gregor Parzefall
<[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
Expand Down Expand Up @@ -657,23 +659,10 @@ void TouchScreenGUI::handleReleaseEvent(size_t evt_id)
// handle the point used for moving view
m_has_move_id = false;

// if this pointer issued a mouse event issue symmetric release here
if (m_move_sent_as_mouse_event) {
SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = m_move_downlocation.X;
translated.MouseInput.Y = m_move_downlocation.Y;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = 0;
translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
if (m_draw_crosshair) {
translated.MouseInput.X = m_screensize.X / 2;
translated.MouseInput.Y = m_screensize.Y / 2;
}
m_receiver->OnEvent(translated);
} else if (!m_move_has_really_moved) {
doRightClick();
if (!m_move_has_really_moved && m_tap_state == TapState::None) {
m_tap_state = TapState::ShortTap;
} else {
m_tap_state = TapState::None;
}
}

Expand Down Expand Up @@ -794,10 +783,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event)
m_move_id = event.TouchInput.ID;
m_move_has_really_moved = false;
m_move_downtime = porting::getTimeMs();
m_move_downlocation = touch_pos;
m_move_sent_as_mouse_event = false;
if (m_draw_crosshair)
m_move_downlocation = v2s32(m_screensize.X / 2, m_screensize.Y / 2);
m_tap_state = TapState::None;
}
}
}
Expand All @@ -820,33 +806,20 @@ void TouchScreenGUI::translateEvent(const SEvent &event)

const double touch_threshold_sq = m_touchscreen_threshold * m_touchscreen_threshold;

if (m_has_move_id) {
if (event.TouchInput.ID == m_move_id &&
(!m_move_sent_as_mouse_event || m_draw_crosshair)) {
if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) {
m_move_has_really_moved = true;
if (m_has_move_id && event.TouchInput.ID == m_move_id) {
if (dir_free.getLengthSQ() > touch_threshold_sq || m_move_has_really_moved) {
m_move_has_really_moved = true;

// update camera_yaw and camera_pitch
m_pointer_pos[event.TouchInput.ID] = touch_pos;
m_pointer_pos[event.TouchInput.ID] = touch_pos;

if (m_tap_state == TapState::None || m_draw_crosshair) {
// adapt to similar behavior as pc screen
const double d = g_settings->getFloat("touchscreen_sensitivity", 0.001f, 10.0f) * 3.0f;

// update camera_yaw and camera_pitch
m_camera_yaw_change -= dir_free.X * d;
m_camera_pitch_change += dir_free.Y * d;

// update shootline
// no need to update (X, Y) when using crosshair since the shootline is not used
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(touch_pos);
}
} else if (event.TouchInput.ID == m_move_id && m_move_sent_as_mouse_event) {
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(touch_pos);
}
}

Expand Down Expand Up @@ -931,40 +904,6 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event)
handleButtonEvent((touch_gui_button_id) current_button_id, event.TouchInput.ID, true);
}

bool TouchScreenGUI::doRightClick()
{
v2s32 mPos = v2s32(m_move_downlocation.X, m_move_downlocation.Y);
if (m_draw_crosshair) {
mPos.X = m_screensize.X / 2;
mPos.Y = m_screensize.Y / 2;
}

SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = mPos.X;
translated.MouseInput.Y = mPos.Y;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = EMBSM_RIGHT;

// update shootline
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(mPos);

translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
m_receiver->OnEvent(translated);

translated.MouseInput.ButtonStates = 0;
translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
m_receiver->OnEvent(translated);

return true;
}

void TouchScreenGUI::applyJoystickStatus()
{
if (m_joystick_triggers_aux1) {
Expand Down Expand Up @@ -1038,37 +977,27 @@ void TouchScreenGUI::step(float dtime)
applyJoystickStatus();

// if a new placed pointer isn't moved for some time start digging
if (m_has_move_id &&
(!m_move_has_really_moved) &&
(!m_move_sent_as_mouse_event)) {
if (m_has_move_id && !m_move_has_really_moved && m_tap_state == TapState::None) {
u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());

if (delta > MIN_DIG_TIME_MS) {
s32 mX = m_move_downlocation.X;
s32 mY = m_move_downlocation.Y;
if (m_draw_crosshair) {
mX = m_screensize.X / 2;
mY = m_screensize.Y / 2;
}
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(v2s32(mX, mY));

SEvent translated {};
translated.EventType = EET_MOUSE_INPUT_EVENT;
translated.MouseInput.X = mX;
translated.MouseInput.Y = mY;
translated.MouseInput.Shift = false;
translated.MouseInput.Control = false;
translated.MouseInput.ButtonStates = EMBSM_LEFT;
translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
verbosestream << "TouchScreenGUI::step left click press" << std::endl;
m_receiver->OnEvent(translated);
m_move_sent_as_mouse_event = true;
m_tap_state = TapState::LongTap;
}
}

// Update the shootline.
// Since not only the pointer position, but also the player position and
// thus the camera position can change, it doesn't suffice to update the
// shootline when a touch event occurs.
// Note that the shootline isn't used if touch_use_crosshair is enabled.
if (!m_draw_crosshair) {
v2s32 pointer_pos = getPointerPos();
m_shootline = m_device
->getSceneManager()
->getSceneCollisionManager()
->getRayFromScreenCoordinates(pointer_pos);
}

m_settings_bar.step(dtime);
m_rare_controls_bar.step(dtime);
}
Expand Down Expand Up @@ -1125,3 +1054,100 @@ void TouchScreenGUI::show()

setVisible(true);
}

v2s32 TouchScreenGUI::getPointerPos()
{
if (m_draw_crosshair)
return v2s32(m_screensize.X / 2, m_screensize.Y / 2);
return m_pointer_pos[m_move_id];
}

void TouchScreenGUI::emitMouseEvent(EMOUSE_INPUT_EVENT type)
{
v2s32 pointer_pos = getPointerPos();

SEvent event{};
event.EventType = EET_MOUSE_INPUT_EVENT;
event.MouseInput.X = pointer_pos.X;
event.MouseInput.Y = pointer_pos.Y;
event.MouseInput.Shift = false;
event.MouseInput.Control = false;
event.MouseInput.ButtonStates = 0;
event.MouseInput.Event = type;
m_receiver->OnEvent(event);
}

void TouchScreenGUI::applyContextControls(const TouchInteractionMode &mode)
{
// Since the pointed thing has already been determined when this function
// is called, we cannot use this function to update the shootline.

bool target_dig_pressed = false;
bool target_place_pressed = false;

u64 now = porting::getTimeMs();

switch (m_tap_state) {
case TapState::ShortTap:
if (mode == SHORT_DIG_LONG_PLACE) {
if (!m_dig_pressed) {
// The button isn't currently pressed, we can press it.
m_dig_pressed_until = now + SIMULATED_CLICK_DURATION_MS;
// We're done with this short tap.
m_tap_state = TapState::None;
} else {
// The button is already pressed, perhaps due to another short tap.
// Release it now, press it again during the next client step.
// We can't release and press during the same client step because
// the digging code simply ignores that.
m_dig_pressed_until = 0;
}
} else {
if (!m_place_pressed) {
// The button isn't currently pressed, we can press it.
m_place_pressed_until = now + SIMULATED_CLICK_DURATION_MS;
// We're done with this short tap.
m_tap_state = TapState::None;
} else {
// The button is already pressed, perhaps due to another short tap.
// Release it now, press it again during the next client step.
// We can't release and press during the same client step because
// the digging code simply ignores that.
m_place_pressed_until = 0;
}
}
break;

case TapState::LongTap:
if (mode == SHORT_DIG_LONG_PLACE)
target_place_pressed = true;
else
target_dig_pressed = true;
break;

case TapState::None:
break;
}

// Apply short taps.
target_dig_pressed |= now < m_dig_pressed_until;
target_place_pressed |= now < m_place_pressed_until;

if (target_dig_pressed && !m_dig_pressed) {
emitMouseEvent(EMIE_LMOUSE_PRESSED_DOWN);
m_dig_pressed = true;

} else if (!target_dig_pressed && m_dig_pressed) {
emitMouseEvent(EMIE_LMOUSE_LEFT_UP);
m_dig_pressed = false;
}

if (target_place_pressed && !m_place_pressed) {
emitMouseEvent(EMIE_RMOUSE_PRESSED_DOWN);
m_place_pressed = true;

} else if (!target_place_pressed && m_place_pressed) {
emitMouseEvent(irr::EMIE_RMOUSE_LEFT_UP);
m_place_pressed = false;
}
}
28 changes: 23 additions & 5 deletions src/gui/touchscreengui.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <unordered_map>
#include <vector>

#include "itemdef.h"
#include "client/tile.h"
#include "client/game.h"

using namespace irr;
using namespace irr::core;
using namespace irr::gui;

enum class TapState
{
None,
ShortTap,
LongTap,
};

typedef enum
{
jump_id = 0,
Expand Down Expand Up @@ -75,6 +83,10 @@ typedef enum
#define SETTINGS_BAR_Y_OFFSET 5
#define RARE_CONTROLS_BAR_Y_OFFSET 5

// Our simulated clicks last some milliseconds so that server-side mods have a
// chance to detect them via l_get_player_control.
#define SIMULATED_CLICK_DURATION_MS 50

extern const std::string button_image_names[];
extern const std::string joystick_image_names[];

Expand Down Expand Up @@ -161,6 +173,7 @@ class TouchScreenGUI
~TouchScreenGUI();

void translateEvent(const SEvent &event);
void applyContextControls(const TouchInteractionMode &mode);

void init(ISimpleTextureSource *tsrc);

Expand Down Expand Up @@ -230,8 +243,6 @@ class TouchScreenGUI
size_t m_move_id;
bool m_move_has_really_moved = false;
u64 m_move_downtime = 0;
bool m_move_sent_as_mouse_event = false;
v2s32 m_move_downlocation = v2s32(-10000, -10000); // off-screen

bool m_has_joystick_id = false;
size_t m_joystick_id;
Expand Down Expand Up @@ -283,9 +294,6 @@ class TouchScreenGUI
// handle pressing hotbar items
bool isHotbarButton(const SEvent &event);

// do a right-click
bool doRightClick();

// handle release event
void handleReleaseEvent(size_t evt_id);

Expand All @@ -300,6 +308,16 @@ class TouchScreenGUI

// rare controls bar
AutoHideButtonBar m_rare_controls_bar;

v2s32 getPointerPos();
void emitMouseEvent(EMOUSE_INPUT_EVENT type);
TapState m_tap_state = TapState::None;

bool m_dig_pressed = false;
u64 m_dig_pressed_until = 0;

bool m_place_pressed = false;
u64 m_place_pressed_until = 0;
};

extern TouchScreenGUI *g_touchscreengui;
Loading

0 comments on commit 5bd2bf9

Please sign in to comment.