浏览代码

platform message csharp regiser

/siyaoH-1.17-PlatformMessage
siyao 4 年前
当前提交
bea46202
共有 14 个文件被更改,包括 684 次插入118 次删除
  1. 4
      com.unity.uiwidgets/Runtime/cupertino/text_field.cs
  2. 4
      com.unity.uiwidgets/Runtime/external/simplejson/SimpleJSON.cs
  3. 7
      com.unity.uiwidgets/Runtime/services/message_codecs.cs
  4. 121
      com.unity.uiwidgets/Runtime/services/platform_channel.cs
  5. 44
      com.unity.uiwidgets/Runtime/services/raw_keyboard.cs
  6. 8
      com.unity.uiwidgets/Runtime/services/system_channels.cs
  7. 214
      com.unity.uiwidgets/Runtime/services/text_input.cs
  8. 4
      com.unity.uiwidgets/Runtime/ui2/channel_buffers.cs
  9. 2
      engine/Build.bee.cs
  10. 24
      engine/src/shell/platform/unity/windows/text_input_plugin.cc
  11. 5
      engine/src/shell/platform/unity/windows/text_input_plugin.h
  12. 72
      Samples/UIWidgetsSamples_2019_4/Assets/Script/TextField.cs
  13. 191
      engine/src/shell/platform/common/cpp/text_input_model.cc
  14. 102
      engine/src/shell/platform/common/cpp/text_input_model.h

4
com.unity.uiwidgets/Runtime/cupertino/text_field.cs


