Browse Source

Set IME position to the caret during text editing on the Windows backend. #303 #305

Co-authored-by: liulun <[email protected]>
Michael Ragazzon 3 years ago
parent
commit
89a357bda4

+ 26 - 0
Backends/RmlUi_Platform_Win32.cpp

@@ -34,6 +34,11 @@
 #include <RmlUi/Core/SystemInterface.h>
 #include <string.h>
 
+// Used to interact with the input method editor (IME). Users of MinGW should manually link to this.
+#ifdef _MSC_VER
+	#pragma comment(lib, "imm32")
+#endif
+
 SystemInterface_Win32::SystemInterface_Win32()
 {
 	LARGE_INTEGER time_ticks_per_second;
@@ -141,6 +146,27 @@ void SystemInterface_Win32::GetClipboardText(Rml::String& text)
 	}
 }
 
+void SystemInterface_Win32::ActivateKeyboard(Rml::Vector2f caret_position, float /*line_height*/)
+{
+	// Adjust the position of the input method editor (IME) to the caret.
+	if (HIMC himc = ImmGetContext(window_handle))
+	{
+		COMPOSITIONFORM comp = {};
+		comp.ptCurrentPos.x = (LONG)caret_position.x;
+		comp.ptCurrentPos.y = (LONG)caret_position.y;
+		comp.dwStyle = CFS_FORCE_POSITION;
+		ImmSetCompositionWindow(himc, &comp);
+
+		CANDIDATEFORM cand = {};
+		cand.dwStyle = CFS_CANDIDATEPOS;
+		cand.ptCurrentPos.x = (LONG)caret_position.x;
+		cand.ptCurrentPos.y = (LONG)caret_position.y;
+		ImmSetCandidateWindow(himc, &cand);
+
+		ImmReleaseContext(window_handle, himc);
+	}
+}
+
 Rml::String RmlWin32::ConvertToUTF8(const std::wstring& wstr)
 {
 	const int count = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);

+ 3 - 1
Backends/RmlUi_Platform_Win32.h

@@ -39,7 +39,7 @@ class SystemInterface_Win32 : public Rml::SystemInterface {
 public:
 	SystemInterface_Win32();
 
-	// Optionally, provide or change the window to be used for setting the mouse cursors and clipboard text.
+	// Optionally, provide or change the window to be used for setting the mouse cursor, clipboard text and IME position.
 	void SetWindow(HWND window_handle);
 
 	// -- Inherited from Rml::SystemInterface  --
@@ -51,6 +51,8 @@ public:
 	void SetClipboardText(const Rml::String& text) override;
 	void GetClipboardText(Rml::String& text) override;
 
+	void ActivateKeyboard(Rml::Vector2f caret_position, float line_height) override;
+
 private:
 	HWND window_handle = nullptr;
 

+ 1 - 1
CMakeLists.txt

@@ -682,7 +682,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING)
 	
 	# Add OS dependencies.
 	if (WIN32)
-		target_link_libraries(shell PRIVATE shlwapi)
+		target_link_libraries(shell PRIVATE shlwapi imm32)
 	elseif(APPLE)
 		target_link_libraries(shell PRIVATE "-framework Cocoa")
 	elseif(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)

+ 5 - 3
Include/RmlUi/Core/SystemInterface.h

@@ -91,10 +91,12 @@ public:
 	/// @param[out] text Retrieved text from clipboard.
 	virtual void GetClipboardText(String& text);
 
-	/// Activate keyboard (for touchscreen devices)
-	virtual void ActivateKeyboard();
+	/// Activate keyboard (for touchscreen devices).
+	/// @param[in] caret_position Position of the caret in absolute window coordinates.
+	/// @param[in] line_height Height of the current line being edited.
+	virtual void ActivateKeyboard(Rml::Vector2f caret_position, float line_height);
 	
-	/// Deactivate keyboard (for touchscreen devices)
+	/// Deactivate keyboard (for touchscreen devices).
 	virtual void DeactivateKeyboard();
 };
 

+ 15 - 9
Source/Core/Elements/WidgetTextInput.cpp

@@ -899,9 +899,6 @@ void WidgetTextInput::ShowCursor(bool show, bool move_to_cursor)
 	if (show)
 	{
 		cursor_visible = true;
-		SetKeyboardActive(true);
-		keyboard_showed = true;
-		
 		cursor_timer = CURSOR_BLINK_TIME;
 		last_update_time = GetSystemInterface()->GetElapsedTime();
 
@@ -923,6 +920,9 @@ void WidgetTextInput::ShowCursor(bool show, bool move_to_cursor)
 			scroll_offset.x = parent->GetScrollLeft();
 			scroll_offset.y = parent->GetScrollTop();
 		}
