From 326eeca306f7bfb53ae3685eef18978dd81e587e Mon Sep 17 00:00:00 2001 From: Muhammad Rifqi Priyo Susanto Date: Wed, 11 Apr 2018 03:55:17 +0700 Subject: [PATCH] Android: Replace movement buttons with joystick (#7126) * Android: Replace movement buttons with joystick Replace movement control buttons (arrows at bottom left screen) with virtual joystick. Joystick has 8 directions (same as keyboard). Basically, just map it to keyboard input. Joystick applies only on left 1/3 of screen. Joystick's position can be fixed by enabling fixed_virtual_joystick setting. Three new images: (1) placeholder joystick, (2) joystick container (background), and (3) joystick cursor. Remove unused images: movement control buttons (*_arrow.png). New data type: touch_gui_joystick_move_id Joystick's fixed position is spaced one button size from bottom and from left of screen. Remove unused variable: m_joystick_downlocation --- builtin/settingtypes.txt | 7 + minetest.conf.example | 9 + src/defaultsettings.cpp | 1 + src/gui/touchscreengui.cpp | 353 ++++++++++++++++++------- src/gui/touchscreengui.h | 90 ++++--- textures/base/pack/down_arrow.png | Bin 373 -> 0 bytes textures/base/pack/joystick_bg.png | Bin 0 -> 5733 bytes textures/base/pack/joystick_center.png | Bin 0 -> 3073 bytes textures/base/pack/joystick_off.png | Bin 0 -> 5880 bytes textures/base/pack/left_arrow.png | Bin 400 -> 0 bytes textures/base/pack/right_arrow.png | Bin 396 -> 0 bytes textures/base/pack/up_arrow.png | Bin 373 -> 0 bytes 12 files changed, 339 insertions(+), 121 deletions(-) delete mode 100644 textures/base/pack/down_arrow.png create mode 100755 textures/base/pack/joystick_bg.png create mode 100755 textures/base/pack/joystick_center.png create mode 100755 textures/base/pack/joystick_off.png delete mode 100644 textures/base/pack/left_arrow.png delete mode 100644 textures/base/pack/right_arrow.png delete mode 100644 textures/base/pack/up_arrow.png diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 99febe1ef..080a57888 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -118,6 +118,13 @@ random_input (Random input) bool false # Continuous forward movement, toggled by autoforward key. continuous_forward (Continuous forward) bool false +# The length in pixels it takes for touch screen interaction to start. +touchscreen_threshold (Touch screen threshold) int 20 0 100 + +# (Android) Fixes the position of virtual joystick. +# If disabled, virtual joystick will center to first-touch's position. +fixed_virtual_joystick (Fixed virtual joystick) bool false + # Enable joysticks enable_joysticks (Enable joysticks) bool false diff --git a/minetest.conf.example b/minetest.conf.example index 46d9ffe65..679f61dc1 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -86,6 +86,15 @@ # type: bool # continuous_forward = false +# The length in pixels it takes for touch screen interaction to start. +# type: int +# touchscreen_threshold = 20 + +# (Android) Fixes the position of virtual joystick. +# If disabled, virtual joystick will center to first-touch's position. +# type: int +# fixed_virtual_joystick = false + # Enable Joysticks # type: bool # enable_joysticks = false diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 586408dcf..0c13e052d 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -408,6 +408,7 @@ void set_default_settings(Settings *settings) settings->setDefault("touchtarget", "true"); settings->setDefault("TMPFolder","/sdcard/" PROJECT_NAME_C "/tmp/"); settings->setDefault("touchscreen_threshold","20"); + settings->setDefault("fixed_virtual_joystick", "false"); settings->setDefault("smooth_lighting", "false"); settings->setDefault("max_simultaneous_block_sends_per_client", "3"); settings->setDefault("emergequeue_limit_diskonly", "8"); diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp index e849b4053..9b5731652 100644 --- a/src/gui/touchscreengui.cpp +++ b/src/gui/touchscreengui.cpp @@ -1,5 +1,7 @@ /* Copyright (C) 2014 sapier +Copyright (C) 2018 srifqi, Muhammad Rifqi Priyo Susanto + 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 @@ -37,15 +39,17 @@ with this program; if not, write to the Free Software Foundation, Inc., using namespace irr::core; -const char** touchgui_button_imagenames = (const char*[]) { - "up_arrow.png", - "down_arrow.png", - "left_arrow.png", - "right_arrow.png", +const char **touchgui_button_imagenames = (const char*[]) { "jump_btn.png", "down.png" }; +const char **touchgui_joystick_imagenames = (const char *[]) { + "joystick_off.png", + "joystick_bg.png", + "joystick_center.png" +}; + static irr::EKEY_CODE id2keycode(touch_gui_button_id id) { std::string key = ""; @@ -143,7 +147,7 @@ void AutoHideButtonBar::init(ISimpleTextureSource* tsrc, m_upper_left = UpperLeft; m_lower_right = LowerRight; - /* init settings bar */ + // init settings bar irr::core::rect current_button = rect(UpperLeft.X, UpperLeft.Y, LowerRight.X, LowerRight.Y); @@ -258,7 +262,7 @@ bool AutoHideButtonBar::isButton(const SEvent &event) } if (m_active) { - /* check for all buttons in vector */ + // check for all buttons in vector std::vector::iterator iter = m_buttons.begin(); @@ -273,11 +277,11 @@ bool AutoHideButtonBar::isButton(const SEvent &event) translated->KeyInput.Shift = false; translated->KeyInput.Char = 0; - /* add this event */ + // add this event translated->KeyInput.PressedDown = true; m_receiver->OnEvent(*translated); - /* remove this event */ + // remove this event translated->KeyInput.PressedDown = false; m_receiver->OnEvent(*translated); @@ -292,7 +296,7 @@ bool AutoHideButtonBar::isButton(const SEvent &event) ++iter; } } else { - /* check for starter button only */ + // check for starter button only if (element == m_starter.guibutton) { m_starter.ids.push_back(event.TouchInput.ID); m_starter.guibutton->setVisible(false); @@ -415,6 +419,8 @@ TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver) m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY; } + m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); + m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); m_screensize = m_device->getVideoDriver()->getScreenSize(); } @@ -435,6 +441,21 @@ void TouchScreenGUI::initButton(touch_gui_button_id id, rect button_rect, m_texturesource, m_device->getVideoDriver()); } +button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, rect button_rect, + int texture_id, bool visible) +{ + button_info *btn = new button_info(); + btn->guibutton = m_guienv->addButton(button_rect, 0, id, L"O"); + btn->guibutton->setVisible(visible); + btn->guibutton->grab(); + btn->ids.clear(); + + load_button_texture(btn, touchgui_joystick_imagenames[texture_id], button_rect, + m_texturesource, m_device->getVideoDriver()); + + return btn; +} + static int getMaxControlPadSize(float density) { return 200 * density * g_settings->getFloat("hud_scaling"); } @@ -449,50 +470,40 @@ int TouchScreenGUI::getGuiButtonSize() void TouchScreenGUI::init(ISimpleTextureSource* tsrc) { - assert(tsrc != 0); + assert(tsrc); - u32 button_size = getGuiButtonSize(); - m_visible = true; - m_texturesource = tsrc; - /* - draw control pad - 0 1 2 - 3 4 5 - for now only 0, 1, 2, and 4 are used - */ - int number = 0; - for (int y = 0; y < 2; ++y) - for (int x = 0; x < 3; ++x, ++number) { - rect button_rect( - x * button_size, m_screensize.Y - button_size * (2 - y), - (x + 1) * button_size, m_screensize.Y - button_size * (1 - y) - ); - touch_gui_button_id id = after_last_element_id; - std::wstring caption; - switch (number) { - case 0: - id = left_id; - caption = L"<"; - break; - case 1: - id = forward_id; - caption = L"^"; - break; - case 2: - id = right_id; - caption = L">"; - break; - case 4: - id = backward_id; - caption = L"v"; - break; - } - if (id != after_last_element_id) { - initButton(id, button_rect, caption, false); - } - } + u32 button_size = getGuiButtonSize(); + m_visible = true; + m_texturesource = tsrc; - /* init jump button */ + /* Init joystick display "button" + * Joystick is placed on bottom left of screen. + */ + if (m_fixed_joystick) { + m_joystick_btn_off = initJoystickButton(joystick_off_id, + rect(button_size, + m_screensize.Y - button_size * 4, + button_size * 4, + m_screensize.Y - button_size), 0); + } else { + m_joystick_btn_off = initJoystickButton(joystick_off_id, + rect(button_size, + m_screensize.Y - button_size * 3, + button_size * 3, + m_screensize.Y - button_size), 0); + } + + m_joystick_btn_bg = initJoystickButton(joystick_bg_id, + rect(button_size, + m_screensize.Y - button_size * 4, + button_size * 4, + m_screensize.Y - button_size), + 1, false); + + m_joystick_btn_center = initJoystickButton(joystick_center_id, + rect(0, 0, button_size, button_size), 2, false); + + // init jump button initButton(jump_id, rect(m_screensize.X-(1.75*button_size), m_screensize.Y - (0.5*button_size), @@ -500,7 +511,7 @@ void TouchScreenGUI::init(ISimpleTextureSource* tsrc) m_screensize.Y), L"x",false); - /* init crunch button */ + // init crunch button initButton(crunch_id, rect(m_screensize.X-(3.25*button_size), m_screensize.Y - (0.5*button_size), @@ -634,7 +645,7 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button, translated->KeyInput.Shift = false; translated->KeyInput.Char = 0; - /* add this event */ + // add this event if (action) { assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end()); @@ -647,12 +658,12 @@ void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button, translated->KeyInput.Key = btn->keycode; m_receiver->OnEvent(*translated); } - /* remove event */ + // remove event if ((!action) || (btn->immediate_release)) { std::vector::iterator pos = std::find(btn->ids.begin(),btn->ids.end(), eventID); - /* has to be in touch list */ + // has to be in touch list assert(pos != btn->ids.end()); btn->ids.erase(pos); @@ -670,23 +681,23 @@ void TouchScreenGUI::handleReleaseEvent(int evt_id) { touch_gui_button_id button = getButtonID(evt_id); - /* handle button events */ + // handle button events if (button != after_last_element_id) { handleButtonEvent(button, evt_id, false); } - /* handle hud button events */ + // handle hud button events else if (isReleaseHUDButton(evt_id)) { - /* nothing to do here */ + // nothing to do here } else if (m_settingsbar.isReleaseButton(evt_id)) { - /* nothing to do here */ + // nothing to do here } else if (m_rarecontrolsbar.isReleaseButton(evt_id)) { - /* nothing to do here */ + // nothing to do here } - /* handle the point used for moving view */ + // handle the point used for moving view else if (evt_id == m_move_id) { m_move_id = -1; - /* if this pointer issued a mouse event issue symmetric release here */ + // if this pointer issued a mouse event issue symmetric release here if (m_move_sent_as_mouse_event) { SEvent* translated = new SEvent; memset(translated,0,sizeof(SEvent)); @@ -701,10 +712,23 @@ void TouchScreenGUI::handleReleaseEvent(int evt_id) delete translated; } else { - /* do double tap detection */ + // do double tap detection doubleTapDetection(); } } + // handle joystick + else if (evt_id == m_joystick_id) { + m_joystick_id = -1; + + // reset joystick + for (unsigned int i = 0; i < 4; i ++) + m_joystick_status[i] = false; + applyJoystickStatus(); + + m_joystick_btn_off->guibutton->setVisible(true); + m_joystick_btn_bg->guibutton->setVisible(false); + m_joystick_btn_center->guibutton->setVisible(false); + } else { infostream << "TouchScreenGUI::translateEvent released unknown button: " @@ -748,7 +772,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) touch_gui_button_id button = getButtonID(event.TouchInput.X, event.TouchInput.Y); - /* handle button events */ + // handle button events if (button != after_last_element_id) { handleButtonEvent(button, eventID, true); m_settingsbar.deactivate(); @@ -756,25 +780,56 @@ void TouchScreenGUI::translateEvent(const SEvent &event) } else if (isHUDButton(event)) { m_settingsbar.deactivate(); m_rarecontrolsbar.deactivate(); - /* already handled in isHUDButton() */ + // already handled in isHUDButton() } else if (m_settingsbar.isButton(event)) { m_rarecontrolsbar.deactivate(); - /* already handled in isSettingsBarButton() */ + // already handled in isSettingsBarButton() } else if (m_rarecontrolsbar.isButton(event)) { m_settingsbar.deactivate(); - /* already handled in isSettingsBarButton() */ + // already handled in isSettingsBarButton() } - /* handle non button events */ + // handle non button events else { m_settingsbar.deactivate(); m_rarecontrolsbar.deactivate(); - /* if we don't already have a moving point make this the moving one */ - if (m_move_id == -1) { - m_move_id = event.TouchInput.ID; - m_move_has_really_moved = false; - m_move_downtime = porting::getTimeMs(); - m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y); - m_move_sent_as_mouse_event = false; + + u32 button_size = getGuiButtonSize(); + s32 dxj = event.TouchInput.X - button_size * 5 / 2; + s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5 / 2; + + /* Select joystick when left 1/3 of screen dragged or + * when joystick tapped (fixed joystick position) + */ + if ((m_fixed_joystick && dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5) || + (!m_fixed_joystick && event.TouchInput.X < m_screensize.X / 3)) { + // If we don't already have a starting point for joystick make this the one. + if (m_joystick_id == -1) { + m_joystick_id = event.TouchInput.ID; + m_joystick_has_really_moved = false; + + m_joystick_btn_off->guibutton->setVisible(false); + m_joystick_btn_bg->guibutton->setVisible(true); + m_joystick_btn_center->guibutton->setVisible(true); + + // If it's a fixed joystick, don't move the joystick "button". + if (!m_fixed_joystick) { + m_joystick_btn_bg->guibutton->setRelativePosition(v2s32( + event.TouchInput.X - button_size * 3 / 2, + event.TouchInput.Y - button_size * 3 / 2)); + } + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + event.TouchInput.X - button_size / 2, + event.TouchInput.Y - button_size / 2)); + } + } else { + // If we don't already have a moving point make this the moving one. + if (m_move_id == -1) { + m_move_id = event.TouchInput.ID; + m_move_has_really_moved = false; + m_move_downtime = porting::getTimeMs(); + m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y); + m_move_sent_as_mouse_event = false; + } } } @@ -803,7 +858,7 @@ void TouchScreenGUI::translateEvent(const SEvent &event) (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) * (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y)); - if ((distance > g_settings->getU16("touchscreen_threshold")) || + if ((distance > m_touchscreen_threshold) || (m_move_has_really_moved)) { m_move_has_really_moved = true; s32 X = event.TouchInput.X; @@ -813,8 +868,8 @@ void TouchScreenGUI::translateEvent(const SEvent &event) s32 dx = X - m_pointerpos[event.TouchInput.ID].X; s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; - /* adapt to similar behaviour as pc screen */ - double d = g_settings->getFloat("mouse_sensitivity") *4; + // adapt to similar behaviour as pc screen + double d = g_settings->getFloat("mouse_sensitivity") * 4; double old_yaw = m_camera_yaw_change; double old_pitch = m_camera_pitch; @@ -835,9 +890,91 @@ void TouchScreenGUI::translateEvent(const SEvent &event) ->getSceneManager() ->getSceneCollisionManager() ->getRayFromScreenCoordinates( - v2s32(event.TouchInput.X,event.TouchInput.Y)); + v2s32(event.TouchInput.X, event.TouchInput.Y)); } - } else { + } + + if (m_joystick_id != -1 && event.TouchInput.ID == m_joystick_id) { + u32 button_size = getGuiButtonSize(); + s32 X = event.TouchInput.X; + s32 Y = event.TouchInput.Y; + + s32 dx = X - m_pointerpos[event.TouchInput.ID].X; + s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y; + if (m_fixed_joystick) { + dx = X - button_size * 5 / 2; + dy = Y - m_screensize.Y + button_size * 5 / 2; + } + + double distance_sq = dx * dx + dy * dy; + + s32 dxj = event.TouchInput.X - button_size * 5 / 2; + s32 dyj = event.TouchInput.Y - m_screensize.Y + button_size * 5 / 2; + bool inside_joystick = (dxj * dxj + dyj * dyj <= button_size * button_size * 1.5 * 1.5); + + if (m_joystick_has_really_moved || + (!m_joystick_has_really_moved && inside_joystick) || + (!m_fixed_joystick && + distance_sq > m_touchscreen_threshold * m_touchscreen_threshold)) { + m_joystick_has_really_moved = true; + double distance = sqrt(distance_sq); + + // angle in degrees + double angle = acos(dx / distance) * 180 / M_PI; + if (dy < 0) + angle *= -1; + // rotate to make comparing easier + angle = fmod(angle + 180 + 22.5, 360); + + // reset state before applying + for (unsigned int i = 0; i < 4; i ++) + m_joystick_status[i] = false; + + if (distance <= m_touchscreen_threshold) { + // do nothing + } else if (angle < 45) + m_joystick_status[j_left] = true; + else if (angle < 90) { + m_joystick_status[j_forward] = true; + m_joystick_status[j_left] = true; + } else if (angle < 135) + m_joystick_status[j_forward] = true; + else if (angle < 180) { + m_joystick_status[j_forward] = true; + m_joystick_status[j_right] = true; + } else if (angle < 225) + m_joystick_status[j_right] = true; + else if (angle < 270) { + m_joystick_status[j_backward] = true; + m_joystick_status[j_right] = true; + } else if (angle < 315) + m_joystick_status[j_backward] = true; + else if (angle <= 360) { + m_joystick_status[j_backward] = true; + m_joystick_status[j_left] = true; + } + + // move joystick "button" + if (distance > button_size) { + s32 ndx = (s32) button_size * dx / distance - (s32) button_size / 2; + s32 ndy = (s32) button_size * dy / distance - (s32) button_size / 2; + if (m_fixed_joystick) { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + button_size * 5 / 2 + ndx, + m_screensize.Y - button_size * 5 / 2 + ndy)); + } else { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + m_pointerpos[event.TouchInput.ID].X + ndx, + m_pointerpos[event.TouchInput.ID].Y + ndy)); + } + } else { + m_joystick_btn_center->guibutton->setRelativePosition(v2s32( + X - button_size / 2, Y - button_size / 2)); + } + } + } + + if (m_move_id == -1 && m_joystick_id == -1) { handleChangedButton(event); } } @@ -862,7 +999,7 @@ void TouchScreenGUI::handleChangedButton(const SEvent &event) continue; } - /* remove old button */ + // remove old button handleButtonEvent((touch_gui_button_id) i,*iter,false); if (current_button_id == after_last_element_id) { @@ -909,7 +1046,7 @@ bool TouchScreenGUI::doubleTapDetection() (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y)); - if (distance > (20 + g_settings->getU16("touchscreen_threshold"))) + if (distance > (20 + m_touchscreen_threshold)) return false; SEvent* translated = new SEvent(); @@ -940,27 +1077,58 @@ bool TouchScreenGUI::doubleTapDetection() } +void TouchScreenGUI::applyJoystickStatus() +{ + for (unsigned int i = 0; i < 4; i ++) { + SEvent translated{}; + translated.EventType = irr::EET_KEY_INPUT_EVENT; + translated.KeyInput.Key = id2keycode(m_joystick_names[i]); + translated.KeyInput.PressedDown = false; + m_receiver->OnEvent(translated); + + if (m_joystick_status[i]) { + translated.KeyInput.PressedDown = true; + m_receiver->OnEvent(translated); + } + } +} + TouchScreenGUI::~TouchScreenGUI() { for (unsigned int i = 0; i < after_last_element_id; i++) { button_info* btn = &m_buttons[i]; - if (btn->guibutton != 0) { + if (btn->guibutton) { btn->guibutton->drop(); btn->guibutton = NULL; } } + + if (m_joystick_btn_off->guibutton) { + m_joystick_btn_off->guibutton->drop(); + m_joystick_btn_off->guibutton = NULL; + } + + if (m_joystick_btn_bg->guibutton) { + m_joystick_btn_bg->guibutton->drop(); + m_joystick_btn_bg->guibutton = NULL; + } + + if (m_joystick_btn_center->guibutton) { + m_joystick_btn_center->guibutton->drop(); + m_joystick_btn_center->guibutton = NULL; + } } void TouchScreenGUI::step(float dtime) { - /* simulate keyboard repeats */ + // simulate keyboard repeats for (unsigned int i = 0; i < after_last_element_id; i++) { button_info* btn = &m_buttons[i]; if (btn->ids.size() > 0) { btn->repeatcounter += dtime; - /* in case we're moving around digging does not happen */ + // in case we're moving around digging does not happen if (m_move_id != -1) m_move_has_really_moved = true; @@ -979,7 +1147,10 @@ void TouchScreenGUI::step(float dtime) } } - /* if a new placed pointer isn't moved for some time start digging */ + // joystick + applyJoystickStatus(); + + // if a new placed pointer isn't moved for some time start digging if ((m_move_id != -1) && (!m_move_has_really_moved) && (!m_move_sent_as_mouse_event)) { @@ -1027,12 +1198,16 @@ void TouchScreenGUI::Toggle(bool visible) m_visible = visible; for (unsigned int i = 0; i < after_last_element_id; i++) { button_info* btn = &m_buttons[i]; - if (btn->guibutton != 0) { + if (btn->guibutton) { btn->guibutton->setVisible(visible); } } - /* clear all active buttons */ + if (m_joystick_btn_off->guibutton) { + m_joystick_btn_off->guibutton->setVisible(visible); + } + + // clear all active buttons if (!visible) { while (m_known_ids.size() > 0) { handleReleaseEvent(m_known_ids.begin()->id); diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h index 9d4150ea6..21c52f756 100644 --- a/src/gui/touchscreengui.h +++ b/src/gui/touchscreengui.h @@ -34,11 +34,7 @@ using namespace irr::core; using namespace irr::gui; typedef enum { - forward_id = 0, - backward_id, - left_id, - right_id, - jump_id, + jump_id = 0, crunch_id, after_last_element_id, settings_starter_id, @@ -51,9 +47,18 @@ typedef enum { range_id, chat_id, inventory_id, - drop_id + drop_id, + forward_id, + backward_id, + left_id, + right_id, + joystick_off_id, + joystick_bg_id, + joystick_center_id } touch_gui_button_id; +typedef enum { j_forward = 0, j_backward, j_left, j_right } touch_gui_joystick_move_id; + typedef enum { AHBB_Dir_Top_Bottom, AHBB_Dir_Bottom_Top, @@ -69,6 +74,7 @@ typedef enum { #define RARE_CONTROLS_BAR_Y_OFFSET 4 extern const char **touchgui_button_imagenames; +extern const char **touchgui_joystick_imagenames; struct button_info { @@ -91,26 +97,26 @@ public: ~AutoHideButtonBar(); - /* add button to be shown */ + // add button to be shown void addButton(touch_gui_button_id id, const wchar_t *caption, const char *btn_image); - /* detect settings bar button events */ + // detect settings bar button events bool isButton(const SEvent &event); - /* handle released hud buttons */ + // handle released hud buttons bool isReleaseButton(int eventID); - /* step handler */ + // step handler void step(float dtime); - /* deactivate button bar */ + // deactivate button bar void deactivate(); - /* hide the whole buttonbar */ + // hide the whole buttonbar void hide(); - /* unhide the buttonbar */ + // unhide the buttonbar void show(); private: @@ -124,12 +130,12 @@ private: v2s32 m_upper_left; v2s32 m_lower_right; - /* show settings bar */ + // show settings bar bool m_active = false; bool m_visible = true; - /* settings bar timeout */ + // settings bar timeout float m_timeout = 0.0f; float m_timeout_value = 3.0f; bool m_initialized = false; @@ -179,14 +185,20 @@ private: IEventReceiver *m_receiver; ISimpleTextureSource *m_texturesource; v2u32 m_screensize; + double m_touchscreen_threshold; std::map> m_hud_rects; std::map m_hud_ids; bool m_visible; // is the gui visible - /* value in degree */ + // value in degree double m_camera_yaw_change = 0.0; double m_camera_pitch = 0.0; + // forward, backward, left, right + touch_gui_button_id m_joystick_names[4] = { + forward_id, backward_id, left_id, right_id}; + bool m_joystick_status[4] = {false, false, false, false}; + /*! * A line starting at the camera and pointing towards the * selected object. @@ -201,22 +213,33 @@ private: bool m_move_sent_as_mouse_event = false; v2s32 m_move_downlocation = v2s32(-10000, -10000); + int m_joystick_id = -1; + bool m_joystick_has_really_moved = false; + bool m_fixed_joystick = false; + button_info *m_joystick_btn_off = nullptr; + button_info *m_joystick_btn_bg = nullptr; + button_info *m_joystick_btn_center = nullptr; + button_info m_buttons[after_last_element_id]; - /* gui button detection */ + // gui button detection touch_gui_button_id getButtonID(s32 x, s32 y); - /* gui button by eventID */ + // gui button by eventID touch_gui_button_id getButtonID(int eventID); - /* check if a button has changed */ + // check if a button has changed void handleChangedButton(const SEvent &event); - /* initialize a button */ + // initialize a button void initButton(touch_gui_button_id id, rect button_rect, std::wstring caption, bool immediate_release, float repeat_delay = BUTTON_REPEAT_DELAY); + // initialize a joystick button + button_info *initJoystickButton(touch_gui_button_id id, rect button_rect, + int texture_id, bool visible = true); + struct id_status { int id; @@ -224,28 +247,31 @@ private: int Y; }; - /* vector to store known ids and their initial touch positions*/ + // vector to store known ids and their initial touch positions std::vector m_known_ids; - /* handle a button event */ + // handle a button event void handleButtonEvent(touch_gui_button_id bID, int eventID, bool action); - /* handle pressed hud buttons */ + // handle pressed hud buttons bool isHUDButton(const SEvent &event); - /* handle released hud buttons */ + // handle released hud buttons bool isReleaseHUDButton(int eventID); - /* handle double taps */ + // handle double taps bool doubleTapDetection(); - /* handle release event */ + // handle release event void handleReleaseEvent(int evt_id); - /* get size of regular gui control button */ + // apply joystick status + void applyJoystickStatus(); + + // get size of regular gui control button int getGuiButtonSize(); - /* doubleclick detection variables */ + // doubleclick detection variables struct key_event { unsigned int down_time; @@ -253,16 +279,16 @@ private: s32 y; }; - /* array for saving last known position of a pointer */ + // array for saving last known position of a pointer v2s32 m_pointerpos[MAX_TOUCH_COUNT]; - /* array for doubletap detection */ + // array for doubletap detection key_event m_key_events[2]; - /* settings bar */ + // settings bar AutoHideButtonBar m_settingsbar; - /* rare controls bar */ + // rare controls bar AutoHideButtonBar m_rarecontrolsbar; }; extern TouchScreenGUI *g_touchscreengui; diff --git a/textures/base/pack/down_arrow.png b/textures/base/pack/down_arrow.png deleted file mode 100644 index 60ac4976248badb05fa0ede7c1c599149ec32f02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?K3?%mjbVdLv&H$efS0Mc#42mn3CIdN4B|(0{ z3=CzH&ss1rFiLv5IEGZjy}h=vx7k6W^`PiMR=rg3#avzmoP3F^Z4&2yZTk6e)8x`e zs(yACDkd+jJ+sekWA*7O(KWi#PlbF+H?(XxuG3K#y?TMYkj<(G-};-hcYx81<13mC zw_PtSmg!O5|L$)}eSG|z=g(#{PuOTb>-+APk}XgFY3-O_EBfb~`S;E5ZWkJ?uP$w> zkozjq((-ib+_@$k9FA951O$Yp0SN~O21Uh{Kt_gQ!-NSDOdK3nfMN>m0xT?59102z z4Gc^`F$E_FUPcy(az+?K5F`Or!KvgV0F`J7Pyz`mwFogXx^S2#y;YpjVd%6%P*Fy~ gX@zuqu^!fG*|rLXhZd{W89?$BmGUi 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 literal 0 HcmV?d00001 diff --git a/textures/base/pack/joystick_center.png b/textures/base/pack/joystick_center.png new file mode 100755 index 0000000000000000000000000000000000000000..fad012c5f1c188c8faba753f93bad6d49370da65 GIT binary patch literal 3073 zcmV+c4F2WFU8GbZ8()Nlj2>E@cM*01IVFL_t(|+U=cPXkAqn z$A9alA0}x+6#F5aA+4eaz0FueP^dx`97;ik7RLvzPZc`IID!u{RWO1&_#jvZ8KHSH z`XEvn1SunE!3k85QYKA7l&WPW*jA~fA4yG`?s?e%V@|I5xXHaYx#yhqhs(M51@;0@0n337pmUaw59oc@+_Js>ze#AcxyrN((!Kb>@hjphnBieP%TY&ph=ZArPX7;uVU}n<0fQNzC z6v!oa`w`$n;9ADQl@jm}a8JrBTm+s0-Z!)Jg%>~)IfoEl1@-_p=uck*@21>+mXS0I zj{>*q+&Q4n%${B9-osK0paU^VcZyh9)+n;tQ#nFUI+e?>dPM@=F}a(MyHyDB!{ry%%0Aq0Oa;RV{&li1K{HV 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 U6)yCO@C(qG!%l}P58`h84{vthUH||9 literal 0 HcmV?d00001 diff --git a/textures/base/pack/left_arrow.png b/textures/base/pack/left_arrow.png deleted file mode 100644 index 6076241c5266a3d529f87a52dd8be8410db426d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 400 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?K3?%mjbVdLv&H$efS0Mc#42mn3CIdN4B|(0{ z3=CzH&ss1rFq(L}IEGZjoxQ$sZL@;}%fpGzUWJ(JVZ<0f6l@RTTgz`2eyLBN1Rf`x~Pjd8t&+M%}x zlGQ$Z>w4~!@VV>0bYf7ibYk80UW233iCx)DJJcE+4=5lsG?T_XPDRMWal2orVc84}O$JX_KbLh*2~7ZJZHXKJ diff --git a/textures/base/pack/right_arrow.png b/textures/base/pack/right_arrow.png deleted file mode 100644 index dc90674406b2b76a15ae576b076ba52a7187edc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?K3?%mjbVdLv&H$efS0Mc#42mn3CIdN4B|(0{ z3=CzH&ss1rFdBHeIEGZjoxQ$yEwdpH%fo}Ld=XvV!J>Mt3-ediynM)cfz4-+VKe`C zW!;|#D~iu7mG{%k^{ae6JveOooJY?~-8jl5BHUX7+8T|Wn82`%<+S?^!RhBWD4%lQ z_oicezln8{>G?Mu_0MD#qfZ?^&?j(?<6+N)1_~kbBR*#R=7(O)J?whRPwZOGe@brO znxbC^ZyfI5Hs_zkw-3*Cm$P@++8jCc(lLvl|8r5*!W)OZ*UpJs^X)^C{&LpCa?|D% zMQwSLcvCz1Vb4_4Y4<{ACx7g_YCG-!7CRqtf$AMBQ!l%ok<)Enefp+T)UC}+XOw=f b{>jW8#(&{x-Q%smkYw<5^>bP0l+XkKljX3M diff --git a/textures/base/pack/up_arrow.png b/textures/base/pack/up_arrow.png deleted file mode 100644 index 840040fcb26f797484dca380263b888a2b151598..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 373 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?K3?%mjbVdLv&H$efS0Mc#42mn3CIdN4B|(0{ z3=CzH&ss1rFiLv5IEGZjy}i0I_lSWAOW>m>?g%dLU{S4usv1+ac{Mj09<0mDkY@k) zWK!hA*dyQg-&YiUm~>8}fT1ky?*dB^L4{bbr$Oz2l>i2 zH1FnOI~X0RFz?CqQ`ViY7Zfh$QsZfnZ?WH>UuVO+I>Y(YabS=!c)I$ztaD0e0svRM Bl)nG~