public GlobalKey<EditableTextState> editableTextKey {
get {
return GlobalKey<EditableTextState>.key();
return _editableTextKey;
public readonly GlobalKey<EditableTextState> _editableTextKey = GlobalKey<EditableTextState>.key();
public bool forcePressEnabled {
get { return true; }
}

4
com.unity.uiwidgets/Runtime/external/simplejson/SimpleJSON.cs


{
var sb = EscapeBuilder;
sb.Length = 0;
if(aText == null) {
// TODO: template fix
aText = "";
}
if (sb.Capacity < aText.Length + aText.Length / 10)
sb.Capacity = aText.Length + aText.Length / 10;
foreach (char c in aText)

7
com.unity.uiwidgets/Runtime/services/message_codecs.cs


using System.Collections;
using System.Globalization;
using System.Text;
using Unity.UIWidgets.external.simplejson;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.services;

writeValue(buffer, entry.Key);
writeValue(buffer, entry.Value);
}
} else if (value is JSONObject js) {
// TODO: template fix
buffer.putUint8(_valueString);
byte[] jsBytes = Encoding.UTF8.GetBytes(js.ToString());
writeSize(buffer, jsBytes.Length);
buffer.putUint8List(jsBytes);
}
else {
throw new ArgumentException(value.ToString());

121
com.unity.uiwidgets/Runtime/services/platform_channel.cs


using UnityEngine;
namespace Unity.UIWidgets.service {
public delegate Future<object> Handler(MethodCall call);
public delegate Future Handler(MethodCall call);
public class MethodChannel {
public MethodChannel(string name, MethodCodec codec, BinaryMessenger binaryMessenger = null) {

this.name = name;
this.codec = codec;
_binaryMessenger = binaryMessenger;
}

public readonly BinaryMessenger _binaryMessenger;
/*public void setMethodCallHandler( Handler handler) {
public void setMethodCallHandler(Handler handler) {
handler == null ? null : (byte[] message) => _handleAsMethodCall(message, handler)
);
}*/
handler == null ? (MessageHandler) null : (byte[] message) => {
_handleAsMethodCall(message, handler);
return Future.value().to<byte[]>();
});
}
Future<byte[]> _handleAsMethodCall(byte[] message, Handler handler) {
MethodCall call = codec.decodeMethodCall(message);
try {
return handler(call).then(obj => { return codec.encodeSuccessEnvelope(obj); }).to<byte[]>();
}
catch (PlatformException e) {
// TODO: check if this works
return Future.value(codec.encodeErrorEnvelope(
code: e.code,
message: e.message,
details: e.details
)).to<byte[]>();
}
catch (MissingPluginException ex) {
return null;
}
catch (Exception e) {
return Future.value(
codec.encodeErrorEnvelope(
code: "error",
message: e.ToString(),
details: null
)
).to<byte[]>();
}
}
// public Future<T> _invokeMethod<T>(string method, bool missingOk, object arguments ) {
// // async

// }
}
// class BasicMessageChannel<T> {
// /// Creates a [BasicMessageChannel] with the specified [name], [codec] and [binaryMessenger].
// ///
// /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
// /// instance is used if [binaryMessenger] is null.
// const BasicMessageChannel(this.name, this.codec, { BinaryMessenger binaryMessenger })
// : assert(name != null),
// assert(codec != null),
// _binaryMessenger = binaryMessenger;
//
// /// The logical channel on which communication happens, not null.
// final String name;
//
// /// The message codec used by this channel, not null.
// final MessageCodec<T> codec;
//
// /// The messenger which sends the bytes for this channel, not null.
// BinaryMessenger get binaryMessenger => _binaryMessenger ?? defaultBinaryMessenger; // ignore: deprecated_member_use_from_same_package
// final BinaryMessenger _binaryMessenger;
//
// /// Sends the specified [message] to the platform plugins on this channel.
// ///
// /// Returns a [Future] which completes to the received response, which may
// /// be null.
// Future<T> send(T message) async {
// return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
// }
class BasicMessageChannel<T> {
/// Creates a [BasicMessageChannel] with the specified [name], [codec] and [binaryMessenger].
///
/// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
/// instance is used if [binaryMessenger] is null.
public BasicMessageChannel(string name, MessageCodec<T> codec, BinaryMessenger binaryMessenger = null) {
D.assert(name != null);
D.assert(codec != null);
this.name = name;
this.codec = codec;
_binaryMessenger = binaryMessenger;
}
/// The logical channel on which communication happens, not null.
public readonly string name;
/// The message codec used by this channel, not null.
public readonly MessageCodec<T> codec;
/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger binaryMessenger {
get { return _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger; }
}
readonly BinaryMessenger _binaryMessenger;
/// Sends the specified [message] to the platform plugins on this channel.
///
/// Returns a [Future] which completes to the received response, which may
/// be null.
public Future<T> send(T message) {
return binaryMessenger.send(name, codec.encodeMessage(message)).then_<T>(result => {
return FutureOr.value(codec.decodeMessage(result));
});
}
public delegate Future<T> Handler(T message);
public void setMessageHandler(Handler handler) {
if (handler == null) {
binaryMessenger.setMessageHandler(name, null);
}
else {
binaryMessenger.setMessageHandler(name,
(byte[] message) => {
return handler(codec.decodeMessage(message)).then_<byte[]>(result => {
return FutureOr.value(codec.encodeMessage(result));
});
});
}
}
}
}

44
com.unity.uiwidgets/Runtime/services/raw_keyboard.cs


using System;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.services;
using UnityEngine;

public class RawKeyboard {
public static readonly RawKeyboard instance = new RawKeyboard();
public static readonly Dictionary<PhysicalKeyboardKey, LogicalKeyboardKey> _keysPressed = new Dictionary<PhysicalKeyboardKey, LogicalKeyboardKey>();

}
RawKeyboard() {
SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
}
readonly List<ValueChanged<RawKeyEvent>> _listeners = new List<ValueChanged<RawKeyEvent>>();

