diff options
-rw-r--r-- | demos/input_method/main.cpp | 135 | ||||
-rw-r--r-- | demos/main/main.cpp | 7 | ||||
-rw-r--r-- | include/cru/platform/native/input_method.hpp | 19 | ||||
-rw-r--r-- | include/cru/platform/native/ui_application.hpp | 3 | ||||
-rw-r--r-- | include/cru/platform/native/window.hpp | 4 | ||||
-rw-r--r-- | include/cru/win/graph/direct/text_layout.hpp | 4 | ||||
-rw-r--r-- | include/cru/win/native/input_method.hpp | 2 | ||||
-rw-r--r-- | include/cru/win/native/window.hpp | 4 | ||||
-rw-r--r-- | src/win/graph/direct/font.cpp | 4 | ||||
-rw-r--r-- | src/win/native/input_method.cpp | 32 | ||||
-rw-r--r-- | src/win/native/ui_application.cpp | 6 | ||||
-rw-r--r-- | src/win/native/window.cpp | 22 | ||||
-rw-r--r-- | src/win/string.cpp | 22 |
13 files changed, 236 insertions, 28 deletions
diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp index e69de29b..01a1a695 100644 --- a/demos/input_method/main.cpp +++ b/demos/input_method/main.cpp @@ -0,0 +1,135 @@ +#include "cru/platform/graph/factory.hpp" +#include "cru/platform/graph/font.hpp" +#include "cru/platform/graph/painter.hpp" +#include "cru/platform/native/input_method.hpp" +#include "cru/platform/native/ui_application.hpp" +#include "cru/platform/native/window.hpp" + +int main() { + using namespace cru::platform; + using namespace cru::platform::graph; + using namespace cru::platform::native; + + auto application = CreateUiApplication(); + + auto graph_factory = application->GetGraphFactory(); + + auto window_resolver = application->CreateWindow(nullptr); + + auto window = window_resolver->Resolve(); + + auto input_method_context = + application->GetInputMethodManager()->GetContext(window); + + auto brush = graph_factory->CreateSolidColorBrush(); + brush->SetColor(colors::black); + + auto odd_clause_brush = graph_factory->CreateSolidColorBrush(); + odd_clause_brush->SetColor(colors::yellow); + auto even_clause_brush = graph_factory->CreateSolidColorBrush(); + even_clause_brush->SetColor(colors::green); + auto target_clause_brush = graph_factory->CreateSolidColorBrush(); + target_clause_brush->SetColor(colors::blue); + + std::shared_ptr<IFont> font = graph_factory->CreateFont("等线", 30); + + auto prompt_text_layout = + graph_factory->CreateTextLayout(font, + "Alt+F1: Enable IME\n" + "Alt+F2: Disable IME\n" + "Alt+F3: Complete composition.\n" + "Alt+F4: Cancel composition."); + + auto no_composition_text_layout = + graph_factory->CreateTextLayout(font, "Not compositioning!"); + + std::optional<CompositionText> optional_composition_text; + std::string committed_text; + + window->PaintEvent()->AddHandler([&](auto) { + auto painter = window->BeginPaint(); + painter->Clear(colors::white); + + painter->DrawText(Point{}, prompt_text_layout.get(), brush.get()); + + auto anchor_y = prompt_text_layout->GetTextBounds().height; + + if (optional_composition_text) { + const auto& composition_text = *optional_composition_text; + auto composition_text_layout = + graph_factory->CreateTextLayout(font, composition_text.text); + + for (int i = 0; i < composition_text.clauses.size(); i++) { + const auto& clause = composition_text.clauses[i]; + auto rects = composition_text_layout->TextRangeRect( + TextRange::FromTwoSides(clause.start, clause.end)); + const auto& b = clause.target + ? target_clause_brush + : (i % 2 ? odd_clause_brush : even_clause_brush); + for (auto& rect : rects) { + rect.top += anchor_y; + painter->FillRectangle(rect, b.get()); + } + } + + painter->DrawText(Point{0, anchor_y}, composition_text_layout.get(), + brush.get()); + + anchor_y += composition_text_layout->GetTextBounds().height; + } else { + painter->DrawText(Point{0, anchor_y}, no_composition_text_layout.get(), + brush.get()); + anchor_y += no_composition_text_layout->GetTextBounds().height; + } + + auto committed_text_layout = + graph_factory->CreateTextLayout(font, committed_text); + + painter->DrawText(Point{0, anchor_y}, committed_text_layout.get(), + brush.get()); + }); + + window->KeyDownEvent()->AddHandler( + [&input_method_context](const NativeKeyEventArgs& args) { + if (args.modifier & key_modifiers::alt) { + switch (args.key) { + case KeyCode::F1: + input_method_context->EnableIME(); + break; + case KeyCode::F2: + input_method_context->DisableIME(); + break; + case KeyCode::F3: + input_method_context->CompleteComposition(); + break; + case KeyCode::F4: + input_method_context->CancelComposition(); + break; + default: + break; + } + } + }); + + window->CharEvent()->AddHandler( + [window, &committed_text](const std::string_view& c) { + committed_text += c; + window->RequestRepaint(); + }); + + input_method_context->CompositionEvent()->AddHandler( + [window, &input_method_context, &optional_composition_text](auto) { + optional_composition_text = input_method_context->GetCompositionText(); + window->RequestRepaint(); + }); + + input_method_context->CompositionEndEvent()->AddHandler( + [window, &input_method_context, &optional_composition_text](auto) { + optional_composition_text = std::nullopt; + window->RequestRepaint(); + }); + + window->SetVisible(true); + + return application->Run(); +} diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 0172838e..21db7dda 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -1,4 +1,5 @@ #include "cru/platform/heap_debug.hpp" +#include "cru/platform/native/ui_application.hpp" #include "cru/platform/native/window.hpp" #include "cru/ui/controls/button.hpp" #include "cru/ui/controls/flex_layout.hpp" @@ -6,9 +7,8 @@ #include "cru/ui/controls/text_block.hpp" #include "cru/ui/ui_host.hpp" #include "cru/ui/window.hpp" -#include "cru/win/native/ui_application.hpp" -using cru::platform::native::win::WinUiApplication; +using cru::platform::native::CreateUiApplication; using cru::ui::Rect; using cru::ui::Thickness; using cru::ui::Window; @@ -22,8 +22,7 @@ int main() { cru::platform::SetupHeapDebug(); #endif - std::unique_ptr<WinUiApplication> application = - std::make_unique<WinUiApplication>(); + auto application = CreateUiApplication(); const auto window = Window::CreateOverlapped(); diff --git a/include/cru/platform/native/input_method.hpp b/include/cru/platform/native/input_method.hpp index 48895f84..4034be4e 100644 --- a/include/cru/platform/native/input_method.hpp +++ b/include/cru/platform/native/input_method.hpp @@ -1,8 +1,10 @@ #pragma once #include "../resource.hpp" #include "base.hpp" + #include "cru/common/event.hpp" +#include <iostream> #include <memory> #include <vector> @@ -21,6 +23,23 @@ struct CompositionText { TextRange selection; }; +inline std::ostream& operator<<(std::ostream& stream, + const CompositionText& composition_text) { + stream << "text: " << composition_text.text << "\n" + << "clauses:\n"; + for (int i = 0; i < composition_text.clauses.size(); i++) { + const auto& clause = composition_text.clauses[i]; + stream << "\t" << i << ". start:" << clause.start << " end:" << clause.end; + if (clause.target) { + stream << " target"; + } + stream << "\n"; + } + stream << "selection: position:" << composition_text.selection.position + << " count:" << composition_text.selection.count; + return stream; +} + struct IInputMethodContext : virtual INativeResource { // Return true if you should draw composition text manually. Return false if // system will take care of that for you. diff --git a/include/cru/platform/native/ui_application.hpp b/include/cru/platform/native/ui_application.hpp index 92222929..006255db 100644 --- a/include/cru/platform/native/ui_application.hpp +++ b/include/cru/platform/native/ui_application.hpp @@ -47,4 +47,7 @@ struct IUiApplication : public virtual INativeResource { virtual ICursorManager* GetCursorManager() = 0; virtual IInputMethodManager* GetInputMethodManager() = 0; }; + +// Bootstrap from this. +std::unique_ptr<IUiApplication> CreateUiApplication(); } // namespace cru::platform::native diff --git a/include/cru/platform/native/window.hpp b/include/cru/platform/native/window.hpp index dcb9effa..49b00023 100644 --- a/include/cru/platform/native/window.hpp +++ b/include/cru/platform/native/window.hpp @@ -3,7 +3,7 @@ #include "base.hpp" #include "cru/common/event.hpp" -#include <string> +#include <string_view> namespace cru::platform::native { // Represents a native window, which exposes some low-level events and @@ -57,7 +57,7 @@ struct INativeWindow : virtual INativeResource { virtual IEvent<NativeMouseButtonEventArgs>* MouseUpEvent() = 0; virtual IEvent<NativeKeyEventArgs>* KeyDownEvent() = 0; virtual IEvent<NativeKeyEventArgs>* KeyUpEvent() = 0; - virtual IEvent<std::string>* CharEvent() = 0; + virtual IEvent<std::string_view>* CharEvent() = 0; }; // See INativeWindow for more info. diff --git a/include/cru/win/graph/direct/text_layout.hpp b/include/cru/win/graph/direct/text_layout.hpp index 5f7be9f3..462a5fd3 100644 --- a/include/cru/win/graph/direct/text_layout.hpp +++ b/include/cru/win/graph/direct/text_layout.hpp @@ -45,8 +45,8 @@ class DWriteTextLayout : public DirectGraphResource, std::string text_; std::wstring w_text_; std::shared_ptr<DWriteFont> font_; - float max_width_ = 0.0f; - float max_height_ = 0.0f; + float max_width_ = 10000.0f; + float max_height_ = 10000.0f; Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_; }; } // namespace cru::platform::graph::win::direct diff --git a/include/cru/win/native/input_method.hpp b/include/cru/win/native/input_method.hpp index 566eada2..8e17abd5 100644 --- a/include/cru/win/native/input_method.hpp +++ b/include/cru/win/native/input_method.hpp @@ -64,6 +64,8 @@ class WinInputMethodContext : public WinNativeResource, private: void OnWindowNativeMessage(WindowNativeMessageEventArgs& args); + std::string GetResultString(); + std::optional<AutoHIMC> TryGetHIMC(); private: diff --git a/include/cru/win/native/window.hpp b/include/cru/win/native/window.hpp index 45f1f16a..83497fa6 100644 --- a/include/cru/win/native/window.hpp +++ b/include/cru/win/native/window.hpp @@ -72,7 +72,7 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { IEvent<platform::native::NativeKeyEventArgs>* KeyUpEvent() override { return &key_up_event_; } - IEvent<std::string>* CharEvent() override { return &char_event_; }; + IEvent<std::string_view>* CharEvent() override { return &char_event_; }; IEvent<WindowNativeMessageEventArgs&>* NativeMessageEvent() { return &native_message_event_; @@ -146,7 +146,7 @@ class WinNativeWindow : public WinNativeResource, public virtual INativeWindow { Event<platform::native::NativeMouseButtonEventArgs> mouse_up_event_; Event<platform::native::NativeKeyEventArgs> key_down_event_; Event<platform::native::NativeKeyEventArgs> key_up_event_; - Event<std::string> char_event_; + Event<std::string_view> char_event_; Event<WindowNativeMessageEventArgs&> native_message_event_; diff --git a/src/win/graph/direct/font.cpp b/src/win/graph/direct/font.cpp index 9b5eb477..6d50fafe 100644 --- a/src/win/graph/direct/font.cpp +++ b/src/win/graph/direct/font.cpp @@ -24,9 +24,9 @@ DWriteFont::DWriteFont(DirectGraphFactory* factory, wff.data(), nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, font_size, buffer.data(), &text_format_)); - ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); + ThrowIfFailed(text_format_->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); ThrowIfFailed( - text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); + text_format_->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); } float DWriteFont::GetFontSize() { return text_format_->GetFontSize(); } diff --git a/src/win/native/input_method.cpp b/src/win/native/input_method.cpp index 73831bd5..091abedd 100644 --- a/src/win/native/input_method.cpp +++ b/src/win/native/input_method.cpp @@ -84,8 +84,8 @@ CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, int clause_size = ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, NULL, 0); int clause_length = clause_size / sizeof(std::uint32_t); - result.reserve(clause_length - 1); if (clause_length) { + result.reserve(clause_length - 1); std::vector<std::uint32_t> clause_data(clause_length); ::ImmGetCompositionString(imm_context, GCS_COMPCLAUSE, clause_data.data(), clause_size); @@ -107,12 +107,21 @@ CompositionClauses GetCompositionClauses(HIMC imm_context, int target_start, std::wstring GetString(HIMC imm_context) { LONG string_size = ::ImmGetCompositionString(imm_context, GCS_COMPSTR, NULL, 0); - std::wstring result((string_size / sizeof(wchar_t)) + 1, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); ::ImmGetCompositionString(imm_context, GCS_COMPSTR, result.data(), string_size); return result; } +std::wstring GetResultString(HIMC imm_context) { + LONG string_size = + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, NULL, 0); + std::wstring result((string_size / sizeof(wchar_t)), 0); + ::ImmGetCompositionString(imm_context, GCS_RESULTSTR, result.data(), + string_size); + return result; +} + CompositionText GetCompositionInfo(HIMC imm_context) { // We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and // convert them into underlines and selection range respectively. @@ -244,6 +253,16 @@ void WinInputMethodContext::OnWindowNativeMessage( switch (message.msg) { case WM_IME_COMPOSITION: { composition_event_.Raise(nullptr); + auto composition_text = GetCompositionText(); + log::Debug( + "WinInputMethodContext: WM_IME_COMPOSITION composition text:\n{}", + composition_text); + if (message.l_param & GCS_RESULTSTR) { + auto result_string = GetResultString(); + log::Debug( + "WinInputMethodContext: WM_IME_COMPOSITION result string: {}", + result_string); + } break; } case WM_IME_STARTCOMPOSITION: { @@ -257,6 +276,15 @@ void WinInputMethodContext::OnWindowNativeMessage( } } +std::string WinInputMethodContext::GetResultString() { + auto optional_himc = TryGetHIMC(); + if (!optional_himc.has_value()) return ""; + auto himc = *std::move(optional_himc); + + auto w_result = win::GetResultString(himc.Get()); + return platform::win::ToUtf8String(w_result); +} + std::optional<AutoHIMC> WinInputMethodContext::TryGetHIMC() { const auto native_window = Resolve(native_window_resolver_.get()); if (native_window == nullptr) return std::nullopt; diff --git a/src/win/native/ui_application.cpp b/src/win/native/ui_application.cpp index 7fba6805..9aa3ebcd 100644 --- a/src/win/native/ui_application.cpp +++ b/src/win/native/ui_application.cpp @@ -13,6 +13,12 @@ #include "timer.hpp" #include "window_manager.hpp" +namespace cru::platform::native { +std::unique_ptr<IUiApplication> CreateUiApplication() { + return std::make_unique<win::WinUiApplication>(); +} +} // namespace cru::platform::native + namespace cru::platform::native::win { WinUiApplication* WinUiApplication::instance = nullptr; diff --git a/src/win/native/window.cpp b/src/win/native/window.cpp index 7ad63769..30c77659 100644 --- a/src/win/native/window.cpp +++ b/src/win/native/window.cpp @@ -290,6 +290,20 @@ bool WinNativeWindow::HandleNativeWindowMessage(HWND hwnd, UINT msg, OnKeyUpInternal(static_cast<int>(w_param)); *result = 0; return true; + case WM_SYSKEYDOWN: + if (l_param & (1 << 29)) { + OnKeyDownInternal(static_cast<int>(w_param)); + *result = 0; + return true; + } + return false; + case WM_SYSKEYUP: + if (l_param & (1 << 29)) { + OnKeyUpInternal(static_cast<int>(w_param)); + *result = 0; + return true; + } + return false; case WM_CHAR: OnCharInternal(static_cast<wchar_t>(w_param)); *result = 0; @@ -419,13 +433,13 @@ void WinNativeWindow::OnCharInternal(wchar_t c) { last_wm_char_event_wparam_ = c; return; } else if (platform::win::IsSurrogatePairTrailing(c)) { - wchar_t s[3] = {last_wm_char_event_wparam_, c, 0}; - auto str = platform::win::ToUtf8String(s); + wchar_t s[2] = {last_wm_char_event_wparam_, c}; + auto str = platform::win::ToUtf8String({s, 2}); char_event_.Raise(str); log::Debug("WinNativeWindow: char event, charactor is {}", str); } else { - wchar_t s[2] = {c, 0}; - auto str = platform::win::ToUtf8String(s); + wchar_t s[1] = {c}; + auto str = platform::win::ToUtf8String({s, 1}); char_event_.Raise(str); log::Debug("WinNativeWindow: char event, charactor is {}", str); } diff --git a/src/win/string.cpp b/src/win/string.cpp index fb9811eb..5518e6af 100644 --- a/src/win/string.cpp +++ b/src/win/string.cpp @@ -8,9 +8,9 @@ namespace cru::platform::win { std::string ToUtf8String(const std::wstring_view& string) { if (string.empty()) return std::string{}; - const auto length = - ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), -1, - nullptr, 0, nullptr, nullptr); + const auto length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), nullptr, 0, nullptr, nullptr); if (length == 0) { throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); @@ -18,9 +18,10 @@ std::string ToUtf8String(const std::wstring_view& string) { std::string result; result.resize(length); - if (::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), -1, - result.data(), static_cast<int>(result.size()), - nullptr, nullptr) == 0) + if (::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), result.data(), + static_cast<int>(result.size()), nullptr, + nullptr) == 0) throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-8."); return result; @@ -29,8 +30,9 @@ std::string ToUtf8String(const std::wstring_view& string) { std::wstring ToUtf16String(const std::string_view& string) { if (string.empty()) return std::wstring{}; - const auto length = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, - string.data(), -1, nullptr, 0); + const auto length = + ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), nullptr, 0); if (length == 0) { throw Win32Error(::GetLastError(), "Failed to convert wide string to UTF-16."); @@ -38,8 +40,8 @@ std::wstring ToUtf16String(const std::string_view& string) { std::wstring result; result.resize(length); - if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), -1, - result.data(), + if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, string.data(), + static_cast<int>(string.size()), result.data(), static_cast<int>(result.size())) == 0) throw win::Win32Error(::GetLastError(), "Failed to convert wide string to UTF-16."); |