diff --git a/Include/RmlUi/Core/TextInputMethodContext.h b/Include/RmlUi/Core/TextInputMethodContext.h index 59d6eb9d1..5f1e7ff5b 100644 --- a/Include/RmlUi/Core/TextInputMethodContext.h +++ b/Include/RmlUi/Core/TextInputMethodContext.h @@ -66,10 +66,13 @@ class RMLUICORE_API TextInputMethodContext { /// @param[in] end The first character *after* the range. virtual void SetText(StringView text, int start, int end) = 0; - /// Update the range of the text being composed (e.g., for visual feedback). + /// Update the range of the text being composed. /// @param[in] start The first character in the range. /// @param[in] end The first character *after* the range. virtual void SetCompositionRange(int start, int end) = 0; + + /// Commit the current composition. + virtual void CommitComposition() = 0; }; } // namespace Rml diff --git a/Samples/basic/ime/data/ime.rml b/Samples/basic/ime/data/ime.rml index f07b3fb03..27be21122 100644 --- a/Samples/basic/ime/data/ime.rml +++ b/Samples/basic/ime/data/ime.rml @@ -25,7 +25,7 @@ input[type="text"], textarea { width: 100%; border: 1px gray; - margin-bottom: 10dp; + margin-bottom: 10dp; } .note { @@ -36,10 +36,10 @@
Note: the emoji keyboard and clipboard history use IME, too.
diff --git a/Source/Core/Elements/WidgetTextInput.cpp b/Source/Core/Elements/WidgetTextInput.cpp index aec3e99d4..39e2cca9a 100644 --- a/Source/Core/Elements/WidgetTextInput.cpp +++ b/Source/Core/Elements/WidgetTextInput.cpp @@ -84,15 +84,17 @@ class WidgetTextInputIMEContext final : public TextInputMethodContext { WidgetTextInputIMEContext(WidgetTextInput* _owner); ~WidgetTextInputIMEContext() = default; - virtual void GetScreenBounds(Vector2f& position, Vector2f& size) const override; - virtual void GetSelectionRange(int& start, int& end) const override; - virtual void SetSelectionRange(int start, int end) override; - virtual void SetCursorPosition(int position) override; - virtual void SetText(StringView text, int start, int end) override; - virtual void SetCompositionRange(int start, int end) override; + void GetScreenBounds(Vector2f& position, Vector2f& size) const override; + void GetSelectionRange(int& start, int& end) const override; + void SetSelectionRange(int start, int end) override; + void SetCursorPosition(int position) override; + void SetText(StringView text, int start, int end) override; + void SetCompositionRange(int start, int end) override; + void CommitComposition() override; private: WidgetTextInput* owner; + String composition; }; WidgetTextInputIMEContext::WidgetTextInputIMEContext(WidgetTextInput* _owner) : owner(_owner) {} @@ -129,6 +131,8 @@ void WidgetTextInputIMEContext::SetText(StringView text, int start, int end) value.replace(start, end - start, text.begin(), text.size()); owner->parent->SetValue(value); + + composition = String(text); } void WidgetTextInputIMEContext::SetCompositionRange(int start, int end) @@ -136,6 +140,40 @@ void WidgetTextInputIMEContext::SetCompositionRange(int start, int end) owner->SetIMERange(start, end); } +void WidgetTextInputIMEContext::CommitComposition() +{ + const int start_byte = owner->ime_composition_begin_index; + const int end_byte = owner->ime_composition_end_index; + + // No composition to commit. + if (start_byte == 0 && end_byte == 0) + return; + + String value = owner->GetAttributeValue(); + + // If the text input has a length restriction, we have to shorten the composition string. + if (owner->GetMaxLength() >= 0) + { + int start = StringUtilities::ConvertByteOffsetToCharacterOffset(value, start_byte); + int end = StringUtilities::ConvertByteOffsetToCharacterOffset(value, end_byte); + + int value_length = (int)StringUtilities::LengthUTF8(value); + int composition_length = (int)StringUtilities::LengthUTF8(composition); + + // The requested text value would exceed the length restriction after replacing the original value. + if (value_length + composition_length - (start - end) > owner->GetMaxLength()) + { + int new_length = owner->GetMaxLength() - (value_length - composition_length); + composition.erase(StringUtilities::ConvertCharacterOffsetToByteOffset(composition, new_length)); + } + } + + RMLUI_ASSERTMSG(end_byte >= start_byte, "Invalid end character offset."); + value.replace(start_byte, end_byte - start_byte, composition.data(), composition.size()); + + owner->parent->SetValue(value); +} + WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensions(0, 0), scroll_offset(0, 0), cursor_position(0, 0), cursor_size(0, 0) { diff --git a/Source/Core/TextInputMethodEditor.cpp b/Source/Core/TextInputMethodEditor.cpp index 426a51720..cc327127b 100644 --- a/Source/Core/TextInputMethodEditor.cpp +++ b/Source/Core/TextInputMethodEditor.cpp @@ -147,6 +147,13 @@ void DefaultTextInputMethodEditor::IMEConfirmComposition(StringView composition) RMLUI_ASSERT(IsComposing()); SetCompositionString(composition); + + RMLUI_ASSERT(!context.expired()); + SharedPtr