_listeners.Remove(listener);
}
Future<object> _handleKeyEvent(object message) {
Debug.Log("message: reach c# from cpp!");
return Future.value().to<object>();
// RawKeyEvent keyEvent = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
// if (keyEvent == null) {
// return;
// }
// if (keyEvent.data is RawKeyEventDataMacOs && keyEvent.logicalKey == LogicalKeyboardKey.fn) {
// // On macOS laptop keyboards, the fn key is used to generate home/end and
// // f1-f12, but it ALSO generates a separate down/up keyEvent for the fn key
// // itself. Other platforms hide the fn key, and just produce the key that
// // it is combined with, so to keep it possible to write cross platform
// // code that looks at which keys are pressed, the fn key is ignored on
// // macOS.
// return;
// }
// if (keyEvent is RawKeyDownEvent) {
// _keysPressed[keyEvent.physicalKey] = keyEvent.logicalKey;
// }
// if (keyEvent is RawKeyUpEvent) {
// // Use the physical key in the key up keyEvent to find the physical key from
// // the corresponding key down keyEvent and remove it, even if the logical
// // keys don't match.
// _keysPressed.remove(keyEvent.physicalKey);
// }
// // Make sure that the modifiers reflect reality, in case a modifier key was
// // pressed/released while the app didn't have focus.
// _synchronizeModifiers(keyEvent);
// if (_listeners.isEmpty) {
// return;
// }
// for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
// if (_listeners.contains(listener)) {
// listener(keyEvent);
// }
// }
}
internal void _handleKeyEvent(Event evt) {
if (_listeners.isEmpty()) {
return;

8
com.unity.uiwidgets/Runtime/services/system_channels.cs


"flutter/textinput",
new JSONMethodCodec()
);
// public static BasicMessageChannel<object> keyEvent = new BasicMessageChannel<object>(
// "flutter/keyevent",
// new JSONMessageCodec()
// );
public static BasicMessageChannel<object> keyEvent = new BasicMessageChannel<object>(
"flutter/keyevent",
new JSONMessageCodec()
);
// public static BasicMessageChannel<string> lifecycle = BasicMessageChannel<string>(
// "flutter/lifecycle",

214
com.unity.uiwidgets/Runtime/services/text_input.cs


using UnityEngine;
namespace Unity.UIWidgets.service {
public enum SmartDashesType {
disabled,
enabled

disabled,
enabled
}
public class TextInputType : IEquatable<TextInputType> {
public readonly int index;
public readonly bool? signed;

}
public static readonly TextInputType text = new TextInputType(0);
public static readonly TextInputType multiline = new TextInputType(1);
public static readonly TextInputType number = numberWithOptions();

return $"{GetType().FullName}(name: {_name}, signed: {signed}, decimal: {decimalNum})";
}
}
public enum TextInputAction {
none,
unspecified,

emergencyCall,
newline
}
public enum TextCapitalization {
words,
sentences,

public class TextInputConfiguration {
public TextInputConfiguration(
TextInputType inputType = null,
bool obscureText = false,
bool autocorrect = true,
bool obscureText = false,
bool autocorrect = true,
SmartDashesType? smartDashesType = null,
SmartQuotesType? smartQuotesType = null,
bool enableSuggestions = true,

TextCapitalization textCapitalization = TextCapitalization.none,
bool unityTouchKeyboard = false
) {
) {
D.assert(inputType != null);
this.smartDashesType =
smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled);

this.keyboardAppearance = keyboardAppearance;
this.unityTouchKeyboard = unityTouchKeyboard;
}
public readonly TextInputType inputType;
public readonly bool obscureText;
public readonly bool autocorrect;

public readonly TextInputAction inputAction;
public readonly TextCapitalization textCapitalization;
public readonly ui.Brightness keyboardAppearance;
public readonly bool unityTouchKeyboard;
public JSONNode toJson() {

return json;
}
}
static partial class TextInputUtils {
internal static TextAffinity? _toTextAffinity(string affinity) {
switch (affinity) {

return FloatingCursorDragState.End;
}
throw new UIWidgetsError(new List<DiagnosticsNode>() {new ErrorSummary($"Unknown text cursor action: {state}")});
throw new UIWidgetsError(new List<DiagnosticsNode>()
{new ErrorSummary($"Unknown text cursor action: {state}")});
Dictionary<string, float?> encoded) {
Dictionary<string, float?> encoded) {
D.assert(encoded.getOrDefault("X") != null,
() => "You must provide a value for the horizontal location of the floating cursor.");
D.assert(encoded.getOrDefault("Y") != null,

}
}
public enum FloatingCursorDragState {
Start,
Update,

public class RawFloatingCursorPoint {
public RawFloatingCursorPoint(
public RawFloatingCursorPoint(
Offset offset = null,
FloatingCursorDragState? state = null
) {

string text = "",
TextSelection selection = null,
TextRange composing = null) {
D.assert(text != null);
this.text = text;
this.composing = composing ?? TextRange.empty;

if (start < text.Length && char.IsLowSurrogate(text[start])) {
start++;
}
this.selection = selection.copyWith(start, end);
}
else {

json["composingExtent"] = composing.end;
return json;
}
public static readonly TextEditingValue empty = new TextEditingValue();
public TextEditingValue copyWith(

return new TextEditingValue(
text: text ?? this.text,
selection: selection ?? this.selection,
text: text ?? this.text,
selection: selection ?? this.selection,
composing: composing ?? this.composing
);
}

newText = selection.textBefore(this.text) + text + selection.textAfter(this.text);
newSelection = TextSelection.collapsed(selection.start + text.Length);
return new TextEditingValue(
text: newText,
text: newText,
selection: newSelection,
composing: TextRange.empty
);

}
return copyWith(text: text.Substring(0, selection.start) +
text.Substring(selection.start + 1),
text.Substring(selection.start + 1),
composing: TextRange.empty);
}
else {

D.assert(!string.IsNullOrEmpty(composeText));
var composeStart = composing == TextRange.empty ? selection.start : composing.start;
var lastComposeEnd = composing == TextRange.empty ? selection.end : composing.end;
composeStart = Mathf.Clamp(composeStart, 0, text.Length);
lastComposeEnd = Mathf.Clamp(lastComposeEnd, 0, text.Length);
var newText = text.Substring(0, composeStart) + composeText + text.Substring(lastComposeEnd);

void bringIntoView(TextPosition textPosition);
bool cutEnabled {
get;
}
bool copyEnabled {
get;
}
bool pasteEnabled {
get;
}
bool selectAllEnabled {
get;
}
bool cutEnabled { get; }
bool copyEnabled { get; }
bool pasteEnabled { get; }
bool selectAllEnabled { get; }
}
public interface TextInputClient {

void updateFloatingCursor(RawFloatingCursorPoint point);
//unity-specific
RawInputKeyResponse globalInputKeyHandler(RawKeyEvent evt);
}

_client = client;
_id = _nextId++;
}
D.assert(() =>{
D.assert(() => {
public bool attached {
public bool attached {
Dictionary<string,object> dictionary = new Dictionary<string, object>();
Dictionary<string, object> dictionary = new Dictionary<string, object>();
dictionary
dictionary
string fontFamily,
float? fontSize,
FontWeight fontWeight,
TextDirection textDirection,
TextAlign textAlign
string fontFamily,
float? fontSize,
FontWeight fontWeight,
TextDirection textDirection,
TextAlign textAlign
Dictionary<string,object> dictionary = new Dictionary<string, object>();
Dictionary<string, object> dictionary = new Dictionary<string, object>();
dictionary["textAlignIndex"] = (int)textAlign;
dictionary["textDirectionIndex"] = (int)textDirection;
dictionary["textAlignIndex"] = (int) textAlign;
dictionary["textDirectionIndex"] = (int) textDirection;
dictionary
dictionary
public void close() {
if (attached) {
TextInput.keyboardDelegate.clearClient();

}
internal readonly Window _window;
TouchScreenKeyboard _keyboard;
}

static internal KeyboardDelegate keyboardDelegate;
public TextInput() {
Debug.Log("init text");
_channel = SystemChannels.textInput;
_channel.setMethodCallHandler(_handleTextInputInvocation);
static void setChannel(MethodChannel newChannel) {
D.assert(() => {
newChannel.setMethodCallHandler(_instance._handleTextInputInvocation);
_instance._channel = newChannel; //..setMethodCallHandler(_instance._handleTextInputInvocation);
return true;
});
}
static readonly TextInput _instance = new TextInput();
_instance._attach(connection, configuration);
if (keyboardDelegate != null) {
keyboardDelegate.Dispose();
}

return connection;
}
void _attach(TextInputConnection connection, TextInputConfiguration configuration) {
D.assert(connection != null);
D.assert(connection._client != null);
D.assert(configuration != null);
// D.assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction));
_channel.invokeMethod(
"TextInput.setClient",
new List<object> {connection._id, configuration.toJson()}
);
_currentConnection = connection;
_currentConfiguration = configuration;
}
MethodChannel _channel;
TextInputConfiguration _currentConfiguration;
Future _handleTextInputInvocation(MethodCall methodCall) {
if (_currentConnection == null)
return Future.value();
string method = methodCall.method;
// The requestExistingInputState request needs to be handled regardless of
// the client ID, as long as we have a _currentConnection.
if (method == "TextInputClient.requestExistingInputState") {
D.assert(_currentConnection._client != null);
_attach(_currentConnection, _currentConfiguration);
TextEditingValue editingValue = _currentConnection._client.currentTextEditingValue;
if (editingValue != null) {
_setEditingState(editingValue);
}
return Future.value();
}
List<object> args = methodCall.arguments as List<object>;
int client = (int) args[0];
// The incoming message was for a different client.
if (client != _currentConnection._id)
return Future.value();
switch (method) {
case "TextInputClient.updateEditingState":
_currentConnection._client.updateEditingValue(TextEditingValue.fromJSON(args[1] as JSONNode),
false); //Dictionary<string,object>));
break;
// case "TextInputClient.performAction":
// _currentConnection._client.performAction(_toTextInputAction(args[1] as String));
// break;
// case "TextInputClient.updateFloatingCursor":
// _currentConnection._client.updateFloatingCursor(_toTextPoint(
// _toTextCursorAction(args[1] as String),
// args[2] as Map<String, dynamic>,
// ));
// break;
// case "TextInputClient.onConnectionClosed":
// _currentConnection._client.connectionClosed();
// break;
default:
throw new MissingPluginException();
}
return Future.value();
}
void _setEditingState(TextEditingValue value) {
D.assert(value != null);
_channel.invokeMethod(
"TextInput.setEditingState",
value.toJSON()
);
}
internal static void Update() {
if (_currentConnection != null && _currentConnection._window == Window.instance) {
(keyboardDelegate as TextInputUpdateListener)?.Update();

return;
}
_currentConnection._client.updateEditingValue(value, isIMEInput);
_currentConnection._client.updateEditingValue(value, isIMEInput);
}
internal static void _performAction(int client, TextInputAction action) {

});
}
}
}
}

4
com.unity.uiwidgets/Runtime/ui2/channel_buffers.cs


using System.Collections.Generic;
using System.Text;
using Unity.UIWidgets.async2;
using Unity.UIWidgets.foundation;
using UnityEngine;
namespace Unity.UIWidgets.ui {

}
_StoredMessage? _pop(string channel) {
_RingBuffer<_StoredMessage> queue = _messages[channel];
Debug.Log("channel pop up");
_RingBuffer<_StoredMessage> queue = _messages.getOrDefault(channel, null);
_StoredMessage? result = queue?.pop();
return result;
}

2
engine/Build.bee.cs


"src/shell/platform/common/cpp/incoming_message_dispatcher.cc",
"src/shell/platform/common/cpp/incoming_message_dispatcher.h",
"src/shell/platform/common/cpp/text_input_model.cc",
"src/shell/platform/common/cpp/text_input_model.h",
"src/shell/platform/embedder/embedder.cc",
"src/shell/platform/embedder/embedder.h",

24
engine/src/shell/platform/unity/windows/text_input_plugin.cc


: channel_(std::make_unique<uiwidgets::MethodChannel<rapidjson::Document>>(
messenger,
kChannelName,
&uiwidgets::JsonMethodCodec::GetInstance()))/*,
active_model_(nullptr)*/ {
&uiwidgets::JsonMethodCodec::GetInstance())),
active_model_(nullptr) {
channel_->SetMethodCallHandler(
[this](
const uiwidgets::MethodCall<rapidjson::Document>& call,

if (method.compare(kShowMethod) == 0 || method.compare(kHideMethod) == 0) {
// These methods are no-ops.
} else if (method.compare(kClearClientMethod) == 0) {
//active_model_ = nullptr;
active_model_ = nullptr;
} else {
// Every following method requires args.
if (!method_call.arguments() || method_call.arguments()->IsNull()) {

return;
}
int client_id = client_id_json.GetInt();
//active_model_ =
// std::make_unique<TextInputModel>(client_id, client_config);
active_model_ =
std::make_unique<TextInputModel>(client_id, client_config);
/*if (active_model_ == nullptr) {
if (active_model_ == nullptr) {
}*/
}
auto text = args.FindMember(kTextKey);
if (text == args.MemberEnd() || text->value.IsNull()) {
result->Error(kBadArgumentError,

"Selection base/extent values invalid.");
return;
}
/* active_model_->SetEditingState(selection_base->value.GetInt(),
active_model_->SetEditingState(selection_base->value.GetInt(),
text->value.GetString());*/
text->value.GetString());
} else {
// Unhandled method.
result->NotImplemented();

result->Success();
}
// void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
// channel_->InvokeMethod(kUpdateEditingStateMethod, model.GetState());
// }
void TextInputPlugin::SendStateUpdate(const TextInputModel& model) {
channel_->InvokeMethod(kUpdateEditingStateMethod, model.GetState());
}
// void TextInputPlugin::EnterPressed(TextInputModel* model) {
// if (model->input_type() == kMultilineInputType) {

5
engine/src/shell/platform/unity/windows/text_input_plugin.h


#include "rapidjson/rapidjson.h"
#include "shell/platform/common/cpp/client_wrapper/include/uiwidgets/binary_messenger.h"
#include "shell/platform/common/cpp/client_wrapper/include/uiwidgets/method_channel.h"
//#include "shell/platform/common/cpp/text_input_model.h"
#include "shell/platform/common/cpp/text_input_model.h"
//#include "shell/platform/windows/keyboard_hook_handler.h"
#include "shell/platform/unity/windows/public/uiwidgets_windows.h"

//void SendStateUpdate(const TextInputModel& model);
//void EnterPressed(TextInputModel* model);
void TextInputPlugin::SendStateUpdate(const TextInputModel& model);
void HandleMethodCall(
const uiwidgets::MethodCall<rapidjson::Document>& method_call,

//std::unique_ptr<TextInputModel> active_model_;
std::unique_ptr<TextInputModel> active_model_;
};
} // namespace uiwidgets

72
Samples/UIWidgetsSamples_2019_4/Assets/Script/TextField.cs


using Unity.UIWidgets.engine2;
using Unity.UIWidgets.widgets;
using ui_ = Unity.UIWidgets.widgets.ui_;
using Unity.UIWidgets.cupertino;
namespace UIWidgetsSample
{
public class TextField : UIWidgetsPanel
{
protected void OnEnable()
{
base.OnEnable();
}
protected override void main()
{
ui_.runApp(new MyApp());
}
class MyApp : StatelessWidget
{
public override Widget build(BuildContext context)
{
return new CupertinoApp(
home: new HomeScreen()
);
}
}
class HomeScreen : StatelessWidget
{
public override Widget build(BuildContext context)
{
return new HomeScreen3();
}
}
public class HomeScreen3 : StatelessWidget
{
public override Widget build(BuildContext context)
{
return new CupertinoPageScaffold(
child: new Center(
child: new MyPrefilledText()
)
);
}
}
public class MyPrefilledText : StatefulWidget
{
public override State createState() => new _MyPrefilledTextState();
}
class _MyPrefilledTextState : State<MyPrefilledText>
{
TextEditingController _textController;
public override void initState()
{
base.initState();
_textController = new TextEditingController(text: "initial text");
}
public override Widget build(BuildContext context)
{
return new CupertinoTextField(controller: _textController);
}
}
}
}

191
engine/src/shell/platform/common/cpp/text_input_model.cc


// Copyright 2013 The UIWidgets 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 "shell/platform/common/cpp/text_input_model.h"
#include <codecvt>
#include <iostream>
#include <locale>
// TODO(awdavies): Need to fix this regarding issue #47.
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";
// Input client configuration keys.
static constexpr char kTextInputAction[] = "inputAction";
static constexpr char kTextInputType[] = "inputType";
static constexpr char kTextInputTypeName[] = "name";
#if defined(_MSC_VER)
// TODO(naifu): This temporary code is to solve link error.(VS2015/2017)
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/8f40dcd8-c67f-4eba-9134-a19b9178e481/vs-2015-rc-linker-stdcodecvt-error
std::locale::id std::codecvt<char32_t, char, _Mbstatet>::id;
#endif // defined(_MSC_VER)
namespace uiwidgets {
TextInputModel::TextInputModel(int client_id, const rapidjson::Value& config)
: client_id_(client_id),
selection_base_(text_.begin()),
selection_extent_(text_.begin()) {
// TODO: Improve error handling during refactoring; this is just minimal
// checking to avoid asserts since RapidJSON is stricter than jsoncpp.
if (config.IsObject()) {
auto input_action = config.FindMember(kTextInputAction);
if (input_action != config.MemberEnd() && input_action->value.IsString()) {
input_action_ = input_action->value.GetString();
}
auto input_type_info = config.FindMember(kTextInputType);
if (input_type_info != config.MemberEnd() &&
input_type_info->value.IsObject()) {
auto input_type = input_type_info->value.FindMember(kTextInputTypeName);
if (input_type != input_type_info->value.MemberEnd() &&
input_type->value.IsString()) {
input_type_ = input_type->value.GetString();
}
}
}
}
TextInputModel::~TextInputModel() = default;
bool TextInputModel::SetEditingState(size_t selection_base,
size_t selection_extent,
const std::string& text) {
if (selection_base > selection_extent) {
return false;
}
// Only checks extent since it is implicitly greater-than-or-equal-to base.
if (selection_extent > text.size()) {
return false;
}
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32conv;
text_ = utf32conv.from_bytes(text);
selection_base_ = text_.begin() + selection_base;
selection_extent_ = text_.begin() + selection_extent;
return true;
}
void TextInputModel::DeleteSelected() {
selection_base_ = text_.erase(selection_base_, selection_extent_);
// Moves extent back to base, so that it is a single cursor placement again.
selection_extent_ = selection_base_;
}
void TextInputModel::AddCharacter(char32_t c) {
if (selection_base_ != selection_extent_) {
DeleteSelected();
}
selection_extent_ = text_.insert(selection_extent_, c);
selection_extent_++;
selection_base_ = selection_extent_;
}
bool TextInputModel::Backspace() {
if (selection_base_ != selection_extent_) {
DeleteSelected();
return true;
}
if (selection_base_ != text_.begin()) {
selection_base_ = text_.erase(selection_base_ - 1, selection_base_);
selection_extent_ = selection_base_;
return true;
}
return false; // No edits happened.
}
bool TextInputModel::Delete() {
if (selection_base_ != selection_extent_) {
DeleteSelected();
return true;
}
if (selection_base_ != text_.end()) {
selection_base_ = text_.erase(selection_base_, selection_base_ + 1);
selection_extent_ = selection_base_;
return true;
}
return false;
}
void TextInputModel::MoveCursorToBeginning() {
selection_base_ = text_.begin();
selection_extent_ = text_.begin();
}
void TextInputModel::MoveCursorToEnd() {
selection_base_ = text_.end();
selection_extent_ = text_.end();
}
bool TextInputModel::MoveCursorForward() {
// If about to move set to the end of the highlight (when not selecting).
if (selection_base_ != selection_extent_) {
selection_base_ = selection_extent_;
return true;
}
// If not at the end, move the extent forward.
if (selection_extent_ != text_.end()) {
selection_extent_++;
selection_base_++;
return true;
}
return false;
}
bool TextInputModel::MoveCursorBack() {
// If about to move set to the beginning of the highlight
// (when not selecting).
if (selection_base_ != selection_extent_) {
selection_extent_ = selection_base_;
return true;
}
// If not at the start, move the beginning backward.
if (selection_base_ != text_.begin()) {
selection_base_--;
selection_extent_--;
return true;
}
return false;
}
std::unique_ptr<rapidjson::Document> TextInputModel::GetState() const {
// TODO(stuartmorgan): Move client_id out up to the plugin so that this
// function just returns the editing state.
auto args = std::make_unique<rapidjson::Document>(rapidjson::kArrayType);
auto& allocator = args->GetAllocator();
args->PushBack(client_id_, allocator);
rapidjson::Value editing_state(rapidjson::kObjectType);
// TODO(awdavies): Most of these are hard-coded for now.
editing_state.AddMember(kComposingBaseKey, -1, allocator);
editing_state.AddMember(kComposingExtentKey, -1, allocator);
editing_state.AddMember(kSelectionAffinityKey, kAffinityDownstream,
allocator);
editing_state.AddMember(kSelectionBaseKey,
static_cast<int>(selection_base_ - text_.begin()),
allocator);
editing_state.AddMember(kSelectionExtentKey,
static_cast<int>(selection_extent_ - text_.begin()),
allocator);
editing_state.AddMember(kSelectionIsDirectionalKey, false, allocator);
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf8conv;
editing_state.AddMember(
kTextKey, rapidjson::Value(utf8conv.to_bytes(text_), allocator).Move(),
allocator);
args->PushBack(editing_state, allocator);
return args;
}
} // namespace uiwidgets

102
engine/src/shell/platform/common/cpp/text_input_model.h


// Copyright 2013 The UIWidgets Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UIWIDGETS_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_
#define UIWIDGETS_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_
#include <memory>
#include <string>
#include "rapidjson/document.h"
namespace uiwidgets {
// Handles underlying text input state, using a simple ASCII model.
//
// Ignores special states like "insert mode" for now.
class TextInputModel {
public:
TextInputModel(int client_id, const rapidjson::Value& config);
virtual ~TextInputModel();
// Attempts to set the text state.
//
// Returns false if the state is not valid (base or extent are out of
// bounds, or base is less than extent).
bool SetEditingState(size_t selection_base,
size_t selection_extent,
const std::string& text);
// Adds a character.
//
// Either appends after the cursor (when selection base and extent are the
// same), or deletes the selected characters, replacing the text with the
// character specified.
void AddCharacter(char32_t c);
// Deletes either the selection, or one character ahead of the cursor.
//
// Deleting one character ahead of the cursor occurs when the selection base
// and extent are the same.
//
// Returns true if any deletion actually occurred.
bool Delete();
// Deletes either the selection, or one character behind the cursor.
//
// Deleting one character behind the cursor occurs when the selection base
// and extent are the same.
//
// Returns true if any deletion actually occurred.
bool Backspace();
// Attempts to move the cursor backward.
//
// Returns true if the cursor could be moved. Changes base and extent to be
// equal to either the extent (if extent is at the end of the string), or
// for extent to be equal to
bool MoveCursorBack();
// Attempts to move the cursor forward.
//
// Returns true if the cursor could be moved.
bool MoveCursorForward();
// Attempts to move the cursor to the beginning.
//
// Returns true if the cursor could be moved.
void MoveCursorToBeginning();
// Attempts to move the cursor to the back.
//
// Returns true if the cursor could be moved.
void MoveCursorToEnd();
// Returns the state in the form of a platform message.
std::unique_ptr<rapidjson::Document> GetState() const;
// Id of the text input client.
int client_id() const { return client_id_; }
// Keyboard type of the client. See available options:
// https://docs.uiwidgets.io/uiwidgets/services/TextInputType-class.html
std::string input_type() const { return input_type_; }
// An action requested by the user on the input client. See available options:
// https://docs.uiwidgets.io/uiwidgets/services/TextInputAction-class.html
std::string input_action() const { return input_action_; }
private:
void DeleteSelected();
std::u32string text_;
int client_id_;
std::string input_type_;
std::string input_action_;
std::u32string::iterator selection_base_;
std::u32string::iterator selection_extent_;
};
} // namespace uiwidgets
#endif // UIWIDGETS_SHELL_PLATFORM_CPP_TEXT_INPUT_MODEL_H_
正在加载...
取消
保存