diff options
-rw-r--r-- | .vscode/settings.json | 3 | ||||
-rw-r--r-- | include/cru/platform/native/base.hpp | 2 | ||||
-rw-r--r-- | include/cru/platform/native/input_method.hpp | 42 | ||||
-rw-r--r-- | include/cru/win/native/input_method.hpp | 56 | ||||
-rw-r--r-- | include/cru/win/native/window.hpp | 2 | ||||
-rw-r--r-- | src/win/native/input_method.cpp | 110 | ||||
-rw-r--r-- | src/win/native/window.cpp | 7 |
7 files changed, 148 insertions, 74 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f06bdfc..3f6f33c8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -71,6 +71,7 @@ "span": "cpp", "string_span": "cpp", "numeric": "cpp", - "pointers": "cpp" + "pointers": "cpp", + "gsl_assert": "cpp" } } diff --git a/include/cru/platform/native/base.hpp b/include/cru/platform/native/base.hpp index 3cbf77c5..e0ecbda7 100644 --- a/include/cru/platform/native/base.hpp +++ b/include/cru/platform/native/base.hpp @@ -11,7 +11,7 @@ struct IUiApplication; struct INativeWindow; struct INativeWindowResolver; struct IInputMethodManager; -struct IInputMethodContextRef; +struct IInputMethodContext; struct Dpi { float x; diff --git a/include/cru/platform/native/input_method.hpp b/include/cru/platform/native/input_method.hpp index cce10d93..00017502 100644 --- a/include/cru/platform/native/input_method.hpp +++ b/include/cru/platform/native/input_method.hpp @@ -4,32 +4,50 @@ #include "cru/common/event.hpp" #include <memory> +#include <vector> namespace cru::platform::native { -// It is a reference, so there is a ref count, remember to destroy it to release -// the ref after use. -struct IInputMethodContextRef : virtual INativeResource { +struct CompositionUnderline { + int start; + int end; +}; + +struct CompositionText { + std::string text; + std::vector<CompositionUnderline> underlines; + int caret_position; +}; + +struct IInputMethodContext : virtual INativeResource { // Return true if you should draw composition text manually. Return false if // system will take care of that for you. virtual bool ShouldManuallyDrawCompositionText() = 0; - // Reset composition string. Use this method to prepare typing. - virtual void Reset() = 0; - // Get the composition string. - virtual std::string GetCompositionText() = 0; + + virtual void EnableIME() = 0; + + virtual void DisableIME() = 0; + + virtual void CompleteComposition() = 0; + + virtual void CancelComposition() = 0; + + virtual const CompositionText& GetCompositionText() = 0; + // Set the candidate window lefttop. Use this method to prepare typing. virtual void SetCandidateWindowPosition(const Point& point) = 0; + // Triggered when user starts composition. virtual IEvent<std::nullptr_t>* CompositionStartEvent() = 0; + // Triggered when user stops composition. virtual IEvent<std::nullptr_t>* CompositionEndEvent() = 0; - // Triggered every time composition text changes, event args is the new - // composition text. - virtual IEvent<std::string>* CompositionTextChangeEvent() = 0; + + // Triggered every time composition text changes. + virtual IEvent<std::nullptr_t>* CompositionEvent() = 0; }; struct IInputMethodManager : virtual INativeResource { - // Get a reference of context of a window. - virtual std::unique_ptr<IInputMethodContextRef> GetContext( + virtual std::unique_ptr<IInputMethodContext> GetContext( INativeWindow* window) = 0; }; } // namespace cru::platform::native diff --git a/include/cru/win/native/input_method.hpp b/include/cru/win/native/input_method.hpp index 7dc9526a..56e678a9 100644 --- a/include/cru/win/native/input_method.hpp +++ b/include/cru/win/native/input_method.hpp @@ -1,5 +1,6 @@ // Some useful information can be found from chromium code: // https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.h +// https://chromium.googlesource.com/chromium/chromium/+/refs/heads/master/ui/base/win/ime_input.cc #pragma once #include "resource.hpp" @@ -10,23 +11,47 @@ #include <imm.h> namespace cru::platform::native::win { -class WinInputMethodContextRef : public WinNativeResource, - public virtual IInputMethodContextRef { +class AutoHIMC : public Object { public: - WinInputMethodContextRef(WinNativeWindow* window); + explicit AutoHIMC(HWND hwnd); - CRU_DELETE_COPY(WinInputMethodContextRef) - CRU_DELETE_MOVE(WinInputMethodContextRef) + CRU_DELETE_COPY(AutoHIMC) - ~WinInputMethodContextRef() override; + AutoHIMC(AutoHIMC&& other); + AutoHIMC& operator=(AutoHIMC&& other); - ::HIMC GetHandle() const { return handle_; } + ~AutoHIMC() override; + + HWND GetHwnd() const { return hwnd_; } + + HIMC Get() const { return handle_; } + + private: + HWND hwnd_; + HIMC handle_; +}; + +class WinInputMethodContext : public WinNativeResource, + public virtual IInputMethodContext { + public: + WinInputMethodContext(gsl::not_null<WinNativeWindow*> window); + + CRU_DELETE_COPY(WinInputMethodContext) + CRU_DELETE_MOVE(WinInputMethodContext) + + ~WinInputMethodContext() override; bool ShouldManuallyDrawCompositionText() override { return true; } - void Reset() override; + void EnableIME() override; + + void DisableIME() override; + + void CompleteComposition() override; - std::string GetCompositionText() override; + void CancelComposition() override; + + const CompositionText& GetCompositionText() override; void SetCandidateWindowPosition(const Point& point) override; @@ -34,22 +59,21 @@ class WinInputMethodContextRef : public WinNativeResource, IEvent<std::nullptr_t>* CompositionEndEvent() override; - IEvent<std::string>* CompositionTextChangeEvent() override; + IEvent<std::nullptr_t>* CompositionEvent() override; private: void OnWindowNativeMessage(WindowNativeMessageEventArgs& args); + std::optional<AutoHIMC> TryGetHIMC(); + private: - [[maybe_unused]] WinNativeWindow* window_; + std::shared_ptr<INativeWindowResolver> native_window_resolver_; std::vector<EventRevokerGuard> event_revoker_guards_; - ::HWND window_handle_; - ::HIMC handle_; - Event<std::nullptr_t> composition_start_event_; Event<std::nullptr_t> composition_end_event_; - Event<std::string> composition_text_change_event_; + Event<std::nullptr_t> composition_event_; }; class WinInputMethodManager : public WinNativeResource, @@ -63,7 +87,7 @@ class WinInputMethodManager : public WinNativeResource, ~WinInputMethodManager() override; public: - std::unique_ptr<IInputMethodContextRef> GetContext( + std::unique_ptr<IInputMethodContext> GetContext( INativeWindow* window) override; }; } // namespace cru::platform::native::win diff --git a/include/cru/win/native/window.hpp b/include/cru/win/native/window.hpp index 2129895c..45f1f16a 100644 --- a/include/cru/win/native/window.hpp +++ b/include/cru/win/native/window.hpp @@ -177,4 +177,6 @@ class WinNativeWindowResolver : public WinNativeResource, private: WinNativeWindow* window_; }; + +WinNativeWindow* Resolve(gsl::not_null<INativeWindowResolver*> resolver); } // namespace cru::platform::native::win diff --git a/src/win/native/input_method.cpp b/src/win/native/input_method.cpp index 4a125e8d..7153b50d 100644 --- a/src/win/native/input_method.cpp +++ b/src/win/native/input_method.cpp @@ -10,85 +10,100 @@ #include <vector> namespace cru::platform::native::win { -WinInputMethodContextRef::WinInputMethodContextRef(WinNativeWindow* window) - : window_(window) { - Expects(window); +AutoHIMC::AutoHIMC(HWND hwnd) : hwnd_(hwnd) { + Expects(hwnd); + handle_ = ::ImmGetContext(hwnd); +} - window_handle_ = window->GetWindowHandle(); - handle_ = ::ImmGetContext(window_handle_); +AutoHIMC::AutoHIMC(AutoHIMC&& other) + : hwnd_(other.hwnd_), handle_(other.handle_) { + other.hwnd_ = nullptr; + other.handle_ = nullptr; +} - // TODO: Events +AutoHIMC& AutoHIMC::operator=(AutoHIMC&& other) { + if (this != &other) { + Object::operator=(std::move(other)); + this->hwnd_ = other.hwnd_; + this->handle_ = other.handle_; + other.hwnd_ = nullptr; + other.handle_ = nullptr; + } + return *this; +} +AutoHIMC::~AutoHIMC() { + if (handle_) { + if (!::ImmReleaseContext(hwnd_, handle_)) + log::Warn("AutoHIMC: Failed to release HIMC."); + } +} + +WinInputMethodContext::WinInputMethodContext( + gsl::not_null<WinNativeWindow*> window) + : native_window_resolver_(window->GetResolver()) { event_revoker_guards_.push_back( EventRevokerGuard(window->NativeMessageEvent()->AddHandler( - std::bind(&WinInputMethodContextRef::OnWindowNativeMessage, this, + std::bind(&WinInputMethodContext::OnWindowNativeMessage, this, std::placeholders::_1)))); } -WinInputMethodContextRef::~WinInputMethodContextRef() { - ::ImmReleaseContext(window_handle_, handle_); -} +WinInputMethodContext::~WinInputMethodContext() {} -void WinInputMethodContextRef::Reset() { - wchar_t s[1] = {L'\0'}; +void WinInputMethodContext::EnableIME() { + // TODO! +} - if (!::ImmSetCompositionStringW(handle_, SCS_SETSTR, static_cast<LPVOID>(s), - sizeof(s), static_cast<LPVOID>(s), - sizeof(s))) { - log::Warn( - "WinInputMethodContextRef: Failed to reset input method context."); - } +void WinInputMethodContext::DisableIME() { + // TODO! } -std::string WinInputMethodContextRef::GetCompositionText() { - const auto length = gsl::narrow_cast<DWORD>( - ::ImmGetCompositionStringW(handle_, GCS_RESULTREADSTR, NULL, 0) + - sizeof(wchar_t)); - std::vector<std::byte> data(length); - const auto result = ::ImmGetCompositionStringW( - handle_, GCS_RESULTREADSTR, static_cast<LPVOID>(data.data()), length); +void WinInputMethodContext::CompleteComposition() { + // TODO! +} - if (result == IMM_ERROR_NODATA) { - return std::string{}; - } else if (result == IMM_ERROR_GENERAL) { - throw new platform::win::Win32Error(::GetLastError(), - "Failed to get composition string."); - } +void WinInputMethodContext::CancelComposition() { + // TODO! +} - return platform::win::ToUtf8String( - std::wstring_view(reinterpret_cast<wchar_t*>(data.data()))); +const CompositionText& WinInputMethodContext::GetCompositionText() { + // TODO! } -void WinInputMethodContextRef::SetCandidateWindowPosition(const Point& point) { +void WinInputMethodContext::SetCandidateWindowPosition(const Point& point) { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return; + auto himc = std::move(optional_himc).value(); + ::CANDIDATEFORM form; form.dwIndex = 1; form.dwStyle = CFS_CANDIDATEPOS; form.ptCurrentPos = DipToPi(point); - if (!::ImmSetCandidateWindow(handle_, &form)) + if (!::ImmSetCandidateWindow(himc.Get(), &form)) log::Debug( - "WinInputMethodContextRef: Failed to set input method candidate window " + "WinInputMethodContext: Failed to set input method candidate window " "position."); } -IEvent<std::nullptr_t>* WinInputMethodContextRef::CompositionStartEvent() { +IEvent<std::nullptr_t>* WinInputMethodContext::CompositionStartEvent() { return &composition_start_event_; } -IEvent<std::nullptr_t>* WinInputMethodContextRef::CompositionEndEvent() { +IEvent<std::nullptr_t>* WinInputMethodContext::CompositionEndEvent() { return &composition_end_event_; }; -IEvent<std::string>* WinInputMethodContextRef::CompositionTextChangeEvent() { - return &composition_text_change_event_; +IEvent<std::nullptr_t>* WinInputMethodContext::CompositionEvent() { + return &composition_event_; } -void WinInputMethodContextRef::OnWindowNativeMessage( +void WinInputMethodContext::OnWindowNativeMessage( WindowNativeMessageEventArgs& args) { const auto message = args.GetWindowMessage(); switch (message.msg) { case WM_IME_COMPOSITION: { - composition_text_change_event_.Raise(this->GetCompositionText()); + composition_event_.Raise(nullptr); break; } case WM_IME_STARTCOMPOSITION: { @@ -102,14 +117,21 @@ void WinInputMethodContextRef::OnWindowNativeMessage( } } +std::optional<AutoHIMC> WinInputMethodContext::TryGetHIMC() { + const auto native_window = Resolve(native_window_resolver_.get()); + if (native_window == nullptr) return std::nullopt; + const auto hwnd = native_window->GetWindowHandle(); + return AutoHIMC{hwnd}; +} + WinInputMethodManager::WinInputMethodManager(WinUiApplication*) {} WinInputMethodManager::~WinInputMethodManager() {} -std::unique_ptr<IInputMethodContextRef> WinInputMethodManager::GetContext( +std::unique_ptr<IInputMethodContext> WinInputMethodManager::GetContext( INativeWindow* window) { Expects(window); const auto w = CheckPlatform<WinNativeWindow>(window, GetPlatformId()); - return std::make_unique<WinInputMethodContextRef>(w); + return std::make_unique<WinInputMethodContext>(w); } } // namespace cru::platform::native::win diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp index 542aabd2..7ad63769 100644 --- a/src/win/native/window.cpp +++ b/src/win/native/window.cpp @@ -439,4 +439,11 @@ void WinNativeWindowResolver::Reset() { Expects(window_); // already reset, can't reset again window_ = nullptr; } + +WinNativeWindow* Resolve(gsl::not_null<INativeWindowResolver*> resolver) { + const auto window = resolver->Resolve(); + return window == nullptr ? nullptr + : CheckPlatform<WinNativeWindow>( + window, WinNativeResource::k_platform_id); +} // namespace cru::platform::native::win } // namespace cru::platform::native::win |