flutter_flutter/shell/platform/windows/text_input_plugin.cc
James Clarke d0d6a4c236
Refactor Win32FlutterWindow in preparation for UWP windowing implementation (#18878)
* Add flutter_windows_view and window_binding_handler

Switch input handling infra to FlutterWindowsView

win32_flutter_window implement WindowBindingHandler

Strip unneeded functionality from win32flutterwindow

Fulfill WindowBindingHandler interface in Win32FlutterWindow

Add implementations for missing input handling in Win32FlutterWindow

Cleanup dead code

Correctly hook up rendering again

Fix resizing

clang-format

Fix clipboard

Cleanup

Rename

Add comments

cleanup

* clang-format

* CR Feedback

* clang-format; gn format

* Fix licensing

* CR feedback

* CR feedback

* CR feedback

* Git rid of unnecessar :: prefixes

* Extract WindowBindingHandlerDelegate as an interface

* Missing file

* Extract physical window bounds as a struct

* CR Feedback

* CR feedback

* clang-format

Co-authored-by: Stuart Morgan <stuartmorgan@google.com>
2020-07-07 06:49:51 -07:00

239 lines
8.7 KiB
C++

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/windows/text_input_plugin.h"
#include <windows.h>
#include <cstdint>
#include <iostream>
#include "flutter/shell/platform/common/cpp/json_method_codec.h"
static constexpr char kSetEditingStateMethod[] = "TextInput.setEditingState";
static constexpr char kClearClientMethod[] = "TextInput.clearClient";
static constexpr char kSetClientMethod[] = "TextInput.setClient";
static constexpr char kShowMethod[] = "TextInput.show";
static constexpr char kHideMethod[] = "TextInput.hide";
static constexpr char kMultilineInputType[] = "TextInputType.multiline";
static constexpr char kUpdateEditingStateMethod[] =
"TextInputClient.updateEditingState";
static constexpr char kPerformActionMethod[] = "TextInputClient.performAction";
static constexpr char kTextInputAction[] = "inputAction";
static constexpr char kTextInputType[] = "inputType";
static constexpr char kTextInputTypeName[] = "name";
static constexpr char kComposingBaseKey[] = "composingBase";
static constexpr char kComposingExtentKey[] = "composingExtent";
static constexpr char kSelectionAffinityKey[] = "selectionAffinity";
static constexpr char kAffinityDownstream[] = "TextAffinity.downstream";
static constexpr char kSelectionBaseKey[] = "selectionBase";
static constexpr char kSelectionExtentKey[] = "selectionExtent";
static constexpr char kSelectionIsDirectionalKey[] = "selectionIsDirectional";
static constexpr char kTextKey[] = "text";
static constexpr char kChannelName[] = "flutter/textinput";
static constexpr char kBadArgumentError[] = "Bad Arguments";
static constexpr char kInternalConsistencyError[] =
"Internal Consistency Error";
namespace flutter {
void TextInputPlugin::TextHook(FlutterWindowsView* view,
const std::u16string& text) {
if (active_model_ == nullptr) {
return;
}
active_model_->AddText(text);
SendStateUpdate(*active_model_);
}
void TextInputPlugin::KeyboardHook(FlutterWindowsView* view,
int key,
int scancode,
int action,
char32_t character) {
if (active_model_ == nullptr) {
return;
}
if (action == WM_KEYDOWN) {
switch (key) {
case VK_LEFT:
if (active_model_->MoveCursorBack()) {
SendStateUpdate(*active_model_);
}
break;
case VK_RIGHT:
if (active_model_->MoveCursorForward()) {
SendStateUpdate(*active_model_);
}
break;
case VK_END:
active_model_->MoveCursorToEnd();
SendStateUpdate(*active_model_);
break;
case VK_HOME:
active_model_->MoveCursorToBeginning();
SendStateUpdate(*active_model_);
break;
case VK_BACK:
if (active_model_->Backspace()) {
SendStateUpdate(*active_model_);
}
break;
case VK_DELETE:
if (active_model_->Delete()) {
SendStateUpdate(*active_model_);
}
break;
case VK_RETURN:
EnterPressed(active_model_.get());
break;
default:
break;
}
}
}
TextInputPlugin::TextInputPlugin(flutter::BinaryMessenger* messenger)
: channel_(std::make_unique<flutter::MethodChannel<rapidjson::Document>>(
messenger,
kChannelName,
&flutter::JsonMethodCodec::GetInstance())),
active_model_(nullptr) {
channel_->SetMethodCallHandler(
[this](
const flutter::MethodCall<rapidjson::Document>& call,
std::unique_ptr<flutter::MethodResult<rapidjson::Document>> result) {
HandleMethodCall(call, std::move(result));
});
}
TextInputPlugin::~TextInputPlugin() = default;
void TextInputPlugin::HandleMethodCall(
const flutter::MethodCall<rapidjson::Document>& method_call,
std::unique_ptr<flutter::MethodResult<rapidjson::Document>> result) {
const std::string& method = method_call.method_name();
if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) {
// These methods are no-ops.
} else if (method.compare(kClearClientMethod) == 0) {
active_model_ = nullptr;
} else if (method.compare(kSetClientMethod) == 0) {
if (!method_call.arguments() || method_call.arguments()->IsNull()) {
result->Error(kBadArgumentError, "Method invoked without args");
return;
}
const rapidjson::Document& args = *method_call.arguments();
const rapidjson::Value& client_id_json = args[0];
const rapidjson::Value& client_config = args[1];
if (client_id_json.IsNull()) {
result->Error(kBadArgumentError, "Could not set client, ID is null.");
return;
}
if (client_config.IsNull()) {
result->Error(kBadArgumentError,
"Could not set client, missing arguments.");
return;
}
client_id_ = client_id_json.GetInt();
input_action_ = "";
auto input_action_json = client_config.FindMember(kTextInputAction);
if (input_action_json != client_config.MemberEnd() &&
input_action_json->value.IsString()) {
input_action_ = input_action_json->value.GetString();
}
input_type_ = "";
auto input_type_info_json = client_config.FindMember(kTextInputType);
if (input_type_info_json != client_config.MemberEnd() &&
input_type_info_json->value.IsObject()) {
auto input_type_json =
input_type_info_json->value.FindMember(kTextInputTypeName);
if (input_type_json != input_type_info_json->value.MemberEnd() &&
input_type_json->value.IsString()) {
input_type_ = input_type_json->value.GetString();
}
}
active_model_ = std::make_unique<TextInputModel>();
} else if (method.compare(kSetEditingStateMethod) == 0) {
if (!method_call.arguments() || method_call.arguments()->IsNull()) {
result->Error(kBadArgumentError, "Method invoked without args");
return;
}
const rapidjson::Document& args = *method_call.arguments();
if (active_model_ == nullptr) {
result->Error(
kInternalConsistencyError,
"Set editing state has been invoked, but no client is set.");
return;
}
auto text = args.FindMember(kTextKey);
if (text == args.MemberEnd() || text->value.IsNull()) {
result->Error(kBadArgumentError,
"Set editing state has been invoked, but without text.");
return;
}
auto selection_base = args.FindMember(kSelectionBaseKey);
auto selection_extent = args.FindMember(kSelectionExtentKey);
if (selection_base == args.MemberEnd() || selection_base->value.IsNull() ||
selection_extent == args.MemberEnd() ||
selection_extent->value.IsNull()) {
result->Error(kInternalConsistencyError,
"Selection base/extent values invalid.");
return;
}
active_model_->SetEditingState(selection_base->value.GetInt(),
selection_extent->value.GetInt(),
text->value.GetString());
} else {
result->NotImplemented();
return;
}
// All error conditions return early, so if nothing has gone wrong indicate
// success.
result->Success();
}
void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
auto& allocator = args->GetAllocator();
args->PushBack(client_id_, allocator);
rapidjson::Value editing_state(rapidjson::kObjectType);
editing_state.AddMember(kComposingBaseKey, -1, allocator);
editing_state.AddMember(kComposingExtentKey, -1, allocator);
editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
allocator);
editing_state.AddMember(kSelectionBaseKey, model.selection_base(), allocator);
editing_state.AddMember(kSelectionExtentKey, model.selection_extent(),
allocator);
editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
editing_state.AddMember(
kTextKey, rapidjson::Value(model.GetText(), allocator).Move(), allocator);
args->PushBack(editing_state, allocator);
channel_->InvokeMethod(kUpdateEditingStateMethod, std::move(args));
}
void TextInputPlugin::EnterPressed(TextInputModel* model) {
if (input_type_ == kMultilineInputType) {
model->AddText(std::u16string({u'\n'}));
SendStateUpdate(*model);
}
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
auto& allocator = args->GetAllocator();
args->PushBack(client_id_, allocator);
args->PushBack(rapidjson::Value(input_action_, allocator).Move(), allocator);
channel_->InvokeMethod(kPerformActionMethod, std::move(args));
}
} // namespace flutter