mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-06-03 06:05:01 -06:00
Add IME keyboard layout and panel metrics support (#3973)
* Add IME keyboard layout and panel metrics support
- Introduced new header and implementation files for IME keyboard layout handling.
- Added structures for viewport metrics, keyboard grid layout, and drawing parameters.
- Implemented functions to compute viewport and panel metrics for the IME dialog.
- Enhanced the IME dialog UI to utilize the new keyboard layout and metrics.
- Updated input handling to support new keyboard interactions and layout adjustments.
- Added caret management and text normalization features in the IME dialog state.
- Improved window positioning logic to accommodate different screen resolutions.
* fix for Maxos Linux builds
* Align IME behavior with real PS4 libSceIme/Dialog flow
Features:
- Add validation helpers for IME option/language/extended-option masks
- Implement `sceImeGetPanelSize` sizing logic
- Replace `sceImeSetTextGeometry` no-op path with real state/argument validation
- Align panel sizing/validation paths closer to PS4 IME behavior
- Improve panel text/caret handling reliability during UI updates
- Add IME panel movement via controller right stick
- Support mouse drag repositioning for the panel
- Respect `FIXED_POSITION` by disabling movement when locked
Updates:
- Tighten `sceImeOpen` checks (user/type/input method, handler, work alignment, reserved, overlap)
- Validate initial/runtime text rules (`\n`/`\r` and surrogate rejection) in open/set-text paths
- Enforce caret bounds and null-caret behavior in `sceImeSetCaret`
- Update `sceImeUpdate` to return `NOT_OPENED` in invalid states and respect handler matching
- Harden `ImeHandler` against null state/callback execution paths
- Optimize keyboard grid rendering by reusing per-frame buffers
- Reduce input callback allocations with fixed-size buffers
- Harden panel update paths for invalid handler/open-state cases
- Clamp panel position to visible screen bounds
- Apply stick deadzone/speed scaling for stable movement
- Keep panel coordinates consistent with IME coordinate mode
* 🤦♂️
* Enhance IME UI and Input Handling
- Introduced BrightenColor function to adjust color brightness for UI elements.
- Improved UTF-16 character handling with new functions to count UTF-16 units and reject input based on UTF-16 limits.
- Added functionality to clamp input buffer to UTF-16 limits, ensuring text input does not exceed specified limits.
- Refactored virtual pad input handling to encapsulate left stick directions and panel movement.
- Enhanced IME UI drawing logic to incorporate new input handling and navigation features.
- Updated keyboard layout handling to support dynamic configurations and improved navigation shortcuts.
- Integrated additional font ranges for better language support in the IME, including Chinese, Arabic, and Thai.
- Improved overall code structure and readability by utilizing modern C++ features such as std::vector and std::array.
* Remove non existing Roboto Medium font from IME initialization
* Enhance IME Dialog and UI with Improved Gamepad Navigation and Key Mapping
- Added support for disabling gamepad input through OrbisImeDisableDevice in ImeDialogState.
- Updated ImeKbLayout to modify key mappings for better character input, including changes to punctuation keys.
- Implemented enhanced left stick navigation with repeat functionality in ImeUi, allowing for smoother input handling.
- Introduced new constants for stick navigation delays and repeat intervals to improve responsiveness.
- Refactored input handling logic to accommodate both virtual and gamepad inputs, ensuring consistent behavior across devices.
- Added functionality to clear all text in the input field with a specific shortcut, aligning with expected user behavior.
* clang
* Enhance IME functionality with improved gamepad navigation and layout handling
* ime: fix specials/accent layout mapping and dynamic panel resize handling
- Expand specials/accent keyboard layouts to a 7-row model and move function keys to fixed bottom rows.
- Add variable row-height support to the keyboard grid (`fixed_bottom_rows`, `bottom_row_h`) and compute row offsets/span heights from layout config.
- Preserve function-row height in `ime_ui` and `ime_dialog_ui` while distributing remaining height across typing rows.
- Make mode-switch focus layout-driven by resolving `SymbolsMode`/`SpecialsMode` action keys in the active layout instead of relying on implicit row assumptions.
- Track panel layout anchor deltas in `ime_dialog_ui` and use press-offset dragging so cursor remains on the originally pressed point when panel dimensions/position change.
* Add IME UI enhancements and shared utilities
- Introduced new states for panel navigation and selector fade in `ime_ui.h`.
- Added a new header file `ime_ui_shared.h` containing utility functions and structures for virtual pad input handling, including deadzone application and stick navigation direction resolution.
- Updated `font_stack.cpp` to include additional Unicode ranges for general punctuation in the font atlas.
* Refactor IME settings: remove deprecated accessibility options and update references to use new settings structure
* Refactor IME UI Navigation and Activation Logic
- Introduced a new mechanism for handling virtual button repeat actions, improving responsiveness for gamepad navigation.
- Added support for stick navigation with adjustable repeat rates and initial delays, enhancing user experience during input.
- Removed the ImeSelectorFadeState structure and related logic to streamline the IME keyboard layout drawing process.
- Simplified the activation logic for menus, ensuring that menu activation is more intuitive and responsive to user input.
- Adjusted the navigation threshold for stick inputs to improve sensitivity and control.
- Cleaned up the code by removing unused variables and consolidating repeated logic into reusable functions.
* Refactor IME UI shared header: streamline code and improve structure
- Removed unused includes and redundant structures.
- Consolidated virtual pad snapshot handling and input state management.
- Introduced new structures for OSK pad input and navigation handling.
- Updated function signatures for clarity and consistency.
- Enhanced keyboard parameter application for OSK shortcuts.
- Improved overall readability and maintainability of the code.
* Refactor OskShortcutActionResult to simplify triangle button press handling
* Enhance IME Keyboard Layout and UI Functionality
- Added new key glyphs for Shift and Caps Lock in ime_kb_layout.h.
- Improved keyboard navigation logic to skip non-action keys.
- Introduced new constants for selector IDs in ime_ui.cpp for better readability.
- Refactored keyboard row and column clamping logic for clarity.
- Enhanced selector drawing logic with fade and pulse effects for better user feedback.
- Implemented functions for cycling keyboard case states and toggling keyboard family modes.
- Added functionality to focus on keyboard action keys based on their actions.
- Improved edit menu handling with new templated functions for better code reuse.
- Updated caret blinking logic to improve text input experience.
- Expanded symbol ranges in font_stack.cpp to include keyboard symbols.
---------
Co-authored-by: w1naenator <valdis.bogdans@hotmail.com>
This commit is contained in:
parent
c79abb6df4
commit
05df651cd8
@ -520,12 +520,16 @@ set(HLE_LIBC_INTERNAL_LIB src/core/libraries/libc_internal/libc_internal.cpp
|
||||
set(IME_LIB src/core/libraries/ime/error_dialog.cpp
|
||||
src/core/libraries/ime/error_dialog.h
|
||||
src/core/libraries/ime/ime_common.h
|
||||
src/core/libraries/ime/ime_kb_layout.cpp
|
||||
src/core/libraries/ime/ime_kb_layout.h
|
||||
src/core/libraries/ime/ime_dialog_ui.cpp
|
||||
src/core/libraries/ime/ime_dialog_ui.h
|
||||
src/core/libraries/ime/ime_dialog.cpp
|
||||
src/core/libraries/ime/ime_dialog.h
|
||||
src/core/libraries/ime/ime_ui.cpp
|
||||
src/core/libraries/ime/ime_ui.h
|
||||
src/core/libraries/ime/ime_ui_shared.cpp
|
||||
src/core/libraries/ime/ime_ui_shared.h
|
||||
src/core/libraries/ime/ime.cpp
|
||||
src/core/libraries/ime/ime.h
|
||||
src/core/libraries/ime/ime_error.h
|
||||
|
||||
@ -163,6 +163,8 @@ std::string getDefaultControllerID();
|
||||
void setDefaultControllerID(std::string id);
|
||||
bool getBackgroundControllerInput();
|
||||
void setBackgroundControllerInput(bool enable, bool is_game_specific = false);
|
||||
bool getLoggingEnabled();
|
||||
void setLoggingEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getFsrEnabled();
|
||||
void setFsrEnabled(bool enable, bool is_game_specific = false);
|
||||
bool getRcasEnabled();
|
||||
|
||||
@ -579,6 +579,8 @@ bool EmulatorSettingsImpl::TransferSettings() {
|
||||
setFromToml(s.motion_controls_enabled, input, "isMotionControlsEnabled");
|
||||
setFromToml(s.use_unified_input_config, input, "useUnifiedInputConfig");
|
||||
setFromToml(s.background_controller_input, input, "backgroundControllerInput");
|
||||
setFromToml(s.ime_accessibility_enabled, input, "imeAccessibilityEnabled");
|
||||
setFromToml(s.ime_url_mail_short_panel, input, "imeUrlMailShortPanel");
|
||||
setFromToml(s.usb_device_backend, input, "usbDeviceBackend");
|
||||
}
|
||||
|
||||
|
||||
@ -291,6 +291,8 @@ struct InputSettings {
|
||||
Setting<bool> use_unified_input_config{true};
|
||||
Setting<std::string> default_controller_id{""};
|
||||
Setting<bool> background_controller_input{false}; // specific
|
||||
Setting<bool> ime_accessibility_enabled{false}; // specific
|
||||
Setting<bool> ime_url_mail_short_panel{false}; // specific
|
||||
Setting<s32> camera_id{-1};
|
||||
|
||||
std::vector<OverrideItem> GetOverrideableFields() const {
|
||||
@ -303,13 +305,18 @@ struct InputSettings {
|
||||
&InputSettings::motion_controls_enabled),
|
||||
make_override<InputSettings>("background_controller_input",
|
||||
&InputSettings::background_controller_input),
|
||||
make_override<InputSettings>("ime_accessibility_enabled",
|
||||
&InputSettings::ime_accessibility_enabled),
|
||||
make_override<InputSettings>("ime_url_mail_short_panel",
|
||||
&InputSettings::ime_url_mail_short_panel),
|
||||
make_override<InputSettings>("camera_id", &InputSettings::camera_id)};
|
||||
}
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(InputSettings, cursor_state, cursor_hide_timeout,
|
||||
usb_device_backend, use_special_pad, special_pad_class,
|
||||
motion_controls_enabled, use_unified_input_config,
|
||||
default_controller_id, background_controller_input, camera_id)
|
||||
default_controller_id, background_controller_input,
|
||||
ime_accessibility_enabled, ime_url_mail_short_panel, camera_id)
|
||||
// -------------------------------
|
||||
// Audio settings
|
||||
// -------------------------------
|
||||
@ -661,6 +668,8 @@ public:
|
||||
SETTING_FORWARD(m_input, UsbDeviceBackend, usb_device_backend)
|
||||
SETTING_FORWARD_BOOL(m_input, MotionControlsEnabled, motion_controls_enabled)
|
||||
SETTING_FORWARD_BOOL(m_input, BackgroundControllerInput, background_controller_input)
|
||||
SETTING_FORWARD_BOOL(m_input, ImeAccessibilityEnabled, ime_accessibility_enabled)
|
||||
SETTING_FORWARD_BOOL(m_input, ImeUrlMailShortPanel, ime_url_mail_short_panel)
|
||||
SETTING_FORWARD(m_input, DefaultControllerId, default_controller_id)
|
||||
SETTING_FORWARD_BOOL(m_input, UsingSpecialPad, use_special_pad)
|
||||
SETTING_FORWARD(m_input, SpecialPadClass, special_pad_class)
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <queue>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/ime/ime.h"
|
||||
#include "core/libraries/ime/ime_dialog.h"
|
||||
#include "core/libraries/ime/ime_error.h"
|
||||
#include "core/libraries/ime/ime_ui.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/tls.h"
|
||||
|
||||
@ -15,6 +19,152 @@ static std::queue<OrbisImeEvent> g_ime_events;
|
||||
static std::unique_ptr<ImeState> g_ime_state;
|
||||
static std::unique_ptr<ImeUi> g_ime_ui;
|
||||
|
||||
namespace {
|
||||
|
||||
u32 GetCompiledSdkVersion() {
|
||||
s32 ver = 0;
|
||||
if (Libraries::Kernel::sceKernelGetCompiledSdkVersion(&ver) != ORBIS_OK) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<u32>(ver);
|
||||
}
|
||||
|
||||
u32 GetImeOptionMask() {
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
u32 mask = static_cast<u32>(sdk > 0x14fffff) << 8;
|
||||
u32 tmp = mask | 0x70ffU;
|
||||
if (sdk > 0x174ffff) {
|
||||
tmp = mask | 0x78ffU;
|
||||
}
|
||||
mask = tmp & 0x69ffU;
|
||||
if (sdk > 0x2ffffff) {
|
||||
mask = tmp;
|
||||
}
|
||||
tmp = mask & 0x59ffU;
|
||||
if (sdk > 0x34fffff) {
|
||||
tmp = mask;
|
||||
}
|
||||
mask = tmp & 0x39ffU;
|
||||
if (sdk > 0x3ffffff) {
|
||||
mask = tmp;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
bool HasInvalidImeOption(u32 option) {
|
||||
return (((GetImeOptionMask() ^ 0xfffffdffU) & option) != 0);
|
||||
}
|
||||
|
||||
u64 GetImeLanguageMask() {
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
u64 mask = (sdk > 0x1ffffff ? 0x1000000ULL : 0ULL) + 0x3fe1fffffULL;
|
||||
u64 tmp = mask & 0x3fd1fffffULL;
|
||||
if (sdk > 0x24fffff) {
|
||||
tmp = mask;
|
||||
}
|
||||
mask = tmp & 0x2031fffffULL;
|
||||
if (sdk > 0x4ffffff) {
|
||||
mask = tmp;
|
||||
}
|
||||
tmp = mask & 0x1ff1fffffULL;
|
||||
if (sdk > 0xfffffff) {
|
||||
tmp = mask;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool IsValidImeExtOption(u32 option) {
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
u32 allow = (sdk < 0x1560000) ? 0x41dfU : 0x4fdfU;
|
||||
if ((option & 0x4080U) == 0x4000U) {
|
||||
return false;
|
||||
}
|
||||
if (sdk <= 0x5ffffff) {
|
||||
allow &= 0x0fdfU;
|
||||
}
|
||||
return ((~allow & option) == 0);
|
||||
}
|
||||
|
||||
bool IsValidImeUserId(Libraries::UserService::OrbisUserServiceUserId user_id) {
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
const u32 uid_u = static_cast<u32>(user_id);
|
||||
|
||||
if (sdk < 0x1500000) {
|
||||
if ((uid_u + 1U) < 2U || (uid_u - 0xfeU) < 2U) {
|
||||
return true;
|
||||
}
|
||||
return user_id >= 0;
|
||||
}
|
||||
|
||||
if (user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID ||
|
||||
user_id == Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_SYSTEM) {
|
||||
return false;
|
||||
}
|
||||
if (user_id == 0xfe) {
|
||||
return true;
|
||||
}
|
||||
return user_id >= 0;
|
||||
}
|
||||
|
||||
u32 CountUtf16Text(const char16_t* text, u32 max_len) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
u32 len = 0;
|
||||
while (len < max_len && text[len] != u'\0') {
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
bool IsValidImeText(const char16_t* text, u32 length, bool multiline, u32 sdk) {
|
||||
if (!text) {
|
||||
return false;
|
||||
}
|
||||
const bool allow_new_line = (sdk >= 0x1560000) && multiline;
|
||||
|
||||
for (u32 i = 0; i < length; ++i) {
|
||||
const char16_t ch = text[i];
|
||||
if (!allow_new_line && (ch == u'\n' || ch == u'\r')) {
|
||||
return false;
|
||||
}
|
||||
if ((static_cast<u16>(ch) & 0xf800U) == 0xd800U) {
|
||||
return false;
|
||||
}
|
||||
if (ch == u'\0') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasInvalidWorkOverlap(const OrbisImeParam* param) {
|
||||
if (!param || !param->inputTextBuffer || !param->work) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto input_addr = reinterpret_cast<uintptr_t>(param->inputTextBuffer);
|
||||
const auto work_addr = reinterpret_cast<uintptr_t>(param->work);
|
||||
const bool expanded = True(param->option & OrbisImeOption::EXPANDED_PREEDIT_BUFFER);
|
||||
const u32 scratch_chars = param->maxTextLength + (expanded ? 0x79U : 0x1fU);
|
||||
|
||||
if (input_addr <= work_addr) {
|
||||
const uintptr_t input_end =
|
||||
input_addr + static_cast<uintptr_t>(scratch_chars) * sizeof(char16_t);
|
||||
if (work_addr < input_end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (work_addr <= input_addr && input_addr <= (work_addr + 0x4fffU)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ImeHandler {
|
||||
public:
|
||||
ImeHandler(const OrbisImeKeyboardParam* param) {
|
||||
@ -83,6 +233,10 @@ public:
|
||||
/* We don't handle any events for ImeKeyboard */
|
||||
return Error::OK;
|
||||
}
|
||||
if (!g_ime_state) {
|
||||
LOG_ERROR(Lib_Ime, "ImeHandler::Update called without active IME state");
|
||||
return Error::INTERNAL;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock{g_ime_state->queue_mutex};
|
||||
|
||||
@ -98,18 +252,20 @@ public:
|
||||
void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) {
|
||||
if (m_ime_mode) {
|
||||
OrbisImeParam param = m_param.ime;
|
||||
if (use_param_handler) {
|
||||
param.handler(param.arg, event);
|
||||
} else {
|
||||
handler(param.arg, event);
|
||||
const OrbisImeEventHandler callback = use_param_handler ? param.handler : handler;
|
||||
if (!callback) {
|
||||
LOG_ERROR(Lib_Ime, "ImeHandler::Execute called with null IME callback");
|
||||
return;
|
||||
}
|
||||
callback(param.arg, event);
|
||||
} else {
|
||||
OrbisImeKeyboardParam param = m_param.key;
|
||||
if (use_param_handler) {
|
||||
param.handler(param.arg, event);
|
||||
} else {
|
||||
handler(param.arg, event);
|
||||
const OrbisImeEventHandler callback = use_param_handler ? param.handler : handler;
|
||||
if (!callback) {
|
||||
LOG_ERROR(Lib_Ime, "ImeHandler::Execute called with null keyboard callback");
|
||||
return;
|
||||
}
|
||||
callback(param.arg, event);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +274,10 @@ public:
|
||||
LOG_WARNING(Lib_Ime, "ImeHandler::SetText received null text pointer");
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
if (!g_ime_state) {
|
||||
LOG_ERROR(Lib_Ime, "ImeHandler::SetText called without active IME state");
|
||||
return Error::INTERNAL;
|
||||
}
|
||||
g_ime_state->SetText(text, length);
|
||||
return Error::OK;
|
||||
}
|
||||
@ -127,6 +287,10 @@ public:
|
||||
LOG_WARNING(Lib_Ime, "ImeHandler::SetCaret received null caret pointer");
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
if (!g_ime_state) {
|
||||
LOG_ERROR(Lib_Ime, "ImeHandler::SetCaret called without active IME state");
|
||||
return Error::INTERNAL;
|
||||
}
|
||||
g_ime_state->SetCaret(caret->index);
|
||||
return Error::OK;
|
||||
}
|
||||
@ -135,6 +299,25 @@ public:
|
||||
return m_ime_mode;
|
||||
}
|
||||
|
||||
OrbisImeEventHandler GetHandler() const {
|
||||
return m_ime_mode ? m_param.ime.handler : m_param.key.handler;
|
||||
}
|
||||
|
||||
u32 GetImeOptionBits() const {
|
||||
return m_ime_mode ? static_cast<u32>(m_param.ime.option) : 0U;
|
||||
}
|
||||
|
||||
u32 GetImeMaxTextLength() const {
|
||||
return m_ime_mode ? m_param.ime.maxTextLength : 0U;
|
||||
}
|
||||
|
||||
u32 GetImeCurrentTextLength() const {
|
||||
if (!m_ime_mode) {
|
||||
return 0U;
|
||||
}
|
||||
return CountUtf16Text(m_param.ime.inputTextBuffer, m_param.ime.maxTextLength);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ImeParam {
|
||||
OrbisImeKeyboardParam key;
|
||||
@ -202,7 +385,8 @@ int PS4_SYSV_ABI sceImeConfigSet() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate() {
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate(s32 index) {
|
||||
(void)index;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -252,66 +436,43 @@ int PS4_SYSV_ABI sceImeForTestFunction() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() {
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(OrbisImePositionAndForm* posForm) {
|
||||
if (!posForm) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) {
|
||||
LOG_INFO(Lib_Ime, "called");
|
||||
|
||||
if (!param) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid param: NULL");
|
||||
if (!param || !width || !height) {
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (!width) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid *width: NULL");
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
if (!height) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid *height: NULL");
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (static_cast<u32>(param->option) & ~0x7BFF) { // Basic check for invalid options
|
||||
LOG_ERROR(Lib_Ime, "Invalid option: {:032b}", static_cast<u32>(param->option));
|
||||
return Error::INVALID_OPTION;
|
||||
}
|
||||
|
||||
switch (param->type) {
|
||||
case OrbisImeType::Default:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Default ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::BasicLatin:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: BasicLatin ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Url:
|
||||
*width = 500; // dummy value
|
||||
*height = 100; // dummy value
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Url ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Mail:
|
||||
// We set our custom sizes, commented sizes are the original ones
|
||||
*width = 500; // 793
|
||||
*height = 100; // 408
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Mail ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
case OrbisImeType::Number:
|
||||
*width = 370;
|
||||
*height = 402;
|
||||
LOG_DEBUG(Lib_Ime, "param->type: Number ({})", static_cast<u32>(param->type));
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Lib_Ime, "Invalid param->type: ({})", static_cast<u32>(param->type));
|
||||
if (static_cast<u32>(param->type) > static_cast<u32>(OrbisImeType::Number)) {
|
||||
return Error::INVALID_TYPE;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Lib_Ime, "IME panel size: width={}, height={}", *width, *height);
|
||||
const u32 option = static_cast<u32>(param->option);
|
||||
if (HasInvalidImeOption(option)) {
|
||||
return Error::INVALID_OPTION;
|
||||
}
|
||||
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
if (param->type == OrbisImeType::Number) {
|
||||
*width = 0x172U;
|
||||
*height = (sdk > 0x16fffffU) ? 0x192U : 0x170U;
|
||||
} else if (param->type == OrbisImeType::BasicLatin || (option & 0xc0000004U) == 4U) {
|
||||
*width = 0x319U;
|
||||
*height = (sdk > 0x16fffffU) ? 0x198U : 0x170U;
|
||||
} else {
|
||||
*width = 0x319U;
|
||||
*height = 0x198U;
|
||||
}
|
||||
|
||||
if ((option & static_cast<u32>(OrbisImeOption::USE_OVER_2K_COORDINATES)) != 0) {
|
||||
*width <<= 1;
|
||||
*height <<= 1;
|
||||
}
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
@ -339,7 +500,11 @@ Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceU
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo() {
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo(u32 resourceId, OrbisImeKeyboardInfo* info) {
|
||||
(void)resourceId;
|
||||
if (info) {
|
||||
*info = {};
|
||||
}
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -463,7 +628,10 @@ int PS4_SYSV_ABI sceImeKeyboardOpenInternal() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode() {
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
u32 mode) {
|
||||
(void)userId;
|
||||
(void)mode;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -476,6 +644,11 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() {
|
||||
Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) {
|
||||
LOG_INFO(Lib_Ime, "called");
|
||||
|
||||
if (g_ime_handler) {
|
||||
LOG_ERROR(Lib_Ime, "IME handler is already open");
|
||||
return Error::BUSY;
|
||||
}
|
||||
|
||||
if (!param) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid param: NULL");
|
||||
return Error::INVALID_ADDRESS;
|
||||
@ -540,42 +713,62 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
|
||||
LOG_DEBUG(Lib_Ime, "extended->ext_keyboard_mode: {}", extended->ext_keyboard_mode);
|
||||
}
|
||||
|
||||
if (param->user_id ==
|
||||
Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { // Todo: check valid user IDs
|
||||
if (!IsValidImeUserId(param->user_id)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid user_id: {}", static_cast<u32>(param->user_id));
|
||||
return Error::INVALID_USER_ID;
|
||||
}
|
||||
|
||||
if (!magic_enum::enum_contains(param->type)) {
|
||||
if (static_cast<u32>(param->type) > static_cast<u32>(OrbisImeType::Number)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid type: {}", static_cast<u32>(param->type));
|
||||
return Error::INVALID_TYPE;
|
||||
}
|
||||
|
||||
if (static_cast<u64>(param->supported_languages) & ~kValidOrbisImeLanguageMask) {
|
||||
const u64 lang_mask = GetImeLanguageMask();
|
||||
if ((~lang_mask & static_cast<u64>(param->supported_languages)) != 0) {
|
||||
LOG_ERROR(Lib_Ime,
|
||||
"Invalid supported_languages\n"
|
||||
"supported_languages: {:064b}\n"
|
||||
"valid_mask: {:064b}",
|
||||
static_cast<u64>(param->supported_languages), kValidOrbisImeLanguageMask);
|
||||
static_cast<u64>(param->supported_languages), lang_mask);
|
||||
return Error::INVALID_SUPPORTED_LANGUAGES;
|
||||
}
|
||||
|
||||
if (!magic_enum::enum_contains(param->enter_label)) {
|
||||
if (static_cast<u32>(param->enter_label) > static_cast<u32>(OrbisImeEnterLabel::Go)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid enter_label: {}", static_cast<u32>(param->enter_label));
|
||||
return Error::INVALID_ENTER_LABEL;
|
||||
}
|
||||
|
||||
if (!magic_enum::enum_contains(param->input_method)) {
|
||||
if (param->input_method != OrbisImeInputMethod::Default) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid input_method: {}", static_cast<u32>(param->input_method));
|
||||
return Error::INVALID_INPUT_METHOD;
|
||||
}
|
||||
|
||||
if (static_cast<u32>(param->option) & ~kValidImeOptionMask) {
|
||||
LOG_ERROR(Lib_Ime, "option has invalid bits set (0x{:X}), mask=(0x{:X})",
|
||||
static_cast<u32>(param->option), kValidImeOptionMask);
|
||||
const u32 option = static_cast<u32>(param->option);
|
||||
if (HasInvalidImeOption(option)) {
|
||||
LOG_ERROR(Lib_Ime, "option has invalid bits set (0x{:X}), mask=(0x{:X})", option,
|
||||
GetImeOptionMask());
|
||||
return Error::INVALID_OPTION;
|
||||
}
|
||||
|
||||
const bool multiline = True(param->option & OrbisImeOption::MULTILINE);
|
||||
const bool password = True(param->option & OrbisImeOption::PASSWORD);
|
||||
if (multiline && password) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid option combination: MULTILINE + PASSWORD");
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
if (multiline && param->type != OrbisImeType::Default &&
|
||||
param->type != OrbisImeType::BasicLatin) {
|
||||
LOG_ERROR(Lib_Ime, "MULTILINE requires type Default or BasicLatin, got {}",
|
||||
static_cast<u32>(param->type));
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
if (password && param->type != OrbisImeType::BasicLatin &&
|
||||
param->type != OrbisImeType::Number) {
|
||||
LOG_ERROR(Lib_Ime, "PASSWORD requires type BasicLatin or Number, got {}",
|
||||
static_cast<u32>(param->type));
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (param->maxTextLength == 0 || param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid maxTextLength: {}", param->maxTextLength);
|
||||
return Error::INVALID_MAX_TEXT_LENGTH;
|
||||
@ -586,9 +779,14 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
|
||||
return Error::INVALID_INPUT_TEXT_BUFFER;
|
||||
}
|
||||
|
||||
bool useHighRes = True(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES);
|
||||
const float maxWidth = useHighRes ? 3840.0f : 1920.0f;
|
||||
const float maxHeight = useHighRes ? 2160.0f : 1080.0f;
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
float maxWidth = 1920.0f;
|
||||
float maxHeight = 1080.0f;
|
||||
if (sdk >= 0x1500000) {
|
||||
const bool use_high_res = True(param->option & OrbisImeOption::USE_OVER_2K_COORDINATES);
|
||||
maxWidth = use_high_res ? 3840.0f : 1920.0f;
|
||||
maxHeight = use_high_res ? 2160.0f : 1080.0f;
|
||||
}
|
||||
|
||||
if (param->posx < 0.0f || param->posx >= maxWidth) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid posx: {}, range: 0.0 - {}", param->posx, maxWidth);
|
||||
@ -599,25 +797,57 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
|
||||
return Error::INVALID_POSY;
|
||||
}
|
||||
|
||||
if (!magic_enum::enum_contains(param->horizontal_alignment)) {
|
||||
if (static_cast<u32>(param->horizontal_alignment) > 2U) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid horizontal_alignment: {}",
|
||||
static_cast<u32>(param->horizontal_alignment));
|
||||
return Error::INVALID_HORIZONTALIGNMENT;
|
||||
}
|
||||
if (!magic_enum::enum_contains(param->vertical_alignment)) {
|
||||
if (static_cast<u32>(param->vertical_alignment) > 2U) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid vertical_alignment: {}",
|
||||
static_cast<u32>(param->vertical_alignment));
|
||||
return Error::INVALID_VERTICALALIGNMENT;
|
||||
}
|
||||
|
||||
if (extended) {
|
||||
u32 ext_option_value = static_cast<u32>(extended->option);
|
||||
if (ext_option_value & ~kValidImeExtOptionMask) {
|
||||
if (static_cast<u32>(extended->priority) >
|
||||
static_cast<u32>(OrbisImePanelPriority::Accent)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid extended->priority: {}",
|
||||
static_cast<u32>(extended->priority));
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
|
||||
const u32 ext_option_value = static_cast<u32>(extended->option);
|
||||
if (!IsValidImeExtOption(ext_option_value)) {
|
||||
LOG_ERROR(Lib_Ime,
|
||||
"Invalid extended->option\n"
|
||||
"option: {:032b}\n"
|
||||
"valid_mask: {:032b}",
|
||||
ext_option_value, kValidImeExtOptionMask);
|
||||
"sdk: 0x{:X}",
|
||||
ext_option_value, sdk);
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
|
||||
if ((extended->ext_keyboard_mode & 0xe3fffffcU) != 0) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid extended->ext_keyboard_mode: 0x{:X}",
|
||||
extended->ext_keyboard_mode);
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(extended->reserved); ++i) {
|
||||
if (extended->reserved[i] != 0) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid extended->reserved: not zeroed");
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
}
|
||||
|
||||
const u32 disable_device = static_cast<u32>(extended->disable_device);
|
||||
if (sdk < 0x1560000) {
|
||||
if (extended->ext_keyboard_filter != nullptr || disable_device != 0 ||
|
||||
extended->ext_keyboard_mode != 0) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid extended fields for SDK < 0x1560000");
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
} else if (disable_device > 7U) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid extended->disable_device: {}", disable_device);
|
||||
return Error::INVALID_EXTENDED;
|
||||
}
|
||||
}
|
||||
@ -626,16 +856,13 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
|
||||
LOG_ERROR(Lib_Ime, "Invalid work: NULL");
|
||||
return Error::INVALID_WORK;
|
||||
}
|
||||
|
||||
// Todo: validate arg
|
||||
if (false) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid arg");
|
||||
return Error::INVALID_ARG;
|
||||
if ((reinterpret_cast<uintptr_t>(param->work) & 0x3U) != 0) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid work alignment: {:p}", param->work);
|
||||
return Error::INVALID_WORK;
|
||||
}
|
||||
|
||||
// Todo: validate handler
|
||||
if (false) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid handler");
|
||||
if (!param->handler) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid handler: NULL");
|
||||
return Error::INVALID_HANDLER;
|
||||
}
|
||||
|
||||
@ -646,11 +873,18 @@ Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExt
|
||||
}
|
||||
}
|
||||
|
||||
if (g_ime_handler) {
|
||||
LOG_ERROR(Lib_Ime, "IME handler is already open");
|
||||
return Error::BUSY;
|
||||
if (HasInvalidWorkOverlap(param)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid overlap between inputTextBuffer and work");
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!IsValidImeText(param->inputTextBuffer, param->maxTextLength, multiline, sdk)) {
|
||||
LOG_ERROR(Lib_Ime, "Invalid initial inputTextBuffer content");
|
||||
return Error::INVALID_TEXT;
|
||||
}
|
||||
|
||||
std::memset(param->work, 0, 0x5000);
|
||||
|
||||
if (extended) {
|
||||
g_ime_handler = std::make_unique<ImeHandler>(param, extended);
|
||||
} else {
|
||||
@ -681,7 +915,8 @@ void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) {
|
||||
param->user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex() {
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex(s32 index) {
|
||||
(void)index;
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -692,8 +927,12 @@ Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) {
|
||||
if (!g_ime_handler) {
|
||||
return Error::NOT_OPENED;
|
||||
}
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
if (!caret) {
|
||||
return Error::INVALID_ADDRESS;
|
||||
return (sdk < 0x1500000U) ? Error::INVALID_PARAM : Error::INVALID_ADDRESS;
|
||||
}
|
||||
if (caret->index > g_ime_handler->GetImeCurrentTextLength()) {
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
|
||||
return g_ime_handler->SetCaret(caret);
|
||||
@ -711,26 +950,69 @@ Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) {
|
||||
return Error::INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
if (sdk < 0x1560000U && length == 0) {
|
||||
return Error::INVALID_PARAM;
|
||||
}
|
||||
if (length != 0) {
|
||||
const bool multiline =
|
||||
(g_ime_handler->GetImeOptionBits() & static_cast<u32>(OrbisImeOption::MULTILINE)) != 0;
|
||||
if (!IsValidImeText(text, length, multiline, sdk)) {
|
||||
return Error::INVALID_TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
return g_ime_handler->SetText(text, length);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry() {
|
||||
LOG_ERROR(Lib_Ime, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry(OrbisImeTextAreaMode mode,
|
||||
const OrbisImeTextGeometry* geometry) {
|
||||
if (!g_ime_handler) {
|
||||
return static_cast<int>(Error::NOT_OPENED);
|
||||
}
|
||||
if (!geometry) {
|
||||
return static_cast<int>(Error::INVALID_ADDRESS);
|
||||
}
|
||||
|
||||
const float x = geometry->x;
|
||||
const float y = geometry->y;
|
||||
if (x < 0.0f || x >= 1920.0f || y < 0.0f || y >= 1080.0f) {
|
||||
return static_cast<int>(Error::INVALID_PARAM);
|
||||
}
|
||||
|
||||
if (mode != OrbisImeTextAreaMode::Select && mode != OrbisImeTextAreaMode::Preedit) {
|
||||
return static_cast<int>(Error::INVALID_PARAM);
|
||||
}
|
||||
|
||||
return static_cast<int>(Error::OK);
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) {
|
||||
if (!g_ime_handler && !g_keyboard_handler) {
|
||||
LOG_ERROR(Lib_Ime, "sceImeUpdate called with no active handler");
|
||||
return Error::NOT_OPENED;
|
||||
}
|
||||
|
||||
const u32 sdk = GetCompiledSdkVersion();
|
||||
bool ime_dispatched = false;
|
||||
|
||||
if (g_ime_handler) {
|
||||
g_ime_handler->Update(handler);
|
||||
if (handler == g_ime_handler->GetHandler()) {
|
||||
g_ime_handler->Update(handler);
|
||||
ime_dispatched = true;
|
||||
} else if (sdk < 0x1500000U) {
|
||||
ime_dispatched = true;
|
||||
} else if (!g_keyboard_handler) {
|
||||
return Error::NOT_OPENED;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_keyboard_handler) {
|
||||
g_keyboard_handler->Update(handler);
|
||||
}
|
||||
|
||||
if (!g_ime_handler && !g_keyboard_handler) {
|
||||
LOG_ERROR(Lib_Ime, "sceImeUpdate called with no active handler");
|
||||
return Error::OK;
|
||||
if (!ime_dispatched && !g_keyboard_handler) {
|
||||
return Error::NOT_OPENED;
|
||||
}
|
||||
|
||||
return Error::OK;
|
||||
|
||||
@ -21,7 +21,7 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo();
|
||||
Error PS4_SYSV_ABI sceImeClose();
|
||||
int PS4_SYSV_ABI sceImeConfigGet();
|
||||
int PS4_SYSV_ABI sceImeConfigSet();
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate();
|
||||
int PS4_SYSV_ABI sceImeConfirmCandidate(s32 index);
|
||||
int PS4_SYSV_ABI sceImeDicAddWord();
|
||||
int PS4_SYSV_ABI sceImeDicDeleteLearnDics();
|
||||
int PS4_SYSV_ABI sceImeDicDeleteUserDics();
|
||||
@ -31,25 +31,27 @@ int PS4_SYSV_ABI sceImeDicReplaceWord();
|
||||
int PS4_SYSV_ABI sceImeDisableController();
|
||||
int PS4_SYSV_ABI sceImeFilterText();
|
||||
int PS4_SYSV_ABI sceImeForTestFunction();
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm();
|
||||
int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(OrbisImePositionAndForm* posForm);
|
||||
Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height);
|
||||
Error PS4_SYSV_ABI sceImeKeyboardClose(Libraries::UserService::OrbisUserServiceUserId userId);
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo();
|
||||
int PS4_SYSV_ABI sceImeKeyboardGetInfo(u32 resourceId, OrbisImeKeyboardInfo* info);
|
||||
Error PS4_SYSV_ABI
|
||||
sceImeKeyboardGetResourceId(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
OrbisImeKeyboardResourceIdArray* resourceIdArray);
|
||||
Error PS4_SYSV_ABI sceImeKeyboardOpen(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
const OrbisImeKeyboardParam* param);
|
||||
int PS4_SYSV_ABI sceImeKeyboardOpenInternal();
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode();
|
||||
int PS4_SYSV_ABI sceImeKeyboardSetMode(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
u32 mode);
|
||||
int PS4_SYSV_ABI sceImeKeyboardUpdate();
|
||||
Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeOpenInternal();
|
||||
void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param);
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex();
|
||||
int PS4_SYSV_ABI sceImeSetCandidateIndex(s32 index);
|
||||
Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret);
|
||||
Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length);
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry();
|
||||
int PS4_SYSV_ABI sceImeSetTextGeometry(OrbisImeTextAreaMode mode,
|
||||
const OrbisImeTextGeometry* geometry);
|
||||
Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler);
|
||||
int PS4_SYSV_ABI sceImeVshClearPreedit();
|
||||
int PS4_SYSV_ABI sceImeVshClose();
|
||||
|
||||
@ -121,7 +121,8 @@ enum class OrbisImeExtOption : u32 {
|
||||
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeExtOption);
|
||||
|
||||
constexpr u32 kValidImeExtOptionMask = static_cast<u32>(
|
||||
OrbisImeExtOption::SET_PRIORITY | OrbisImeExtOption::PRIORITY_FULL_WIDTH |
|
||||
OrbisImeExtOption::SET_COLOR | OrbisImeExtOption::SET_PRIORITY |
|
||||
OrbisImeExtOption::PRIORITY_SHIFT | OrbisImeExtOption::PRIORITY_FULL_WIDTH |
|
||||
OrbisImeExtOption::PRIORITY_FIXED_PANEL | OrbisImeExtOption::DISABLE_POINTER |
|
||||
OrbisImeExtOption::ENABLE_ADDITIONAL_DICTIONARY | OrbisImeExtOption::DISABLE_STARTUP_SE |
|
||||
OrbisImeExtOption::DISABLE_LIST_FOR_EXT_KEYBOARD |
|
||||
@ -375,6 +376,16 @@ enum class OrbisImeKeyboardType : u32 {
|
||||
HUNGARIAN = 37,
|
||||
};
|
||||
|
||||
enum class OrbisImeKeyboardDeviceType : u32 {
|
||||
Keyboard = 0,
|
||||
Osk = 1,
|
||||
};
|
||||
|
||||
enum class OrbisImeKeyboardStatus : u32 {
|
||||
Disconnected = 0,
|
||||
Connected = 1,
|
||||
};
|
||||
|
||||
enum class OrbisImeDeviceType : u32 {
|
||||
None = 0,
|
||||
Controller = 1,
|
||||
@ -438,6 +449,16 @@ struct OrbisImeKeyboardResourceIdArray {
|
||||
u32 resource_id[5];
|
||||
};
|
||||
|
||||
struct OrbisImeKeyboardInfo {
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
OrbisImeKeyboardDeviceType device;
|
||||
OrbisImeKeyboardType type;
|
||||
u32 repeat_delay;
|
||||
u32 repeat_rate;
|
||||
OrbisImeKeyboardStatus status;
|
||||
s8 reserved[12];
|
||||
};
|
||||
|
||||
enum class OrbisImeCaretMovementDirection : u32 {
|
||||
Still = 0,
|
||||
Left = 1,
|
||||
@ -462,6 +483,23 @@ enum class OrbisImePanelType : u32 {
|
||||
Accessibility = 6,
|
||||
};
|
||||
|
||||
struct OrbisImePositionAndForm {
|
||||
OrbisImePanelType type;
|
||||
f32 posx;
|
||||
f32 posy;
|
||||
OrbisImeHorizontalAlignment horizontal_alignment;
|
||||
OrbisImeVerticalAlignment vertical_alignment;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
struct OrbisImeTextGeometry {
|
||||
f32 x;
|
||||
f32 y;
|
||||
u32 width;
|
||||
u32 height;
|
||||
};
|
||||
|
||||
union OrbisImeEventParam {
|
||||
OrbisImeRect rect;
|
||||
OrbisImeEditText text;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -27,14 +27,14 @@ enum class OrbisImeDialogEndStatus : u32 {
|
||||
|
||||
struct OrbisImeDialogResult {
|
||||
OrbisImeDialogEndStatus endstatus;
|
||||
s32 reserved[12];
|
||||
s8 reserved[12];
|
||||
};
|
||||
|
||||
Error PS4_SYSV_ABI sceImeDialogAbort();
|
||||
Error PS4_SYSV_ABI sceImeDialogForceClose();
|
||||
Error PS4_SYSV_ABI sceImeDialogForTestFunction();
|
||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState();
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm();
|
||||
int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(s64 param_1);
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(OrbisImePositionAndForm* posForm);
|
||||
Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u32* width,
|
||||
u32* height);
|
||||
Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* param,
|
||||
@ -43,10 +43,12 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(const OrbisImeDialogParam* p
|
||||
Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result);
|
||||
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus();
|
||||
Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal2();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal3();
|
||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition();
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal(OrbisImeDialogParam* param,
|
||||
OrbisImeParamExtended* extended);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal2(int* param_1, u32* param_2, u32 param_3, u64 param_4);
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal3(int* param_1, u32* param_2, u32 param_3, u64 param_4,
|
||||
u32 param_5, u32 param_6);
|
||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition(s32 posx, s32 posy);
|
||||
Error PS4_SYSV_ABI sceImeDialogTerm();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -9,26 +9,49 @@
|
||||
#include "common/cstring.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ime/ime_dialog.h"
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
class ImeDialogUi;
|
||||
} // namespace Libraries::ImeDialog
|
||||
|
||||
namespace Libraries::Ime {
|
||||
struct ImePanelMetrics;
|
||||
}
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
class ImeDialogState final {
|
||||
friend ImeDialogUi;
|
||||
|
||||
bool input_changed = false;
|
||||
int caret_index = 0;
|
||||
int caret_byte_index = 0;
|
||||
bool caret_dirty = false;
|
||||
bool use_over2k = false;
|
||||
OrbisImePositionAndForm panel_layout{};
|
||||
bool panel_layout_valid = false;
|
||||
u32 panel_req_width = 0;
|
||||
u32 panel_req_height = 0;
|
||||
OrbisImeExtOption ext_option = OrbisImeExtOption::DEFAULT;
|
||||
OrbisImeDisableDevice disable_device = OrbisImeDisableDevice::DEFAULT;
|
||||
OrbisImePanelPriority panel_priority = OrbisImePanelPriority::Default;
|
||||
Libraries::Ime::ImeStyleConfig style_config{};
|
||||
|
||||
s32 user_id{};
|
||||
bool is_multi_line{};
|
||||
bool is_numeric{};
|
||||
bool fixed_position{};
|
||||
OrbisImeType type{};
|
||||
OrbisImeLanguage supported_languages{};
|
||||
OrbisImeEnterLabel enter_label{};
|
||||
OrbisImeTextFilter text_filter{};
|
||||
OrbisImeExtKeyboardFilter keyboard_filter{};
|
||||
u32 max_text_length{};
|
||||
char16_t* text_buffer{};
|
||||
std::vector<char16_t> original_text;
|
||||
std::vector<char> title;
|
||||
std::vector<char> placeholder;
|
||||
|
||||
@ -48,8 +71,10 @@ public:
|
||||
ImeDialogState(ImeDialogState&& other) noexcept;
|
||||
ImeDialogState& operator=(ImeDialogState&& other);
|
||||
|
||||
bool CopyTextToOrbisBuffer();
|
||||
bool CopyTextToOrbisBuffer(bool use_original);
|
||||
bool CallTextFilter();
|
||||
bool NormalizeNewlines();
|
||||
bool ClampCurrentTextToMaxLen();
|
||||
|
||||
private:
|
||||
bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status);
|
||||
@ -61,11 +86,83 @@ private:
|
||||
};
|
||||
|
||||
class ImeDialogUi final : public ImGui::Layer {
|
||||
enum class PanelSelectionTarget : u8 {
|
||||
Input = 0,
|
||||
Prediction = 1,
|
||||
Close = 2,
|
||||
Keyboard = 3,
|
||||
};
|
||||
|
||||
enum class EditMenuPopup : u8 {
|
||||
None = 0,
|
||||
Main = 1,
|
||||
Actions = 2,
|
||||
};
|
||||
|
||||
ImeDialogState* state{};
|
||||
OrbisImeDialogStatus* status{};
|
||||
OrbisImeDialogResult* result{};
|
||||
|
||||
bool first_render = true;
|
||||
bool accept_armed = false;
|
||||
bool native_input_active = false;
|
||||
bool pointer_navigation_active = true;
|
||||
EditMenuPopup edit_menu_popup = EditMenuPopup::None;
|
||||
bool menu_activate_armed = true;
|
||||
bool l2_shortcut_armed = true;
|
||||
bool request_input_focus = false;
|
||||
bool request_input_select_all = false;
|
||||
bool text_select_mode = false;
|
||||
bool pending_input_selection_apply = false;
|
||||
bool prev_virtual_cross_down = false;
|
||||
bool prev_virtual_lstick_left_down = false;
|
||||
bool prev_virtual_lstick_right_down = false;
|
||||
bool prev_virtual_lstick_up_down = false;
|
||||
bool prev_virtual_lstick_down_down = false;
|
||||
int left_stick_repeat_dir = 0;
|
||||
double left_stick_next_repeat_time = 0.0;
|
||||
double virtual_cross_next_repeat_time = 0.0;
|
||||
double virtual_triangle_next_repeat_time = 0.0;
|
||||
u32 prev_virtual_buttons = 0;
|
||||
bool prev_virtual_square_down = false;
|
||||
bool prev_virtual_l1_down = false;
|
||||
bool prev_virtual_r1_down = false;
|
||||
bool prev_virtual_dpad_left_down = false;
|
||||
bool prev_virtual_dpad_right_down = false;
|
||||
bool prev_virtual_dpad_up_down = false;
|
||||
bool prev_virtual_dpad_down_down = false;
|
||||
double virtual_square_next_repeat_time = 0.0;
|
||||
double virtual_l1_next_repeat_time = 0.0;
|
||||
double virtual_r1_next_repeat_time = 0.0;
|
||||
double virtual_dpad_left_next_repeat_time = 0.0;
|
||||
double virtual_dpad_right_next_repeat_time = 0.0;
|
||||
double virtual_dpad_up_next_repeat_time = 0.0;
|
||||
double virtual_dpad_down_next_repeat_time = 0.0;
|
||||
Libraries::Ime::ImeEdgeWrapNavState panel_vertical_nav_state{};
|
||||
bool panel_position_initialized = false;
|
||||
bool panel_layout_anchor_initialized = false;
|
||||
bool panel_drag_active = false;
|
||||
bool gamepad_input_capture_active = false;
|
||||
ImVec2 panel_position{};
|
||||
ImVec2 panel_layout_anchor{};
|
||||
ImVec2 panel_drag_press_offset{};
|
||||
int input_cursor_utf16 = 0;
|
||||
int input_cursor_byte = 0;
|
||||
int input_selection_start_byte = 0;
|
||||
int input_selection_end_byte = 0;
|
||||
int text_select_anchor_utf16 = -1;
|
||||
int text_select_focus_utf16 = -1;
|
||||
int top_virtual_col = 0;
|
||||
PanelSelectionTarget panel_selection = PanelSelectionTarget::Keyboard;
|
||||
int pending_keyboard_row = -1;
|
||||
int pending_keyboard_col = -1;
|
||||
int last_keyboard_selected_row = 0;
|
||||
int last_keyboard_selected_col = 0;
|
||||
int edit_menu_index = 0;
|
||||
Libraries::Ime::ImeKbLayoutSelection kb_layout_selection{};
|
||||
Libraries::Ime::ImeKbLayoutSelection last_nav_layout_selection{};
|
||||
bool nav_layout_selection_initialized = false;
|
||||
Libraries::Ime::ImeKbLayoutFamily kb_alpha_family = Libraries::Ime::ImeKbLayoutFamily::Latin;
|
||||
std::mutex draw_mutex;
|
||||
|
||||
public:
|
||||
@ -79,10 +176,13 @@ public:
|
||||
void Draw() override;
|
||||
|
||||
private:
|
||||
void FinishDialog(OrbisImeDialogEndStatus endstatus, bool restore_original, const char* reason);
|
||||
void Free();
|
||||
|
||||
void DrawInputText();
|
||||
void DrawMultiLineInputText();
|
||||
bool DrawInputText(const Libraries::Ime::ImePanelMetrics& metrics,
|
||||
bool pointer_selection_enabled);
|
||||
bool DrawMultiLineInputText(const Libraries::Ime::ImePanelMetrics& metrics,
|
||||
bool pointer_selection_enabled);
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data);
|
||||
};
|
||||
|
||||
2267
src/core/libraries/ime/ime_kb_layout.cpp
Normal file
2267
src/core/libraries/ime/ime_kb_layout.cpp
Normal file
File diff suppressed because it is too large
Load Diff
561
src/core/libraries/ime/ime_kb_layout.h
Normal file
561
src/core/libraries/ime/ime_kb_layout.h
Normal file
@ -0,0 +1,561 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <imgui.h>
|
||||
|
||||
#include "core/libraries/ime/ime_common.h"
|
||||
|
||||
namespace Libraries::Ime {
|
||||
|
||||
struct ImeViewportMetrics {
|
||||
ImVec2 size{};
|
||||
ImVec2 offset{};
|
||||
float scale_x = 1.0f;
|
||||
float scale_y = 1.0f;
|
||||
float ui_scale = 1.0f;
|
||||
float base_w = 1920.0f;
|
||||
float base_h = 1080.0f;
|
||||
};
|
||||
|
||||
struct ImeKbGridLayout {
|
||||
ImVec2 pos{};
|
||||
ImVec2 size{};
|
||||
float key_gap_x = 0.0f;
|
||||
float key_gap_y = 0.0f;
|
||||
// Base row height used for non-fixed rows.
|
||||
float key_h = 0.0f;
|
||||
// Optional fixed-size bottom rows (used by accents/specials to preserve function-row height).
|
||||
float bottom_row_h = 0.0f;
|
||||
int fixed_bottom_rows = 0;
|
||||
int cols = 10;
|
||||
int rows = 6;
|
||||
float corner_radius = 0.0f;
|
||||
};
|
||||
|
||||
// Standardized OSK selection indexing:
|
||||
// - Keyboard grid uses zero-based row/column indexing.
|
||||
// - Top panel row placement is driven by ImeTopPanelLayoutConfig::row/row_span.
|
||||
// - Keyboard rows follow active ImeKbLayoutModel::rows.
|
||||
// - Function row count is driven by ImeKbLayoutModel::function_rows.
|
||||
struct ImeSelectionGridIndex {
|
||||
static constexpr int DefaultKeyboardRows = 6;
|
||||
static constexpr int DefaultKeyboardCols = 10;
|
||||
static constexpr int DefaultTopPanelRow = 0;
|
||||
static constexpr int DefaultTopPanelRows = 1;
|
||||
static constexpr int DefaultFunctionRows = 2;
|
||||
|
||||
static constexpr int PanelMinRow = 0;
|
||||
static constexpr int PanelMaxRow = 6; // Legacy default for 6-row keyboard layouts.
|
||||
static constexpr int PanelTopRow = 0;
|
||||
static constexpr int PanelKeyboardMinRow = 1;
|
||||
static constexpr int PanelKeyboardMaxRow = 6; // Legacy default for 6-row keyboard layouts.
|
||||
|
||||
static constexpr int KeyboardMinRow = 0;
|
||||
static constexpr int KeyboardMaxRow = 5; // Legacy default for 6-row keyboard layouts.
|
||||
static constexpr int KeyboardMinCol = 0;
|
||||
static constexpr int KeyboardMaxCol = 9; // Legacy default for 10-column keyboard layouts.
|
||||
|
||||
static constexpr int TopRowMinCol = 0;
|
||||
static constexpr int TopRowPredictionMaxCol = 8;
|
||||
static constexpr int TopRowCloseCol = 9;
|
||||
|
||||
static constexpr int PanelTopRowFromConfig(int top_panel_row = DefaultTopPanelRow) {
|
||||
return std::max(PanelMinRow, top_panel_row);
|
||||
}
|
||||
|
||||
static constexpr int PanelTopRowsFromConfig(int top_panel_rows = DefaultTopPanelRows) {
|
||||
return std::max(1, top_panel_rows);
|
||||
}
|
||||
|
||||
static constexpr int KeyboardMaxRowForRows(int keyboard_rows) {
|
||||
return std::max(0, keyboard_rows - 1);
|
||||
}
|
||||
|
||||
static constexpr int KeyboardMaxColForCols(int keyboard_cols) {
|
||||
return std::max(0, keyboard_cols - 1);
|
||||
}
|
||||
|
||||
static constexpr int PanelKeyboardMinRowForTopPanel(int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return PanelTopRowFromConfig(top_panel_row) + PanelTopRowsFromConfig(top_panel_rows);
|
||||
}
|
||||
|
||||
static constexpr int PanelKeyboardMaxRowForKeyboardRows(
|
||||
int keyboard_rows, int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return PanelKeyboardMinRowForTopPanel(top_panel_row, top_panel_rows) +
|
||||
KeyboardMaxRowForRows(std::max(1, keyboard_rows));
|
||||
}
|
||||
|
||||
static constexpr int PanelMaxRowForKeyboardRows(int keyboard_rows,
|
||||
int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return PanelKeyboardMaxRowForKeyboardRows(keyboard_rows, top_panel_row, top_panel_rows);
|
||||
}
|
||||
|
||||
static constexpr int ResolveFunctionRows(int keyboard_rows,
|
||||
int configured_function_rows = DefaultFunctionRows) {
|
||||
return keyboard_rows > 2
|
||||
? std::min(std::max(0, configured_function_rows), keyboard_rows - 1)
|
||||
: 0;
|
||||
}
|
||||
|
||||
static constexpr int ResolveTypingRows(int keyboard_rows,
|
||||
int configured_function_rows = DefaultFunctionRows) {
|
||||
const int rows = std::max(1, keyboard_rows);
|
||||
return std::max(1, rows - ResolveFunctionRows(rows, configured_function_rows));
|
||||
}
|
||||
|
||||
static constexpr int KeyboardFunctionMinRow(
|
||||
int keyboard_rows, int configured_function_rows = DefaultFunctionRows) {
|
||||
const int rows = std::max(1, keyboard_rows);
|
||||
const int function_rows = ResolveFunctionRows(rows, configured_function_rows);
|
||||
return function_rows > 0 ? (rows - function_rows) : rows;
|
||||
}
|
||||
|
||||
static constexpr bool IsKeyboardFunctionRow(
|
||||
int keyboard_row, int keyboard_rows, int configured_function_rows = DefaultFunctionRows) {
|
||||
const int rows = std::max(1, keyboard_rows);
|
||||
return keyboard_row >= KeyboardFunctionMinRow(rows, configured_function_rows) &&
|
||||
keyboard_row <= KeyboardMaxRowForRows(rows);
|
||||
}
|
||||
|
||||
static constexpr int ClampPanelRow(int row, int keyboard_rows = DefaultKeyboardRows,
|
||||
int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return std::clamp(row, PanelMinRow,
|
||||
PanelMaxRowForKeyboardRows(keyboard_rows, top_panel_row, top_panel_rows));
|
||||
}
|
||||
|
||||
static constexpr int ClampKeyboardRow(int row, int keyboard_rows = DefaultKeyboardRows) {
|
||||
return std::clamp(row, KeyboardMinRow, KeyboardMaxRowForRows(keyboard_rows));
|
||||
}
|
||||
|
||||
static constexpr int ClampKeyboardCol(int col, int keyboard_cols = DefaultKeyboardCols) {
|
||||
return std::clamp(col, KeyboardMinCol, KeyboardMaxColForCols(keyboard_cols));
|
||||
}
|
||||
|
||||
static constexpr int ClampTopRowCol(int col) {
|
||||
return std::clamp(col, TopRowMinCol, TopRowCloseCol);
|
||||
}
|
||||
|
||||
static constexpr int ClampTopPredictionCol(int col) {
|
||||
return std::clamp(col, TopRowMinCol, TopRowPredictionMaxCol);
|
||||
}
|
||||
|
||||
static constexpr int TopToKeyboardCol(int top_col, int keyboard_cols = DefaultKeyboardCols) {
|
||||
return ClampKeyboardCol(top_col, keyboard_cols);
|
||||
}
|
||||
|
||||
static constexpr int KeyboardToTopCol(int keyboard_col) {
|
||||
return ClampTopRowCol(keyboard_col);
|
||||
}
|
||||
|
||||
static constexpr int PanelToKeyboardRow(int panel_row, int keyboard_rows = DefaultKeyboardRows,
|
||||
int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return ClampKeyboardRow(panel_row -
|
||||
PanelKeyboardMinRowForTopPanel(top_panel_row, top_panel_rows),
|
||||
keyboard_rows);
|
||||
}
|
||||
|
||||
static constexpr int KeyboardToPanelRow(int keyboard_row,
|
||||
int keyboard_rows = DefaultKeyboardRows,
|
||||
int top_panel_row = DefaultTopPanelRow,
|
||||
int top_panel_rows = DefaultTopPanelRows) {
|
||||
return ClampPanelRow(keyboard_row +
|
||||
PanelKeyboardMinRowForTopPanel(top_panel_row, top_panel_rows),
|
||||
keyboard_rows, top_panel_row, top_panel_rows);
|
||||
}
|
||||
|
||||
static int GridColumnFromX(float x, float left, float width, int min_col, int max_col) {
|
||||
if (max_col < min_col || width <= 0.0f) {
|
||||
return min_col;
|
||||
}
|
||||
const float t = std::clamp((x - left) / width, 0.0f, 0.9999f);
|
||||
const int span = max_col - min_col + 1;
|
||||
const int offset = std::clamp(static_cast<int>(t * static_cast<float>(span)), 0, span - 1);
|
||||
return min_col + offset;
|
||||
}
|
||||
};
|
||||
|
||||
enum class ImeKbLayoutFamily : u8 {
|
||||
Latin = 0,
|
||||
Symbols = 1,
|
||||
Specials = 2,
|
||||
};
|
||||
|
||||
enum class ImeKbCaseState : u8 {
|
||||
Lower = 0,
|
||||
Upper = 1,
|
||||
CapsLock = 2,
|
||||
};
|
||||
|
||||
enum class ImeKbLayoutId : u8 {
|
||||
LatinLower = 0,
|
||||
LatinUpper = 1,
|
||||
LatinCapsLock = 2,
|
||||
SymbolsPage1 = 3,
|
||||
SymbolsPage2 = 4,
|
||||
SpecialsPage1 = 5,
|
||||
SpecialsPage2 = 6,
|
||||
};
|
||||
|
||||
struct ImeKbLayoutSelection {
|
||||
ImeKbLayoutFamily family = ImeKbLayoutFamily::Latin;
|
||||
ImeKbCaseState case_state = ImeKbCaseState::Lower;
|
||||
u8 page = 0;
|
||||
};
|
||||
|
||||
enum class ImeKbKeyAction : u8 {
|
||||
// Disabled key slot. It is not selectable and should not expose any visible label.
|
||||
None = 0,
|
||||
Character = 1,
|
||||
Shift = 2,
|
||||
SymbolsMode = 3,
|
||||
SpecialsMode = 4,
|
||||
Space = 5,
|
||||
Backspace = 6,
|
||||
ArrowLeft = 7,
|
||||
ArrowRight = 8,
|
||||
ArrowUp = 9,
|
||||
ArrowDown = 10,
|
||||
Keyboard = 11,
|
||||
Menu = 12,
|
||||
Settings = 13,
|
||||
NewLine = 14,
|
||||
Done = 15,
|
||||
PagePrev = 16,
|
||||
PageNext = 17,
|
||||
};
|
||||
|
||||
enum class ImeKbKeyGlyph : u8 {
|
||||
None = 0,
|
||||
Backspace = 1,
|
||||
ArrowLeft = 2,
|
||||
ArrowRight = 3,
|
||||
ArrowUp = 4,
|
||||
ArrowDown = 5,
|
||||
ShiftOutline = 6,
|
||||
ShiftFilled = 7,
|
||||
CapsLockFilled = 8,
|
||||
};
|
||||
|
||||
struct ImeKbKeySpec {
|
||||
u8 row = 0;
|
||||
u8 col = 0;
|
||||
u8 col_span = 1;
|
||||
u8 row_span = 1;
|
||||
// Disabled key contract:
|
||||
// - action == ImeKbKeyAction::None
|
||||
// - label == nullptr
|
||||
// - hotkey_label == nullptr
|
||||
// - glyph == ImeKbKeyGlyph::None
|
||||
// This means the key loses both label visibility and functionality.
|
||||
const char* label = nullptr;
|
||||
const char* hotkey_label = nullptr;
|
||||
ImeKbKeyAction action = ImeKbKeyAction::None;
|
||||
ImeKbKeyGlyph glyph = ImeKbKeyGlyph::None;
|
||||
};
|
||||
|
||||
struct ImeKbLayoutModel {
|
||||
const ImeKbKeySpec* keys = nullptr;
|
||||
std::size_t key_count = 0;
|
||||
u8 cols = 10;
|
||||
u8 rows = 6;
|
||||
u8 function_rows = static_cast<u8>(ImeSelectionGridIndex::DefaultFunctionRows);
|
||||
};
|
||||
|
||||
enum class ImeTopPanelElementId : u8 {
|
||||
Prediction = 0,
|
||||
Close = 1,
|
||||
};
|
||||
|
||||
struct ImeTopPanelElementSpec {
|
||||
ImeTopPanelElementId id = ImeTopPanelElementId::Prediction;
|
||||
u8 col = 0;
|
||||
u8 col_span = 1;
|
||||
};
|
||||
|
||||
struct ImeTopPanelLayoutConfig {
|
||||
const ImeTopPanelElementSpec* elements = nullptr;
|
||||
std::size_t element_count = 0;
|
||||
u8 cols = 10;
|
||||
u8 row = static_cast<u8>(ImeSelectionGridIndex::DefaultTopPanelRow);
|
||||
u8 row_span = static_cast<u8>(ImeSelectionGridIndex::DefaultTopPanelRows);
|
||||
};
|
||||
|
||||
// Mirrors OrbisImeParamExtended color buckets so UI styling can be themed
|
||||
// through one config and optionally overridden by game-provided SET_COLOR.
|
||||
struct ImeStyleConfig {
|
||||
OrbisImeColor color_base{18, 18, 18, 255};
|
||||
OrbisImeColor color_line{70, 70, 70, 255};
|
||||
OrbisImeColor color_text_field{22, 37, 60, 255};
|
||||
OrbisImeColor color_preedit{35, 35, 35, 255};
|
||||
OrbisImeColor color_button_default{35, 35, 35, 255};
|
||||
OrbisImeColor color_button_function{60, 60, 60, 255};
|
||||
OrbisImeColor color_button_symbol{78, 78, 78, 255};
|
||||
OrbisImeColor color_text{230, 230, 230, 255};
|
||||
OrbisImeColor color_special{30, 90, 170, 255};
|
||||
};
|
||||
|
||||
// Shared edge-wrap hold state for controller navigation.
|
||||
// It tracks last successful move direction/time and active wrap hold window.
|
||||
struct ImeEdgeWrapNavState {
|
||||
int last_step_row = 0;
|
||||
int last_step_col = 0;
|
||||
double last_step_time = -1.0;
|
||||
int hold_step_row = 0;
|
||||
int hold_step_col = 0;
|
||||
double hold_release_time = 0.0;
|
||||
bool hold_active = false;
|
||||
};
|
||||
|
||||
inline void ResetImeEdgeWrapHold(ImeEdgeWrapNavState& state) {
|
||||
state.hold_step_row = 0;
|
||||
state.hold_step_col = 0;
|
||||
state.hold_release_time = 0.0;
|
||||
state.hold_active = false;
|
||||
}
|
||||
|
||||
inline void ResetImeEdgeWrapNav(ImeEdgeWrapNavState& state) {
|
||||
state.last_step_row = 0;
|
||||
state.last_step_col = 0;
|
||||
state.last_step_time = -1.0;
|
||||
ResetImeEdgeWrapHold(state);
|
||||
}
|
||||
|
||||
inline bool ShouldDelayImeEdgeWrap(ImeEdgeWrapNavState& state, int step_row, int step_col,
|
||||
bool repeat_hint, bool wraps, double now, double hold_delay_sec,
|
||||
double repeat_window_sec) {
|
||||
(void)repeat_window_sec;
|
||||
if (step_row == 0 && step_col == 0) {
|
||||
return false;
|
||||
}
|
||||
if (state.hold_active && (state.hold_step_row != step_row || state.hold_step_col != step_col)) {
|
||||
ResetImeEdgeWrapHold(state);
|
||||
}
|
||||
|
||||
if (!wraps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool same_wrap_direction =
|
||||
state.hold_active && state.hold_step_row == step_row && state.hold_step_col == step_col;
|
||||
if (!(repeat_hint || same_wrap_direction)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!same_wrap_direction) {
|
||||
state.hold_step_row = step_row;
|
||||
state.hold_step_col = step_col;
|
||||
state.hold_release_time = now + hold_delay_sec;
|
||||
state.hold_active = true;
|
||||
}
|
||||
return now < state.hold_release_time;
|
||||
}
|
||||
|
||||
inline void CommitImeEdgeWrapStep(ImeEdgeWrapNavState& state, int step_row, int step_col,
|
||||
double now) {
|
||||
state.last_step_row = step_row;
|
||||
state.last_step_col = step_col;
|
||||
state.last_step_time = now;
|
||||
ResetImeEdgeWrapHold(state);
|
||||
}
|
||||
|
||||
inline const ImeKbKeySpec* ResolveImeKeyboardKeyAt(const ImeKbLayoutModel& layout, int row,
|
||||
int col) {
|
||||
const int grid_cols = std::max(1, static_cast<int>(layout.cols));
|
||||
const int grid_rows = std::max(1, static_cast<int>(layout.rows));
|
||||
if (row < 0 || row >= grid_rows || col < 0 || col >= grid_cols) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!layout.keys || layout.key_count == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ImeKbKeySpec* resolved = nullptr;
|
||||
for (std::size_t i = 0; i < layout.key_count; ++i) {
|
||||
const auto& key = layout.keys[i];
|
||||
if (key.col_span == 0 || key.row_span == 0) {
|
||||
continue;
|
||||
}
|
||||
if (key.row >= grid_rows || key.col >= grid_cols) {
|
||||
continue;
|
||||
}
|
||||
const int row_start = static_cast<int>(key.row);
|
||||
const int col_start = static_cast<int>(key.col);
|
||||
const int row_span = std::max(1, static_cast<int>(key.row_span));
|
||||
const int col_span = std::max(1, static_cast<int>(key.col_span));
|
||||
const int row_end = std::min(grid_rows, row_start + row_span);
|
||||
const int col_end = std::min(grid_cols, col_start + col_span);
|
||||
if (row >= row_start && row < row_end && col >= col_start && col < col_end) {
|
||||
// Match DrawImeKeyboardGrid occupancy behavior: later keys override earlier ones.
|
||||
resolved = &key;
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
inline bool DoesImeKeyboardStepCrossGridEdge(int from_row, int from_col, int step_row, int step_col,
|
||||
int grid_rows, int grid_cols) {
|
||||
if (step_row == 0 && step_col == 0) {
|
||||
return false;
|
||||
}
|
||||
if (from_row < 0 || from_row >= grid_rows || from_col < 0 || from_col >= grid_cols) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int next_row = from_row + step_row;
|
||||
const int next_col = from_col + step_col;
|
||||
return next_row < 0 || next_row >= grid_rows || next_col < 0 || next_col >= grid_cols;
|
||||
}
|
||||
|
||||
inline bool DoesImeKeyboardNavigationWrap(const ImeKbLayoutModel& layout, int from_row,
|
||||
int from_col, int step_row, int step_col) {
|
||||
if (step_row == 0 && step_col == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int grid_cols = std::max(1, static_cast<int>(layout.cols));
|
||||
const int grid_rows = std::max(1, static_cast<int>(layout.rows));
|
||||
if (from_row < 0 || from_row >= grid_rows || from_col < 0 || from_col >= grid_cols) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto* origin_key = ResolveImeKeyboardKeyAt(layout, from_row, from_col);
|
||||
if (origin_key && origin_key->action == ImeKbKeyAction::None) {
|
||||
origin_key = nullptr;
|
||||
}
|
||||
|
||||
int row = from_row;
|
||||
int col = from_col;
|
||||
bool crossed_wrap = false;
|
||||
const int max_steps = std::max(1, grid_rows * grid_cols);
|
||||
for (int i = 0; i < max_steps; ++i) {
|
||||
crossed_wrap = crossed_wrap || DoesImeKeyboardStepCrossGridEdge(
|
||||
row, col, step_row, step_col, grid_rows, grid_cols);
|
||||
|
||||
const int next_row = row + step_row;
|
||||
const int next_col = col + step_col;
|
||||
row = (next_row + grid_rows) % grid_rows;
|
||||
col = (next_col + grid_cols) % grid_cols;
|
||||
|
||||
const auto* candidate_key = ResolveImeKeyboardKeyAt(layout, row, col);
|
||||
if (!candidate_key || candidate_key->action == ImeKbKeyAction::None) {
|
||||
continue;
|
||||
}
|
||||
if (origin_key && candidate_key == origin_key) {
|
||||
continue;
|
||||
}
|
||||
return crossed_wrap;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ImeKbDrawParams {
|
||||
ImeKbLayoutSelection selection{};
|
||||
const ImeKbLayoutModel* layout_model = nullptr;
|
||||
OrbisImeLanguage supported_languages = static_cast<OrbisImeLanguage>(0);
|
||||
OrbisImeEnterLabel enter_label = OrbisImeEnterLabel::Default;
|
||||
bool show_selection_highlight = true;
|
||||
bool allow_nav_input = true;
|
||||
bool use_imgui_lstick_nav = true;
|
||||
bool allow_activate_input = true;
|
||||
bool external_nav_left = false;
|
||||
bool external_nav_right = false;
|
||||
bool external_nav_up = false;
|
||||
bool external_nav_down = false;
|
||||
bool external_nav_left_repeat = false;
|
||||
bool external_nav_right_repeat = false;
|
||||
bool external_nav_up_repeat = false;
|
||||
bool external_nav_down_repeat = false;
|
||||
bool external_activate_pressed = false;
|
||||
bool external_activate_repeat = false;
|
||||
bool reset_nav_state = false;
|
||||
int requested_selected_row = -1;
|
||||
int requested_selected_col = -1;
|
||||
ImU32 key_bg_default = IM_COL32(35, 35, 35, 255);
|
||||
ImU32 key_bg_function = IM_COL32(60, 60, 60, 255);
|
||||
ImU32 key_bg_symbol = IM_COL32(78, 78, 78, 255);
|
||||
ImU32 key_border = IM_COL32(80, 80, 80, 255);
|
||||
ImU32 key_done = IM_COL32(30, 90, 170, 255);
|
||||
ImU32 key_text = IM_COL32(230, 230, 230, 255);
|
||||
ImU32 key_hotkey_text = IM_COL32(220, 220, 220, 255);
|
||||
};
|
||||
|
||||
struct ImeKbDrawState {
|
||||
bool done_pressed = false;
|
||||
ImeKbKeyAction pressed_action = ImeKbKeyAction::None;
|
||||
const char* pressed_label = nullptr;
|
||||
u16 pressed_keycode = 0;
|
||||
char16_t pressed_character = u'\0';
|
||||
// Logical grid cursor. If the cursor cell is disabled, selected_center points to the
|
||||
// visible nearest enabled fallback key used for drawing and activation.
|
||||
int selected_row = -1;
|
||||
int selected_col = -1;
|
||||
ImVec2 selected_center{};
|
||||
bool hovered = false;
|
||||
bool clicked = false;
|
||||
};
|
||||
|
||||
struct ImePanelMetricsConfig {
|
||||
float panel_w = 0.0f;
|
||||
float panel_h = 0.0f;
|
||||
bool multiline = false;
|
||||
bool show_title = true;
|
||||
float base_font_size = 0.0f;
|
||||
ImVec2 window_pos{};
|
||||
};
|
||||
|
||||
struct ImePanelMetrics {
|
||||
float panel_w = 0.0f;
|
||||
float panel_h = 0.0f;
|
||||
float padding_x = 0.0f;
|
||||
float padding_bottom = 0.0f;
|
||||
float label_h = 0.0f;
|
||||
float input_h = 0.0f;
|
||||
float predict_h = 0.0f;
|
||||
float close_w = 0.0f;
|
||||
float keys_h = 0.0f;
|
||||
float key_gap = 0.0f;
|
||||
float key_h = 0.0f;
|
||||
float corner_radius = 0.0f;
|
||||
float label_font_scale = 1.0f;
|
||||
float input_font_scale = 1.0f;
|
||||
float key_font_scale = 1.0f;
|
||||
float predict_gap = 0.0f;
|
||||
ImVec2 input_pos_local{};
|
||||
ImVec2 input_size{};
|
||||
ImVec2 input_pos_screen{};
|
||||
ImVec2 predict_pos{};
|
||||
ImVec2 predict_size{};
|
||||
ImVec2 close_pos{};
|
||||
ImVec2 close_size{};
|
||||
ImVec2 kb_pos{};
|
||||
ImVec2 kb_size{};
|
||||
};
|
||||
|
||||
ImeViewportMetrics ComputeImeViewportMetrics(bool use_over2k);
|
||||
ImePanelMetrics ComputeImePanelMetrics(const ImePanelMetricsConfig& config);
|
||||
ImeKbLayoutId ResolveImeKeyboardLayoutId(const ImeKbLayoutSelection& selection);
|
||||
const ImeKbLayoutModel& GetImeKeyboardLayout(ImeKbLayoutId id);
|
||||
const ImeKbLayoutModel& GetImeKeyboardLayout(const ImeKbLayoutSelection& selection);
|
||||
const ImeTopPanelLayoutConfig& GetImeTopPanelLayoutConfig();
|
||||
const ImeTopPanelLayoutConfig& GetImeTopPanelLayoutConfig(ImeKbLayoutId id);
|
||||
const ImeTopPanelLayoutConfig& GetImeTopPanelLayoutConfig(const ImeKbLayoutSelection& selection);
|
||||
ImeStyleConfig GetDefaultImeStyleConfig();
|
||||
ImeStyleConfig ResolveImeStyleConfig(const OrbisImeParamExtended* extended);
|
||||
ImU32 ImeColorToImU32(const OrbisImeColor& color);
|
||||
ImVec4 ImeColorToImVec4(const OrbisImeColor& color);
|
||||
void ApplyImeStyleToKeyboardDrawParams(const ImeStyleConfig& style, ImeKbDrawParams& params);
|
||||
void AddImeKeyboardGlyphsToFontRanges(ImFontGlyphRangesBuilder& builder);
|
||||
|
||||
void DrawImeKeyboardGrid(const ImeKbGridLayout& layout, const ImeKbDrawParams& params,
|
||||
ImeKbDrawState& state);
|
||||
|
||||
} // namespace Libraries::Ime
|
||||
File diff suppressed because it is too large
Load Diff
@ -11,12 +11,14 @@
|
||||
#include "common/cstring.h"
|
||||
#include "common/types.h"
|
||||
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
#include "ime.h"
|
||||
|
||||
namespace Libraries::Ime {
|
||||
|
||||
class ImeHandler;
|
||||
class ImeUi;
|
||||
struct ImePanelMetrics;
|
||||
|
||||
class ImeState {
|
||||
friend class ImeHandler;
|
||||
@ -28,6 +30,9 @@ class ImeState {
|
||||
|
||||
// A character can hold up to 4 bytes in UTF-8
|
||||
Common::CString<ORBIS_IME_MAX_TEXT_LENGTH * 4 + 1> current_text;
|
||||
int caret_index = 0;
|
||||
int caret_byte_index = 0;
|
||||
bool caret_dirty = false;
|
||||
|
||||
std::queue<OrbisImeEvent> event_queue;
|
||||
std::mutex queue_mutex;
|
||||
@ -52,11 +57,81 @@ private:
|
||||
};
|
||||
|
||||
class ImeUi : public ImGui::Layer {
|
||||
enum class PanelSelectionTarget : u8 {
|
||||
Input = 0,
|
||||
Prediction = 1,
|
||||
Close = 2,
|
||||
Keyboard = 3,
|
||||
};
|
||||
|
||||
enum class EditMenuPopup : u8 {
|
||||
None = 0,
|
||||
Main = 1,
|
||||
Actions = 2,
|
||||
};
|
||||
|
||||
ImeState* state{};
|
||||
const OrbisImeParam* ime_param{};
|
||||
const OrbisImeParamExtended* extended_param{};
|
||||
ImeStyleConfig style_config{};
|
||||
|
||||
bool first_render = true;
|
||||
bool accept_armed = false;
|
||||
bool native_input_active = false;
|
||||
bool pointer_navigation_active = true;
|
||||
EditMenuPopup edit_menu_popup = EditMenuPopup::None;
|
||||
bool menu_activate_armed = true;
|
||||
bool l2_shortcut_armed = true;
|
||||
bool request_input_focus = false;
|
||||
bool request_input_select_all = false;
|
||||
bool text_select_mode = false;
|
||||
bool pending_input_selection_apply = false;
|
||||
bool prev_virtual_cross_down = false;
|
||||
bool prev_virtual_lstick_left_down = false;
|
||||
bool prev_virtual_lstick_right_down = false;
|
||||
bool prev_virtual_lstick_up_down = false;
|
||||
bool prev_virtual_lstick_down_down = false;
|
||||
int left_stick_repeat_dir = 0;
|
||||
double left_stick_next_repeat_time = 0.0;
|
||||
double virtual_cross_next_repeat_time = 0.0;
|
||||
double virtual_triangle_next_repeat_time = 0.0;
|
||||
u32 prev_virtual_buttons = 0;
|
||||
bool prev_virtual_square_down = false;
|
||||
bool prev_virtual_l1_down = false;
|
||||
bool prev_virtual_r1_down = false;
|
||||
bool prev_virtual_dpad_left_down = false;
|
||||
bool prev_virtual_dpad_right_down = false;
|
||||
bool prev_virtual_dpad_up_down = false;
|
||||
bool prev_virtual_dpad_down_down = false;
|
||||
double virtual_square_next_repeat_time = 0.0;
|
||||
double virtual_l1_next_repeat_time = 0.0;
|
||||
double virtual_r1_next_repeat_time = 0.0;
|
||||
double virtual_dpad_left_next_repeat_time = 0.0;
|
||||
double virtual_dpad_right_next_repeat_time = 0.0;
|
||||
double virtual_dpad_up_next_repeat_time = 0.0;
|
||||
double virtual_dpad_down_next_repeat_time = 0.0;
|
||||
ImeEdgeWrapNavState panel_vertical_nav_state{};
|
||||
bool panel_position_initialized = false;
|
||||
bool panel_drag_active = false;
|
||||
bool gamepad_input_capture_active = false;
|
||||
ImVec2 panel_position{};
|
||||
int input_cursor_utf16 = 0;
|
||||
int input_cursor_byte = 0;
|
||||
int input_selection_start_byte = 0;
|
||||
int input_selection_end_byte = 0;
|
||||
int text_select_anchor_utf16 = -1;
|
||||
int text_select_focus_utf16 = -1;
|
||||
int top_virtual_col = 0;
|
||||
PanelSelectionTarget panel_selection = PanelSelectionTarget::Keyboard;
|
||||
int pending_keyboard_row = -1;
|
||||
int pending_keyboard_col = -1;
|
||||
int last_keyboard_selected_row = 0;
|
||||
int last_keyboard_selected_col = 0;
|
||||
int edit_menu_index = 0;
|
||||
ImeKbLayoutSelection kb_layout_selection{};
|
||||
ImeKbLayoutSelection last_nav_layout_selection{};
|
||||
bool nav_layout_selection_initialized = false;
|
||||
ImeKbLayoutFamily kb_alpha_family = ImeKbLayoutFamily::Latin;
|
||||
std::mutex draw_mutex;
|
||||
|
||||
public:
|
||||
@ -71,9 +146,9 @@ public:
|
||||
private:
|
||||
void Free();
|
||||
|
||||
void DrawInputText();
|
||||
bool DrawInputText(const ImePanelMetrics& metrics, bool pointer_selection_enabled);
|
||||
|
||||
static int InputTextCallback(ImGuiInputTextCallbackData* data);
|
||||
};
|
||||
|
||||
}; // namespace Libraries::Ime
|
||||
}; // namespace Libraries::Ime
|
||||
|
||||
1098
src/core/libraries/ime/ime_ui_shared.cpp
Normal file
1098
src/core/libraries/ime/ime_ui_shared.cpp
Normal file
File diff suppressed because it is too large
Load Diff
358
src/core/libraries/ime/ime_ui_shared.h
Normal file
358
src/core/libraries/ime/ime_ui_shared.h
Normal file
@ -0,0 +1,358 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ime/ime_kb_layout.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Libraries::Ime {
|
||||
|
||||
struct VirtualPadSnapshot {
|
||||
u32 buttons = 0;
|
||||
ImVec2 left_stick{};
|
||||
ImVec2 panel_delta{};
|
||||
float l2_analog = 0.0f;
|
||||
};
|
||||
|
||||
constexpr double kPanelEdgeWrapHoldDelaySec = 0.5;
|
||||
constexpr double kRepeatIntentWindowSec = 0.45;
|
||||
constexpr float kSelectorFadeOutDurationSec = 0.2f;
|
||||
constexpr float kSelectorPressPulseDurationSec = 0.2f;
|
||||
constexpr float kSelectorPressPulseExpandBorderFactor = 1.0f;
|
||||
|
||||
ImVec4 BrightenColor(ImU32 color, float delta);
|
||||
int Utf16CountFromUtf8Range(const char* text, const char* end = nullptr);
|
||||
int Utf8ByteIndexFromUtf16Index(const char* text, int utf16_index);
|
||||
bool RejectInputCharByUtf16Limit(const ImGuiInputTextCallbackData* data, int max_utf16);
|
||||
bool ClampInputBufferToUtf16Limit(ImGuiInputTextCallbackData* data, int max_utf16);
|
||||
void DrawInactiveCaretOverlay(const ImRect& frame_rect, const char* text, int caret_byte,
|
||||
int selection_start_byte, int selection_end_byte,
|
||||
bool multiline = false);
|
||||
|
||||
struct SelectorFadeState {
|
||||
ImVec2 current_min{};
|
||||
ImVec2 current_max{};
|
||||
float current_corner_radius = 0.0f;
|
||||
bool current_visible = false;
|
||||
|
||||
ImVec2 previous_min{};
|
||||
ImVec2 previous_max{};
|
||||
float previous_corner_radius = 0.0f;
|
||||
bool previous_visible = false;
|
||||
double previous_started_at = 0.0;
|
||||
double press_pulse_started_at = -1.0;
|
||||
};
|
||||
|
||||
void TriggerSelectorPressPulse(SelectorFadeState& state, double now);
|
||||
float ComputePressPulseExpand(double pulse_started_at, double now, float pulse_duration_sec,
|
||||
float max_expand_px);
|
||||
void UpdateSelectorFadeState(SelectorFadeState& state, ImVec2 pos, ImVec2 size, float inset,
|
||||
float corner_radius, bool selected, double now);
|
||||
void DrawSelectorFadeState(const SelectorFadeState& state, ImDrawList* draw_list,
|
||||
ImU32 overlay_color, ImU32 border_color, float border_thickness,
|
||||
float fade_duration_sec, double now, float current_expand_px = 0.0f);
|
||||
|
||||
ImeKbLayoutSelection ResolveInitialKbLayoutSelection(OrbisImeExtOption ext_option,
|
||||
OrbisImePanelPriority panel_priority);
|
||||
void InitializeDefaultOskSelectionAnchor(const ImeKbLayoutSelection& layout_selection,
|
||||
OrbisImeExtOption ext_option, int& pending_row,
|
||||
int& pending_col, int& last_row, int& last_col);
|
||||
VirtualPadSnapshot ReadVirtualPadSnapshot(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
float delta_time, bool include_imgui_fallback = true);
|
||||
|
||||
struct OskPadInputState {
|
||||
u32& prev_virtual_buttons;
|
||||
bool& prev_virtual_cross_down;
|
||||
bool& prev_virtual_lstick_left_down;
|
||||
bool& prev_virtual_lstick_right_down;
|
||||
bool& prev_virtual_lstick_up_down;
|
||||
bool& prev_virtual_lstick_down_down;
|
||||
int& left_stick_repeat_dir;
|
||||
double& left_stick_next_repeat_time;
|
||||
double& virtual_cross_next_repeat_time;
|
||||
bool& prev_virtual_dpad_left_down;
|
||||
bool& prev_virtual_dpad_right_down;
|
||||
bool& prev_virtual_dpad_up_down;
|
||||
bool& prev_virtual_dpad_down_down;
|
||||
double& virtual_dpad_left_next_repeat_time;
|
||||
double& virtual_dpad_right_next_repeat_time;
|
||||
double& virtual_dpad_up_next_repeat_time;
|
||||
double& virtual_dpad_down_next_repeat_time;
|
||||
};
|
||||
|
||||
struct OskPadInputFrame {
|
||||
u32 virtual_buttons = 0;
|
||||
u32 prev_virtual_buttons = 0;
|
||||
struct {
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool up = false;
|
||||
bool down = false;
|
||||
} virtual_lstick_dirs{};
|
||||
bool cross_down = false;
|
||||
float l2_analog = 0.0f;
|
||||
bool panel_activate_pressed_raw = false;
|
||||
bool panel_activate_repeat_raw = false;
|
||||
bool virtual_nav_left = false;
|
||||
bool virtual_nav_right = false;
|
||||
bool virtual_nav_up = false;
|
||||
bool virtual_nav_down = false;
|
||||
bool virtual_nav_left_repeat = false;
|
||||
bool virtual_nav_right_repeat = false;
|
||||
bool virtual_nav_up_repeat = false;
|
||||
bool virtual_nav_down_repeat = false;
|
||||
bool stick_nav_left = false;
|
||||
bool stick_nav_right = false;
|
||||
bool stick_nav_up = false;
|
||||
bool stick_nav_down = false;
|
||||
bool stick_nav_left_repeat = false;
|
||||
bool stick_nav_right_repeat = false;
|
||||
bool stick_nav_up_repeat = false;
|
||||
bool stick_nav_down_repeat = false;
|
||||
bool nav_left = false;
|
||||
bool nav_right = false;
|
||||
bool nav_up = false;
|
||||
bool nav_down = false;
|
||||
bool nav_left_repeat = false;
|
||||
bool nav_right_repeat = false;
|
||||
bool nav_up_repeat = false;
|
||||
bool nav_down_repeat = false;
|
||||
bool raw_osk_control_input = false;
|
||||
bool virtual_control_input = false;
|
||||
bool osk_control_input = false;
|
||||
};
|
||||
|
||||
struct OskVirtualPadInputView {
|
||||
const OskPadInputFrame& frame;
|
||||
double repeat_delay = 0.0;
|
||||
double repeat_rate = 0.0;
|
||||
|
||||
explicit OskVirtualPadInputView(const OskPadInputFrame& input_frame, const ImGuiIO& io);
|
||||
bool Down(Libraries::Pad::OrbisPadButtonDataOffset button) const;
|
||||
bool Pressed(Libraries::Pad::OrbisPadButtonDataOffset button) const;
|
||||
bool RepeatPressed(Libraries::Pad::OrbisPadButtonDataOffset button, bool& prev_down_state,
|
||||
double& next_repeat_time, bool* out_repeat = nullptr) const;
|
||||
};
|
||||
|
||||
struct OskShortcutRepeatState {
|
||||
bool& prev_square_down;
|
||||
bool& prev_l1_down;
|
||||
bool& prev_r1_down;
|
||||
bool& l2_shortcut_armed;
|
||||
double& square_next_repeat_time;
|
||||
double& l1_next_repeat_time;
|
||||
double& r1_next_repeat_time;
|
||||
double& triangle_next_repeat_time;
|
||||
};
|
||||
|
||||
struct OskShortcutActionResult {
|
||||
ImeKbKeyAction action = ImeKbKeyAction::None;
|
||||
bool clear_all = false;
|
||||
};
|
||||
|
||||
OskShortcutActionResult EvaluateOskShortcutAction(bool allow_osk_shortcuts, bool menu_modal,
|
||||
bool evaluate_action,
|
||||
const OskPadInputFrame& panel_input,
|
||||
const OskVirtualPadInputView& virtual_pad_input,
|
||||
u32 prev_virtual_buttons,
|
||||
ImeKbLayoutFamily layout_family,
|
||||
OskShortcutRepeatState& repeat_state);
|
||||
|
||||
void CycleKeyboardCaseState(ImeKbLayoutSelection& selection);
|
||||
void ToggleKeyboardFamilyMode(ImeKbLayoutSelection& selection, ImeKbLayoutFamily& alpha_family,
|
||||
ImeKbLayoutFamily target_family);
|
||||
bool FocusKeyboardActionKeySelection(const ImeKbLayoutSelection& selection, ImeKbKeyAction action,
|
||||
int& out_row, int& out_col);
|
||||
void FlipKeyboardModePage(ImeKbLayoutSelection& selection, int direction);
|
||||
|
||||
template <typename KeyboardDrawParams>
|
||||
inline void ApplyOskPanelNavToKeyboardParams(KeyboardDrawParams& kb_params,
|
||||
const bool allow_osk_shortcuts,
|
||||
const OskPadInputFrame& panel_input) {
|
||||
kb_params.external_nav_left =
|
||||
allow_osk_shortcuts && (panel_input.virtual_nav_left || panel_input.stick_nav_left);
|
||||
kb_params.external_nav_right =
|
||||
allow_osk_shortcuts && (panel_input.virtual_nav_right || panel_input.stick_nav_right);
|
||||
kb_params.external_nav_up =
|
||||
allow_osk_shortcuts && (panel_input.virtual_nav_up || panel_input.stick_nav_up);
|
||||
kb_params.external_nav_down =
|
||||
allow_osk_shortcuts && (panel_input.virtual_nav_down || panel_input.stick_nav_down);
|
||||
kb_params.external_nav_left_repeat =
|
||||
allow_osk_shortcuts &&
|
||||
((panel_input.virtual_nav_left && panel_input.virtual_nav_left_repeat) ||
|
||||
(panel_input.stick_nav_left && panel_input.stick_nav_left_repeat));
|
||||
kb_params.external_nav_right_repeat =
|
||||
allow_osk_shortcuts &&
|
||||
((panel_input.virtual_nav_right && panel_input.virtual_nav_right_repeat) ||
|
||||
(panel_input.stick_nav_right && panel_input.stick_nav_right_repeat));
|
||||
kb_params.external_nav_up_repeat =
|
||||
allow_osk_shortcuts && ((panel_input.virtual_nav_up && panel_input.virtual_nav_up_repeat) ||
|
||||
(panel_input.stick_nav_up && panel_input.stick_nav_up_repeat));
|
||||
kb_params.external_nav_down_repeat =
|
||||
allow_osk_shortcuts &&
|
||||
((panel_input.virtual_nav_down && panel_input.virtual_nav_down_repeat) ||
|
||||
(panel_input.stick_nav_down && panel_input.stick_nav_down_repeat));
|
||||
}
|
||||
|
||||
OskPadInputFrame ComputeOskPadInputFrame(const VirtualPadSnapshot& virtual_pad,
|
||||
bool allow_osk_shortcuts, bool first_render,
|
||||
OskPadInputState& state);
|
||||
void CommitOskPadInputFrame(const OskPadInputFrame& frame, OskPadInputState& state);
|
||||
|
||||
void DisarmMenuActivate(bool& menu_activate_armed);
|
||||
void RearmMenuActivateOnRelease(bool activate_down, bool& menu_activate_armed);
|
||||
bool ConsumeMenuActivatePress(bool panel_activate_pressed, bool opened_menu_this_frame,
|
||||
bool& menu_activate_armed);
|
||||
|
||||
template <typename EditMenuPopupT>
|
||||
inline void OpenOskMainEditMenu(EditMenuPopupT& popup, int& edit_menu_index,
|
||||
bool& menu_activate_armed) {
|
||||
popup = EditMenuPopupT::Main;
|
||||
edit_menu_index = 0;
|
||||
DisarmMenuActivate(menu_activate_armed);
|
||||
}
|
||||
|
||||
template <typename EditMenuPopupT>
|
||||
inline void OpenOskActionsEditMenu(EditMenuPopupT& popup, int& edit_menu_index,
|
||||
bool& menu_activate_armed) {
|
||||
popup = EditMenuPopupT::Actions;
|
||||
edit_menu_index = 0;
|
||||
DisarmMenuActivate(menu_activate_armed);
|
||||
}
|
||||
|
||||
template <typename EditMenuPopupT>
|
||||
inline bool CloseOskEditMenuOnCancel(EditMenuPopupT& popup, bool& cancel_pressed,
|
||||
bool& menu_activate_armed) {
|
||||
if (popup == EditMenuPopupT::None || !cancel_pressed) {
|
||||
return false;
|
||||
}
|
||||
cancel_pressed = false;
|
||||
popup = EditMenuPopupT::None;
|
||||
menu_activate_armed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename EditMenuPopupT, typename PanelMetricsT, typename ApplyActionFn>
|
||||
inline bool DrawAndHandleOskEditMenuPopup(
|
||||
EditMenuPopupT& popup, int& edit_menu_index, const PanelMetricsT& metrics, ImDrawList* draw,
|
||||
bool pointer_navigation_active, bool nav_up, bool nav_down, bool cross_down,
|
||||
bool panel_activate_pressed, bool opened_menu_this_frame, bool& menu_activate_armed,
|
||||
bool clipboard_ready, int id_base, const char* item_button_id, bool close_on_outside_click,
|
||||
ApplyActionFn&& apply_action) {
|
||||
if (popup == EditMenuPopupT::None || draw == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr std::array<const char*, 3> kMainMenuItems = {"Select", "Select All", "Paste"};
|
||||
constexpr std::array<const char*, 2> kActionMenuItems = {"Copy", "Paste"};
|
||||
const bool is_main_menu = (popup == EditMenuPopupT::Main);
|
||||
const int item_count = is_main_menu ? static_cast<int>(kMainMenuItems.size())
|
||||
: static_cast<int>(kActionMenuItems.size());
|
||||
const auto item_label = [&](int index) -> const char* {
|
||||
return is_main_menu ? kMainMenuItems[static_cast<std::size_t>(index)]
|
||||
: kActionMenuItems[static_cast<std::size_t>(index)];
|
||||
};
|
||||
const auto item_enabled = [&](int index) {
|
||||
if ((is_main_menu && index == 2) || (!is_main_menu && index == 1)) {
|
||||
return clipboard_ready;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!pointer_navigation_active) {
|
||||
if (nav_up) {
|
||||
edit_menu_index = (edit_menu_index + item_count - 1) % item_count;
|
||||
} else if (nav_down) {
|
||||
edit_menu_index = (edit_menu_index + 1) % item_count;
|
||||
}
|
||||
}
|
||||
edit_menu_index = std::clamp(edit_menu_index, 0, item_count - 1);
|
||||
|
||||
const float menu_w = std::min(metrics.kb_size.x * 0.42f, 280.0f);
|
||||
const float menu_inner_pad = std::max(6.0f, metrics.key_gap * 0.7f);
|
||||
const float item_gap = std::max(3.0f, metrics.key_gap * 0.35f);
|
||||
const float item_h = std::max(28.0f, metrics.key_h * 0.70f);
|
||||
const float menu_h = menu_inner_pad * 2.0f + item_h * static_cast<float>(item_count) +
|
||||
item_gap * static_cast<float>(item_count - 1);
|
||||
const ImVec2 menu_pos{
|
||||
metrics.kb_pos.x + (metrics.kb_size.x - menu_w) * 0.5f,
|
||||
metrics.kb_pos.y + (metrics.kb_size.y - menu_h) * 0.5f,
|
||||
};
|
||||
const ImVec2 menu_max{menu_pos.x + menu_w, menu_pos.y + menu_h};
|
||||
draw->AddRectFilled(menu_pos, menu_max, IM_COL32(16, 16, 16, 245), metrics.corner_radius);
|
||||
draw->AddRect(menu_pos, menu_max, IM_COL32(100, 100, 100, 255), metrics.corner_radius);
|
||||
|
||||
RearmMenuActivateOnRelease(cross_down, menu_activate_armed);
|
||||
const bool menu_activate = ConsumeMenuActivatePress(
|
||||
panel_activate_pressed, opened_menu_this_frame, menu_activate_armed);
|
||||
bool click_activate = false;
|
||||
for (int i = 0; i < item_count; ++i) {
|
||||
const float item_y = menu_pos.y + menu_inner_pad + i * (item_h + item_gap);
|
||||
const ImVec2 item_pos{menu_pos.x + menu_inner_pad, item_y};
|
||||
const ImVec2 item_size{menu_w - menu_inner_pad * 2.0f, item_h};
|
||||
const bool selected = (i == edit_menu_index);
|
||||
const bool enabled = item_enabled(i);
|
||||
const ImU32 item_bg = !enabled ? IM_COL32(30, 30, 30, 255)
|
||||
: selected ? IM_COL32(60, 96, 146, 255)
|
||||
: IM_COL32(45, 45, 45, 255);
|
||||
draw->AddRectFilled(item_pos, {item_pos.x + item_size.x, item_pos.y + item_size.y}, item_bg,
|
||||
metrics.corner_radius * 0.6f);
|
||||
draw->AddRect(item_pos, {item_pos.x + item_size.x, item_pos.y + item_size.y},
|
||||
IM_COL32(90, 90, 90, 255), metrics.corner_radius * 0.6f);
|
||||
|
||||
ImGui::PushID(id_base + i);
|
||||
ImGui::SetCursorScreenPos(item_pos);
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true);
|
||||
ImGui::InvisibleButton(item_button_id, item_size);
|
||||
ImGui::PopItemFlag();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
edit_menu_index = i;
|
||||
}
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left) && enabled) {
|
||||
edit_menu_index = i;
|
||||
click_activate = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
const char* label = item_label(i);
|
||||
const ImVec2 text_size = ImGui::CalcTextSize(label);
|
||||
const ImVec2 text_pos{
|
||||
item_pos.x + (item_size.x - text_size.x) * 0.5f,
|
||||
item_pos.y + (item_size.y - text_size.y) * 0.5f,
|
||||
};
|
||||
const ImU32 text_col =
|
||||
enabled ? IM_COL32(232, 232, 232, 255) : IM_COL32(128, 128, 128, 255);
|
||||
draw->AddText(text_pos, text_col, label);
|
||||
}
|
||||
|
||||
const bool outside_click_close = close_on_outside_click && pointer_navigation_active &&
|
||||
!opened_menu_this_frame &&
|
||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left, false) &&
|
||||
!ImGui::IsMouseHoveringRect(menu_pos, menu_max, false);
|
||||
if (outside_click_close) {
|
||||
popup = EditMenuPopupT::None;
|
||||
menu_activate_armed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((menu_activate || click_activate) && item_enabled(edit_menu_index)) {
|
||||
const EditMenuPopupT previous_popup = popup;
|
||||
apply_action(previous_popup, edit_menu_index);
|
||||
if (popup != EditMenuPopupT::None && popup != previous_popup) {
|
||||
edit_menu_index = 0;
|
||||
}
|
||||
if (popup == EditMenuPopupT::None) {
|
||||
menu_activate_armed = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ime
|
||||
@ -47,6 +47,8 @@ struct SwVersionStruct {
|
||||
u32 hex_representation;
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret);
|
||||
|
||||
struct AuthInfoData {
|
||||
u64 paid;
|
||||
u64 caps[4];
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/pad/pad_errors.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "input/controller.h"
|
||||
#include "pad.h"
|
||||
|
||||
@ -339,7 +340,21 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& contro
|
||||
return 1;
|
||||
}
|
||||
|
||||
const bool gamepad_input_intercepted = ImGui::Core::IsGamepadInputCaptured();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (gamepad_input_intercepted) {
|
||||
pData[i] = {};
|
||||
pData[i].buttons = OrbisPadButtonDataOffset::Intercepted;
|
||||
pData[i].leftStick = {128, 128};
|
||||
pData[i].rightStick = {128, 128};
|
||||
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
pData[i].connected = connected;
|
||||
pData[i].timestamp = states[i].time;
|
||||
pData[i].connectedCount = connected_count;
|
||||
pData[i].deviceUniqueDataLen = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
pData[i].buttons = states[i].buttonsState;
|
||||
pData[i].leftStick.x = states[i].axes[static_cast<int>(Input::Axis::LeftX)];
|
||||
pData[i].leftStick.y = states[i].axes[static_cast<int>(Input::Axis::LeftY)];
|
||||
|
||||
@ -39,10 +39,12 @@ constexpr ImWchar kArabicRanges[] = {
|
||||
};
|
||||
|
||||
constexpr ImWchar kSymbolsRanges[] = {
|
||||
0x2000, 0x206F, // General punctuation
|
||||
0x20A0, 0x20CF, // Currency symbols
|
||||
0x2100, 0x214F, // Letterlike symbols
|
||||
0x2190, 0x21FF, // Arrows
|
||||
0x2200, 0x22FF, // Math operators
|
||||
0x2300, 0x23FF, // Misc technical (includes keyboard symbol)
|
||||
0x2460, 0x24FF, // Enclosed alphanumerics
|
||||
0x25A0, 0x25FF, // Geometric shapes
|
||||
0x2600, 0x26FF, // Misc symbols
|
||||
@ -96,6 +98,7 @@ const ImWchar* GetPrimaryTextRanges(ImFontAtlas* atlas) {
|
||||
rb.AddRanges(atlas->GetGlyphRangesCyrillic());
|
||||
rb.AddRanges(atlas->GetGlyphRangesVietnamese());
|
||||
rb.AddRanges(kLatinExtendedRanges);
|
||||
rb.AddRanges(kSymbolsRanges);
|
||||
rb.BuildRanges(&ranges);
|
||||
}
|
||||
return ranges.Data;
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <imgui.h>
|
||||
|
||||
@ -32,11 +34,31 @@ static std::deque<std::pair<bool, ImGui::Layer*>> change_layers{};
|
||||
static std::mutex change_layers_mutex{};
|
||||
|
||||
static ImGuiID dock_id;
|
||||
static std::atomic<std::uint32_t> force_gamepad_input_capture_count{0};
|
||||
|
||||
namespace ImGui {
|
||||
|
||||
namespace Core {
|
||||
|
||||
void AcquireGamepadInputCapture() {
|
||||
force_gamepad_input_capture_count.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ReleaseGamepadInputCapture() {
|
||||
std::uint32_t expected = force_gamepad_input_capture_count.load(std::memory_order_relaxed);
|
||||
while (expected != 0) {
|
||||
if (force_gamepad_input_capture_count.compare_exchange_weak(
|
||||
expected, expected - 1, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG_WARNING(ImGui, "ReleaseGamepadInputCapture called with no active capture");
|
||||
}
|
||||
|
||||
bool IsGamepadInputCaptured() {
|
||||
return force_gamepad_input_capture_count.load(std::memory_order_relaxed) > 0;
|
||||
}
|
||||
|
||||
void Initialize(const ::Vulkan::Instance& instance, const Frontend::WindowSDL& window,
|
||||
const u32 image_count, vk::Format surface_format,
|
||||
const vk::AllocationCallbacks* allocator) {
|
||||
@ -161,14 +183,28 @@ bool ProcessEvent(SDL_Event* event) {
|
||||
}
|
||||
case SDL_EVENT_TEXT_INPUT:
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
if (IsGamepadInputCaptured()) {
|
||||
// Keep keyboard events flowing through the regular input-binding path while IME/OSK
|
||||
// captures gamepad input, so keyboard equivalents of pad buttons still update the
|
||||
// virtual controller state.
|
||||
return false;
|
||||
}
|
||||
const auto& io = GetIO();
|
||||
return io.WantCaptureKeyboard && io.Ctx->NavWindow != nullptr &&
|
||||
io.Ctx->NavWindow->ID != dock_id;
|
||||
}
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: {
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: {
|
||||
if (IsGamepadInputCaptured()) {
|
||||
// Let controller events continue to input bindings so OSK shortcuts and virtual
|
||||
// controller state keep updating; game-side pad reads are blocked in libScePad.
|
||||
return false;
|
||||
}
|
||||
const auto& io = GetIO();
|
||||
return io.NavActive && io.Ctx->NavWindow != nullptr && io.Ctx->NavWindow->ID != dock_id;
|
||||
}
|
||||
|
||||
@ -28,6 +28,10 @@ void Shutdown(const vk::Device& device);
|
||||
|
||||
bool ProcessEvent(SDL_Event* event);
|
||||
|
||||
void AcquireGamepadInputCapture();
|
||||
void ReleaseGamepadInputCapture();
|
||||
bool IsGamepadInputCaptured();
|
||||
|
||||
ImGuiID NewFrame(bool is_reusing_frame = false);
|
||||
|
||||
void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user