Skip to content

Commit

Permalink
Windows: IME window follows left edge of composing text, to match nat…
Browse files Browse the repository at this point in the history
…ive Windows apps. (flutter#34292)
  • Loading branch information
moko256 authored Jun 30, 2022
1 parent 26226f0 commit fd9b132
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 21 deletions.
21 changes: 13 additions & 8 deletions shell/platform/windows/text_input_manager_win32.cc
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,19 @@ void TextInputManagerWin32::MoveImeWindow(HIMC imm_context) {
if (GetFocus() != window_handle_ || !ime_active_) {
return;
}
LONG x = caret_rect_.left();
LONG y = caret_rect_.top();
::SetCaretPos(x, y);

COMPOSITIONFORM cf = {CFS_POINT, {x, y}};
::ImmSetCompositionWindow(imm_context, &cf);

CANDIDATEFORM candidate_form = {0, CFS_EXCLUDE, {x, y}, {0, 0, 0, 0}};
LONG left = caret_rect_.left();
LONG top = caret_rect_.top();
LONG right = caret_rect_.right();
LONG bottom = caret_rect_.bottom();
::SetCaretPos(left, bottom);

// Set the position of composition text.
COMPOSITIONFORM composition_form = {CFS_POINT, {left, top}};
::ImmSetCompositionWindow(imm_context, &composition_form);

// Set the position of candidate window.
CANDIDATEFORM candidate_form = {
0, CFS_EXCLUDE, {left, bottom}, {left, top, right, bottom}};
::ImmSetCandidateWindow(imm_context, &candidate_form);
}

Expand Down
4 changes: 2 additions & 2 deletions shell/platform/windows/text_input_plugin.cc
Original file line number Diff line number Diff line change
Expand Up @@ -378,10 +378,10 @@ Rect TextInputPlugin::GetCursorRect() const {
Point transformed_point = {
composing_rect_.left() * editabletext_transform_[0][0] +
composing_rect_.top() * editabletext_transform_[1][0] +
editabletext_transform_[3][0] + composing_rect_.width(),
editabletext_transform_[3][0],
composing_rect_.left() * editabletext_transform_[0][1] +
composing_rect_.top() * editabletext_transform_[1][1] +
editabletext_transform_[3][1] + composing_rect_.height()};
editabletext_transform_[3][1]};
return {transformed_point, composing_rect_.size()};
}

Expand Down
97 changes: 86 additions & 11 deletions shell/platform/windows/text_input_plugin_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,20 @@ static std::unique_ptr<std::vector<uint8_t>> CreateResponse(bool handled) {
return JsonMessageCodec::GetInstance().EncodeMessage(*response_doc);
}

class EmptyTextInputPluginDelegate : public TextInputPluginDelegate {
class MockTextInputPluginDelegate : public TextInputPluginDelegate {
public:
void OnCursorRectUpdated(const Rect& rect) override {}
void OnResetImeComposing() override { ime_was_reset_ = true; }
MockTextInputPluginDelegate() {}
virtual ~MockTextInputPluginDelegate() = default;

bool ime_was_reset() const { return ime_was_reset_; }
// Prevent copying.
MockTextInputPluginDelegate(MockTextInputPluginDelegate const&) = delete;
MockTextInputPluginDelegate& operator=(MockTextInputPluginDelegate const&) =
delete;

private:
bool ime_was_reset_ = false;
MOCK_METHOD1(OnCursorRectUpdated, void(const Rect&));
MOCK_METHOD0(OnResetImeComposing, void());
};

} // namespace

TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
Expand All @@ -55,7 +59,7 @@ TEST(TextInputPluginTest, TextMethodsWorksWithEmptyModel) {
[&received_scancode, &handled_message, &unhandled_message](
const std::string& channel, const uint8_t* message,
size_t message_size, BinaryReply reply) {});
EmptyTextInputPluginDelegate delegate;
MockTextInputPluginDelegate delegate;

int redispatch_scancode = 0;
TextInputPlugin handler(&messenger, &delegate);
Expand All @@ -76,14 +80,15 @@ TEST(TextInputPluginTest, ClearClientResetsComposing) {
BinaryReply reply) {});
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};

