aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--demos/input_method/main.cpp135
-rw-r--r--demos/main/main.cpp7
-rw-r--r--include/cru/platform/native/input_method.hpp19
-rw-r--r--include/cru/platform/native/ui_application.hpp3
-rw-r--r--include/cru/platform/native/window.hpp4
-rw-r--r--include/cru/win/graph/direct/text_layout.hpp4
-rw-r--r--include/cru/win/native/input_method.hpp2
-rw-r--r--include/cru/win/native/window.hpp4
-rw-r--r--src/win/graph/direct/font.cpp4
-rw-r--r--src/win/native/input_method.cpp32
-rw-r--r--src/win/native/ui_application.cpp6
-rw-r--r--src/win/native/window.cpp22
-rw-r--r--src/win/string.cpp22
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.");