548 lines
17 KiB
C++
548 lines
17 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2013-2015 SuperTuxKart-Team
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 3
|
|
// 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 General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "config/user_config.hpp"
|
|
#include "graphics/graphics_restrictions.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "guiengine/engine.hpp"
|
|
#include "guiengine/screen_keyboard.hpp"
|
|
#include "guiengine/layout_manager.hpp"
|
|
#include "guiengine/widget.hpp"
|
|
#include "guiengine/widgets/button_widget.hpp"
|
|
#include "guiengine/widgets/CGUIEditBox.hpp"
|
|
#include "states_screens/state_manager.hpp"
|
|
#include "utils/log.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
using namespace GUIEngine;
|
|
// ============================================================================
|
|
ScreenKeyboard::KeyboardLayoutProportions
|
|
ScreenKeyboard::getKeyboardLayoutProportions() const
|
|
{
|
|
return
|
|
{
|
|
{2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
|
|
{1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1},
|
|
{2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
|
|
{3, 2, 10, 2, 3}
|
|
};
|
|
} // getKeyboardLayoutProportions
|
|
|
|
// ============================================================================
|
|
ScreenKeyboard::KeyboardLayout*
|
|
ScreenKeyboard::getKeyboardLayout(ButtonsType bt) const
|
|
{
|
|
static KeyboardLayout layout_lower =
|
|
{{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"},
|
|
{"Separator", "a", "s", "d", "f", "g", "h", "j", "k", "l", "Separator"},
|
|
{"Shift", "z", "x", "c", "v", "b", "n", "m", "?", "Back"},
|
|
{"123", ",", "Space", ".", "Enter"}};
|
|
|
|
static KeyboardLayout layout_upper =
|
|
{{"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"},
|
|
{"Separator", "A", "S", "D", "F", "G", "H", "J", "K", "L", "Separator"},
|
|
{"Shift", "Z", "X", "C", "V", "B", "N", "M", "!", "Back"},
|
|
{"123", ",", "Space", ".", "Enter"}};
|
|
|
|
static KeyboardLayout layout_digits =
|
|
{{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"},
|
|
{"Separator", "@", "#", "$", "%", "^", "&", "*", "(", ")", "Separator"},
|
|
{"Shift", "-", "+", ":", ";", "\"", "\'", "/", "?", "Back"},
|
|
{"Text", ",", "Space", ".", "Enter"}};
|
|
|
|
static KeyboardLayout layout_digits2 =
|
|
{{"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"},
|
|
{"Separator", "@", "[", "]", "{", "}", "~", "`", "\\", "|", "Separator"},
|
|
{"Shift", "_", "=", ":", ";", "<", ">", "/", "!", "Back"},
|
|
{"Text", ",", "Space", ".", "Enter"}};
|
|
|
|
KeyboardLayout* keys = NULL;
|
|
switch (bt)
|
|
{
|
|
case BUTTONS_LOWER:
|
|
keys = &layout_lower;
|
|
break;
|
|
case BUTTONS_UPPER:
|
|
keys = &layout_upper;
|
|
break;
|
|
case BUTTONS_DIGITS:
|
|
keys = &layout_digits;
|
|
break;
|
|
case BUTTONS_DIGITS2:
|
|
keys = &layout_digits2;
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
return keys;
|
|
} // getKeyboardLayout
|
|
|
|
// ============================================================================
|
|
ScreenKeyboard* ScreenKeyboard::m_screen_keyboard = NULL;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** The screen keyboard constructor
|
|
* \param percent_width A relative value in range of 0.0 to 1.0 that
|
|
* determines width of the screen that will be used by the keyboard.
|
|
* \param percent_height A relative value in range of 0.0 to 1.0 that
|
|
* determines height of the screen that will be used by the keyboard.
|
|
* \param edit_box The edit box that is assigned to the keyboard.
|
|
*/
|
|
ScreenKeyboard::ScreenKeyboard(float percent_width, float percent_height,
|
|
CGUIEditBox* edit_box)
|
|
{
|
|
if (m_screen_keyboard != NULL)
|
|
{
|
|
delete m_screen_keyboard;
|
|
Log::warn("GUIEngine", "Showing a screen keyboard while the previous "
|
|
"one is still open. Destroying the previous keyboard.");
|
|
}
|
|
|
|
m_screen_keyboard = this;
|
|
m_buttons_type = BUTTONS_NONE;
|
|
m_percent_width = std::min(std::max(percent_width, 0.0f), 1.0f);
|
|
m_percent_height = std::min(std::max(percent_height, 0.0f), 1.0f);
|
|
m_irrlicht_window = NULL;
|
|
m_edit_box = edit_box;
|
|
m_back_button = NULL;
|
|
m_repeat_time = 0;
|
|
m_back_button_pressed = false;
|
|
m_schedule_close = false;
|
|
} // ScreenKeyboard
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** The screen keyboard destructor
|
|
*/
|
|
ScreenKeyboard::~ScreenKeyboard()
|
|
{
|
|
m_screen_keyboard = NULL;
|
|
|
|
GUIEngine::getGUIEnv()->removeFocus(m_irrlicht_window);
|
|
m_irrlicht_window->remove();
|
|
|
|
if (input_manager)
|
|
input_manager->setMode(m_previous_mode);
|
|
|
|
elementsWereDeleted();
|
|
} // ~ScreenKeyboard
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Screen keyboard initialization, needs to be called after new to take into
|
|
* account for runtime polymorphism
|
|
*/
|
|
void ScreenKeyboard::init()
|
|
{
|
|
const core::dimension2d<u32>& frame_size = irr_driver->getFrameSize();
|
|
|
|
int margin = 0;
|
|
int w = int(frame_size.Width * m_percent_width);
|
|
int h = int(frame_size.Height * m_percent_height);
|
|
int x = frame_size.Width/2 - w/2;
|
|
int y = frame_size.Height - h - margin;
|
|
|
|
if (m_edit_box != NULL)
|
|
{
|
|
core::rect<s32> pos = m_edit_box->getAbsolutePosition();
|
|
|
|
if (pos.LowerRightCorner.Y + 5 > y)
|
|
{
|
|
y = margin;
|
|
}
|
|
}
|
|
|
|
m_area = core::rect<s32>(x, y, x + w, y + h);
|
|
|
|
m_irrlicht_window = GUIEngine::getGUIEnv()->addWindow(m_area, true);
|
|
m_irrlicht_window->setDrawTitlebar(false);
|
|
m_irrlicht_window->getCloseButton()->setVisible(false);
|
|
m_irrlicht_window->setDraggable(UserConfigParams::m_artist_debug_mode);
|
|
|
|
m_previous_mode=input_manager->getMode();
|
|
input_manager->setMode(InputManager::MENU);
|
|
|
|
createButtons();
|
|
assignButtons(getDefaultButtonsType());
|
|
|
|
Widget* button_widget = getWidget<ButtonWidget>("Back");
|
|
assert(button_widget != NULL);
|
|
m_back_button = button_widget->getIrrlichtElement<IGUIButton>();
|
|
} // init
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Creates all button widgets
|
|
*/
|
|
void ScreenKeyboard::createButtons()
|
|
{
|
|
const auto& layout_proportions = getKeyboardLayoutProportions();
|
|
int rows_num = layout_proportions.size();
|
|
int pos_y = 3;
|
|
|
|
const int margin = 2;
|
|
int height = (m_area.getHeight() - 2 * pos_y) / rows_num - margin;
|
|
|
|
for (int i = 0; i < rows_num; i++)
|
|
{
|
|
float pos_x = 3;
|
|
int total_width = m_area.getWidth() - 2 * (int)pos_x;
|
|
|
|
int total_padding = irr_driver->getDevice()->getLeftPadding() +
|
|
irr_driver->getDevice()->getRightPadding();
|
|
if (total_width - total_padding > 0)
|
|
total_width -= total_padding;
|
|
|
|
char tmp[100];
|
|
sprintf(tmp, "%i", pos_y + (height + margin) * i);
|
|
std::string pos_y_str = tmp;
|
|
|
|
int total_proportions = 0;
|
|
|
|
for (int value : layout_proportions[i])
|
|
{
|
|
total_proportions += value;
|
|
}
|
|
|
|
int cols_num = layout_proportions[i].size();
|
|
|
|
for (int j = 0; j < cols_num; j++)
|
|
{
|
|
ButtonWidget* button = new ButtonWidget();
|
|
|
|
float width = (float)total_width * layout_proportions[i][j]
|
|
/ total_proportions - margin;
|
|
|
|
char width_str[100];
|
|
sprintf(width_str, "%i", (int)roundf(width / (SkinConfig::getHorizontalInnerPadding(button->getType(), button)+1.0f)));
|
|
|
|
char height_str[100];
|
|
sprintf(height_str, "%i", (int)roundf(height / (SkinConfig::getVerticalInnerPadding(button->getType(), button)+1.0f)));
|
|
|
|
char tmp[100];
|
|
sprintf(tmp, "%i", (int)roundf(pos_x));
|
|
std::string pos_x_str = tmp;
|
|
|
|
button->setParent(m_irrlicht_window);
|
|
button->m_properties[PROP_WIDTH] = width_str;
|
|
button->m_properties[PROP_HEIGHT] = height_str;
|
|
button->m_properties[PROP_X] = pos_x_str;
|
|
button->m_properties[PROP_Y] = pos_y_str;
|
|
m_widgets.push_back(button);
|
|
m_buttons.push_back(button);
|
|
|
|
pos_x += width + margin;
|
|
}
|
|
}
|
|
|
|
LayoutManager::calculateLayout(m_widgets, this);
|
|
addWidgetsRecursively(m_widgets);
|
|
|
|
assert(m_buttons.size() > 0);
|
|
m_buttons[0]->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
|
|
} // createButtons
|
|
|
|
// ----------------------------------------------------------------------------
|
|
core::stringw ScreenKeyboard::getKeyName(std::string key_id)
|
|
{
|
|
core::stringw key_name;
|
|
if (key_id == "Enter")
|
|
{
|
|
key_name = L"\u21B2";
|
|
}
|
|
else if (key_id == "Shift")
|
|
{
|
|
key_name = L"\u21E7";
|
|
}
|
|
else if (key_id == "Back")
|
|
{
|
|
key_name = L"\u21E6";
|
|
}
|
|
else if (key_id == "Space")
|
|
{
|
|
key_name = L"\u2423";
|
|
}
|
|
else
|
|
{
|
|
key_name = StringUtils::utf8ToWide(key_id);
|
|
}
|
|
return key_name;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that allows to select one of the available buttons layout
|
|
* \param buttons_type One of the available buttons type
|
|
*/
|
|
void ScreenKeyboard::assignButtons(ButtonsType buttons_type)
|
|
{
|
|
const auto& layout_proportions = getKeyboardLayoutProportions();
|
|
int rows_num = layout_proportions.size();
|
|
|
|
m_buttons_type = buttons_type;
|
|
KeyboardLayout* keys = getKeyboardLayout(buttons_type);
|
|
|
|
unsigned int current_button_id = 0;
|
|
|
|
for (int i = 0; i < rows_num; i++)
|
|
{
|
|
int cols_num = layout_proportions[i].size();
|
|
|
|
for (int j = 0; j < cols_num; j++)
|
|
{
|
|
std::string key = keys != NULL ? (*keys)[i][j] : "?";
|
|
const core::stringw& key_name = getKeyName(key);
|
|
|
|
assert(current_button_id < m_buttons.size());
|
|
ButtonWidget* button = m_buttons[current_button_id];
|
|
|
|
if (key == "Separator")
|
|
{
|
|
button->setVisible(false);
|
|
}
|
|
else
|
|
{
|
|
button->setVisible(true);
|
|
button->setText(key_name);
|
|
button->m_properties[PROP_ID] = key;
|
|
}
|
|
|
|
current_button_id++;
|
|
}
|
|
}
|
|
} // assignButtons
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void ScreenKeyboard::onUpdate(float dt)
|
|
{
|
|
if (m_back_button->isPressed() || m_back_button_pressed)
|
|
{
|
|
const unsigned int repeat_rate = 40;
|
|
const unsigned int repeat_delay = 400;
|
|
|
|
SEvent event;
|
|
event.KeyInput.Key = IRR_KEY_BACK;
|
|
event.KeyInput.Char = 0;
|
|
event.EventType = EET_KEY_INPUT_EVENT;
|
|
event.KeyInput.PressedDown = true;
|
|
event.KeyInput.Control = false;
|
|
event.KeyInput.Shift = false;
|
|
|
|
if (m_repeat_time == 0)
|
|
{
|
|
m_edit_box->OnEvent(event);
|
|
}
|
|
|
|
while (m_repeat_time > repeat_delay + repeat_rate)
|
|
{
|
|
m_edit_box->OnEvent(event);
|
|
m_repeat_time -= repeat_rate;
|
|
}
|
|
|
|
m_repeat_time += (unsigned int)(dt * 1000);
|
|
}
|
|
|
|
if (!m_back_button->isPressed())
|
|
{
|
|
m_back_button_pressed = false;
|
|
m_repeat_time = 0;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that handles irrlicht events
|
|
* \param event Irrlicht event
|
|
* \return Block event if true
|
|
*/
|
|
bool ScreenKeyboard::onEvent(const SEvent &event)
|
|
{
|
|
if (event.EventType == EET_MOUSE_INPUT_EVENT)
|
|
{
|
|
core::position2d<s32> point(event.MouseInput.X, event.MouseInput.Y);
|
|
|
|
if (m_edit_box->isPointInside(point))
|
|
{
|
|
m_edit_box->OnEvent(event);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
bool is_point_inside = m_irrlicht_window->isPointInside(point);
|
|
|
|
if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
|
|
{
|
|
if (!is_point_inside)
|
|
{
|
|
m_schedule_close = true;
|
|
return true;
|
|
}
|
|
}
|
|
else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
|
|
{
|
|
if (!is_point_inside && m_schedule_close)
|
|
{
|
|
dismiss();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
m_schedule_close = false;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that handles buttons events
|
|
* \param eventSource Button ID
|
|
* \return Block event if edit box is assigned
|
|
*/
|
|
EventPropagation ScreenKeyboard::processEvent(const std::string& eventSource)
|
|
{
|
|
if (m_edit_box == NULL)
|
|
return EVENT_LET;
|
|
|
|
SEvent event;
|
|
bool send_event = false;
|
|
bool close_keyboard = false;
|
|
std::vector<char32_t> chars;
|
|
|
|
if (eventSource == "Shift")
|
|
{
|
|
switch (m_buttons_type)
|
|
{
|
|
case BUTTONS_UPPER:
|
|
assignButtons(BUTTONS_LOWER);
|
|
break;
|
|
case BUTTONS_LOWER:
|
|
assignButtons(BUTTONS_UPPER);
|
|
break;
|
|
case BUTTONS_DIGITS:
|
|
assignButtons(BUTTONS_DIGITS2);
|
|
break;
|
|
case BUTTONS_DIGITS2:
|
|
assignButtons(BUTTONS_DIGITS);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (eventSource == "123")
|
|
{
|
|
assignButtons(BUTTONS_DIGITS);
|
|
}
|
|
else if (eventSource == "Text")
|
|
{
|
|
assignButtons(BUTTONS_LOWER);
|
|
}
|
|
else if (eventSource == "Enter")
|
|
{
|
|
event.KeyInput.Key = IRR_KEY_RETURN;
|
|
chars.push_back(0);
|
|
send_event = true;
|
|
close_keyboard = true;
|
|
}
|
|
else if (eventSource == "Back")
|
|
{
|
|
send_event = false;
|
|
m_back_button_pressed = true;
|
|
}
|
|
else if (eventSource == "Space")
|
|
{
|
|
event.KeyInput.Key = IRR_KEY_UNKNOWN;
|
|
chars.push_back(U' ');
|
|
send_event = true;
|
|
}
|
|
else if (eventSource.size() > 0)
|
|
{
|
|
event.KeyInput.Key = IRR_KEY_UNKNOWN;
|
|
// For possible emoji ligatures
|
|
const std::u32string& s = StringUtils::utf8ToUtf32(eventSource);
|
|
for (char32_t c : s)
|
|
chars.push_back(c);
|
|
send_event = true;
|
|
}
|
|
|
|
if (send_event)
|
|
{
|
|
event.EventType = EET_KEY_INPUT_EVENT;
|
|
event.KeyInput.PressedDown = true;
|
|
event.KeyInput.Control = false;
|
|
event.KeyInput.Shift = false;
|
|
for (char32_t c : chars)
|
|
{
|
|
event.KeyInput.Char = c;
|
|
m_edit_box->OnEvent(event);
|
|
}
|
|
}
|
|
|
|
if (close_keyboard)
|
|
{
|
|
dismiss();
|
|
}
|
|
|
|
return EVENT_BLOCK;
|
|
} // processEvent
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that closes the keyboard
|
|
*/
|
|
void ScreenKeyboard::dismiss()
|
|
{
|
|
delete m_screen_keyboard;
|
|
m_screen_keyboard = NULL;
|
|
} // dismiss
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that handles escape pressed event
|
|
*/
|
|
bool ScreenKeyboard::onEscapePressed()
|
|
{
|
|
dismiss();
|
|
return true;
|
|
} // onEscapePressed
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** A function that determines if (native) screen keyboard should be activated
|
|
*/
|
|
bool ScreenKeyboard::shouldUseScreenKeyboard()
|
|
{
|
|
bool always_use_screen_keyboard =
|
|
UserConfigParams::m_screen_keyboard == 2;
|
|
|
|
if (UserConfigParams::m_screen_keyboard == 1)
|
|
return true;
|
|
|
|
return always_use_screen_keyboard;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Returns true if system screen keyboard is available
|
|
*/
|
|
bool ScreenKeyboard::hasSystemScreenKeyboard()
|
|
{
|
|
if (GraphicsRestrictions::isDisabled(GraphicsRestrictions::GR_SYSTEM_SCREEN_KEYBOARD))
|
|
return false;
|
|
|
|
return irr_driver->getDevice()->hasOnScreenKeyboard();
|
|
}
|