From 7eff91ff20765ba4e7f94a92de6fc0ffa2fc4f2f Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sat, 27 Mar 2021 15:15:32 -0400 Subject: [PATCH] applets/swkbd: Implement the Qt Software Keyboard frontend The Qt Software Keyboard frontend attempts to mimic the software keyboard rendered by the Nintendo Switch. This frontend implements multiple keyboard types, such as the normal software keyboard, the numeric pad software keyboard and the inline software keyboard. Keyboard and controller input is also supported in this frontend. Keyboard input is handled as native keyboard input, and so the on-screen keyboard cannot be navigated with the keyboard arrow keys as the arrow keys are used to move the text cursor. Controller input is translated into mouse hover movements on the onscreen keyboard or their respective button actions (B for backspace, A for entering the selected button, L/R for moving the text cursor, etc). The text check dialogs can also be confirmed with controller input through the use of the OverlayDialog Massive thanks to Rei for creating all the UI for the various keyboards and OverlayDialog. This would not have been possible without his excellent work. Co-authored-by: Its-Rei --- src/yuzu/CMakeLists.txt | 1 + src/yuzu/applets/software_keyboard.cpp | 1635 ++++++++++- src/yuzu/applets/software_keyboard.h | 267 +- src/yuzu/applets/software_keyboard.ui | 3503 ++++++++++++++++++++++++ src/yuzu/main.cpp | 112 + src/yuzu/main.h | 14 + 6 files changed, 5518 insertions(+), 14 deletions(-) create mode 100644 src/yuzu/applets/software_keyboard.ui diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 3e00ff39f..cc0790e07 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(yuzu applets/profile_select.h applets/software_keyboard.cpp applets/software_keyboard.h + applets/software_keyboard.ui applets/web_browser.cpp applets/web_browser.h bootmanager.cpp diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp index da0fed774..fd3368479 100644 --- a/src/yuzu/applets/software_keyboard.cpp +++ b/src/yuzu/applets/software_keyboard.cpp @@ -1,18 +1,1641 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include + +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/input_interpreter.h" +#include "ui_software_keyboard.h" #include "yuzu/applets/software_keyboard.h" #include "yuzu/main.h" +#include "yuzu/util/overlay_dialog.h" -QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator() {} +namespace { -QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {} +using namespace Service::AM::Applets; -QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(QWidget* parent) : QDialog(parent) {} +constexpr float BASE_HEADER_FONT_SIZE = 23.0f; +constexpr float BASE_SUB_FONT_SIZE = 17.0f; +constexpr float BASE_EDITOR_FONT_SIZE = 26.0f; +constexpr float BASE_CHAR_BUTTON_FONT_SIZE = 28.0f; +constexpr float BASE_LABEL_BUTTON_FONT_SIZE = 18.0f; +constexpr float BASE_ICON_BUTTON_SIZE = 36.0f; +[[maybe_unused]] constexpr float BASE_WIDTH = 1280.0f; +constexpr float BASE_HEIGHT = 720.0f; -QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; +} // Anonymous namespace -QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {} +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_) + : QDialog(parent), ui{std::make_unique()}, system{system_}, + is_inline{is_inline_}, initialize_parameters{std::move(initialize_parameters_)} { + ui->setupUi(this); + + setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::CustomizeWindowHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_TranslucentBackground); + + keyboard_buttons = {{ + {{ + { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + }, + { + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + }, + { + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_return, + }, + { + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + }, + { + ui->button_shift, + ui->button_shift, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_space, + ui->button_ok, + }, + }}, + {{ + { + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + }, + { + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + }, + { + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_return_shift, + }, + { + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + }, + { + ui->button_shift_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_space_shift, + ui->button_ok_shift, + }, + }}, + }}; + + numberpad_buttons = {{ + { + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + }, + { + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + }, + { + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_ok_num, + }, + { + nullptr, + ui->button_0_num, + nullptr, + ui->button_ok_num, + }, + }}; + + all_buttons = { + ui->button_1, + ui->button_2, + ui->button_3, + ui->button_4, + ui->button_5, + ui->button_6, + ui->button_7, + ui->button_8, + ui->button_9, + ui->button_0, + ui->button_minus, + ui->button_backspace, + ui->button_q, + ui->button_w, + ui->button_e, + ui->button_r, + ui->button_t, + ui->button_y, + ui->button_u, + ui->button_i, + ui->button_o, + ui->button_p, + ui->button_slash, + ui->button_return, + ui->button_a, + ui->button_s, + ui->button_d, + ui->button_f, + ui->button_g, + ui->button_h, + ui->button_j, + ui->button_k, + ui->button_l, + ui->button_colon, + ui->button_apostrophe, + ui->button_z, + ui->button_x, + ui->button_c, + ui->button_v, + ui->button_b, + ui->button_n, + ui->button_m, + ui->button_comma, + ui->button_dot, + ui->button_question, + ui->button_exclamation, + ui->button_ok, + ui->button_shift, + ui->button_space, + ui->button_hash, + ui->button_left_bracket, + ui->button_right_bracket, + ui->button_dollar, + ui->button_percent, + ui->button_circumflex, + ui->button_ampersand, + ui->button_asterisk, + ui->button_left_parenthesis, + ui->button_right_parenthesis, + ui->button_underscore, + ui->button_backspace_shift, + ui->button_q_shift, + ui->button_w_shift, + ui->button_e_shift, + ui->button_r_shift, + ui->button_t_shift, + ui->button_y_shift, + ui->button_u_shift, + ui->button_i_shift, + ui->button_o_shift, + ui->button_p_shift, + ui->button_at, + ui->button_return_shift, + ui->button_a_shift, + ui->button_s_shift, + ui->button_d_shift, + ui->button_f_shift, + ui->button_g_shift, + ui->button_h_shift, + ui->button_j_shift, + ui->button_k_shift, + ui->button_l_shift, + ui->button_semicolon, + ui->button_quotation, + ui->button_z_shift, + ui->button_x_shift, + ui->button_c_shift, + ui->button_v_shift, + ui->button_b_shift, + ui->button_n_shift, + ui->button_m_shift, + ui->button_less_than, + ui->button_greater_than, + ui->button_plus, + ui->button_equal, + ui->button_ok_shift, + ui->button_shift_shift, + ui->button_space_shift, + ui->button_1_num, + ui->button_2_num, + ui->button_3_num, + ui->button_backspace_num, + ui->button_4_num, + ui->button_5_num, + ui->button_6_num, + ui->button_ok_num, + ui->button_7_num, + ui->button_8_num, + ui->button_9_num, + ui->button_0_num, + }; + + SetupMouseHover(); + + if (!initialize_parameters.ok_text.empty()) { + ui->button_ok->setText(QString::fromStdU16String(initialize_parameters.ok_text)); + } + + ui->label_header->setText(QString::fromStdU16String(initialize_parameters.header_text)); + ui->label_sub->setText(QString::fromStdU16String(initialize_parameters.sub_text)); + + current_text = initialize_parameters.initial_text; + cursor_position = initialize_parameters.initial_cursor_position; + + SetTextDrawType(); + + for (auto* button : all_buttons) { + connect(button, &QPushButton::clicked, this, [this, button](bool) { + if (is_inline) { + InlineKeyboardButtonClicked(button); + } else { + NormalKeyboardButtonClicked(button); + } + }); + } + + // TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend + if (system.IsPoweredOn()) { + input_interpreter = std::make_unique(system); + } +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() { + StopInputThread(); +} + +void QtSoftwareKeyboardDialog::ShowNormalKeyboard(QPoint pos, QSize size) { + if (isVisible()) { + return; + } + + MoveAndResizeWindow(pos, size); + + SetKeyboardType(); + SetPasswordMode(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + switch (text_check_result) { + case SwkbdTextCheckResult::Success: + case SwkbdTextCheckResult::Silent: + default: + break; + case SwkbdTextCheckResult::Failure: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + QString{}, tr("OK"), Qt::AlignCenter); + dialog.exec(); + + StartInputThread(); + break; + } + case SwkbdTextCheckResult::Confirm: { + StopInputThread(); + + OverlayDialog dialog(this, system, QString{}, QString::fromStdU16String(text_check_message), + tr("Cancel"), tr("OK"), Qt::AlignCenter); + if (dialog.exec() == QDialog::Accepted) { + emit SubmitNormalText(SwkbdResult::Ok, current_text); + break; + } + + StartInputThread(); + break; + } + } +} + +void QtSoftwareKeyboardDialog::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, QSize size) { + MoveAndResizeWindow(pos, size); + + ui->topOSK->setStyleSheet(QStringLiteral("background: rgba(0, 0, 0, 0);")); + + ui->headerOSK->hide(); + ui->subOSK->hide(); + ui->inputOSK->hide(); + ui->charactersOSK->hide(); + ui->inputBoxOSK->hide(); + ui->charactersBoxOSK->hide(); + + initialize_parameters.max_text_length = appear_parameters.max_text_length; + initialize_parameters.min_text_length = appear_parameters.min_text_length; + initialize_parameters.type = appear_parameters.type; + initialize_parameters.key_disable_flags = appear_parameters.key_disable_flags; + initialize_parameters.enable_backspace_button = appear_parameters.enable_backspace_button; + initialize_parameters.enable_return_button = appear_parameters.enable_return_button; + initialize_parameters.disable_cancel_button = initialize_parameters.disable_cancel_button; + + SetKeyboardType(); + SetControllerImage(); + DisableKeyboardButtons(); + SetBackspaceOkEnabled(); + + open(); +} + +void QtSoftwareKeyboardDialog::HideInlineKeyboard() { + StopInputThread(); + QDialog::hide(); +} + +void QtSoftwareKeyboardDialog::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + current_text = text_parameters.input_text; + cursor_position = text_parameters.cursor_position; + + SetBackspaceOkEnabled(); +} + +void QtSoftwareKeyboardDialog::ExitKeyboard() { + StopInputThread(); + QDialog::done(QDialog::Accepted); +} + +void QtSoftwareKeyboardDialog::open() { + QDialog::open(); + + row = 0; + column = 0; + + const auto* const curr_button = + keyboard_buttons[static_cast(bottom_osk_index)][row][column]; + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + + StartInputThread(); +} + +void QtSoftwareKeyboardDialog::reject() { + // Pressing the ESC key in a dialog calls QDialog::reject(). + // We will override this behavior to the "Cancel" action on the software keyboard. + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, current_text); + } +} + +void QtSoftwareKeyboardDialog::keyPressEvent(QKeyEvent* event) { + if (!is_inline) { + QDialog::keyPressEvent(event); + return; + } + + const auto entered_key = event->key(); + + switch (entered_key) { + case Qt::Key_Escape: + QDialog::keyPressEvent(event); + return; + case Qt::Key_Backspace: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Return: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + return; + case Qt::Key_Left: + MoveTextCursorDirection(Direction::Left); + return; + case Qt::Key_Right: + MoveTextCursorDirection(Direction::Right); + return; + default: + break; + } + + const auto entered_text = event->text(); + + if (entered_text.isEmpty()) { + return; + } + + InlineTextInsertString(entered_text.toStdU16String()); +} + +void QtSoftwareKeyboardDialog::MoveAndResizeWindow(QPoint pos, QSize size) { + QDialog::move(pos); + QDialog::resize(size); + + // High DPI + const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f; + + RescaleKeyboardElements(size.width(), size.height(), dpi_scale); +} + +void QtSoftwareKeyboardDialog::RescaleKeyboardElements(float width, float height, float dpi_scale) { + const auto header_font_size = BASE_HEADER_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto sub_font_size = BASE_SUB_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto editor_font_size = BASE_EDITOR_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto char_button_font_size = + BASE_CHAR_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + const auto label_button_font_size = + BASE_LABEL_BUTTON_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale; + + QFont header_font(QStringLiteral("MS Shell Dlg 2"), header_font_size, QFont::Normal); + QFont sub_font(QStringLiteral("MS Shell Dlg 2"), sub_font_size, QFont::Normal); + QFont editor_font(QStringLiteral("MS Shell Dlg 2"), editor_font_size, QFont::Normal); + QFont char_button_font(QStringLiteral("MS Shell Dlg 2"), char_button_font_size, QFont::Normal); + QFont label_button_font(QStringLiteral("MS Shell Dlg 2"), label_button_font_size, + QFont::Normal); + + ui->label_header->setFont(header_font); + ui->label_sub->setFont(sub_font); + ui->line_edit_osk->setFont(editor_font); + ui->text_edit_osk->setFont(editor_font); + ui->label_characters->setFont(sub_font); + ui->label_characters_box->setFont(sub_font); + + ui->label_shift->setFont(label_button_font); + ui->label_shift_shift->setFont(label_button_font); + ui->label_cancel->setFont(label_button_font); + ui->label_cancel_shift->setFont(label_button_font); + ui->label_cancel_num->setFont(label_button_font); + ui->label_enter->setFont(label_button_font); + ui->label_enter_shift->setFont(label_button_font); + ui->label_enter_num->setFont(label_button_font); + + for (auto* button : all_buttons) { + if (button == ui->button_return || button == ui->button_return_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + button->setFont(label_button_font); + continue; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + button->setFont(label_button_font); + button->setIconSize(QSize(BASE_ICON_BUTTON_SIZE, BASE_ICON_BUTTON_SIZE) * + (height / BASE_HEIGHT)); + continue; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || + button == ui->button_ok_num) { + button->setFont(label_button_font); + continue; + } + + button->setFont(char_button_font); + } +} + +void QtSoftwareKeyboardDialog::SetKeyboardType() { + switch (initialize_parameters.type) { + case SwkbdType::Normal: + case SwkbdType::Qwerty: + case SwkbdType::Unknown3: + case SwkbdType::Latin: + case SwkbdType::SimplifiedChinese: + case SwkbdType::TraditionalChinese: + case SwkbdType::Korean: + default: { + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 320); + ui->verticalLayout_2->setStretch(1, 400); + + ui->gridLineOSK->setRowStretch(5, 94); + ui->gridBoxOSK->setRowStretch(2, 81); + break; + } + case SwkbdType::NumberPad: { + bottom_osk_index = BottomOSKIndex::NumberPad; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->verticalLayout_2->setStretch(0, 370); + ui->verticalLayout_2->setStretch(1, 350); + + ui->gridLineOSK->setRowStretch(5, 144); + ui->gridBoxOSK->setRowStretch(2, 131); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetPasswordMode() { + switch (initialize_parameters.password_mode) { + case SwkbdPasswordMode::Disabled: + default: + ui->line_edit_osk->setEchoMode(QLineEdit::Normal); + break; + case SwkbdPasswordMode::Enabled: + ui->line_edit_osk->setEchoMode(QLineEdit::Password); + break; + } +} + +void QtSoftwareKeyboardDialog::SetTextDrawType() { + switch (initialize_parameters.text_draw_type) { + case SwkbdTextDrawType::Line: + case SwkbdTextDrawType::DownloadCode: { + ui->topOSK->setCurrentIndex(0); + + if (initialize_parameters.max_text_length <= 10) { + ui->gridLineOSK->setColumnStretch(0, 390); + ui->gridLineOSK->setColumnStretch(1, 500); + ui->gridLineOSK->setColumnStretch(2, 390); + } else { + ui->gridLineOSK->setColumnStretch(0, 130); + ui->gridLineOSK->setColumnStretch(1, 1020); + ui->gridLineOSK->setColumnStretch(2, 130); + } + + if (is_inline) { + return; + } + + connect(ui->line_edit_osk, &QLineEdit::textChanged, [this](const QString& changed_string) { + const auto is_valid = ValidateInputText(changed_string); + + const auto text_length = static_cast(changed_string.length()); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::cursorPositionChanged, + [this](int old_cursor_position, int new_cursor_position) { + ui->button_backspace->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_shift->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + ui->button_backspace_num->setEnabled( + initialize_parameters.enable_backspace_button && new_cursor_position > 0); + + ui->line_edit_osk->setFocus(); + }); + + connect(ui->line_edit_osk, &QLineEdit::returnPressed, [this] { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + }); + + ui->line_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->line_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->line_edit_osk->setMaxLength(initialize_parameters.max_text_length); + ui->line_edit_osk->setCursorPosition(initialize_parameters.initial_cursor_position); + + ui->label_characters->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + case SwkbdTextDrawType::Box: + default: { + ui->topOSK->setCurrentIndex(1); + + if (is_inline) { + return; + } + + connect(ui->text_edit_osk, &QTextEdit::textChanged, [this] { + if (static_cast(ui->text_edit_osk->toPlainText().length()) > + initialize_parameters.max_text_length) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } + + const auto is_valid = ValidateInputText(ui->text_edit_osk->toPlainText()); + + const auto text_length = static_cast(ui->text_edit_osk->toPlainText().length()); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(text_length) + .arg(initialize_parameters.max_text_length)); + + ui->button_ok->setEnabled(is_valid); + ui->button_ok_shift->setEnabled(is_valid); + ui->button_ok_num->setEnabled(is_valid); + + ui->text_edit_osk->setFocus(); + }); + + connect(ui->text_edit_osk, &QTextEdit::cursorPositionChanged, [this] { + const auto new_cursor_position = ui->text_edit_osk->textCursor().position(); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + new_cursor_position > 0); + + ui->text_edit_osk->setFocus(); + }); + + ui->text_edit_osk->setPlaceholderText( + QString::fromStdU16String(initialize_parameters.guide_text)); + ui->text_edit_osk->setText(QString::fromStdU16String(initialize_parameters.initial_text)); + ui->text_edit_osk->moveCursor(initialize_parameters.initial_cursor_position == 0 + ? QTextCursor::Start + : QTextCursor::End); + + ui->label_characters_box->setText(QStringLiteral("%1/%2") + .arg(initialize_parameters.initial_text.size()) + .arg(initialize_parameters.max_text_length)); + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetControllerImage() { + const auto controller_type = Settings::values.players.GetValue()[8].connected + ? Settings::values.players.GetValue()[8].controller_type + : Settings::values.players.GetValue()[0].controller_type; + + const QString theme = [] { + if (QIcon::themeName().contains(QStringLiteral("dark")) || + QIcon::themeName().contains(QStringLiteral("midnight"))) { + return QStringLiteral("_dark"); + } else { + return QString{}; + } + }(); + + switch (controller_type) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::GameCube: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_pro%1.png);").arg(theme)); + break; + case Settings::ControllerType::DualJoyconDetached: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_dual_joycon%1.png);").arg(theme)); + break; + case Settings::ControllerType::LeftJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_left%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::RightJoycon: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_single_joycon_right%1.png);") + .arg(theme)); + break; + case Settings::ControllerType::Handheld: + ui->icon_controller->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_shift->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + ui->icon_controller_num->setStyleSheet( + QStringLiteral("image: url(:/overlay/controller_handheld%1.png);").arg(theme)); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::DisableKeyboardButtons() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + default: { + for (const auto& keys : keyboard_buttons) { + for (const auto& rows : keys) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + } + + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + ui->button_space->setDisabled(key_disable_flags.space); + ui->button_space_shift->setDisabled(key_disable_flags.space); + + ui->button_at->setDisabled(key_disable_flags.at || key_disable_flags.username); + + ui->button_percent->setDisabled(key_disable_flags.percent || key_disable_flags.username); + + ui->button_slash->setDisabled(key_disable_flags.slash); + + ui->button_1->setDisabled(key_disable_flags.numbers); + ui->button_2->setDisabled(key_disable_flags.numbers); + ui->button_3->setDisabled(key_disable_flags.numbers); + ui->button_4->setDisabled(key_disable_flags.numbers); + ui->button_5->setDisabled(key_disable_flags.numbers); + ui->button_6->setDisabled(key_disable_flags.numbers); + ui->button_7->setDisabled(key_disable_flags.numbers); + ui->button_8->setDisabled(key_disable_flags.numbers); + ui->button_9->setDisabled(key_disable_flags.numbers); + ui->button_0->setDisabled(key_disable_flags.numbers); + + ui->button_return->setEnabled(initialize_parameters.enable_return_button); + ui->button_return_shift->setEnabled(initialize_parameters.enable_return_button); + break; + } + case BottomOSKIndex::NumberPad: { + for (const auto& rows : numberpad_buttons) { + for (auto* button : rows) { + if (!button) { + continue; + } + + button->setEnabled(true); + } + } + break; + } + } +} + +void QtSoftwareKeyboardDialog::SetBackspaceOkEnabled() { + if (is_inline) { + ui->button_ok->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(current_text.size() >= + initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(current_text.size() >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + cursor_position > 0); + } else { + const auto text_length = [this] { + if (ui->topOSK->currentIndex() == 1) { + return static_cast(ui->text_edit_osk->toPlainText().length()); + } else { + return static_cast(ui->line_edit_osk->text().length()); + } + }(); + + const auto normal_cursor_position = [this] { + if (ui->topOSK->currentIndex() == 1) { + return ui->text_edit_osk->textCursor().position(); + } else { + return ui->line_edit_osk->cursorPosition(); + } + }(); + + ui->button_ok->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_shift->setEnabled(text_length >= initialize_parameters.min_text_length); + ui->button_ok_num->setEnabled(text_length >= initialize_parameters.min_text_length); + + ui->button_backspace->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_shift->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + ui->button_backspace_num->setEnabled(initialize_parameters.enable_backspace_button && + normal_cursor_position > 0); + } +} + +bool QtSoftwareKeyboardDialog::ValidateInputText(const QString& input_text) { + const auto& key_disable_flags = initialize_parameters.key_disable_flags; + + const auto input_text_length = static_cast(input_text.length()); + + if (input_text_length < initialize_parameters.min_text_length || + input_text_length > initialize_parameters.max_text_length) { + return false; + } + + if (key_disable_flags.space && input_text.contains(QLatin1Char{' '})) { + return false; + } + + if ((key_disable_flags.at || key_disable_flags.username) && + input_text.contains(QLatin1Char{'@'})) { + return false; + } + + if ((key_disable_flags.percent || key_disable_flags.username) && + input_text.contains(QLatin1Char{'%'})) { + return false; + } + + if (key_disable_flags.slash && input_text.contains(QLatin1Char{'/'})) { + return false; + } + + if ((key_disable_flags.backslash || key_disable_flags.username) && + input_text.contains(QLatin1Char('\\'))) { + return false; + } + + if (key_disable_flags.numbers && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return c.isDigit(); })) { + return false; + } + + if (bottom_osk_index == BottomOSKIndex::NumberPad && + std::any_of(input_text.begin(), input_text.end(), [](QChar c) { return !c.isDigit(); })) { + return false; + } + + return true; +} + +void QtSoftwareKeyboardDialog::ChangeBottomOSKIndex() { + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + bottom_osk_index = BottomOSKIndex::UpperCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + break; + case BottomOSKIndex::UpperCase: + if (caps_lock_enabled) { + caps_lock_enabled = false; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_off.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock")); + + bottom_osk_index = BottomOSKIndex::LowerCase; + ui->bottomOSK->setCurrentIndex(static_cast(bottom_osk_index)); + } else { + caps_lock_enabled = true; + + ui->button_shift_shift->setStyleSheet( + QStringLiteral("background-image: url(:/overlay/osk_button_shift_lock_on.png);" + "\nbackground-position: left top;" + "\nbackground-repeat: no-repeat;" + "\nbackground-origin: content;")); + + ui->button_shift_shift->setIconSize(ui->button_shift->iconSize()); + ui->button_backspace_shift->setIconSize(ui->button_backspace->iconSize()); + + ui->label_shift_shift->setText(QStringLiteral("Caps Lock Off")); + } + break; + case BottomOSKIndex::NumberPad: + default: + break; + } +} + +void QtSoftwareKeyboardDialog::NormalKeyboardButtonClicked(QPushButton* button) { + if (button == ui->button_ampersand) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("&")); + } else { + ui->line_edit_osk->insert(QStringLiteral("&")); + } + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral("\n")); + } else { + ui->line_edit_osk->insert(QStringLiteral("\n")); + } + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(QStringLiteral(" ")); + } else { + ui->line_edit_osk->insert(QStringLiteral(" ")); + } + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (ui->topOSK->currentIndex() == 1) { + auto text_cursor = ui->text_edit_osk->textCursor(); + ui->text_edit_osk->setTextCursor(text_cursor); + text_cursor.deletePreviousChar(); + } else { + ui->line_edit_osk->backspace(); + } + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Ok, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Ok, ui->line_edit_osk->text().toStdU16String()); + } + return; + } + + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->insertPlainText(button->text()); + } else { + ui->line_edit_osk->insert(button->text()); + } + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineKeyboardButtonClicked(QPushButton* button) { + if (!button->isEnabled()) { + return; + } + + if (button == ui->button_ampersand) { + InlineTextInsertString(u"&"); + return; + } + + if (button == ui->button_return || button == ui->button_return_shift) { + InlineTextInsertString(u"\n"); + return; + } + + if (button == ui->button_space || button == ui->button_space_shift) { + InlineTextInsertString(u" "); + return; + } + + if (button == ui->button_shift || button == ui->button_shift_shift) { + ChangeBottomOSKIndex(); + return; + } + + if (button == ui->button_backspace || button == ui->button_backspace_shift || + button == ui->button_backspace_num) { + if (cursor_position <= 0 || current_text.empty()) { + cursor_position = 0; + return; + } + + --cursor_position; + + current_text.erase(cursor_position, 1); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); + return; + } + + if (button == ui->button_ok || button == ui->button_ok_shift || button == ui->button_ok_num) { + emit SubmitInlineText(SwkbdReplyType::DecidedEnter, current_text, cursor_position); + return; + } + + InlineTextInsertString(button->text().toStdU16String()); + + // Revert the keyboard to lowercase if the shift key is active. + if (bottom_osk_index == BottomOSKIndex::UpperCase && !caps_lock_enabled) { + // This is set to true since ChangeBottomOSKIndex will change bottom_osk_index to LowerCase + // if bottom_osk_index is UpperCase and caps_lock_enabled is true. + caps_lock_enabled = true; + ChangeBottomOSKIndex(); + } +} + +void QtSoftwareKeyboardDialog::InlineTextInsertString(std::u16string_view string) { + if ((current_text.size() + string.size()) > initialize_parameters.max_text_length) { + return; + } + + current_text.insert(cursor_position, string); + + cursor_position += static_cast(string.size()); + + SetBackspaceOkEnabled(); + + emit SubmitInlineText(SwkbdReplyType::ChangedString, current_text, cursor_position); +} + +void QtSoftwareKeyboardDialog::SetupMouseHover() { + // setFocus() has a bug where continuously changing focus will cause the focus UI to + // mysteriously disappear. A workaround we have found is using the mouse to hover over + // the buttons to act in place of the button focus. As a result, we will have to set + // a blank cursor when hovering over all the buttons and set a no focus policy so the + // buttons do not stay in focus in addition to the mouse hover. + for (auto* button : all_buttons) { + button->setCursor(QCursor(Qt::BlankCursor)); + button->setFocusPolicy(Qt::NoFocus); + } +} + +template +void QtSoftwareKeyboardDialog::HandleButtonPressedOnce() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonPressedOnce(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +template +void QtSoftwareKeyboardDialog::HandleButtonHold() { + const auto f = [this](HIDButton button) { + if (input_interpreter->IsButtonHeld(button)) { + TranslateButtonPress(button); + } + }; + + (f(T), ...); +} + +void QtSoftwareKeyboardDialog::TranslateButtonPress(HIDButton button) { + switch (button) { + case HIDButton::A: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: + keyboard_buttons[static_cast(bottom_osk_index)][row][column]->click(); + break; + case BottomOSKIndex::NumberPad: + numberpad_buttons[row][column]->click(); + break; + default: + break; + } + break; + case HIDButton::B: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_backspace->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_backspace_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_backspace_num->click(); + break; + default: + break; + } + break; + case HIDButton::X: + if (is_inline) { + emit SubmitInlineText(SwkbdReplyType::DecidedCancel, current_text, cursor_position); + } else { + if (ui->topOSK->currentIndex() == 1) { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->text_edit_osk->toPlainText().toStdU16String()); + } else { + emit SubmitNormalText(SwkbdResult::Cancel, + ui->line_edit_osk->text().toStdU16String()); + } + } + break; + case HIDButton::Y: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_space->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_space_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::LStick: + case HIDButton::RStick: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_shift->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_shift_shift->click(); + break; + case BottomOSKIndex::NumberPad: + default: + break; + } + break; + case HIDButton::L: + MoveTextCursorDirection(Direction::Left); + break; + case HIDButton::R: + MoveTextCursorDirection(Direction::Right); + break; + case HIDButton::Plus: + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + ui->button_ok->click(); + break; + case BottomOSKIndex::UpperCase: + ui->button_ok_shift->click(); + break; + case BottomOSKIndex::NumberPad: + ui->button_ok_num->click(); + break; + default: + break; + } + break; + case HIDButton::DLeft: + case HIDButton::LStickLeft: + case HIDButton::RStickLeft: + MoveButtonDirection(Direction::Left); + break; + case HIDButton::DUp: + case HIDButton::LStickUp: + case HIDButton::RStickUp: + MoveButtonDirection(Direction::Up); + break; + case HIDButton::DRight: + case HIDButton::LStickRight: + case HIDButton::RStickRight: + MoveButtonDirection(Direction::Right); + break; + case HIDButton::DDown: + case HIDButton::LStickDown: + case HIDButton::RStickDown: + MoveButtonDirection(Direction::Down); + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveButtonDirection(Direction direction) { + // Changes the row or column index depending on the direction. + auto move_direction = [this, direction](std::size_t max_rows, std::size_t max_columns) { + switch (direction) { + case Direction::Left: + column = (column + max_columns - 1) % max_columns; + break; + case Direction::Up: + row = (row + max_rows - 1) % max_rows; + break; + case Direction::Right: + column = (column + 1) % max_columns; + break; + case Direction::Down: + row = (row + 1) % max_rows; + break; + default: + break; + } + }; + + switch (bottom_osk_index) { + case BottomOSKIndex::LowerCase: + case BottomOSKIndex::UpperCase: { + const auto index = static_cast(bottom_osk_index); + + const auto* const prev_button = keyboard_buttons[index][row][column]; + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + auto* curr_button = keyboard_buttons[index][row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NORMAL, NUM_COLUMNS_NORMAL); + curr_button = keyboard_buttons[index][row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + case BottomOSKIndex::NumberPad: { + const auto* const prev_button = numberpad_buttons[row][column]; + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + auto* curr_button = numberpad_buttons[row][column]; + + while (!curr_button || !curr_button->isEnabled() || curr_button == prev_button) { + move_direction(NUM_ROWS_NUMPAD, NUM_COLUMNS_NUMPAD); + curr_button = numberpad_buttons[row][column]; + } + + // This is a workaround for setFocus() randomly not showing focus in the UI + QCursor::setPos(curr_button->mapToGlobal(curr_button->rect().center())); + break; + } + default: + break; + } +} + +void QtSoftwareKeyboardDialog::MoveTextCursorDirection(Direction direction) { + switch (direction) { + case Direction::Left: + if (is_inline) { + if (cursor_position <= 0) { + cursor_position = 0; + } else { + --cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Left); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() - 1); + } + } + break; + case Direction::Right: + if (is_inline) { + if (cursor_position >= static_cast(current_text.size())) { + cursor_position = static_cast(current_text.size()); + } else { + ++cursor_position; + emit SubmitInlineText(SwkbdReplyType::MovedCursor, current_text, cursor_position); + } + } else { + if (ui->topOSK->currentIndex() == 1) { + ui->text_edit_osk->moveCursor(QTextCursor::Right); + } else { + ui->line_edit_osk->setCursorPosition(ui->line_edit_osk->cursorPosition() + 1); + } + } + break; + default: + break; + } +} + +void QtSoftwareKeyboardDialog::StartInputThread() { + if (input_thread_running) { + return; + } + + input_thread_running = true; + + input_thread = std::thread(&QtSoftwareKeyboardDialog::InputThread, this); +} + +void QtSoftwareKeyboardDialog::StopInputThread() { + input_thread_running = false; + + if (input_thread.joinable()) { + input_thread.join(); + } + + if (input_interpreter) { + input_interpreter->ResetButtonStates(); + } +} + +void QtSoftwareKeyboardDialog::InputThread() { + while (input_thread_running) { + input_interpreter->PollInput(); + + HandleButtonPressedOnce(); + + HandleButtonHold(); + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowInitializeKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardInitialize, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowNormalKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowNormal, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardShowTextCheck, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowShowInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardShowInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowHideInlineKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardHideInline, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowInlineTextChanged, &main_window, + &GMainWindow::SoftwareKeyboardInlineTextChanged, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowExitKeyboard, &main_window, + &GMainWindow::SoftwareKeyboardExit, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitNormalText, this, + &QtSoftwareKeyboard::SubmitNormalText, Qt::QueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardSubmitInlineText, this, + &QtSoftwareKeyboard::SubmitInlineText, Qt::QueuedConnection); +} QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function submit_normal_callback_, + std::function + submit_inline_callback_) { + if (is_inline) { + submit_inline_callback = std::move(submit_inline_callback_); + } else { + submit_normal_callback = std::move(submit_normal_callback_); + } + + LOG_INFO(Service_AM, + "\nKeyboardInitializeParameters:" + "\nok_text={}" + "\nheader_text={}" + "\nsub_text={}" + "\nguide_text={}" + "\ninitial_text={}" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\ninitial_cursor_position={}" + "\ntype={}" + "\npassword_mode={}" + "\ntext_draw_type={}" + "\nkey_disable_flags={}" + "\nuse_blur_background={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + Common::UTF16ToUTF8(initialize_parameters.ok_text), + Common::UTF16ToUTF8(initialize_parameters.header_text), + Common::UTF16ToUTF8(initialize_parameters.sub_text), + Common::UTF16ToUTF8(initialize_parameters.guide_text), + Common::UTF16ToUTF8(initialize_parameters.initial_text), + initialize_parameters.max_text_length, initialize_parameters.min_text_length, + initialize_parameters.initial_cursor_position, initialize_parameters.type, + initialize_parameters.password_mode, initialize_parameters.text_draw_type, + initialize_parameters.key_disable_flags.raw, initialize_parameters.use_blur_background, + initialize_parameters.enable_backspace_button, + initialize_parameters.enable_return_button, + initialize_parameters.disable_cancel_button); + + emit MainWindowInitializeKeyboard(is_inline, std::move(initialize_parameters)); +} + +void QtSoftwareKeyboard::ShowNormalKeyboard() const { + emit MainWindowShowNormalKeyboard(); +} + +void QtSoftwareKeyboard::ShowTextCheckDialog( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const { + emit MainWindowShowTextCheckDialog(text_check_result, text_check_message); +} + +void QtSoftwareKeyboard::ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const { + LOG_INFO(Service_AM, + "\nInlineAppearParameters:" + "\nmax_text_length={}" + "\nmin_text_length={}" + "\nkey_top_scale_x={}" + "\nkey_top_scale_y={}" + "\nkey_top_translate_x={}" + "\nkey_top_translate_y={}" + "\ntype={}" + "\nkey_disable_flags={}" + "\nkey_top_as_floating={}" + "\nenable_backspace_button={}" + "\nenable_return_button={}" + "\ndisable_cancel_button={}", + appear_parameters.max_text_length, appear_parameters.min_text_length, + appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, + appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, + appear_parameters.type, appear_parameters.key_disable_flags.raw, + appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, + appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); + + emit MainWindowShowInlineKeyboard(std::move(appear_parameters)); +} + +void QtSoftwareKeyboard::HideInlineKeyboard() const { + emit MainWindowHideInlineKeyboard(); +} + +void QtSoftwareKeyboard::InlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) const { + LOG_INFO(Service_AM, + "\nInlineTextParameters:" + "\ninput_text={}" + "\ncursor_position={}", + Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); + + emit MainWindowInlineTextChanged(std::move(text_parameters)); +} + +void QtSoftwareKeyboard::ExitKeyboard() const { + emit MainWindowExitKeyboard(); +} + +void QtSoftwareKeyboard::SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const { + submit_normal_callback(result, submitted_text); +} + +void QtSoftwareKeyboard::SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, + s32 cursor_position) const { + submit_inline_callback(reply_type, submitted_text, cursor_position); +} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h index 8427c0a6c..1a03c098c 100644 --- a/src/yuzu/applets/software_keyboard.h +++ b/src/yuzu/applets/software_keyboard.h @@ -1,28 +1,228 @@ -// Copyright 2018 yuzu Emulator Project +// Copyright 2021 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once +#include +#include +#include +#include + #include #include #include "core/frontend/applets/software_keyboard.h" -class GMainWindow; +enum class HIDButton : u8; -class QtSoftwareKeyboardValidator final : public QValidator { -public: - explicit QtSoftwareKeyboardValidator(); - State validate(QString& input, int& pos) const override; -}; +class InputInterpreter; + +namespace Core { +class System; +} + +namespace Ui { +class QtSoftwareKeyboardDialog; +} + +class GMainWindow; class QtSoftwareKeyboardDialog final : public QDialog { Q_OBJECT public: - QtSoftwareKeyboardDialog(QWidget* parent); + QtSoftwareKeyboardDialog(QWidget* parent, Core::System& system_, bool is_inline_, + Core::Frontend::KeyboardInitializeParameters initialize_parameters_); ~QtSoftwareKeyboardDialog() override; + + void ShowNormalKeyboard(QPoint pos, QSize size); + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + + void ShowInlineKeyboard(Core::Frontend::InlineAppearParameters appear_parameters, QPoint pos, + QSize size); + + void HideInlineKeyboard(); + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + + void ExitKeyboard(); + +signals: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + +public slots: + void open() override; + void reject() override; + +protected: + /// We override the keyPressEvent for inputting text into the inline software keyboard. + void keyPressEvent(QKeyEvent* event) override; + +private: + enum class Direction { + Left, + Up, + Right, + Down, + }; + + enum class BottomOSKIndex { + LowerCase, + UpperCase, + NumberPad, + }; + + /** + * Moves and resizes the window to a specified position and size. + * + * @param pos Top-left window position + * @param size Window size + */ + void MoveAndResizeWindow(QPoint pos, QSize size); + + /** + * Rescales all keyboard elements to account for High DPI displays. + * + * @param width Window width + * @param height Window height + * @param dpi_scale Display scaling factor + */ + void RescaleKeyboardElements(float width, float height, float dpi_scale); + + /// Sets the keyboard type based on initialize_parameters. + void SetKeyboardType(); + + /// Sets the password mode based on initialize_parameters. + void SetPasswordMode(); + + /// Sets the text draw type based on initialize_parameters. + void SetTextDrawType(); + + /// Sets the controller image at the bottom left of the software keyboard. + void SetControllerImage(); + + /// Disables buttons based on initialize_parameters. + void DisableKeyboardButtons(); + + /// Changes whether the backspace or/and ok buttons should be enabled or disabled. + void SetBackspaceOkEnabled(); + + /** + * Validates the input text sent in based on the parameters in initialize_parameters. + * + * @param input_text Input text + * + * @returns True if the input text is valid, false otherwise. + */ + bool ValidateInputText(const QString& input_text); + + /// Switches between LowerCase and UpperCase (Shift and Caps Lock) + void ChangeBottomOSKIndex(); + + /// Processes a keyboard button click from the UI as normal keyboard input. + void NormalKeyboardButtonClicked(QPushButton* button); + + /// Processes a keyboard button click from the UI as inline keyboard input. + void InlineKeyboardButtonClicked(QPushButton* button); + + /** + * Inserts a string of arbitrary length into the current_text at the current cursor position. + * This is only used for the inline software keyboard. + */ + void InlineTextInsertString(std::u16string_view string); + + /// Setup the mouse hover workaround for "focusing" buttons. This should only be called once. + void SetupMouseHover(); + + /** + * Handles button presses and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonPressedOnce(); + + /** + * Handles button holds and converts them into keyboard input. + * + * @tparam HIDButton The list of buttons that can be converted into keyboard input. + */ + template + void HandleButtonHold(); + + /** + * Translates a button press to focus or click a keyboard button. + * + * @param button The button press to process. + */ + void TranslateButtonPress(HIDButton button); + + /** + * Moves the focus of a button in a certain direction. + * + * @param direction The direction to move. + */ + void MoveButtonDirection(Direction direction); + + /** + * Moves the text cursor in a certain direction. + * + * @param direction The direction to move. + */ + void MoveTextCursorDirection(Direction direction); + + void StartInputThread(); + void StopInputThread(); + + /// The thread where input is being polled and processed. + void InputThread(); + + std::unique_ptr ui; + + Core::System& system; + + // True if it is the inline software keyboard. + bool is_inline; + + // Common software keyboard initialize parameters. + Core::Frontend::KeyboardInitializeParameters initialize_parameters; + + // Used only by the inline software keyboard since the QLineEdit or QTextEdit is hidden. + std::u16string current_text; + s32 cursor_position{0}; + + static constexpr std::size_t NUM_ROWS_NORMAL = 5; + static constexpr std::size_t NUM_COLUMNS_NORMAL = 12; + static constexpr std::size_t NUM_ROWS_NUMPAD = 4; + static constexpr std::size_t NUM_COLUMNS_NUMPAD = 4; + + // Stores the normal keyboard layout. + std::array, NUM_ROWS_NORMAL>, 2> + keyboard_buttons; + // Stores the numberpad keyboard layout. + std::array, NUM_ROWS_NUMPAD> numberpad_buttons; + + // Contains a set of all buttons used in keyboard_buttons and numberpad_buttons. + std::array all_buttons; + + std::size_t row{0}; + std::size_t column{0}; + + BottomOSKIndex bottom_osk_index{BottomOSKIndex::LowerCase}; + std::atomic caps_lock_enabled{false}; + + std::unique_ptr input_interpreter; + + std::thread input_thread; + + std::atomic input_thread_running{}; }; class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { @@ -31,4 +231,55 @@ class QtSoftwareKeyboard final : public QObject, public Core::Frontend::Software public: explicit QtSoftwareKeyboard(GMainWindow& parent); ~QtSoftwareKeyboard() override; + + void InitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, + std::function + submit_normal_callback_, + std::function + submit_inline_callback_) override; + + void ShowNormalKeyboard() const override; + + void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const override; + + void ShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const override; + + void HideInlineKeyboard() const override; + + void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; + + void ExitKeyboard() const override; + +signals: + void MainWindowInitializeKeyboard( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) const; + + void MainWindowShowNormalKeyboard() const; + + void MainWindowShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) const; + + void MainWindowShowInlineKeyboard( + Core::Frontend::InlineAppearParameters appear_parameters) const; + + void MainWindowHideInlineKeyboard() const; + + void MainWindowInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const; + + void MainWindowExitKeyboard() const; + +private: + void SubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text) const; + + void SubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position) const; + + mutable std::function + submit_normal_callback; + mutable std::function + submit_inline_callback; }; diff --git a/src/yuzu/applets/software_keyboard.ui b/src/yuzu/applets/software_keyboard.ui new file mode 100644 index 000000000..b0a1fcde9 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.ui @@ -0,0 +1,3503 @@ + + + QtSoftwareKeyboardDialog + + + + 0 + 0 + 1280 + 720 + + + + Software Keyboard + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + 0 + 100 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 17 + + + + 0/32 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 26 + 50 + false + + + + Qt::StrongFocus + + + + + + 32 + + + Enter Text + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 23 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + 17 + + + + + + + + + + + Qt::Horizontal + + + + 127 + 20 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + 17 + + + + 0/500 + + + + + + + + + + + 0 + + + 14 + + + 9 + + + 14 + + + 9 + + + + + + 26 + + + + Qt::StrongFocus + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Shift + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + - + + + + + + + + 1 + 1 + + + + + 28 + + + + ' + + + + + + + + 1 + 1 + + + + + 28 + + + + / + + + + + + + + 1 + 1 + + + + + 28 + + + + ! + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + w + + + + + + + + 1 + 1 + + + + + 28 + + + + r + + + + + + + + 1 + 1 + + + + + 28 + + + + e + + + + + + + + 1 + 1 + + + + + 28 + + + + q + + + + + + + + 1 + 1 + + + + + 28 + + + + u + + + + + + + + 1 + 1 + + + + + 28 + + + + y + + + + + + + + 1 + 1 + + + + + 28 + + + + t + + + + + + + + 1 + 1 + + + + + 28 + + + + o + + + + + + + + 1 + 1 + + + + + 28 + + + + p + + + + + + + + 1 + 1 + + + + + 28 + + + + i + + + + + + + + 1 + 1 + + + + + 28 + + + + a + + + + + + + + 1 + 1 + + + + + 28 + + + + s + + + + + + + + 1 + 1 + + + + + 28 + + + + d + + + + + + + + 1 + 1 + + + + + 28 + + + + f + + + + + + + + 1 + 1 + + + + + 28 + + + + h + + + + + + + + 1 + 1 + + + + + 28 + + + + j + + + + + + + + 1 + 1 + + + + + 28 + + + + g + + + + + + + + 1 + 1 + + + + + 28 + + + + k + + + + + + + + 1 + 1 + + + + + 28 + + + + l + + + + + + + + 1 + 1 + + + + + 28 + + + + : + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + z + + + + + + + + 1 + 1 + + + + + 28 + + + + c + + + + + + + + 1 + 1 + + + + + 28 + + + + x + + + + + + + + 1 + 1 + + + + + 28 + + + + v + + + + + + + + 1 + 1 + + + + + 28 + + + + m + + + + + + + + 1 + 1 + + + + + 28 + + + + , + + + + + + + + 1 + 1 + + + + + 28 + + + + n + + + + + + + + 1 + 1 + + + + + 28 + + + + b + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + ? + + + + + + + + 1 + 1 + + + + + 28 + + + + . + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + + 0 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Caps Lock + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + _ + + + + + + + + 1 + 1 + + + + + 28 + + + + " + + + + + + + + 1 + 1 + + + + + 28 + + + + @ + + + + + + + + 1 + 1 + + + + + 28 + + + + = + + + + + + + + 1 + 1 + + + + + 28 + + + + && + + + + + + + + 1 + 1 + + + + + 28 + + + + * + + + + + + + + 1 + 1 + + + + + 28 + + + + ) + + + + + + + + 1 + 1 + + + + + 28 + + + + ( + + + + + + + + 1 + 1 + + + + + 28 + + + + W + + + + + + + + 1 + 1 + + + + + 28 + + + + R + + + + + + + + 1 + 1 + + + + + 28 + + + + E + + + + + + + + 1 + 1 + + + + + 28 + + + + Q + + + + + + + + 1 + 1 + + + + + 28 + + + + U + + + + + + + + 1 + 1 + + + + + 28 + + + + Y + + + + + + + + 1 + 1 + + + + + 28 + + + + T + + + + + + + + 1 + 1 + + + + + 28 + + + + O + + + + + + + + 1 + 1 + + + + + 28 + + + + P + + + + + + + + 1 + 1 + + + + + 28 + + + + I + + + + + + + + 1 + 1 + + + + + 28 + + + + A + + + + + + + + 1 + 1 + + + + + 28 + + + + S + + + + + + + + 1 + 1 + + + + + 28 + + + + D + + + + + + + + 1 + 1 + + + + + 28 + + + + F + + + + + + + + 1 + 1 + + + + + 28 + + + + H + + + + + + + + 1 + 1 + + + + + 28 + + + + J + + + + + + + + 1 + 1 + + + + + 28 + + + + G + + + + + + + + 1 + 1 + + + + + 28 + + + + K + + + + + + + + 1 + 1 + + + + + 28 + + + + L + + + + + + + + 1 + 1 + + + + + 28 + + + + ; + + + + + + + + 1 + 1 + + + + + 18 + + + + Return + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + Z + + + + + + + + 1 + 1 + + + + + 28 + + + + C + + + + + + + + 1 + 1 + + + + + 28 + + + + X + + + + + + + + 1 + 1 + + + + + 28 + + + + V + + + + + + + + 1 + 1 + + + + + 28 + + + + M + + + + + + + + 1 + 1 + + + + + 28 + + + + < + + + + + + + + 1 + 1 + + + + + 28 + + + + N + + + + + + + + 1 + 1 + + + + + 28 + + + + B + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + true + + + false + + + + + + + + 1 + 1 + + + + + 28 + + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + > + + + + + + + + 1 + 1 + + + + + 28 + + + + # + + + + + + + + 1 + 1 + + + + + 28 + + + + ] + + + + + + + + 1 + 1 + + + + + 28 + + + + $ + + + + + + + + 1 + 1 + + + + + 28 + + + + [ + + + + + + + + 1 + 1 + + + + + 28 + + + + ^ + + + + + + + + 1 + 1 + + + + + 28 + + + + % + + + + + + + + 1 + 1 + + + + + 18 + + + + Space + + + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + + + + 1 + 1 + + + + + 18 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Cancel + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + 18 + + + + Enter + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + + + 1 + 1 + + + + + 28 + + + + 6 + + + + + + + + 1 + 1 + + + + + 28 + + + + 4 + + + + + + + + 1 + 1 + + + + + 28 + + + + 9 + + + + + + + + 1 + 1 + + + + + 28 + + + + 5 + + + + + + + + 1 + 1 + + + + + 18 + + + + OK + + + + + + + + 1 + 1 + + + + + 28 + + + + 7 + + + + + + + + 1 + 1 + + + + + 28 + + + + 8 + + + + + + + + 1 + 1 + + + + + 28 + + + + 2 + + + + + + + + 1 + 1 + + + + + 28 + + + + 1 + + + + + + + + 1 + 1 + + + + + 28 + + + + 0 + + + + + + + + 1 + 1 + + + + + 28 + + + + 3 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + + + button_1 + button_2 + button_3 + button_4 + button_5 + button_6 + button_7 + button_8 + button_9 + button_0 + button_minus + button_backspace + button_q + button_w + button_e + button_r + button_t + button_y + button_u + button_i + button_o + button_p + button_slash + button_return + button_a + button_s + button_d + button_f + button_g + button_h + button_j + button_k + button_l + button_colon + button_apostrophe + button_z + button_x + button_c + button_v + button_b + button_n + button_m + button_comma + button_dot + button_question + button_exclamation + button_ok + button_shift + button_space + button_hash + button_left_bracket + button_right_bracket + button_dollar + button_percent + button_circumflex + button_ampersand + button_asterisk + button_left_parenthesis + button_right_parenthesis + button_underscore + button_backspace_shift + button_q_shift + button_w_shift + button_e_shift + button_r_shift + button_t_shift + button_y_shift + button_u_shift + button_i_shift + button_o_shift + button_p_shift + button_at + button_return_shift + button_a_shift + button_s_shift + button_d_shift + button_f_shift + button_g_shift + button_h_shift + button_j_shift + button_k_shift + button_l_shift + button_semicolon + button_quotation + button_z_shift + button_x_shift + button_c_shift + button_v_shift + button_b_shift + button_n_shift + button_m_shift + button_less_than + button_greater_than + button_plus + button_equal + button_ok_shift + button_shift_shift + button_space_shift + button_1_num + button_2_num + button_3_num + button_backspace_num + button_4_num + button_5_num + button_6_num + button_ok_num + button_7_num + button_8_num + button_9_num + button_0_num + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ce83dee27..5f6cdc0c6 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -466,6 +466,114 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } +void GMainWindow::SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) { + if (software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is already initialized!"); + return; + } + + software_keyboard = new QtSoftwareKeyboardDialog(render_window, Core::System::GetInstance(), + is_inline, std::move(initialize_parameters)); + + if (is_inline) { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitInlineText, this, + [this](Service::AM::Applets::SwkbdReplyType reply_type, std::u16string submitted_text, + s32 cursor_position) { + emit SoftwareKeyboardSubmitInlineText(reply_type, submitted_text, cursor_position); + }, + Qt::QueuedConnection); + } else { + connect( + software_keyboard, &QtSoftwareKeyboardDialog::SubmitNormalText, this, + [this](Service::AM::Applets::SwkbdResult result, std::u16string submitted_text) { + emit SoftwareKeyboardSubmitNormalText(result, submitted_text); + }, + Qt::QueuedConnection); + } +} + +void GMainWindow::SoftwareKeyboardShowNormal() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = layout.screen.left; + const auto y = layout.screen.top; + const auto w = layout.screen.GetWidth(); + const auto h = layout.screen.GetHeight(); + + software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardShowTextCheck( + Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->ShowTextCheckDialog(text_check_result, text_check_message); +} + +void GMainWindow::SoftwareKeyboardShowInline( + Core::Frontend::InlineAppearParameters appear_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + const auto& layout = render_window->GetFramebufferLayout(); + + const auto x = + static_cast(layout.screen.left + (0.5f * layout.screen.GetWidth() * + ((2.0f * appear_parameters.key_top_translate_x) + + (1.0f - appear_parameters.key_top_scale_x)))); + const auto y = + static_cast(layout.screen.top + (layout.screen.GetHeight() * + ((2.0f * appear_parameters.key_top_translate_y) + + (1.0f - appear_parameters.key_top_scale_y)))); + const auto w = static_cast(layout.screen.GetWidth() * appear_parameters.key_top_scale_x); + const auto h = static_cast(layout.screen.GetHeight() * appear_parameters.key_top_scale_y); + + software_keyboard->ShowInlineKeyboard(std::move(appear_parameters), + render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); +} + +void GMainWindow::SoftwareKeyboardHideInline() { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->HideInlineKeyboard(); +} + +void GMainWindow::SoftwareKeyboardInlineTextChanged( + Core::Frontend::InlineTextParameters text_parameters) { + if (!software_keyboard) { + LOG_ERROR(Frontend, "The software keyboard is not initialized!"); + return; + } + + software_keyboard->InlineTextChanged(std::move(text_parameters)); +} + +void GMainWindow::SoftwareKeyboardExit() { + if (!software_keyboard) { + return; + } + + software_keyboard->ExitKeyboard(); + + software_keyboard = nullptr; +} + void GMainWindow::WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args, bool is_local) { #ifdef YUZU_USE_QT_WEB_ENGINE @@ -1009,6 +1117,10 @@ void GMainWindow::ConnectWidgetEvents() { connect(this, &GMainWindow::EmulationStopping, render_window, &GRenderWindow::OnEmulationStopping); + // Software Keyboard Applet + connect(this, &GMainWindow::EmulationStarting, this, &GMainWindow::SoftwareKeyboardExit); + connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit); + connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar); } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 4c8a879d2..7f1e50a5b 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -135,6 +135,11 @@ signals: void ProfileSelectorFinishedSelection(std::optional uuid); + void SoftwareKeyboardSubmitNormalText(Service::AM::Applets::SwkbdResult result, + std::u16string submitted_text); + void SoftwareKeyboardSubmitInlineText(Service::AM::Applets::SwkbdReplyType reply_type, + std::u16string submitted_text, s32 cursor_position); + void WebBrowserExtractOfflineRomFS(); void WebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason, std::string last_url); @@ -143,6 +148,15 @@ public slots: void OnExecuteProgram(std::size_t program_index); void ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters); + void SoftwareKeyboardInitialize( + bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters); + void SoftwareKeyboardShowNormal(); + void SoftwareKeyboardShowTextCheck(Service::AM::Applets::SwkbdTextCheckResult text_check_result, + std::u16string text_check_message); + void SoftwareKeyboardShowInline(Core::Frontend::InlineAppearParameters appear_parameters); + void SoftwareKeyboardHideInline(); + void SoftwareKeyboardInlineTextChanged(Core::Frontend::InlineTextParameters text_parameters); + void SoftwareKeyboardExit(); void ErrorDisplayDisplayError(QString error_code, QString error_text); void ProfileSelectorSelectProfile(); void WebBrowserOpenWebPage(std::string_view main_url, std::string_view additional_args,