浏览代码

text edit keyboard

/main
fzhangtj 6 年前
当前提交
1262bcba
共有 8 个文件被更改,包括 505 次插入73 次删除
  1. 6
      Runtime/editor/editor_window.cs
  2. 2
      Runtime/rendering/editable.cs
  3. 218
      Runtime/service/text_input.cs
  4. 33
      Runtime/widgets/editable_text.cs
  5. 32
      Samples/UIWidgetSample/TextInput.unity
  6. 96
      Samples/UIWidgetSample/TextInputCanvas.cs
  7. 180
      Runtime/service/keyboard.cs
  8. 11
      Runtime/service/keyboard.cs.meta

6
Runtime/editor/editor_window.cs


}
if (this._textInput != null) {
this._textInput.OnGUI();
this._textInput.keyboardManager.OnGUI();
}
}

using (this.getScope()) {
if (this._textInput != null) {
this._textInput.keyboardManager.Update();
}
this._timerProvider.update(this.flushMicrotasks);
this.flushMicrotasks();
}

2
Runtime/rendering/editable.cs


D.assert(this._textLayoutLastWidth == this.constraints.maxWidth);
var effectiveOffset = offset + this._paintOffset;
if (this._selection != null) {
if (this._selection != null && this._selection.isValid) {
if (this._selection.isCollapsed && this._showCursor.value && this.cursorColor != null) {
this._paintCaret(context.canvas, effectiveOffset);
}

218
Runtime/service/text_input.cs


using System;
using System.Collections.Generic;
public class TextInputType:IEquatable<TextInputType> {
public readonly int index;
public readonly bool? signed;
public readonly bool? decimalNum;
TextInputType(int index, bool? signed = null, bool? decimalNum = null) {
this.index = index;
this.signed = signed;
this.decimalNum = decimalNum;
}
public static TextInputType numberWithOptions(bool signed = false, bool decimalNum = false) {
return new TextInputType(2, signed: signed, decimalNum: decimalNum);
}
public static readonly TextInputType text = new TextInputType(0);
public static readonly TextInputType multiline = new TextInputType(1);
public static readonly TextInputType number = numberWithOptions();
public static readonly TextInputType phone = new TextInputType(3);
public static readonly TextInputType datetime = new TextInputType(4);
public static readonly TextInputType emailAddress = new TextInputType(5);
public static readonly TextInputType url = new TextInputType(6);
public static List<string> _names = new List<string> {
"text", "multiline", "number", "phone", "datetime", "emailAddress", "url"
};
public Dictionary<string, object> toJson() {
return new Dictionary<string, object>() {
{"name", this._name},
{"signed", this.signed},
{"decimal", this.decimalNum}
};
}
string _name => $"TextInputType.{_names[this.index]}";
public bool Equals(TextInputType other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this.index == other.index && this.signed == other.signed && this.decimalNum == other.decimalNum;
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((TextInputType) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = this.index;
hashCode = (hashCode * 397) ^ this.signed.GetHashCode();
hashCode = (hashCode * 397) ^ this.decimalNum.GetHashCode();
return hashCode;
}
}
public static bool operator ==(TextInputType left, TextInputType right) {
return Equals(left, right);
}
public static bool operator !=(TextInputType left, TextInputType right) {
return !Equals(left, right);
}
public override string ToString() {
return $"{this.GetType().FullName}(name: {this._name}, signed: {this.signed}, decimal: {this.decimalNum})";
}
}
public class TextEditingValue : IEquatable<TextEditingValue> {
public readonly string text;
public readonly TextSelection selection;

scrollPageUp,
scrollPageDown,
}
// text client
public class TextInputConfiguration {
public TextInputConfiguration(TextInputType inputType = null,
bool obscureText = false, bool autocorrect = true, TextInputAction inputAction = TextInputAction.done) {
this.inputType = inputType ?? TextInputType.text;
this.inputAction = inputAction;
this.obscureText = obscureText;
this.autocorrect = autocorrect;
}
public readonly TextInputType inputType;
public readonly bool obscureText;
public readonly bool autocorrect;
public readonly TextInputAction inputAction;
public Dictionary<string, object> toJson() {
return new Dictionary<string, object>() {
{"inputType", this.inputType.toJson()},
{"obscureText", this.obscureText},
{"autocorrect", this.autocorrect},
{"inputAction", this.inputAction.ToString()}
};
}
}
public class TextInputConnection {
internal TextInputConnection(TextInputClient client, TextInput textInput) {

public void setEditingState(TextEditingValue value) {
D.assert(this.attached);
this._textInput._value = value;
this._textInput.keyboardManager.setEditingState(value);
}
public void setCompositionCursorPos(double x, double y) {

public void close() {
if (this.attached) {
this._textInput.keyboardManager.clearClient();
this._textInput._value = null;
this._textInput._scheduleHide();
public void show() {
D.assert(this.attached);
Input.imeCompositionMode = IMECompositionMode.On;
this._textInput.keyboardManager.show();
}
TouchScreenKeyboard _keyboard;
internal TextEditingValue _value;
string _lastCompositionString;
public readonly KeyboadManager keyboardManager;
public TextInputConnection attach(TextInputClient client) {
public TextInput() {
this.keyboardManager = new KeyboadManager(this);
}
public TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) {
this.keyboardManager.setClient(connection._id, configuration);
Input.imeCompositionMode = IMECompositionMode.On;
public void OnGUI() {
public void setCompositionCursorPos(double x, double y) {
Input.compositionCursorPos = new Vector2((float) x, (float) y);
}
internal void _updateEditingState(int client, TextEditingValue value) {
var currentEvent = Event.current;
if (currentEvent.type == EventType.KeyDown) {
var action = TextInputUtils.getInputAction(currentEvent);
if (action != null) {
this._performAction(this._currentConnection._id, action.Value);
}
if (client != this._currentConnection._id) {
return;
}
if (action == null || action == TextInputAction.newline) {
if (currentEvent.keyCode == KeyCode.None) {
this._value = this._value.clearCompose().insert(new string(currentEvent.character, 1));
this._updateEditingState(this._currentConnection._id, this._value);
}
}
this._currentConnection._client.updateEditingValue(value);
}
currentEvent.Use();
internal void _performAction(int client, TextInputAction action) {
if (this._currentConnection == null) {
return;
if (!string.IsNullOrEmpty(Input.compositionString) &&
this._lastCompositionString != Input.compositionString) {
this._value = this._value.compose(Input.compositionString);
this._updateEditingState(this._currentConnection._id, this._value);
if (client != this._currentConnection._id) {
return;
this._lastCompositionString = Input.compositionString;
}
public void setCompositionCursorPos(double x, double y) {
Input.compositionCursorPos = new Vector2((float) x, (float) y);
}
void _updateEditingState(int client, TextEditingValue value) {
Window.instance.run(() => {
if (this._currentConnection == null) {
return;
}
if (client != this._currentConnection._id) {
return;
}
this._currentConnection._client.updateEditingValue(value);
});
this._currentConnection._client.performAction(action);
void _performAction(int client, TextInputAction action) {
Window.instance.run(() => {
bool _hidePending = false;
internal void _scheduleHide() {
if (this._hidePending) {
return;
}
this._hidePending = true;
Window.instance.scheduleMicrotask(() => {
this._hidePending = false;
return;
}
this.keyboardManager.hide();
if (client != this._currentConnection._id) {
return;
this._currentConnection._client.performAction(action);
});
}
}

33
Runtime/widgets/editable_text.cs


public readonly Color cursorColor;
public readonly int maxLines;
public readonly int? maxLines;
public readonly bool autofocus;

public readonly TextInputType keyboardType;
public readonly TextInputAction? textInputAction;
public readonly ValueChanged<string> onChanged;
public readonly VoidCallback onEditingComplete;

public EditableText(TextEditingController controller, FocusNode focusNode, TextStyle style,
Color cursorColor, bool obscureText = false, bool autocorrect = false,
TextAlign textAlign = TextAlign.left, TextDirection? textDirection = null,
double? textScaleFactor = null, int maxLines = 1,
double? textScaleFactor = null, int? maxLines = 1,
TextInputType keyboardType = null, TextInputAction? textInputAction = null,
ValueChanged<string> onChanged = null, VoidCallback onEditingComplete = null,
ValueChanged<string> onSubmitted = null, SelectionChangedCallback onSelectionChanged = null,
List<TextInputFormatter> inputFormatters = null, bool rendererIgnoresPointer = false,

D.assert(focusNode != null);
D.assert(style != null);
D.assert(cursorColor != null);
D.assert(maxLines == null || maxLines > 0);
this.keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline);
this.scrollPadding = scrollPadding ?? EdgeInsets.all(20.0);
this.controller = controller;
this.focusNode = focusNode;

this.textAlign = textAlign;
this.textDirection = textDirection;
this.textScaleFactor = textScaleFactor;
this.textInputAction = textInputAction;
this.cursorColor = cursorColor;
this.maxLines = maxLines;
this.autofocus = autofocus;

defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<double?>("textScaleFactor", this.textScaleFactor,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<int>("maxLines", this.maxLines, defaultValue: 1));
properties.add(new DiagnosticsProperty<int?>("maxLines", this.maxLines, defaultValue: 1));
properties.add(new DiagnosticsProperty<TextInputType>("keyboardType", this.keyboardType, defaultValue: null));
}
}

if (!this._hasInputConnection) {
TextEditingValue localValue = this._value;
this._lastKnownRemoteTextEditingValue = localValue;
this._textInputConnection = Window.instance.textInput.attach(this);
this._textInputConnection = Window.instance.textInput.attach(this, new TextInputConfiguration(
inputType: this.widget.keyboardType,
obscureText: this.widget.obscureText,
autocorrect: this.widget.autocorrect,
inputAction: this.widget.textInputAction ?? ((this.widget.keyboardType == TextInputType.multiline) ?
TextInputAction.newline: TextInputAction.done)
));
this._textInputConnection.show();
}
void _closeInputConnectionIfNeeded() {

}
void _startOrStopCursorTimerIfNeeded() {
if (this._cursorTimer == null && this._hasFocus && this._value.selection.isCollapsed) {
if (this._cursorTimer == null && this._hasFocus && this._value.selection.isCollapsed &&
!Window.instance.textInput.keyboardManager.textInputOnKeyboard()) {
this._startCursorTimer();
}
else if (this._cursorTimer != null && (!this._hasFocus || !this._value.selection.isCollapsed)) {

if (this.widget.obscureText) {
text = new string(RenderEditable.obscuringCharacter, text.Length);
int o = this._obscureShowCharTicksPending > 0 ? this._obscureLatestCharIndex : -1;
if (o >= 0 && o < text.Length) {
if (!Window.instance.textInput.keyboardManager.textInputOnKeyboard() && o >= 0 && o < text.Length) {
text = text.Substring(0, o) + this._value.text.Substring(o, 1) + text.Substring(o + 1);
}
}

public readonly Color cursorColor;
public readonly ValueNotifier<bool> showCursor;
public readonly bool hasFocus;
public readonly int maxLines;
public readonly int? maxLines;
public readonly Color selectionColor;
public readonly double textScaleFactor;
public readonly TextAlign textAlign;

public _Editable(TextSpan textSpan = null, TextEditingValue value = null,
Color cursorColor = null, ValueNotifier<bool> showCursor = null, bool hasFocus = false,
int maxLines = 0, Color selectionColor = null, double textScaleFactor = 1.0,
int? maxLines = null, Color selectionColor = null, double textScaleFactor = 1.0,
TextDirection? textDirection = null, bool obscureText = false, TextAlign textAlign = TextAlign.left,
bool autocorrect = false, ViewportOffset offset = null, SelectionChangedHandler onSelectionChanged = null,
CaretChangedHandler onCaretChanged = null, bool rendererIgnoresPointer = false,

32
Samples/UIWidgetSample/TextInput.unity


m_GameObject: {fileID: 244594849}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_ClearFlags: 2
m_BackGroundColor: {r: 1, g: 1, b: 1, a: 1}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}

m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 0}
m_Pivot: {x: 0.5, y: 0.5}

m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &1142533064
RectTransform:
m_ObjectHideFlags: 0

m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Texture: {fileID: 0}
m_UVRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
--- !u!222 &1142533066
CanvasRenderer:
m_ObjectHideFlags: 0

m_Father: {fileID: 304189374}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 160, y: -305}
m_SizeDelta: {x: 300, y: 400}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1387978528
MonoBehaviour:

m_Calls: []
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
m_Texture: {fileID: 0}
m_UVRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
--- !u!222 &1387978529
CanvasRenderer:
m_ObjectHideFlags: 0

m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!224 &1399904531
RectTransform:
m_ObjectHideFlags: 0

m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 10, y: 0}
m_Pivot: {x: 0.5, y: 0.5}

96
Samples/UIWidgetSample/TextInputCanvas.cs


using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.service;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace UIWidgetsSample {

}
protected override Widget getWidget() {
return new TextInputSample(key: null, title: this.gameObject.name);
return new EditableInputTypeWidget();
// return new TextInputSample(key: null, title: this.gameObject.name);
}
class _TextInputSampleState : State<TextInputSample> {

);
return container;
}
}
}
public class EditableInputTypeWidget : StatelessWidget {
Widget rowWidgets(string title, Widget widget) {
return new Container(
height: 80,
//width:300,
child: new Row(
children: new List<Widget> {
new Container(width: 100, child: new Text(title)),
new Flexible(child: new Container(child: widget, padding: EdgeInsets.all(4), decoration:
new BoxDecoration(border: Border.all(color: Color.black))))
// widget,
}
));
}
void textSubmitted(string text) {
Debug.Log($"text submitted {text}");
}
public override Widget build(BuildContext context) {
List<Widget> widgets = new List<Widget>();
var style = new TextStyle();
var cursorColor = new Color(0xFF000000);
widgets.Add(this.rowWidgets("Default", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("Multiple Line", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, maxLines: 4, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("ObscureText", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, obscureText: true, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("Number", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, keyboardType: TextInputType.number, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("Phone", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, keyboardType: TextInputType.phone, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("Email", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, keyboardType: TextInputType.emailAddress, onSubmitted:this.textSubmitted)))));
widgets.Add(this.rowWidgets("Url", new EditStateProvider(builder: ((buildContext, controller, node) =>
new EditableText(controller, node, style, cursorColor, keyboardType: TextInputType.url, onSubmitted:this.textSubmitted)))));
return new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widgets);
}
}
public class EditStateProvider : StatefulWidget {
public delegate EditableText EditableBuilder(BuildContext context,
TextEditingController controller, FocusNode focusNode);
public readonly EditableBuilder builder;
public EditStateProvider(Key key = null, EditableBuilder builder = null) : base(key) {
D.assert(builder != null);
this.builder = builder;
}
public override State createState() {
return new _EditStateProviderState();
}
}
internal class _EditStateProviderState : State<EditStateProvider> {
TextEditingController _controller;
FocusNode _focusNode;
public override void initState() {
base.initState();
this._focusNode = new FocusNode();
this._controller = new TextEditingController("");
}
public override void dispose() {
this._focusNode.dispose();
base.dispose();
}
public override Widget build(BuildContext context) {
return this.widget.builder(context, this._controller, this._focusNode);
}
}
}

180
Runtime/service/keyboard.cs


using Unity.UIWidgets.ui;
using UnityEngine;
namespace Unity.UIWidgets.service {
public class KeyboadManager {
int _client;
string _lastCompositionString;
TextInputConfiguration _configuration;
TextEditingValue _value;
TouchScreenKeyboard _keyboard;
RangeInt? _pendingSelection;
bool _screenKeyboardDone;
readonly TextInput _textInput;
public KeyboadManager(TextInput textInput) {
this._textInput = textInput;
}
public void Update() {
if (!TouchScreenKeyboard.isSupported) {
return;
}
if (this._client == 0 || this._keyboard == null) {
return;
}
if (this._keyboard.canSetSelection && this._pendingSelection != null) {
this._keyboard.selection = this._pendingSelection.Value;
this._pendingSelection = null;
}
if (this._keyboard.status == TouchScreenKeyboard.Status.Done) {
if (!this._screenKeyboardDone) {
this._screenKeyboardDone = true;
Window.instance.run(() => {
this._textInput._performAction(this._client,
TextInputAction.done);
});
}
}
else if (this._keyboard.status == TouchScreenKeyboard.Status.Visible) {
var keyboardSelection = this._keyboard.selection;
var newValue = new TextEditingValue(
this._keyboard.text,
this._keyboard.canGetSelection
? new TextSelection(keyboardSelection.start, keyboardSelection.end)
: this._value.selection
);
var changed = this._value != newValue;
this._value = newValue;
if (changed) {
Window.instance.run(() => {
this._textInput._updateEditingState(this._client,
this._value);
});
}
}
}
public void OnGUI() {
if (TouchScreenKeyboard.isSupported) {
return;
}
if (this._client == 0) {
return;
}
var currentEvent = Event.current;
if (currentEvent != null && currentEvent.type == EventType.KeyDown) {
var action = TextInputUtils.getInputAction(currentEvent);
if (action != null) {
Window.instance.run(() => { this._textInput._performAction(this._client, action.Value); });
}
if (action == null || action == TextInputAction.newline) {
if (currentEvent.keyCode == KeyCode.None) {
this._value = this._value.clearCompose().insert(new string(currentEvent.character, 1));
Window.instance.run(() => { this._textInput._updateEditingState(this._client, this._value); });
}
}
currentEvent.Use();
}
if (!string.IsNullOrEmpty(Input.compositionString) &&
this._lastCompositionString != Input.compositionString) {
this._value = this._value.compose(Input.compositionString);
Window.instance.run(() => { this._textInput._updateEditingState(this._client, this._value); });
}
this._lastCompositionString = Input.compositionString;
}
public void show() {
if (!TouchScreenKeyboard.isSupported) {
return;
}
var secure = this._configuration.obscureText;
var multiline = this._configuration.inputType == TextInputType.multiline;
var autocorrection = this._configuration.autocorrect;
this._keyboard = TouchScreenKeyboard.Open(this._value.text,
getKeyboardTypeForConfiguration(this._configuration),
autocorrection, multiline, secure, false, "", 0);
this._pendingSelection = null;
this._screenKeyboardDone = false;
if (this._value.selection != null && this._value.selection.isValid) {
int start = this._value.selection.start;
int end = this._value.selection.end;
this._pendingSelection = new RangeInt(start, end - start);
}
}
public void clearClient() {
this._client = 0;
}
public void setClient(int client, TextInputConfiguration configuration) {
this._client = client;
this._configuration = configuration;
}
public void hide() {
if (this._keyboard != null) {
this._keyboard.active = false;
this._keyboard = null;
}
}
public void setEditingState(TextEditingValue state) {
this._value = state;
if (this._keyboard != null && this._keyboard.active) {
this._keyboard.text = state.text;
if (this._value.selection != null && this._value.selection.isValid) {
int start = this._value.selection.start;
int end = this._value.selection.end;
this._pendingSelection = new RangeInt(start, end - start);
RangeInt selection = new RangeInt(state.selection.start, end - start);
if (this._keyboard.canGetSelection) {
this._pendingSelection = null;
this._keyboard.selection = selection;
}
else {
this._pendingSelection = selection;
}
}
}
}
public bool textInputOnKeyboard() {
return TouchScreenKeyboard.isSupported;
}
static TouchScreenKeyboardType getKeyboardTypeForConfiguration(TextInputConfiguration config) {
var inputType = config.inputType;
if (inputType.index == TextInputType.url.index) {
return TouchScreenKeyboardType.URL;
}
if (inputType.index == TextInputType.emailAddress.index) {
return TouchScreenKeyboardType.EmailAddress;
}
if (inputType.index == TextInputType.phone.index) {
return TouchScreenKeyboardType.PhonePad;
}
if (inputType.index == TextInputType.number.index) {
return TouchScreenKeyboardType.NumberPad;
}
return TouchScreenKeyboardType.Default;
}
}
}

11
Runtime/service/keyboard.cs.meta


fileFormatVersion: 2
guid: a68d8110957bf45098c74b97511af2cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存