#include "stdafx.h" #include "overlay_osk.h" #include "Emu/RSX/RSXThread.h" #include "Emu/Cell/Modules/cellSysutil.h" LOG_CHANNEL(osk, "OSK"); namespace rsx { namespace overlays { void osk_dialog::Close(bool ok) { fade_animation.current = color4f(1.f); fade_animation.end = color4f(0.f); fade_animation.duration = 0.5f; fade_animation.on_finish = [this, ok] { if (on_osk_close) { Emu.CallAfter([this, ok]() { on_osk_close(ok ? CELL_MSGDIALOG_BUTTON_OK : CELL_MSGDIALOG_BUTTON_ESCAPE); }); } visible = false; close(true, true); }; fade_animation.active = true; } void osk_dialog::add_panel(const osk_panel& panel) { // On PS3 apparently only 7 panels are added, the rest is ignored if (m_panels.size() < 7) { // Don't add this panel if there already exists one with the same panel mode for (const auto& existing : m_panels) { if (existing.osk_panel_mode == panel.osk_panel_mode) { return; } } m_panels.push_back(panel); } } void osk_dialog::step_panel(bool next_panel) { const size_t num_panels = m_panels.size(); if (num_panels > 0) { if (next_panel) { m_panel_index = (m_panel_index + 1) % num_panels; } else if (m_panel_index > 0) { m_panel_index = (m_panel_index - 1) % num_panels; } else { m_panel_index = num_panels - 1; } } update_panel(); } void osk_dialog::update_panel() { ASSERT(m_panel_index < m_panels.size()); const auto& panel = m_panels[m_panel_index]; num_rows = panel.num_rows; num_columns = panel.num_columns; cell_size_x = panel.cell_size_x; cell_size_y = panel.cell_size_y; update_layout(); const u32 cell_count = num_rows * num_columns; m_grid.resize(cell_count); num_shift_layers_by_charset.clear(); const position2u grid_origin = { m_frame.x, m_frame.y + 30u + m_preview.h }; const u32 old_index = (selected_y * num_columns) + selected_x; u32 index = 0; for (const auto& props : panel.layout) { for (u32 c = 0; c < props.num_cell_hz; ++c) { const auto row = (index / num_columns); const auto col = (index % num_columns); verify(HERE), row < num_rows && col < num_columns; auto& _cell = m_grid[index++]; _cell.button_flag = props.type_flags; _cell.pos = { grid_origin.x + col * cell_size_x, grid_origin.y + row * cell_size_y }; _cell.backcolor = props.color; _cell.callback = props.callback; _cell.outputs = props.outputs; _cell.selected = false; // Add shift layers for (u32 layer = 0; layer < _cell.outputs.size(); ++layer) { // Only add a shift layer if at least one default button has content in a layer if (props.type_flags != button_flags::_default) { continue; } size_t cell_shift_layers = 0; for (size_t i = 0; i < _cell.outputs[layer].size(); ++i) { if (_cell.outputs[layer][i].empty() == false) { cell_shift_layers = i + 1; } } if (layer >= num_shift_layers_by_charset.size()) { num_shift_layers_by_charset.push_back(u32(cell_shift_layers)); } else { num_shift_layers_by_charset[layer] = std::max(num_shift_layers_by_charset[layer], u32(cell_shift_layers)); } } switch (props.type_flags) { default: case button_flags::_default: _cell.enabled = true; break; case button_flags::_space: _cell.enabled = !(flags & CELL_OSKDIALOG_NO_SPACE); break; case button_flags::_return: _cell.enabled = !(flags & CELL_OSKDIALOG_NO_RETURN); break; case button_flags::_shift: _cell.enabled |= !_cell.outputs.empty(); break; case button_flags::_layer: _cell.enabled |= !num_shift_layers_by_charset.empty(); break; } if (props.num_cell_hz == 1) [[likely]] { _cell.flags = border_flags::default_cell; } else if (c == 0) { // Leading cell _cell.flags = border_flags::start_cell; } else if (c == (props.num_cell_hz - 1)) { // Last cell _cell.flags = border_flags::end_cell; } else { // Middle cell _cell.flags = border_flags::middle_cell; } } } verify(HERE), num_shift_layers_by_charset.size(); for (u32 layer = 0; layer < num_shift_layers_by_charset.size(); ++layer) { verify(HERE), num_shift_layers_by_charset[layer]; } // Reset to first shift layer in the first charset, because the panel changed and we don't know if the layers are similar between panels. m_selected_charset = 0; selected_z = 0; // Enable/Disable the control buttons based on the current layout. update_controls(); // Roughly keep x and y selection in grid if possible. Jumping to (0,0) would be annoying. Needs to be done after updating the control buttons. update_selection_by_index(old_index); m_update = true; } void osk_dialog::update_layout() { const u16 preview_height = (flags & CELL_OSKDIALOG_NO_RETURN) ? 40 : 90; // Place elements with absolute positioning u16 frame_w = u16(num_columns * cell_size_x); u16 frame_h = u16(num_rows * cell_size_y) + 30 + preview_height; u16 frame_x = (1280 - frame_w) / 2; u16 frame_y = (720 - frame_h) / 2; m_frame.set_pos(frame_x, frame_y); m_frame.set_size(frame_w, frame_h); m_title.set_pos(frame_x, frame_y); m_title.set_size(frame_w, 30); m_title.set_padding(15, 0, 5, 0); m_preview.set_pos(frame_x, frame_y + 30); m_preview.set_size(frame_w, preview_height); m_preview.set_padding(15, 0, 10, 0); m_btn_cancel.set_pos(frame_x, frame_y + frame_h + 10); m_btn_cancel.set_size(140, 30); m_btn_cancel.set_text("Cancel"); m_btn_cancel.set_text_vertical_adjust(5); m_btn_space.set_pos(frame_x + 100, frame_y + frame_h + 10); m_btn_space.set_size(100, 30); m_btn_space.set_text("Space"); m_btn_space.set_text_vertical_adjust(5); m_btn_delete.set_pos(frame_x + 200, frame_y + frame_h + 10); m_btn_delete.set_size(100, 30); m_btn_delete.set_text("Backspace"); m_btn_delete.set_text_vertical_adjust(5); m_btn_shift.set_pos(frame_x + 320, frame_y + frame_h + 10); m_btn_shift.set_size(80, 30); m_btn_shift.set_text("Shift"); m_btn_shift.set_text_vertical_adjust(5); m_btn_accept.set_pos(frame_x + 400, frame_y + frame_h + 10); m_btn_accept.set_size(100, 30); m_btn_accept.set_text("Accept"); m_btn_accept.set_text_vertical_adjust(5); m_update = true; } void osk_dialog::initialize_layout(const std::u32string & title, const std::u32string & initial_text) { m_background.set_size(1280, 720); m_background.back_color.a = 0.8f; m_frame.back_color = { 0.2f, 0.2f, 0.2f, 1.f }; m_title.set_text(title); m_title.back_color.a = 0.f; if (initial_text.empty()) { m_preview.set_text(get_placeholder()); m_preview.caret_position = 0; m_preview.fore_color.a = 0.5f; // Muted contrast for hint text } else { m_preview.set_text(initial_text); m_preview.caret_position = ::narrow(initial_text.length()); m_preview.fore_color.a = 1.f; } m_btn_shift.set_image_resource(resource_config::standard_image_resource::select); m_btn_accept.set_image_resource(resource_config::standard_image_resource::start); m_btn_space.set_image_resource(resource_config::standard_image_resource::triangle); m_btn_delete.set_image_resource(resource_config::standard_image_resource::square); if (g_cfg.sys.enter_button_assignment == enter_button_assign::circle) { m_btn_cancel.set_image_resource(resource_config::standard_image_resource::cross); } else { m_btn_cancel.set_image_resource(resource_config::standard_image_resource::circle); } m_update = true; visible = true; exit = false; fade_animation.current = color4f(0.f); fade_animation.end = color4f(1.f); fade_animation.duration = 0.5f; fade_animation.active = true; } void osk_dialog::update_controls() { const bool shift_enabled = num_shift_layers_by_charset[m_selected_charset] > 1; const bool layer_enabled = num_shift_layers_by_charset.size() > 1; for (auto& cell : m_grid) { switch (cell.button_flag) { case button_flags::_shift: cell.enabled = shift_enabled; break; case button_flags::_layer: cell.enabled = layer_enabled; break; default: break; } } m_update = true; } std::pair osk_dialog::get_cell_geometry(u32 index) { const auto index_limit = (num_columns * num_rows) - 1; u32 start_index = index; u32 count = 0; while (start_index > index_limit && start_index >= num_columns) { // Try one row above start_index -= num_columns; } // Find first cell while (!(m_grid[start_index].flags & border_flags::left) && start_index) { --start_index; } // Find last cell while (true) { const auto current_index = (start_index + count); verify(HERE), current_index <= index_limit; if (m_grid[current_index].flags & border_flags::right) { ++count; break; } ++count; } return std::make_pair(start_index, count); } void osk_dialog::update_selection_by_index(u32 index) { auto select_cell = [&](u32 index, bool state) { const auto info = get_cell_geometry(index); // Tag all in range for (u32 _index = info.first, _ctr = 0; _ctr < info.second; ++_index, ++_ctr) { m_grid[_index].selected = state; } }; // 1. Deselect current const auto current_index = (selected_y * num_columns) + selected_x; select_cell(current_index, false); // 2. Select new selected_y = index / num_columns; selected_x = index % num_columns; select_cell(index, true); } void osk_dialog::on_button_pressed(pad_button button_press) { const auto index_limit = (num_columns * num_rows) - 1; auto on_accept = [&]() { const u32 current_index = (selected_y * num_columns) + selected_x; const auto& current_cell = m_grid[current_index]; u32 output_count = 0; if (m_selected_charset < current_cell.outputs.size()) { output_count = ::size32(current_cell.outputs[m_selected_charset]); } if (output_count) { const auto _z = std::clamp(selected_z, 0u, output_count - 1u); const auto& str = current_cell.outputs[m_selected_charset][_z]; if (current_cell.callback) { current_cell.callback(str); } else { on_default_callback(str); } } }; switch (button_press) { case pad_button::L1: { m_preview.move_caret(edit_text::direction::left); m_update = true; break; } case pad_button::R1: { m_preview.move_caret(edit_text::direction::right); m_update = true; break; } case pad_button::dpad_right: { u32 current_index = (selected_y * num_columns) + selected_x; while (true) { const auto current = get_cell_geometry(current_index); current_index = current.first + current.second; if (current_index > index_limit) { break; } if (m_grid[get_cell_geometry(current_index).first].enabled) { update_selection_by_index(current_index); m_update = true; break; } } break; } case pad_button::dpad_left: { u32 current_index = (selected_y * num_columns) + selected_x; while (current_index > 0) { const auto current = get_cell_geometry(current_index); if (current.first) { current_index = current.first - 1; if (m_grid[get_cell_geometry(current_index).first].enabled) { update_selection_by_index(current_index); m_update = true; break; } } else { break; } } break; } case pad_button::dpad_down: { u32 current_index = (selected_y * num_columns) + selected_x; while (true) { current_index += num_columns; if (current_index > index_limit) { break; } if (m_grid[get_cell_geometry(current_index).first].enabled) { update_selection_by_index(current_index); m_update = true; break; } } break; } case pad_button::dpad_up: { u32 current_index = (selected_y * num_columns) + selected_x; while (current_index >= num_columns) { current_index -= num_columns; if (m_grid[get_cell_geometry(current_index).first].enabled) { update_selection_by_index(current_index); m_update = true; break; } } break; } case pad_button::select: { on_shift(U""); break; } case pad_button::start: { Close(true); break; } case pad_button::triangle: { on_space(U""); break; } case pad_button::square: { on_backspace(U""); break; } case pad_button::cross: { on_accept(); break; } case pad_button::circle: { Close(false); break; } case pad_button::L2: { step_panel(false); break; } case pad_button::R2: { step_panel(true); break; } default: break; } } void osk_dialog::on_text_changed() { const auto ws = u32string_to_utf16(m_preview.text); const auto length = (ws.length() + 1) * sizeof(char16_t); memcpy(osk_text, ws.c_str(), length); if (on_osk_input_entered) { on_osk_input_entered(); } m_update = true; } void osk_dialog::on_default_callback(const std::u32string& str) { if (str.empty()) { return; } // Append to output text if (m_preview.text == get_placeholder()) { m_preview.caret_position = ::narrow(str.length()); m_preview.set_text(str); m_preview.fore_color.a = 1.f; } else { if (m_preview.text.length() == char_limit) { return; } const auto new_str = m_preview.text + str; if (new_str.length() <= char_limit) { m_preview.insert_text(str); } } on_text_changed(); } void osk_dialog::on_shift(const std::u32string&) { const u32 max = num_shift_layers_by_charset[m_selected_charset]; selected_z = (selected_z + 1) % max; m_update = true; } void osk_dialog::on_layer(const std::u32string&) { const u32 num_charsets = std::max(::size32(num_shift_layers_by_charset), 1); m_selected_charset = (m_selected_charset + 1) % num_charsets; const u32 max_z_layer = num_shift_layers_by_charset[m_selected_charset] - 1; if (selected_z > max_z_layer) { selected_z = max_z_layer; } update_controls(); m_update = true; } void osk_dialog::on_space(const std::u32string&) { if (!(flags & CELL_OSKDIALOG_NO_SPACE)) { on_default_callback(U" "); } else { // Beep or give some other kind of visual feedback } } void osk_dialog::on_backspace(const std::u32string&) { m_preview.erase(); if (m_preview.text.empty()) { m_preview.set_text(get_placeholder()); } on_text_changed(); } void osk_dialog::on_enter(const std::u32string&) { if (!(flags & CELL_OSKDIALOG_NO_RETURN)) { on_default_callback(U"\n"); } else { // Beep or give some other kind of visual feedback } } std::u32string osk_dialog::get_placeholder() { if (m_password_mode) { return U"[Enter Password]"; } return U"[Enter Text]"; } void osk_dialog::update() { if (fade_animation.active) { fade_animation.update(rsx::get_current_renderer()->vblank_count); m_update = true; } } compiled_resource osk_dialog::get_compiled() { if (!visible) { return {}; } if (m_update) { m_cached_resource.clear(); m_cached_resource.add(m_background.get_compiled()); m_cached_resource.add(m_frame.get_compiled()); m_cached_resource.add(m_title.get_compiled()); m_cached_resource.add(m_preview.get_compiled()); m_cached_resource.add(m_btn_accept.get_compiled()); m_cached_resource.add(m_btn_cancel.get_compiled()); m_cached_resource.add(m_btn_shift.get_compiled()); m_cached_resource.add(m_btn_space.get_compiled()); m_cached_resource.add(m_btn_delete.get_compiled()); overlay_element tmp; label m_label; u16 buffered_cell_count = 0; bool render_label = false; const color4f disabled_back_color = { 0.3f, 0.3f, 0.3f, 1.f }; const color4f disabled_fore_color = { 0.8f, 0.8f, 0.8f, 1.f }; const color4f normal_fore_color = { 0.f, 0.f, 0.f, 1.f }; m_label.back_color = { 0.f, 0.f, 0.f, 0.f }; m_label.set_padding(0, 0, 10, 0); for (const auto& c : m_grid) { u16 x = u16(c.pos.x); u16 y = u16(c.pos.y); u16 w = cell_size_x; u16 h = cell_size_y; if (c.flags & border_flags::left) { x++; w--; buffered_cell_count = 0; } if (c.flags & border_flags::right) { w--; u32 output_count = 0; if (m_selected_charset < c.outputs.size()) { output_count = ::size32(c.outputs[m_selected_charset]); } if (output_count) { const u16 offset_x = u16(buffered_cell_count * cell_size_x); const u16 full_width = u16(offset_x + cell_size_x); m_label.set_pos(x - offset_x, y); m_label.set_size(full_width, cell_size_y); m_label.fore_color = c.enabled ? normal_fore_color : disabled_fore_color; const auto _z = (selected_z < output_count) ? selected_z : output_count - 1u; m_label.set_text(c.outputs[m_selected_charset][_z]); m_label.align_text(rsx::overlays::overlay_element::text_align::center); render_label = true; } } if (c.flags & border_flags::top) { y++; h--; } if (c.flags & border_flags::bottom) { h--; } buffered_cell_count++; tmp.back_color = c.enabled? c.backcolor : disabled_back_color; tmp.set_pos(x, y); tmp.set_size(w, h); tmp.pulse_effect_enabled = c.selected; m_cached_resource.add(tmp.get_compiled()); if (render_label) { m_label.pulse_effect_enabled = c.selected; m_cached_resource.add(m_label.get_compiled()); } } m_update = false; } fade_animation.apply(m_cached_resource); return m_cached_resource; } void osk_dialog::Create(const std::string& title, const std::u16string& message, char16_t* init_text, u32 charlimit, u32 prohibit_flags, u32 panel_flag, u32 first_view_panel) { state = OskDialogState::Open; flags = prohibit_flags; char_limit = charlimit; callback_t shift_cb = std::bind(&osk_dialog::on_shift, this, std::placeholders::_1); callback_t layer_cb = std::bind(&osk_dialog::on_layer, this, std::placeholders::_1); callback_t space_cb = std::bind(&osk_dialog::on_space, this, std::placeholders::_1); callback_t delete_cb = std::bind(&osk_dialog::on_backspace, this, std::placeholders::_1); callback_t enter_cb = std::bind(&osk_dialog::on_enter, this, std::placeholders::_1); if (panel_flag & CELL_OSKDIALOG_PANELMODE_PASSWORD) { // If password was requested, then password has to be the only osk panel mode available to the user // first_view_panel can be ignored add_panel(osk_panel_password(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); // TODO: hide entered text with * m_password_mode = true; } else if (panel_flag == CELL_OSKDIALOG_PANELMODE_DEFAULT || panel_flag == CELL_OSKDIALOG_PANELMODE_DEFAULT_NO_JAPANESE) { // Prefer the systems settings // first_view_panel is ignored switch (g_cfg.sys.language) { case CELL_SYSUTIL_LANG_JAPANESE: if (panel_flag == CELL_OSKDIALOG_PANELMODE_DEFAULT_NO_JAPANESE) add_panel(osk_panel_english(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); else add_panel(osk_panel_japanese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_FRENCH: add_panel(osk_panel_french(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_SPANISH: add_panel(osk_panel_spanish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_GERMAN: add_panel(osk_panel_german(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_ITALIAN: add_panel(osk_panel_italian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_DANISH: add_panel(osk_panel_danish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_NORWEGIAN: add_panel(osk_panel_norwegian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_DUTCH: add_panel(osk_panel_dutch(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_FINNISH: add_panel(osk_panel_finnish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_SWEDISH: add_panel(osk_panel_swedish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_PORTUGUESE_PT: add_panel(osk_panel_portuguese_pt(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_PORTUGUESE_BR: add_panel(osk_panel_portuguese_br(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_TURKISH: add_panel(osk_panel_turkey(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_POLISH: add_panel(osk_panel_polish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_RUSSIAN: add_panel(osk_panel_russian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_KOREAN: add_panel(osk_panel_korean(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_CHINESE_T: add_panel(osk_panel_traditional_chinese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_CHINESE_S: add_panel(osk_panel_simplified_chinese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; case CELL_SYSUTIL_LANG_ENGLISH_US: case CELL_SYSUTIL_LANG_ENGLISH_GB: default: add_panel(osk_panel_english(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); break; } } else { // Append osk modes. // TODO: find out the exact order if (panel_flag & CELL_OSKDIALOG_PANELMODE_LATIN) { add_panel(osk_panel_latin(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_ENGLISH) { add_panel(osk_panel_english(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_FRENCH) { add_panel(osk_panel_french(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_SPANISH) { add_panel(osk_panel_spanish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_ITALIAN) { add_panel(osk_panel_italian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_GERMAN) { add_panel(osk_panel_german(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_TURKEY) { add_panel(osk_panel_turkey(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_POLISH) { add_panel(osk_panel_polish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_RUSSIAN) { add_panel(osk_panel_russian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_DANISH) { add_panel(osk_panel_danish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_NORWEGIAN) { add_panel(osk_panel_norwegian(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_DUTCH) { add_panel(osk_panel_dutch(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_SWEDISH) { add_panel(osk_panel_swedish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_FINNISH) { add_panel(osk_panel_finnish(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_PORTUGUESE) { add_panel(osk_panel_portuguese_pt(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_PORTUGUESE_BRAZIL) { add_panel(osk_panel_portuguese_br(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_KOREAN) { add_panel(osk_panel_korean(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_TRADITIONAL_CHINESE) { add_panel(osk_panel_traditional_chinese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_SIMPLIFIED_CHINESE) { add_panel(osk_panel_simplified_chinese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_JAPANESE) { add_panel(osk_panel_japanese(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_JAPANESE_HIRAGANA) { add_panel(osk_panel_japanese_hiragana(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_JAPANESE_KATAKANA) { add_panel(osk_panel_japanese_katakana(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_ALPHABET) { add_panel(osk_panel_alphabet_half_width(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_ALPHABET_FULL_WIDTH) { add_panel(osk_panel_alphabet_full_width(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_NUMERAL) { add_panel(osk_panel_numeral_half_width(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_NUMERAL_FULL_WIDTH) { add_panel(osk_panel_numeral_full_width(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } if (panel_flag & CELL_OSKDIALOG_PANELMODE_URL) { add_panel(osk_panel_url(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } // Get initial panel based on first_view_panel for (size_t i = 0; i < m_panels.size(); ++i) { if (first_view_panel == m_panels[i].osk_panel_mode) { m_panel_index = i; break; } } } // Fallback to english in case we forgot something if (m_panels.empty()) { osk.error("No OSK panel found. Using english panel."); add_panel(osk_panel_english(shift_cb, layer_cb, space_cb, delete_cb, enter_cb)); } initialize_layout(utf16_to_u32string(message), utf16_to_u32string(init_text)); update_panel(); g_fxo->init("OSK Thread", [this, tbit = alloc_thread_bit()] { g_thread_bit = tbit; if (auto error = run_input_loop()) { rsx_log.error("Osk input loop exited with error code=%d", error); } thread_bits &= ~tbit; thread_bits.notify_all(); }); } } }