EmptyTextInputPluginDelegate delegate;
MockTextInputPluginDelegate delegate;
TextInputPlugin handler(&messenger, &delegate);

EXPECT_CALL(delegate, OnResetImeComposing());

auto& codec = JsonMethodCodec::GetInstance();
auto message = codec.EncodeMethodCall({"TextInput.clearClient", nullptr});
messenger.SimulateEngineMessage(kChannelName, message->data(),
message->size(), reply_handler);
EXPECT_TRUE(delegate.ime_was_reset());
}

// Verify that the embedder sends state update messages to the framework during
Expand All @@ -96,7 +101,7 @@ TEST(TextInputPluginTest, VerifyComposingSendStateUpdate) {
BinaryReply reply) { sent_message = true; });
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};

EmptyTextInputPluginDelegate delegate;
MockTextInputPluginDelegate delegate;
TextInputPlugin handler(&messenger, &delegate);

auto& codec = JsonMethodCodec::GetInstance();
Expand Down Expand Up @@ -152,7 +157,7 @@ TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
[&received_scancode, &handled_message, &unhandled_message](
const std::string& channel, const uint8_t* message,
size_t message_size, BinaryReply reply) {});
EmptyTextInputPluginDelegate delegate;
MockTextInputPluginDelegate delegate;

int redispatch_scancode = 0;
TextInputPlugin handler(&messenger, &delegate);
Expand Down Expand Up @@ -199,5 +204,75 @@ TEST(TextInputPluginTest, TextEditingWorksWithDeltaModel) {
// Passes if it did not crash
}

TEST(TextInputPluginTest, TransformCursorRect) {
// A position of `EditableText`.
double view_x = 100;
double view_y = 200;

// A position and size of marked text, in `EditableText` local coordinates.
double ime_x = 3;
double ime_y = 4;
double ime_width = 50;
double ime_height = 60;

// Transformation matrix.
std::array<std::array<double, 4>, 4> editabletext_transform = {
1.0, 0.0, 0.0, view_x, //
0.0, 1.0, 0.0, view_y, //
0.0, 0.0, 0.0, 0.0, //
0.0, 0.0, 0.0, 1.0};

TestBinaryMessenger messenger([](const std::string& channel,
const uint8_t* message, size_t message_size,
BinaryReply reply) {});
BinaryReply reply_handler = [](const uint8_t* reply, size_t reply_size) {};

MockTextInputPluginDelegate delegate;
TextInputPlugin handler(&messenger, &delegate);

auto& codec = JsonMethodCodec::GetInstance();

EXPECT_CALL(delegate, OnCursorRectUpdated(Rect{{view_x, view_y}, {0, 0}}));

{
auto arguments =
std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
auto& allocator = arguments->GetAllocator();

rapidjson::Value transoform(rapidjson::kArrayType);
for (int i = 0; i < 4 * 4; i++) {
// Pack 2-dimensional array by column-major order.
transoform.PushBack(editabletext_transform[i % 4][i / 4], allocator);
}

arguments->AddMember("transform", transoform, allocator);

auto message = codec.EncodeMethodCall(
{"TextInput.setEditableSizeAndTransform", std::move(arguments)});
messenger.SimulateEngineMessage(kChannelName, message->data(),
message->size(), reply_handler);
}

EXPECT_CALL(delegate,
OnCursorRectUpdated(Rect{{view_x + ime_x, view_y + ime_y},
{ime_width, ime_height}}));

{
auto arguments =
std::make_unique<rapidjson::Document>(rapidjson::kObjectType);
auto& allocator = arguments->GetAllocator();

arguments->AddMember("x", ime_x, allocator);
arguments->AddMember("y", ime_y, allocator);
arguments->AddMember("width", ime_width, allocator);
arguments->AddMember("height", ime_height, allocator);

auto message = codec.EncodeMethodCall(
{"TextInput.setMarkedTextRect", std::move(arguments)});
messenger.SimulateEngineMessage(kChannelName, message->data(),
message->size(), reply_handler);
}
}

} // namespace testing
} // namespace flutter

0 comments on commit fd9b132

Please sign in to comment.