/* 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 the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "touchscreengui.h" #include "irrlichttypes.h" #include "irr_v2d.h" #include "log.h" #include "client/keycode.h" #include "settings.h" #include "gettime.h" #include "util/numeric.h" #include "porting.h" #include "client/guiscalingfilter.h" #include #include #include using namespace irr::core; const char **button_imagenames = (const char *[]) { "jump_btn.png", "down.png", "zoom.png", "aux_btn.png" }; const char **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 = ""; switch (id) { case forward_id: key = "forward"; break; case left_id: key = "left"; break; case right_id: key = "right"; break; case backward_id: key = "backward"; break; case inventory_id: key = "inventory"; break; case drop_id: key = "drop"; break; case jump_id: key = "jump"; break; case crunch_id: key = "sneak"; break; case zoom_id: key = "zoom"; break; case special1_id: key = "special1"; break; case fly_id: key = "freemove"; break; case noclip_id: key = "noclip"; break; case fast_id: key = "fastmove"; break; case debug_id: key = "toggle_debug"; break; case toggle_chat_id: key = "toggle_chat"; break; case minimap_id: key = "minimap"; break; case chat_id: key = "chat"; break; case camera_id: key = "camera_mode"; break; case range_id: key = "rangeselect"; break; default: break; } assert(!key.empty()); return keyname_to_keycode(g_settings->get("keymap_" + key).c_str()); } TouchScreenGUI *g_touchscreengui; static void load_button_texture(button_info *btn, const char *path, const rect &button_rect, ISimpleTextureSource *tsrc, video::IVideoDriver *driver) { unsigned int tid; video::ITexture *texture = guiScalingImageButton(driver, tsrc->getTexture(path, &tid), button_rect.getWidth(), button_rect.getHeight()); if (texture) { btn->guibutton->setUseAlphaChannel(true); if (g_settings->getBool("gui_scaling_filter")) { rect txr_rect = rect(0, 0, button_rect.getWidth(), button_rect.getHeight()); btn->guibutton->setImage(texture, txr_rect); btn->guibutton->setPressedImage(texture, txr_rect); btn->guibutton->setScaleImage(false); } else { btn->guibutton->setImage(texture); btn->guibutton->setPressedImage(texture); btn->guibutton->setScaleImage(true); } btn->guibutton->setDrawBorder(false); btn->guibutton->setText(L""); } } AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver) : m_driver(device->getVideoDriver()), m_guienv(device->getGUIEnvironment()), m_receiver(receiver) { } void AutoHideButtonBar::init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id, const v2s32 &UpperLeft, const v2s32 &LowerRight, autohide_button_bar_dir dir, float timeout) { m_texturesource = tsrc; m_upper_left = UpperLeft; m_lower_right = LowerRight; // init settings bar irr::core::rect current_button = rect(UpperLeft.X, UpperLeft.Y, LowerRight.X, LowerRight.Y); m_starter.guibutton = m_guienv->addButton(current_button, nullptr, button_id, L"", nullptr); m_starter.guibutton->grab(); m_starter.repeatcounter = -1; m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant m_starter.immediate_release = true; m_starter.ids.clear(); load_button_texture(&m_starter, starter_img, current_button, m_texturesource, m_driver); m_dir = dir; m_timeout_value = timeout; m_initialized = true; } AutoHideButtonBar::~AutoHideButtonBar() { if (m_starter.guibutton) { m_starter.guibutton->setVisible(false); m_starter.guibutton->drop(); } } void AutoHideButtonBar::addButton(touch_gui_button_id button_id, const wchar_t *caption, const char *btn_image) { if (!m_initialized) { errorstream << "AutoHideButtonBar::addButton not yet initialized!" << std::endl; return; } int button_size = 0; if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) button_size = m_lower_right.X - m_upper_left.X; else button_size = m_lower_right.Y - m_upper_left.Y; irr::core::rect current_button; if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) { int x_start = 0; int x_end = 0; if (m_dir == AHBB_Dir_Left_Right) { x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size()) + (button_size * 0.25); x_end = x_start + button_size; } else { x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - (button_size * 0.25); x_start = x_end - button_size; } current_button = rect(x_start, m_upper_left.Y, x_end, m_lower_right.Y); } else { double y_start = 0; double y_end = 0; if (m_dir == AHBB_Dir_Top_Bottom) { y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size()) + (button_size * 0.25); y_end = y_start + button_size; } else { y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size()) - (button_size * 0.25); y_start = y_end - button_size; } current_button = rect(m_upper_left.X, y_start, m_lower_right.Y, y_end); } auto *btn = new button_info(); btn->guibutton = m_guienv->addButton(current_button, nullptr, button_id, caption, nullptr); btn->guibutton->grab(); btn->guibutton->setVisible(false); btn->guibutton->setEnabled(false); btn->repeatcounter = -1; btn->keycode = id2keycode(button_id); btn->immediate_release = true; btn->ids.clear(); load_button_texture(btn, btn_image, current_button, m_texturesource, m_driver); m_buttons.push_back(btn); } void AutoHideButtonBar::addToggleButton(touch_gui_button_id button_id, const wchar_t *caption, const char *btn_image_1, const char *btn_image_2) { addButton(button_id, caption, btn_image_1); button_info *btn = m_buttons.back(); btn->togglable = 1; btn->textures.push_back(btn_image_1); btn->textures.push_back(btn_image_2); } bool AutoHideButtonBar::isButton(const SEvent &event) { IGUIElement *rootguielement = m_guienv->getRootGUIElement(); if (rootguielement == nullptr) return false; gui::IGUIElement *element = rootguielement->getElementFromPoint( core::position2d(event.TouchInput.X, event.TouchInput.Y)); if (element == nullptr) return false; if (m_active) { // check for all buttons in vector auto iter = m_buttons.begin(); while (iter != m_buttons.end()) { if ((*iter)->guibutton == element) { auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); translated->EventType = irr::EET_KEY_INPUT_EVENT; translated->KeyInput.Key = (*iter)->keycode; translated->KeyInput.Control = false; translated->KeyInput.Shift = false; translated->KeyInput.Char = 0; // add this event translated->KeyInput.PressedDown = true; m_receiver->OnEvent(*translated); // remove this event translated->KeyInput.PressedDown = false; m_receiver->OnEvent(*translated); delete translated; (*iter)->ids.push_back(event.TouchInput.ID); m_timeout = 0; if ((*iter)->togglable == 1) { (*iter)->togglable = 2; load_button_texture(*iter, (*iter)->textures[1], (*iter)->guibutton->getRelativePosition(), m_texturesource, m_driver); } else if ((*iter)->togglable == 2) { (*iter)->togglable = 1; load_button_texture(*iter, (*iter)->textures[0], (*iter)->guibutton->getRelativePosition(), m_texturesource, m_driver); } return true; } ++iter; } } else { // check for starter button only if (element == m_starter.guibutton) { m_starter.ids.push_back(event.TouchInput.ID); m_starter.guibutton->setVisible(false); m_starter.guibutton->setEnabled(false); m_active = true; m_timeout = 0; auto iter = m_buttons.begin(); while (iter != m_buttons.end()) { (*iter)->guibutton->setVisible(true); (*iter)->guibutton->setEnabled(true); ++iter; } return true; } } return false; } void AutoHideButtonBar::step(float dtime) { if (m_active) { m_timeout += dtime; if (m_timeout > m_timeout_value) deactivate(); } } void AutoHideButtonBar::deactivate() { if (m_visible) { m_starter.guibutton->setVisible(true); m_starter.guibutton->setEnabled(true); } m_active = false; auto iter = m_buttons.begin(); while (iter != m_buttons.end()) { (*iter)->guibutton->setVisible(false); (*iter)->guibutton->setEnabled(false); ++iter; } } void AutoHideButtonBar::hide() { m_visible = false; m_starter.guibutton->setVisible(false); m_starter.guibutton->setEnabled(false); auto iter = m_buttons.begin(); while (iter != m_buttons.end()) { (*iter)->guibutton->setVisible(false); (*iter)->guibutton->setEnabled(false); ++iter; } } void AutoHideButtonBar::show() { m_visible = true; if (m_active) { auto iter = m_buttons.begin(); while (iter != m_buttons.end()) { (*iter)->guibutton->setVisible(true); (*iter)->guibutton->setEnabled(true); ++iter; } } else { m_starter.guibutton->setVisible(true); m_starter.guibutton->setEnabled(true); } } TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver): m_device(device), m_guienv(device->getGUIEnvironment()), m_receiver(receiver), m_settingsbar(device, receiver), m_rarecontrolsbar(device, receiver) { for (auto &button : m_buttons) { button.guibutton = nullptr; button.repeatcounter = -1; button.repeatdelay = BUTTON_REPEAT_DELAY; } m_touchscreen_threshold = g_settings->getU16("touchscreen_threshold"); m_fixed_joystick = g_settings->getBool("fixed_virtual_joystick"); m_joystick_triggers_special1 = g_settings->getBool("virtual_joystick_triggers_aux"); m_screensize = m_device->getVideoDriver()->getScreenSize(); button_size = MYMIN(m_screensize.Y / 4.5f, porting::getDisplayDensity() * g_settings->getFloat("hud_scaling") * 65.0f); } void TouchScreenGUI::initButton(touch_gui_button_id id, const rect &button_rect, const std::wstring &caption, bool immediate_release, float repeat_delay) { button_info *btn = &m_buttons[id]; btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, caption.c_str()); btn->guibutton->grab(); btn->repeatcounter = -1; btn->repeatdelay = repeat_delay; btn->keycode = id2keycode(id); btn->immediate_release = immediate_release; btn->ids.clear(); load_button_texture(btn, button_imagenames[id], button_rect, m_texturesource, m_device->getVideoDriver()); } button_info *TouchScreenGUI::initJoystickButton(touch_gui_button_id id, const rect &button_rect, int texture_id, bool visible) { auto *btn = new button_info(); btn->guibutton = m_guienv->addButton(button_rect, nullptr, id, L"O"); btn->guibutton->setVisible(visible); btn->guibutton->grab(); btn->ids.clear(); load_button_texture(btn, joystick_imagenames[texture_id], button_rect, m_texturesource, m_device->getVideoDriver()); return btn; } void TouchScreenGUI::init(ISimpleTextureSource *tsrc) { assert(tsrc); m_visible = true; m_texturesource = tsrc; /* 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 - button_size, m_screensize.X - (0.25 * button_size), m_screensize.Y), L"x", false); // init crunch button initButton(crunch_id, rect(m_screensize.X - (3.25 * button_size), m_screensize.Y - button_size, m_screensize.X - (1.75 * button_size), m_screensize.Y), L"H", false); // init zoom button initButton(zoom_id, rect(m_screensize.X - (1.25 * button_size), m_screensize.Y - (4 * button_size), m_screensize.X - (0.25 * button_size), m_screensize.Y - (3 * button_size)), L"z", false); // init special1/aux button if (!m_joystick_triggers_special1) initButton(special1_id, rect(m_screensize.X - (1.25 * button_size), m_screensize.Y - (2.5 * button_size), m_screensize.X - (0.25 * button_size), m_screensize.Y - (1.5 * button_size)), L"spc1", false); m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id, v2s32(m_screensize.X - (1.25 * button_size), m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1.0) * button_size) + (0.5 * button_size)), v2s32(m_screensize.X - (0.25 * button_size), m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size) + (0.5 * button_size)), AHBB_Dir_Right_Left, 3.0); m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png"); m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png"); m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png"); m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png"); m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png"); m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png"); m_settingsbar.addButton(minimap_id, L"minimap", "minimap_btn.png"); // Chat is shown by default, so chat_hide_btn.png is shown first. m_settingsbar.addToggleButton(toggle_chat_id, L"togglechat", "chat_hide_btn.png", "chat_show_btn.png"); m_rarecontrolsbar.init(m_texturesource, "rare_controls.png", rare_controls_starter_id, v2s32(0.25 * button_size, m_screensize.Y - ((RARE_CONTROLS_BAR_Y_OFFSET + 1.0) * button_size) + (0.5 * button_size)), v2s32(0.75 * button_size, m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size) + (0.5 * button_size)), AHBB_Dir_Left_Right, 2.0); m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png"); m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png"); m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png"); } touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y) { IGUIElement *rootguielement = m_guienv->getRootGUIElement(); if (rootguielement != nullptr) { gui::IGUIElement *element = rootguielement->getElementFromPoint(core::position2d(x, y)); if (element) for (unsigned int i = 0; i < after_last_element_id; i++) if (element == m_buttons[i].guibutton) return (touch_gui_button_id) i; } return after_last_element_id; } touch_gui_button_id TouchScreenGUI::getButtonID(size_t eventID) { for (unsigned int i = 0; i < after_last_element_id; i++) { button_info *btn = &m_buttons[i]; auto id = std::find(btn->ids.begin(), btn->ids.end(), eventID); if (id != btn->ids.end()) return (touch_gui_button_id) i; } return after_last_element_id; } bool TouchScreenGUI::isHUDButton(const SEvent &event) { // check if hud item is pressed for (auto &hud_rect : m_hud_rects) { if (hud_rect.second.isPointInside(v2s32(event.TouchInput.X, event.TouchInput.Y))) { auto *translated = new SEvent(); memset(translated, 0, sizeof(SEvent)); translated->EventType = irr::EET_KEY_INPUT_EVENT; translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + hud_rect.first); translated->KeyInput.Control --trackplacer.lua --holds code for the track-placing system. the default 'track' item will be a craftitem that places rails as needed. this will neither place or change switches nor place vertical rails. --all new trackplacer code local tp={ tracks={} } function tp.register_tracktype(nnprefix, n_suffix) if tp.tracks[nnprefix] then return end--due to the separate registration of slopes and flats for the same nnpref, definition would be overridden here. just don't. tp.tracks[nnprefix]={ default=n_suffix, single_conn={}, single_conn_1={}, single_conn_2={}, double_conn={}, double_conn_1={}, double_conn_2={}, --keys:conn1_conn2 (example:1_4) --values:{name=x, param2=x} twcycle={}, twrotate={},--indexed by suffix, list, tells order of rotations modify={}, } end function tp.add_double_conn(nnprefix, suffix, rotation, conns) local nodename=nnprefix.."_"..suffix..rotation for i=0,3 do tp.tracks[nnprefix].double_conn[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].double_conn[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].double_conn_1[((conns.conn1+4*i)%16).."_"..((conns.conn2+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].double_conn_2[((conns.conn2+4*i)%16).."_"..((conns.conn1+4*i)%16)]={name=nodename, param2=i} end tp.tracks[nnprefix].modify[nodename]=true end function tp.add_single_conn(nnprefix, suffix, rotation, conns) local nodename=nnprefix.."_"..suffix..rotation for i=0,3 do tp.tracks[nnprefix].single_conn[((conns.conn1+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].single_conn[((conns.conn2+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].single_conn_1[((conns.conn1+4*i)%16)]={name=nodename, param2=i} tp.tracks[nnprefix].single_conn_2[((conns.conn2+4*i)%16)]={name=nodename, param2=i} end tp.tracks[nnprefix].modify[nodename]=true end function tp.add_worked(nnprefix, suffix, rotation, cycle_follows) tp.tracks[nnprefix].twcycle[suffix]=cycle_follows if not tp.tracks[nnprefix].twrotate[suffix] then tp.tracks[nnprefix].twrotate[suffix]={} end table.insert(tp.tracks[nnprefix].twrotate[suffix], rotation) end --[[ rewrite algorithm. selection criteria: these will never be changed or even selected: - tracks being already connected on both sides - tracks that are already connected on one side but are not bendable to the desired position the following situations can occur: 1. there are two more than two rails around 1.1 there is one or more subset(s) that can be directly connected -> choose the first possibility 2.2 not -> choose the first one and orient straight 2. there's exactly 1 rail around -> choose and orient straight 3. there's no rail around -> set straight ]] local function istrackandbc(pos_p, conn) local tpos = pos_p local cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c)) if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then local cconns=advtrains.get_track_connections(cnode.name, cnode.param2) return advtrains.conn_matches_to(conn, cconns) end --try the same 1 node below tpos = {x=tpos.x, y=tpos.y-1, z=tpos.z} cnode=minetest.get_node(advtrains.dirCoordSet(tpos, conn.c)) if advtrains.is_track_and_drives_on(cnode.name, advtrains.all_tracktypes) then local cconns=advtrains.get_track_connections(cnode.name, cnode.param2) return advtrains.conn_matches_to(conn, cconns) end return false end function tp.find_already_connected(pos) local dnode=minetest.get_node(pos) local dconns=advtrains.get_track_connections(dnode.name, dnode.param2) local found_conn for connid, conn in ipairs(dconns) do if istrackandbc(pos, conn) then if found_conn then --we found one in previous iteration return true, true --signal that it's connected else found_conn = conn.c end end end return found_conn end function tp.rail_and_can_be_bent(originpos, conn) local pos=advtrains.dirCoordSet(originpos, conn) local newdir=(conn+8)%16 local node=minetest.get_node(pos) if not advtrains.is_track_and_drives_on(node.name, advtrains