+
+		SetKeyboardActive(true);
+		keyboard_showed = true;
 	}
 	else
 	{
@@ -1265,16 +1265,22 @@ void WidgetTextInput::GetLineSelection(String& pre_selection, String& selection,
 
 void WidgetTextInput::SetKeyboardActive(bool active)
 {
-	SystemInterface* system = GetSystemInterface();
-	if (system) {
-		if (active) 
+	if (SystemInterface* system = GetSystemInterface())
+	{
+		if (active)
 		{
-			system->ActivateKeyboard();
-		} else 
+			// Activate the keyboard and submit the cursor position and line height to enable clients to adjust the input method editor (IME). Note
+			// that the cursor is extended by one pixel along the top and bottom, we reverse this extension here.
+			const Vector2f element_offset = parent->GetAbsoluteOffset() - scroll_offset;
+			const Vector2f absolute_cursor_position = element_offset + cursor_position + Vector2f(0, 1);
+			const float line_height = cursor_size.y - 2.f;
+			system->ActivateKeyboard(absolute_cursor_position, line_height);
+		}
+		else
 		{
 			system->DeactivateKeyboard();
 		}
 	}
 }
-	
+
 } // namespace Rml

+ 4 - 11
Source/Core/SystemInterface.cpp

@@ -102,7 +102,6 @@ int SystemInterface::TranslateString(String& translated, const String& input)
 	return 0;
 }
 
-// Joins the path of an RML or RCSS file with the path of a resource specified within the file.
 void SystemInterface::JoinPath(String& translated_path, const String& document_path, const String& path)
 {
 	// If the path is absolute, strip the leading / and return it.
@@ -137,15 +136,9 @@ void SystemInterface::JoinPath(String& translated_path, const String& document_p
 	URL url(Replace(translated_path, ':', '|') + Replace(path, '\\', '/'));
 	translated_path = Replace(url.GetPathedFileName(), '|', ':');
 }
-	
-// Activate keyboard (for touchscreen devices)
-void SystemInterface::ActivateKeyboard() 
-{
-}
-	
-// Deactivate keyboard (for touchscreen devices)
-void SystemInterface::DeactivateKeyboard() 
-{
-}
+
+void SystemInterface::ActivateKeyboard(Rml::Vector2f /*caret_position*/, float /*line_height*/) {}
+
+void SystemInterface::DeactivateKeyboard() {}
 
 } // namespace Rml

+ 6 - 11
Source/Debugger/DebuggerSystemInterface.cpp

@@ -79,17 +79,12 @@ void DebuggerSystemInterface::GetClipboardText(String& text)
 	application_interface->GetClipboardText(text);
 }
 
-// Activate keyboard (for touchscreen devices)
-void DebuggerSystemInterface::ActivateKeyboard()
+void DebuggerSystemInterface::ActivateKeyboard(Rml::Vector2f caret_position, float line_height)
 {
-	application_interface->ActivateKeyboard();
-}
-	
-// Deactivate keyboard (for touchscreen devices)
-void DebuggerSystemInterface::DeactivateKeyboard()
-{
-	application_interface->DeactivateKeyboard();
+	application_interface->ActivateKeyboard(caret_position, line_height);
 }
 
-}
-}
+void DebuggerSystemInterface::DeactivateKeyboard() { application_interface->DeactivateKeyboard(); }
+
+} // namespace Debugger
+} // namespace Rml

+ 3 - 3
Source/Debugger/DebuggerSystemInterface.h

@@ -79,10 +79,10 @@ public:
 	/// @param[out] text Retrieved text from clipboard.
 	void GetClipboardText(String& text) override;
 
-	/// Activate keyboard (for touchscreen devices)
-	void ActivateKeyboard() override;
+	/// Activate keyboard (for touchscreen devices).
+	void ActivateKeyboard(Rml::Vector2f caret_position, float line_height) override;
 	
-	/// Deactivate keyboard (for touchscreen devices)
+	/// Deactivate keyboard (for touchscreen devices).
 	void DeactivateKeyboard() override;
 private:
 	Rml::SystemInterface* application_interface;

+ 5 - 0
changelog.md

@@ -37,6 +37,7 @@ The `<textarea>` and `<input type="text">` elements have been improved in severa
 - Fixed several issues where the text cursor would be offset from the text editing operations. In particular after word wrapping, or when suppressed characters were present in the text field's value. #313
 - Fixed an issue where Windows newline endings (\r\n) would produce an excessive space character.
 - Fixed operation of page up/down numpad keys being swapped.
+- The input method editor (IME) is now positioned at the caret during text editing on the Windows backend. #303 #305 (thanks @xland)
 
 ### Lua plugin
 
@@ -53,6 +54,10 @@ The `<textarea>` and `<input type="text">` elements have been improved in severa
 - Win32 backend: Fix slow input handling especially with CJK input. #311
 - Logging a message without an installed system interface will now be written to cout instead of crashing the application.
 
+### Breaking changes
+
+- Changed the signature of the keyboard activation in the system interface, it now passes the caret position and line height: `SystemInterface::ActivateKeyboard(Rml::Vector2f caret_position, float line_height)`.
+
 
 ## RmlUi 4.4