GitHub
6 年前
当前提交
34ce78c0
共有 37 个文件被更改,包括 2544 次插入 和 205 次删除
-
39Assets/UIWidgets/Tests/CanvasAndLayers.cs
-
30Assets/UIWidgets/Tests/Paragraph.cs
-
12Assets/UIWidgets/editor/editor_window.cs
-
3Assets/UIWidgets/foundation/basic_types.cs
-
2Assets/UIWidgets/foundation/diagnostics.cs
-
1Assets/UIWidgets/painting/basic_types.cs
-
10Assets/UIWidgets/painting/text_painter.cs
-
2Assets/UIWidgets/painting/text_span.cs
-
297Assets/UIWidgets/painting/text_style.cs
-
18Assets/UIWidgets/rendering/editable.cs
-
27Assets/UIWidgets/service/text_editing.cs
-
12Assets/UIWidgets/ui/painting/canvas.cs
-
41Assets/UIWidgets/ui/painting/canvas_impl.cs
-
7Assets/UIWidgets/ui/painting/draw_cmd.cs
-
6Assets/UIWidgets/ui/painting/painting.cs
-
34Assets/UIWidgets/ui/painting/picture.cs
-
1Assets/UIWidgets/ui/painting/txt/font_manager.cs
-
2Assets/UIWidgets/ui/painting/txt/mesh_generator.cs
-
239Assets/UIWidgets/ui/text.cs
-
2Assets/UIWidgets/ui/txt/linebreaker.cs
-
18Assets/UIWidgets/ui/txt/paint_record.cs
-
135Assets/UIWidgets/ui/txt/paragraph.cs
-
16Assets/UIWidgets/ui/txt/paragraph_builder.cs
-
3Assets/UIWidgets/ui/window.cs
-
2Assets/UIWidgets/widgets/binding.cs
-
387Assets/UIWidgets/widgets/focus_manager.cs
-
4Assets/UIWidgets/widgets/focus_manager.cs.meta
-
66Assets/UIWidgets/Tests/EditableTextWiget.cs
-
3Assets/UIWidgets/Tests/EditableTextWiget.cs.meta
-
108Assets/UIWidgets/service/text_formatter.cs
-
3Assets/UIWidgets/service/text_formatter.cs.meta
-
544Assets/UIWidgets/service/text_input.cs
-
3Assets/UIWidgets/service/text_input.cs.meta
-
589Assets/UIWidgets/widgets/editable_text.cs
-
3Assets/UIWidgets/widgets/editable_text.cs.meta
-
77Assets/UIWidgets/widgets/focus_scope.cs
-
3Assets/UIWidgets/widgets/focus_scope.cs.meta
|
|||
namespace UIWidgets.widgets { |
|||
public class FocusManager { |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.ui; |
|||
|
|||
namespace UIWidgets.widgets |
|||
{ |
|||
public class FocusNode : ChangeNotifier { |
|||
internal FocusScopeNode _parent; |
|||
internal FocusManager _manager; |
|||
internal bool _hasKeyboardToken = false; |
|||
|
|||
public bool hasFocus |
|||
{ |
|||
get |
|||
{ |
|||
FocusNode node = null; |
|||
if (_manager != null) |
|||
{ |
|||
node = _manager._currentFocus; |
|||
} |
|||
return node == this; |
|||
} |
|||
} |
|||
|
|||
public bool consumeKeyboardToken() |
|||
{ |
|||
if (!_hasKeyboardToken) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
_hasKeyboardToken = false; |
|||
return true; |
|||
} |
|||
|
|||
public void unfocus() |
|||
{ |
|||
if (_parent != null) |
|||
{ |
|||
_parent._resignFocus(this); |
|||
} |
|||
D.assert(_parent == null); |
|||
D.assert(_manager == null); |
|||
} |
|||
|
|||
public override void dispose() { |
|||
if (_manager != null) |
|||
{ |
|||
_manager._willDisposeFocusNode(this); |
|||
} |
|||
|
|||
if (_parent != null) |
|||
{ |
|||
_parent._resignFocus(this); |
|||
} |
|||
D.assert(_parent == null); |
|||
D.assert(_manager == null); |
|||
base.dispose(); |
|||
} |
|||
|
|||
internal void _notify() |
|||
{ |
|||
notifyListeners(); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Format("{0} hasFocus: {1}", Diagnostics.describeIdentity(this), hasFocus); |
|||
} |
|||
} |
|||
|
|||
public class FocusScopeNode:DiagnosticableTree |
|||
{ |
|||
|
|||
internal FocusManager _manager; |
|||
internal FocusScopeNode _parent; |
|||
|
|||
internal FocusScopeNode _nextSibling; |
|||
internal FocusScopeNode _previousSibling; |
|||
|
|||
internal FocusScopeNode _firstChild; |
|||
internal FocusScopeNode _lastChild; |
|||
|
|||
internal FocusNode _focus; |
|||
|
|||
public bool isFirstFocus |
|||
{ |
|||
get { return _parent == null || _parent._firstChild == this; } |
|||
} |
|||
|
|||
internal void _prepend(FocusScopeNode child) |
|||
{ |
|||
D.assert(child != this); |
|||
D.assert(child != _firstChild); |
|||
D.assert(child != _lastChild); |
|||
D.assert(child == null); |
|||
D.assert(child._manager == null); |
|||
D.assert(child._nextSibling == null); |
|||
D.assert(child._previousSibling == null); |
|||
D.assert(() => |
|||
{ |
|||
var node = this; |
|||
while (node._parent != null) |
|||
{ |
|||
node = node._parent; |
|||
} |
|||
D.assert(node != child); |
|||
return true; |
|||
}); |
|||
child._parent = this; |
|||
child._nextSibling = _firstChild; |
|||
if (_firstChild != null) |
|||
{ |
|||
_firstChild._previousSibling = child; |
|||
} |
|||
_firstChild = child; |
|||
_lastChild = _lastChild ?? child; |
|||
child._updateManager(_manager); |
|||
} |
|||
|
|||
void _updateManager(FocusManager manager) |
|||
{ |
|||
Action<FocusScopeNode> update = null; |
|||
update = (child) => |
|||
{ |
|||
if (child._manager == manager) |
|||
return; |
|||
child._manager = manager; |
|||
// We don't proactively null out the manager for FocusNodes because the
|
|||
// manager holds the currently active focus node until the end of the
|
|||
// microtask, even if that node is detached from the focus tree.
|
|||
if (manager != null && child._focus != null) |
|||
{ |
|||
child._focus._manager = manager; |
|||
} |
|||
child._visitChildren(update); |
|||
}; |
|||
update(this); |
|||
} |
|||
|
|||
void _visitChildren(Action<FocusScopeNode> vistor) { |
|||
FocusScopeNode child = _firstChild; |
|||
while (child != null) { |
|||
vistor.Invoke(child); |
|||
child = child._nextSibling; |
|||
} |
|||
} |
|||
|
|||
private bool _debugUltimatePreviousSiblingOf(FocusScopeNode child, FocusScopeNode equals) |
|||
{ |
|||
while (child._previousSibling != null) |
|||
{ |
|||
D.assert(child._previousSibling != child); |
|||
child = child._previousSibling; |
|||
} |
|||
|
|||
return child == equals; |
|||
} |
|||
|
|||
private bool _debugUltimateNextSiblingOf(FocusScopeNode child, FocusScopeNode equals) |
|||
{ |
|||
while (child._nextSibling != null) |
|||
{ |
|||
D.assert(child._nextSibling != child); |
|||
child = child._nextSibling; |
|||
} |
|||
|
|||
return child == equals; |
|||
} |
|||
|
|||
internal void _remove(FocusScopeNode child) |
|||
{ |
|||
D.assert(child._parent == this); |
|||
D.assert(child._manager == _manager); |
|||
D.assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); |
|||
D.assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); |
|||
if (child._previousSibling == null) |
|||
{ |
|||
D.assert(_firstChild == child); |
|||
_firstChild = child._nextSibling; |
|||
} |
|||
else |
|||
{ |
|||
child._previousSibling._nextSibling = child._nextSibling; |
|||
} |
|||
|
|||
if (child._nextSibling == null) { |
|||
D.assert(_lastChild == child); |
|||
_lastChild = child._previousSibling; |
|||
} else { |
|||
child._nextSibling._previousSibling = child._previousSibling; |
|||
} |
|||
|
|||
child._previousSibling = null; |
|||
child._nextSibling = null; |
|||
child._parent = null; |
|||
child._updateManager(null); |
|||
} |
|||
|
|||
internal void _didChangeFocusChain() { |
|||
if (isFirstFocus && _manager != null) |
|||
_manager._markNeedsUpdate(); |
|||
} |
|||
|
|||
public void requestFocus(FocusNode node) |
|||
{ |
|||
D.assert(node != null); |
|||
if (_focus == node) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (_focus != null) |
|||
{ |
|||
_focus.unfocus(); |
|||
} |
|||
|
|||
node._hasKeyboardToken = true; |
|||
_setFocus(node); |
|||
} |
|||
|
|||
public void autofocus(FocusNode node) { |
|||
D.assert(node != null); |
|||
if (_focus == null) |
|||
{ |
|||
node._hasKeyboardToken = true; |
|||
_setFocus(node); |
|||
} |
|||
} |
|||
|
|||
public void reparentIfNeeded(FocusNode node) { |
|||
D.assert(node != null); |
|||
if (node._parent == null || node._parent == this) |
|||
return; |
|||
node.unfocus(); |
|||
D.assert(node._parent == null); |
|||
if (_focus == null) |
|||
_setFocus(node); |
|||
} |
|||
|
|||
internal void _setFocus(FocusNode node) { |
|||
D.assert(node != null); |
|||
D.assert(node._parent == null); |
|||
D.assert(_focus == null); |
|||
_focus = node; |
|||
_focus._parent = this; |
|||
_focus._manager = _manager; |
|||
_focus._hasKeyboardToken = true; |
|||
_didChangeFocusChain(); |
|||
} |
|||
|
|||
internal void _resignFocus(FocusNode node) { |
|||
D.assert(node != null); |
|||
if (_focus != node) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_focus._parent = null; |
|||
_focus._manager = null; |
|||
_focus = null; |
|||
_didChangeFocusChain(); |
|||
} |
|||
|
|||
public void setFirstFocus(FocusScopeNode child) |
|||
{ |
|||
D.assert(child != null); |
|||
D.assert(child._parent == null || child._parent == this); |
|||
if (_firstChild == child) |
|||
{ |
|||
return; |
|||
} |
|||
child.detach(); |
|||
_prepend(child); |
|||
D.assert(child._parent == this); |
|||
_didChangeFocusChain(); |
|||
} |
|||
|
|||
public void reparentScopeIfNeeded(FocusScopeNode child) { |
|||
D.assert(child != null); |
|||
if (child._parent == null || child._parent == this) |
|||
return; |
|||
if (child.isFirstFocus) |
|||
setFirstFocus(child); |
|||
else |
|||
child.detach(); |
|||
} |
|||
|
|||
public void detach() { |
|||
_didChangeFocusChain(); |
|||
if (_parent != null) |
|||
{ |
|||
_parent._remove(this); |
|||
} |
|||
D.assert(_parent == null); |
|||
} |
|||
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
|||
base.debugFillProperties(properties); |
|||
if (_focus != null) |
|||
properties.add(new DiagnosticsProperty<FocusNode>("focus", _focus)); |
|||
} |
|||
|
|||
public override List<DiagnosticsNode> debugDescribeChildren() { |
|||
var children = new List<DiagnosticsNode>(); |
|||
if (_firstChild != null) { |
|||
FocusScopeNode child = _firstChild; |
|||
int count = 1; |
|||
while (true) { |
|||
children.Add(child.toDiagnosticsNode(name: string.Format("child {0}", count))); |
|||
if (child == _lastChild) |
|||
break; |
|||
child = child._nextSibling; |
|||
count += 1; |
|||
} |
|||
} |
|||
return children; |
|||
} |
|||
} |
|||
|
|||
public class FocusManager |
|||
{ |
|||
public FocusManager() |
|||
{ |
|||
rootScope._manager = this; |
|||
D.assert(rootScope._firstChild == null); |
|||
D.assert(rootScope._lastChild == null); |
|||
} |
|||
|
|||
public readonly FocusScopeNode rootScope = new FocusScopeNode(); |
|||
internal FocusNode _currentFocus; |
|||
|
|||
|
|||
internal void _willDisposeFocusNode(FocusNode node) { |
|||
D.assert(node != null); |
|||
if (_currentFocus == node) |
|||
_currentFocus = null; |
|||
} |
|||
|
|||
|
|||
bool _haveScheduledUpdate = false; |
|||
internal void _markNeedsUpdate() { |
|||
if (_haveScheduledUpdate) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_haveScheduledUpdate = true; |
|||
Window.instance.scheduleMicrotask(_update); |
|||
} |
|||
|
|||
|
|||
internal FocusNode _findNextFocus() { |
|||
FocusScopeNode scope = rootScope; |
|||
while (scope._firstChild != null) |
|||
scope = scope._firstChild; |
|||
return scope._focus; |
|||
} |
|||
|
|||
internal void _update() |
|||
{ |
|||
_haveScheduledUpdate = false; |
|||
var nextFocus = _findNextFocus(); |
|||
if (_currentFocus == nextFocus) |
|||
return; |
|||
var previousFocus = _currentFocus; |
|||
_currentFocus = nextFocus; |
|||
if (previousFocus != null) |
|||
{ |
|||
previousFocus._notify(); |
|||
} |
|||
|
|||
if (_currentFocus != null) |
|||
{ |
|||
_currentFocus._notify(); |
|||
} |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
var status = _haveScheduledUpdate ? " UPDATE SCHEDULED" : ""; |
|||
var indent = " "; |
|||
return string.Format("{1}{2}\n{0}currentFocus: {3}\n{4}", indent, Diagnostics.describeIdentity(this), |
|||
status, _currentFocus, rootScope.toStringDeep(prefixLineOne: indent, prefixOtherLines: indent)); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 78895a8736b34e4085f9324116273d59 |
|||
timeCreated: 1536819300 |
|||
guid: f645f061c05e472285a90ddf48ebeeed |
|||
timeCreated: 1536895704 |
|
|||
using UIWidgets.editor; |
|||
using UIWidgets.painting; |
|||
using UIWidgets.widgets; |
|||
using UnityEditor; |
|||
using UnityEngine; |
|||
using Color = UIWidgets.ui.Color; |
|||
namespace UIWidgets.Tests |
|||
{ |
|||
public class EditableTextWiget: EditorWindow |
|||
{ |
|||
private WindowAdapter windowAdapter; |
|||
|
|||
private PaintingBinding paintingBinding; |
|||
|
|||
private Widget root; |
|||
|
|||
private Widget image; |
|||
|
|||
[MenuItem("UIWidgetsTests/EditableTextWiget")] |
|||
public static void renderWidgets() { |
|||
EditorWindow.GetWindow(typeof(EditableTextWiget)); |
|||
} |
|||
|
|||
EditableTextWiget() { |
|||
} |
|||
|
|||
void OnGUI() { |
|||
if (this.windowAdapter != null) { |
|||
this.windowAdapter.OnGUI(); |
|||
} |
|||
} |
|||
|
|||
private void Update() { |
|||
if (this.windowAdapter != null) { |
|||
this.windowAdapter.Update(); |
|||
} |
|||
} |
|||
|
|||
private void OnEnable() { |
|||
this.paintingBinding = new PaintingBinding(null); |
|||
paintingBinding.initInstances(); |
|||
this.windowAdapter = new WindowAdapter(this); |
|||
this.root = new widgets.Container( |
|||
width: 200, |
|||
height: 200, |
|||
margin: EdgeInsets.all(30.0), |
|||
padding: EdgeInsets.all(15.0), |
|||
color: ui.Color.fromARGB(255, 244, 190, 85), |
|||
child: new EditableText( |
|||
maxLines: 100, |
|||
controller: new TextEditingController("click to edit"), |
|||
focusNode: new FocusNode(), |
|||
style: new TextStyle(), |
|||
selectionColor: Color.fromARGB(255, 255, 0, 0), |
|||
cursorColor: Color.fromARGB(255, 0, 0, 0) |
|||
) |
|||
); |
|||
this.windowAdapter.attachRootWidget(root); |
|||
this.titleContent = new GUIContent("EditableTextWiget"); |
|||
} |
|||
|
|||
void OnDestroy() { |
|||
this.windowAdapter = null; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1438fd697c854ab783eb666a6a62115c |
|||
timeCreated: 1537236780 |
|
|||
using System; |
|||
using System.Text.RegularExpressions; |
|||
using UIWidgets.foundation; |
|||
|
|||
namespace UIWidgets.service |
|||
{ |
|||
public abstract class TextInputFormatter |
|||
{ |
|||
public delegate TextEditingValue TextInputFormatFunction(TextEditingValue oldValue, |
|||
TextEditingValue newValue); |
|||
|
|||
public abstract TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue); |
|||
|
|||
static TextInputFormatter withFunction(TextInputFormatFunction formatFunction) |
|||
{ |
|||
return new _SimpleTextInputFormatter(formatFunction); |
|||
} |
|||
} |
|||
|
|||
internal class _SimpleTextInputFormatter : TextInputFormatter |
|||
{ |
|||
public readonly TextInputFormatFunction formatFunction; |
|||
|
|||
internal _SimpleTextInputFormatter(TextInputFormatFunction formatFunction) |
|||
{ |
|||
D.assert(formatFunction != null); |
|||
this.formatFunction = formatFunction; |
|||
} |
|||
|
|||
public override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) |
|||
{ |
|||
return formatFunction(oldValue, newValue); |
|||
} |
|||
} |
|||
|
|||
public class BlacklistingTextInputFormatter : TextInputFormatter |
|||
{ |
|||
public readonly Regex blacklistedPattern; |
|||
|
|||
public readonly string replacementString; |
|||
|
|||
public static readonly BlacklistingTextInputFormatter singleLineFormatter |
|||
= new BlacklistingTextInputFormatter(new Regex(@"\n")); |
|||
|
|||
public BlacklistingTextInputFormatter(Regex blacklistedPattern, string replacementString = "") |
|||
{ |
|||
D.assert(blacklistedPattern != null); |
|||
this.blacklistedPattern = blacklistedPattern; |
|||
this.replacementString = replacementString; |
|||
} |
|||
|
|||
|
|||
public override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) |
|||
{ |
|||
return Util._selectionAwareTextManipulation(newValue, |
|||
(substring) => blacklistedPattern.Replace(substring, replacementString)); |
|||
} |
|||
} |
|||
|
|||
internal static class Util |
|||
{ |
|||
internal static TextEditingValue _selectionAwareTextManipulation(TextEditingValue value, |
|||
Func<string, string> substringManipulation) |
|||
{ |
|||
int selectionStartIndex = value.selection.start; |
|||
int selectionEndIndex = value.selection.end; |
|||
string manipulatedText; |
|||
TextSelection manipulatedSelection = null; |
|||
if (selectionStartIndex < 0 || selectionEndIndex < 0) |
|||
{ |
|||
manipulatedText = substringManipulation(value.text); |
|||
} |
|||
else |
|||
{ |
|||
var beforeSelection = substringManipulation( |
|||
value.text.Substring(0, selectionStartIndex) |
|||
); |
|||
var inSelection = substringManipulation( |
|||
value.text.Substring(selectionStartIndex, selectionEndIndex - selectionStartIndex) |
|||
); |
|||
var afterSelection = substringManipulation( |
|||
value.text.Substring(selectionEndIndex) |
|||
); |
|||
manipulatedText = beforeSelection + inSelection + afterSelection; |
|||
if (value.selection.baseOffset > value.selection.extentOffset) |
|||
{ |
|||
manipulatedSelection = value.selection.copyWith( |
|||
baseOffset: beforeSelection.Length + inSelection.Length, |
|||
extentOffset: beforeSelection.Length |
|||
); |
|||
} |
|||
else |
|||
{ |
|||
manipulatedSelection = value.selection.copyWith( |
|||
baseOffset: beforeSelection.Length, |
|||
extentOffset: beforeSelection.Length + inSelection.Length |
|||
); |
|||
} |
|||
} |
|||
|
|||
return new TextEditingValue( |
|||
text: manipulatedText, |
|||
selection: manipulatedSelection ?? TextSelection.collapsed(offset: -1), |
|||
composing: manipulatedText == value.text ? value.composing : TextRange.empty |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 6fbc4090466b444f8423c68d5928dfa7 |
|||
timeCreated: 1537166345 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using UIWidgets.foundation; |
|||
using UnityEngine; |
|||
|
|||
namespace UIWidgets.service |
|||
{ |
|||
public class TextEditingValue:IEquatable<TextEditingValue> |
|||
{ |
|||
public readonly string text; |
|||
public readonly TextSelection selection; |
|||
public readonly TextRange composing; |
|||
|
|||
public TextEditingValue(string text = "", TextSelection selection = null, TextRange composing = null) |
|||
{ |
|||
this.text = text; |
|||
this.selection = selection ?? TextSelection.collapsed(-1); |
|||
this.composing = composing ?? TextRange.empty; |
|||
} |
|||
|
|||
public TextEditingValue copyWith(string text = null, TextSelection selection = null, TextRange composing = null) |
|||
{ |
|||
return new TextEditingValue( |
|||
text??this.text, selection??this.selection, composing??this.composing |
|||
); |
|||
} |
|||
|
|||
public TextEditingValue insert(string text) |
|||
{ |
|||
string newText; |
|||
TextSelection newSelection; |
|||
if (string.IsNullOrEmpty(text)) |
|||
{ |
|||
return this; |
|||
} |
|||
newText = selection.textBefore(this.text) + text + selection.textAfter(this.text); |
|||
newSelection = TextSelection.collapsed(selection.start + text.Length); |
|||
return new TextEditingValue( |
|||
text: newText, selection: newSelection,composing:TextRange.empty |
|||
); |
|||
} |
|||
|
|||
public TextEditingValue deleteSelection() |
|||
{ |
|||
if (selection.isCollapsed) |
|||
{ |
|||
if (selection.start == 0) |
|||
{ |
|||
return this; |
|||
} |
|||
return this.copyWith(text: text.Substring(0, selection.start - 1) + selection.textAfter(this.text), |
|||
selection: TextSelection.collapsed(selection.start - 1)); |
|||
} |
|||
else |
|||
{ |
|||
var newText = selection.textBefore(this.text) + selection.textAfter(this.text); |
|||
return this.copyWith(text: newText, selection: TextSelection.collapsed(selection.start)); |
|||
} |
|||
} |
|||
|
|||
public TextEditingValue moveLeft() |
|||
{ |
|||
return moveSelection(-1); |
|||
} |
|||
|
|||
public TextEditingValue moveRight() |
|||
{ |
|||
return moveSelection(1); |
|||
} |
|||
|
|||
public TextEditingValue extendLeft() |
|||
{ |
|||
return moveExtent(-1); |
|||
} |
|||
|
|||
public TextEditingValue extendRight() |
|||
{ |
|||
return moveExtent(1); |
|||
} |
|||
|
|||
public TextEditingValue moveExtent(int move) |
|||
{ |
|||
int offset = selection.extentOffset + move; |
|||
offset = Math.Max(0, offset); |
|||
offset = Math.Min(offset, text.Length); |
|||
return this.copyWith(selection: selection.copyWith(extentOffset: offset)); |
|||
} |
|||
|
|||
public TextEditingValue moveSelection(int move) |
|||
{ |
|||
int offset = selection.baseOffset + move; |
|||
offset = Math.Max(0, offset); |
|||
offset = Math.Min(offset, text.Length); |
|||
return this.copyWith(selection: TextSelection.collapsed(offset)); |
|||
} |
|||
|
|||
public TextEditingValue compose(string composeText) |
|||
{ |
|||
D.assert(!string.IsNullOrEmpty(composeText)); |
|||
var composeStart = composing == TextRange.empty ? selection.start : composing.start; |
|||
var lastComposeEnd =composing == TextRange.empty ? selection.end : composing.end; |
|||
var newText = text.Substring(0, composeStart) + composeText + text.Substring(lastComposeEnd); |
|||
var componseEnd = composeStart + composeText.Length; |
|||
return new TextEditingValue( |
|||
text: newText, selection: TextSelection.collapsed(componseEnd), |
|||
composing: new TextRange(composeStart, componseEnd) |
|||
); |
|||
} |
|||
|
|||
public TextEditingValue clearCompose() |
|||
{ |
|||
if (composing == TextRange.empty) |
|||
{ |
|||
return this; |
|||
} |
|||
return new TextEditingValue( |
|||
text: text.Substring(0, composing.start) + text.Substring(composing.end), |
|||
selection: TextSelection.collapsed(composing.start), |
|||
composing: TextRange.empty |
|||
); |
|||
} |
|||
|
|||
public static readonly TextEditingValue empty = new TextEditingValue(); |
|||
|
|||
public bool Equals(TextEditingValue other) |
|||
{ |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return string.Equals(text, other.text) && Equals(selection, other.selection) && Equals(composing, other.composing); |
|||
} |
|||
|
|||
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 Equals((TextEditingValue) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
var hashCode = (text != null ? text.GetHashCode() : 0); |
|||
hashCode = (hashCode * 397) ^ (selection != null ? selection.GetHashCode() : 0); |
|||
hashCode = (hashCode * 397) ^ (composing != null ? composing.GetHashCode() : 0); |
|||
return hashCode; |
|||
} |
|||
} |
|||
|
|||
public static bool operator ==(TextEditingValue left, TextEditingValue right) |
|||
{ |
|||
return Equals(left, right); |
|||
} |
|||
|
|||
public static bool operator !=(TextEditingValue left, TextEditingValue right) |
|||
{ |
|||
return !Equals(left, right); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Format("Text: {0}, Selection: {1}, Composing: {2}", text, selection, composing); |
|||
} |
|||
} |
|||
|
|||
public interface TextInputClient |
|||
{ |
|||
void updateEditingValue(TextEditingValue value); |
|||
// void performAction(TextInputAction action);
|
|||
} |
|||
|
|||
public enum TextInputAction { |
|||
done, |
|||
newline, |
|||
} |
|||
|
|||
public class TextInputConnection |
|||
{ |
|||
|
|||
internal TextInputConnection(TextInputClient client, TextInput textInput) |
|||
{ |
|||
D.assert(client != null); |
|||
D.assert(textInput != null); |
|||
_client = client; |
|||
_textInput = textInput; |
|||
_id = _nextId++; |
|||
} |
|||
|
|||
public bool attached |
|||
{ |
|||
get { return _textInput._currentConnection == this; } |
|||
} |
|||
|
|||
public void setEditingState(TextEditingValue value) |
|||
{ |
|||
D.assert(attached); |
|||
_textInput._value = value; |
|||
} |
|||
|
|||
public void setCompositionCursorPos(double x, double y) |
|||
{ |
|||
D.assert(attached); |
|||
_textInput.setCompositionCursorPos(x, y); |
|||
} |
|||
|
|||
public void close() |
|||
{ |
|||
if (attached) |
|||
{ |
|||
_textInput._currentConnection = null; |
|||
_textInput._value = null; |
|||
Input.imeCompositionMode = IMECompositionMode.Auto; |
|||
} |
|||
D.assert(!attached); |
|||
} |
|||
|
|||
private static int _nextId = 1; |
|||
internal readonly int _id; |
|||
internal readonly TextInputClient _client; |
|||
internal readonly TextInput _textInput; |
|||
} |
|||
|
|||
public class TextInput |
|||
{ |
|||
internal TextInputConnection _currentConnection; |
|||
internal TextEditingValue _value; |
|||
static Dictionary<Event, TextEditOp> s_Keyactions; |
|||
private string _lastCompositionString; |
|||
|
|||
public TextInputConnection attach(TextInputClient client) |
|||
{ |
|||
D.assert(client != null); |
|||
var connection = new TextInputConnection(client, this); |
|||
_currentConnection = connection; |
|||
Input.imeCompositionMode = IMECompositionMode.On; |
|||
return connection; |
|||
} |
|||
|
|||
public void OnGUI() |
|||
{ |
|||
if (_currentConnection == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var currentEvent = Event.current; |
|||
if (currentEvent.type == EventType.KeyDown) |
|||
{ |
|||
bool handled = handleKeyEvent(currentEvent); |
|||
if (!handled) |
|||
{ |
|||
if (currentEvent.keyCode == KeyCode.None) |
|||
{ |
|||
_value = _value.clearCompose().insert(new string(currentEvent.character, 1)); |
|||
_currentConnection._client.updateEditingValue(_value); |
|||
} |
|||
} |
|||
currentEvent.Use(); |
|||
} |
|||
|
|||
if (!string.IsNullOrEmpty(Input.compositionString) && _lastCompositionString != Input.compositionString) |
|||
{ |
|||
_value = _value.compose(Input.compositionString); |
|||
_currentConnection._client.updateEditingValue(_value); |
|||
} |
|||
|
|||
_lastCompositionString = Input.compositionString; |
|||
} |
|||
|
|||
public void setCompositionCursorPos(double x, double y) |
|||
{ |
|||
Input.compositionCursorPos = new Vector2((float)x, (float)y); |
|||
} |
|||
|
|||
private bool handleKeyEvent(Event e) |
|||
{ |
|||
initKeyActions(); |
|||
EventModifiers m = e.modifiers; |
|||
e.modifiers &= ~EventModifiers.CapsLock; |
|||
if (s_Keyactions.ContainsKey(e)) |
|||
{ |
|||
TextEditOp op = s_Keyactions[e]; |
|||
var newValue = performOperation(op); |
|||
if (_value != newValue) |
|||
{ |
|||
_value = newValue; |
|||
_currentConnection._client.updateEditingValue(_value); |
|||
} |
|||
e.modifiers = m; |
|||
return true; |
|||
} |
|||
e.modifiers = m; |
|||
return false; |
|||
} |
|||
|
|||
TextEditingValue performOperation(TextEditOp operation) |
|||
{ |
|||
switch (operation) |
|||
{ |
|||
case TextEditOp.MoveLeft: |
|||
return _value.moveLeft(); |
|||
case TextEditOp.MoveRight: |
|||
return _value.moveRight(); |
|||
// case TextEditOp.MoveUp: MoveUp(); break;
|
|||
// case TextEditOp.MoveDown: MoveDown(); break;
|
|||
// case TextEditOp.MoveLineStart: MoveLineStart(); break;
|
|||
// case TextEditOp.MoveLineEnd: MoveLineEnd(); break;
|
|||
// case TextEditOp.MoveWordRight: MoveWordRight(); break;
|
|||
// case TextEditOp.MoveToStartOfNextWord: MoveToStartOfNextWord(); break;
|
|||
// case TextEditOp.MoveToEndOfPreviousWord: MoveToEndOfPreviousWord(); break;
|
|||
// case TextEditOp.MoveWordLeft: MoveWordLeft(); break;
|
|||
// case TextEditOp.MoveTextStart: MoveTextStart(); break;
|
|||
// case TextEditOp.MoveTextEnd: MoveTextEnd(); break;
|
|||
// case TextEditOp.MoveParagraphForward: MoveParagraphForward(); break;
|
|||
// case TextEditOp.MoveParagraphBackward: MoveParagraphBackward(); break;
|
|||
// case TextEditOp.MoveGraphicalLineStart: MoveGraphicalLineStart(); break;
|
|||
// case TextEditOp.MoveGraphicalLineEnd: MoveGraphicalLineEnd(); break;
|
|||
case TextEditOp.SelectLeft: |
|||
return _value.extendLeft(); |
|||
case TextEditOp.SelectRight: |
|||
return _value.extendRight(); |
|||
// case TextEditOp.SelectUp: SelectUp(); break;
|
|||
// case TextEditOp.SelectDown: SelectDown(); break;
|
|||
// case TextEditOp.SelectWordRight: SelectWordRight(); break;
|
|||
// case TextEditOp.SelectWordLeft: SelectWordLeft(); break;
|
|||
// case TextEditOp.SelectToEndOfPreviousWord: SelectToEndOfPreviousWord(); break;
|
|||
// case TextEditOp.SelectToStartOfNextWord: SelectToStartOfNextWord(); break;
|
|||
//
|
|||
// case TextEditOp.SelectTextStart: SelectTextStart(); break;
|
|||
// case TextEditOp.SelectTextEnd: SelectTextEnd(); break;
|
|||
// case TextEditOp.ExpandSelectGraphicalLineStart: ExpandSelectGraphicalLineStart(); break;
|
|||
// case TextEditOp.ExpandSelectGraphicalLineEnd: ExpandSelectGraphicalLineEnd(); break;
|
|||
// case TextEditOp.SelectParagraphForward: SelectParagraphForward(); break;
|
|||
// case TextEditOp.SelectParagraphBackward: SelectParagraphBackward(); break;
|
|||
// case TextEditOp.SelectGraphicalLineStart: SelectGraphicalLineStart(); break;
|
|||
// case TextEditOp.SelectGraphicalLineEnd: SelectGraphicalLineEnd(); break;
|
|||
// case TextEditOp.Delete: return Delete();
|
|||
case TextEditOp.Backspace: |
|||
return _value.deleteSelection(); |
|||
// _value.composing
|
|||
// _value = _value.
|
|||
// case TextEditOp.Cut: return Cut();
|
|||
// case TextEditOp.Copy: Copy(); break;
|
|||
// case TextEditOp.Paste: return Paste();
|
|||
// case TextEditOp.SelectAll: SelectAll(); break;
|
|||
// case TextEditOp.SelectNone: SelectNone(); break;
|
|||
// case TextEditOp.DeleteWordBack: return DeleteWordBack(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
|
|||
// case TextEditOp.DeleteLineBack: return DeleteLineBack();
|
|||
// case TextEditOp.DeleteWordForward: return DeleteWordForward(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
|
|||
default: |
|||
Debug.Log("Unimplemented: " + operation); |
|||
break; |
|||
} |
|||
|
|||
return _value; |
|||
} |
|||
|
|||
|
|||
static void initKeyActions() |
|||
{ |
|||
if (s_Keyactions != null) |
|||
return; |
|||
s_Keyactions = new Dictionary<Event, TextEditOp>(); |
|||
|
|||
// key mappings shared by the platforms
|
|||
mapKey("left", TextEditOp.MoveLeft); |
|||
mapKey("right", TextEditOp.MoveRight); |
|||
mapKey("up", TextEditOp.MoveUp); |
|||
mapKey("down", TextEditOp.MoveDown); |
|||
|
|||
mapKey("#left", TextEditOp.SelectLeft); |
|||
mapKey("#right", TextEditOp.SelectRight); |
|||
mapKey("#up", TextEditOp.SelectUp); |
|||
mapKey("#down", TextEditOp.SelectDown); |
|||
|
|||
mapKey("delete", TextEditOp.Delete); |
|||
mapKey("backspace", TextEditOp.Backspace); |
|||
mapKey("#backspace", TextEditOp.Backspace); |
|||
|
|||
// OSX is the special case for input shortcuts
|
|||
if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) |
|||
{ |
|||
// Keyboard mappings for mac
|
|||
// TODO mapKey ("home", TextEditOp.ScrollStart);
|
|||
// TODO mapKey ("end", TextEditOp.ScrollEnd);
|
|||
// TODO mapKey ("page up", TextEditOp.ScrollPageUp);
|
|||
// TODO mapKey ("page down", TextEditOp.ScrollPageDown);
|
|||
|
|||
mapKey("^left", TextEditOp.MoveGraphicalLineStart); |
|||
mapKey("^right", TextEditOp.MoveGraphicalLineEnd); |
|||
// TODO mapKey ("^up", TextEditOp.ScrollPageUp);
|
|||
// TODO mapKey ("^down", TextEditOp.ScrollPageDown);
|
|||
|
|||
mapKey("&left", TextEditOp.MoveWordLeft); |
|||
mapKey("&right", TextEditOp.MoveWordRight); |
|||
mapKey("&up", TextEditOp.MoveParagraphBackward); |
|||
mapKey("&down", TextEditOp.MoveParagraphForward); |
|||
|
|||
mapKey("%left", TextEditOp.MoveGraphicalLineStart); |
|||
mapKey("%right", TextEditOp.MoveGraphicalLineEnd); |
|||
mapKey("%up", TextEditOp.MoveTextStart); |
|||
mapKey("%down", TextEditOp.MoveTextEnd); |
|||
|
|||
mapKey("#home", TextEditOp.SelectTextStart); |
|||
mapKey("#end", TextEditOp.SelectTextEnd); |
|||
// TODO mapKey ("#page up", TextEditOp.SelectPageUp);
|
|||
// TODO mapKey ("#page down", TextEditOp.SelectPageDown);
|
|||
|
|||
mapKey("#^left", TextEditOp.ExpandSelectGraphicalLineStart); |
|||
mapKey("#^right", TextEditOp.ExpandSelectGraphicalLineEnd); |
|||
mapKey("#^up", TextEditOp.SelectParagraphBackward); |
|||
mapKey("#^down", TextEditOp.SelectParagraphForward); |
|||
|
|||
mapKey("#&left", TextEditOp.SelectWordLeft); |
|||
mapKey("#&right", TextEditOp.SelectWordRight); |
|||
mapKey("#&up", TextEditOp.SelectParagraphBackward); |
|||
mapKey("#&down", TextEditOp.SelectParagraphForward); |
|||
|
|||
mapKey("#%left", TextEditOp.ExpandSelectGraphicalLineStart); |
|||
mapKey("#%right", TextEditOp.ExpandSelectGraphicalLineEnd); |
|||
mapKey("#%up", TextEditOp.SelectTextStart); |
|||
mapKey("#%down", TextEditOp.SelectTextEnd); |
|||
|
|||
mapKey("%a", TextEditOp.SelectAll); |
|||
mapKey("%x", TextEditOp.Cut); |
|||
mapKey("%c", TextEditOp.Copy); |
|||
mapKey("%v", TextEditOp.Paste); |
|||
|
|||
// emacs-like keybindings
|
|||
mapKey("^d", TextEditOp.Delete); |
|||
mapKey("^h", TextEditOp.Backspace); |
|||
mapKey("^b", TextEditOp.MoveLeft); |
|||
mapKey("^f", TextEditOp.MoveRight); |
|||
mapKey("^a", TextEditOp.MoveLineStart); |
|||
mapKey("^e", TextEditOp.MoveLineEnd); |
|||
|
|||
mapKey("&delete", TextEditOp.DeleteWordForward); |
|||
mapKey("&backspace", TextEditOp.DeleteWordBack); |
|||
mapKey("%backspace", TextEditOp.DeleteLineBack); |
|||
} |
|||
else |
|||
{ |
|||
// Windows/Linux keymappings
|
|||
mapKey("home", TextEditOp.MoveGraphicalLineStart); |
|||
mapKey("end", TextEditOp.MoveGraphicalLineEnd); |
|||
// TODO mapKey ("page up", TextEditOp.MovePageUp);
|
|||
// TODO mapKey ("page down", TextEditOp.MovePageDown);
|
|||
|
|||
mapKey("%left", TextEditOp.MoveWordLeft); |
|||
mapKey("%right", TextEditOp.MoveWordRight); |
|||
mapKey("%up", TextEditOp.MoveParagraphBackward); |
|||
mapKey("%down", TextEditOp.MoveParagraphForward); |
|||
|
|||
mapKey("^left", TextEditOp.MoveToEndOfPreviousWord); |
|||
mapKey("^right", TextEditOp.MoveToStartOfNextWord); |
|||
mapKey("^up", TextEditOp.MoveParagraphBackward); |
|||
mapKey("^down", TextEditOp.MoveParagraphForward); |
|||
|
|||
mapKey("#^left", TextEditOp.SelectToEndOfPreviousWord); |
|||
mapKey("#^right", TextEditOp.SelectToStartOfNextWord); |
|||
mapKey("#^up", TextEditOp.SelectParagraphBackward); |
|||
mapKey("#^down", TextEditOp.SelectParagraphForward); |
|||
|
|||
mapKey("#home", TextEditOp.SelectGraphicalLineStart); |
|||
mapKey("#end", TextEditOp.SelectGraphicalLineEnd); |
|||
// TODO mapKey ("#page up", TextEditOp.SelectPageUp);
|
|||
// TODO mapKey ("#page down", TextEditOp.SelectPageDown);
|
|||
|
|||
mapKey("^delete", TextEditOp.DeleteWordForward); |
|||
mapKey("^backspace", TextEditOp.DeleteWordBack); |
|||
mapKey("%backspace", TextEditOp.DeleteLineBack); |
|||
|
|||
mapKey("^a", TextEditOp.SelectAll); |
|||
mapKey("^x", TextEditOp.Cut); |
|||
mapKey("^c", TextEditOp.Copy); |
|||
mapKey("^v", TextEditOp.Paste); |
|||
mapKey("#delete", TextEditOp.Cut); |
|||
mapKey("^insert", TextEditOp.Copy); |
|||
mapKey("#insert", TextEditOp.Paste); |
|||
} |
|||
} |
|||
|
|||
static void mapKey(string key, TextEditOp action) |
|||
{ |
|||
s_Keyactions[Event.KeyboardEvent(key)] = action; |
|||
} |
|||
} |
|||
|
|||
internal enum TextEditOp |
|||
{ |
|||
MoveLeft, |
|||
MoveRight, |
|||
MoveUp, |
|||
MoveDown, |
|||
MoveLineStart, |
|||
MoveLineEnd, |
|||
MoveTextStart, |
|||
MoveTextEnd, |
|||
MovePageUp, |
|||
MovePageDown, |
|||
MoveGraphicalLineStart, |
|||
MoveGraphicalLineEnd, |
|||
MoveWordLeft, |
|||
MoveWordRight, |
|||
MoveParagraphForward, |
|||
MoveParagraphBackward, |
|||
MoveToStartOfNextWord, |
|||
MoveToEndOfPreviousWord, |
|||
SelectLeft, |
|||
SelectRight, |
|||
SelectUp, |
|||
SelectDown, |
|||
SelectTextStart, |
|||
SelectTextEnd, |
|||
SelectPageUp, |
|||
SelectPageDown, |
|||
ExpandSelectGraphicalLineStart, |
|||
ExpandSelectGraphicalLineEnd, |
|||
SelectGraphicalLineStart, |
|||
SelectGraphicalLineEnd, |
|||
SelectWordLeft, |
|||
SelectWordRight, |
|||
SelectToEndOfPreviousWord, |
|||
SelectToStartOfNextWord, |
|||
SelectParagraphBackward, |
|||
SelectParagraphForward, |
|||
Delete, |
|||
Backspace, |
|||
DeleteWordBack, |
|||
DeleteWordForward, |
|||
DeleteLineBack, |
|||
Cut, |
|||
Copy, |
|||
Paste, |
|||
SelectAll, |
|||
SelectNone, |
|||
ScrollStart, |
|||
ScrollEnd, |
|||
ScrollPageUp, |
|||
ScrollPageDown, |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 74a6b923efea4cc6bc243bd2ee179330 |
|||
timeCreated: 1537164543 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using RSG; |
|||
using UIWidgets.animation; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.gestures; |
|||
using UIWidgets.painting; |
|||
using UIWidgets.rendering; |
|||
using UIWidgets.service; |
|||
using UIWidgets.ui; |
|||
using UnityEngine; |
|||
using Color = UIWidgets.ui.Color; |
|||
using Rect = UIWidgets.ui.Rect; |
|||
using TextStyle = UIWidgets.painting.TextStyle; |
|||
|
|||
namespace UIWidgets.widgets |
|||
{ |
|||
public delegate void SelectionChangedCallback(TextSelection selection, SelectionChangedCause cause); |
|||
|
|||
public class TextEditingController : ValueNotifier<TextEditingValue> |
|||
{ |
|||
public TextEditingController(string text) : base(text == null |
|||
? TextEditingValue.empty |
|||
: new TextEditingValue(text)) |
|||
{ |
|||
} |
|||
|
|||
private TextEditingController(TextEditingValue value) : base(value ?? TextEditingValue.empty) |
|||
{ |
|||
} |
|||
|
|||
public TextEditingController fromValue(TextEditingValue value) |
|||
{ |
|||
return new TextEditingController(value); |
|||
} |
|||
|
|||
public string text |
|||
{ |
|||
get { return value.text; } |
|||
|
|||
set |
|||
{ |
|||
this.value = this.value.copyWith(text: value, selection: TextSelection.collapsed(-1), |
|||
composing: TextRange.empty); |
|||
} |
|||
} |
|||
|
|||
public TextSelection selection |
|||
{ |
|||
get { return value.selection; } |
|||
|
|||
set |
|||
{ |
|||
if (value.start > text.Length || value.end > text.Length) |
|||
{ |
|||
throw new UIWidgetsError(string.Format("invalid text selection: {0}", value)); |
|||
} |
|||
|
|||
this.value = this.value.copyWith(selection: value, composing: TextRange.empty); |
|||
} |
|||
} |
|||
|
|||
public void clear() |
|||
{ |
|||
value = TextEditingValue.empty; |
|||
} |
|||
|
|||
public void clearComposing() |
|||
{ |
|||
value = value.copyWith(composing: TextRange.empty); |
|||
} |
|||
} |
|||
|
|||
public class EditableText : StatefulWidget |
|||
{ |
|||
public readonly TextEditingController controller; |
|||
|
|||
public readonly FocusNode focusNode; |
|||
|
|||
public readonly bool obscureText; |
|||
|
|||
public readonly bool autocorrect; |
|||
|
|||
public readonly TextStyle style; |
|||
|
|||
public readonly TextAlign textAlign; |
|||
|
|||
public readonly TextDirection? textDirection; |
|||
|
|||
public readonly double textScaleFactor; |
|||
|
|||
public readonly Color cursorColor; |
|||
|
|||
public readonly int maxLines; |
|||
|
|||
public readonly bool autofocus; |
|||
|
|||
public readonly Color selectionColor; |
|||
|
|||
public readonly ValueChanged<string> onChanged; |
|||
public readonly ValueChanged<string> onSubmitted; |
|||
|
|||
public readonly SelectionChangedCallback onSelectionChanged; |
|||
|
|||
public readonly List<TextInputFormatter> inputFormatters; |
|||
|
|||
public readonly bool rendererIgnoresPointer; |
|||
|
|||
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 = 1.0, int maxLines = 1, |
|||
bool autofocus = false, Color selectionColor = null, ValueChanged<string> onChanged = null, |
|||
ValueChanged<string> onSubmitted = null, SelectionChangedCallback onSelectionChanged = null, |
|||
List<TextInputFormatter> inputFormatters = null, bool rendererIgnoresPointer = false, |
|||
Key key = null) : base(key) |
|||
{ |
|||
D.assert(controller != null); |
|||
D.assert(focusNode != null); |
|||
D.assert(style != null); |
|||
D.assert(cursorColor != null); |
|||
this.controller = controller; |
|||
this.focusNode = focusNode; |
|||
this.obscureText = obscureText; |
|||
this.autocorrect = autocorrect; |
|||
this.style = style; |
|||
this.textAlign = textAlign; |
|||
this.textDirection = textDirection; |
|||
this.textScaleFactor = textScaleFactor; |
|||
this.cursorColor = cursorColor; |
|||
this.maxLines = maxLines; |
|||
this.autofocus = autofocus; |
|||
this.selectionColor = selectionColor; |
|||
this.onChanged = onChanged; |
|||
this.onSubmitted = onSubmitted; |
|||
this.onSelectionChanged = onSelectionChanged; |
|||
this.rendererIgnoresPointer = rendererIgnoresPointer; |
|||
if (maxLines == 1) |
|||
{ |
|||
this.inputFormatters = new List<TextInputFormatter>(); |
|||
this.inputFormatters.Add(BlacklistingTextInputFormatter.singleLineFormatter); |
|||
if (inputFormatters != null) |
|||
{ |
|||
this.inputFormatters.AddRange(inputFormatters); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.inputFormatters = inputFormatters; |
|||
} |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new EditableTextState(); |
|||
} |
|||
|
|||
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
|||
base.debugFillProperties(properties); |
|||
properties.add(new DiagnosticsProperty<TextEditingController>("controller", controller)); |
|||
properties.add(new DiagnosticsProperty<FocusNode>("focusNode", focusNode)); |
|||
properties.add(new DiagnosticsProperty<bool>("obscureText", obscureText, defaultValue: false)); |
|||
properties.add(new DiagnosticsProperty<bool>("autocorrect", autocorrect, defaultValue: true)); |
|||
if (style != null) |
|||
{ |
|||
style.debugFillProperties(properties); |
|||
} |
|||
properties.add(new EnumProperty<TextAlign>("textAlign", textAlign, defaultValue: null)); |
|||
properties.add(new EnumProperty<TextDirection?>("textDirection", textDirection, defaultValue: null)); |
|||
properties.add(new DiagnosticsProperty<double>("textScaleFactor", textScaleFactor, defaultValue: null)); |
|||
properties.add(new DiagnosticsProperty<int>("maxLines", maxLines, defaultValue: 1)); |
|||
properties.add(new DiagnosticsProperty<bool>("autofocus", autofocus, defaultValue: false)); |
|||
} |
|||
} |
|||
|
|||
public class EditableTextState: State<EditableText>, TextInputClient |
|||
{ |
|||
|
|||
const int _kObscureShowLatestCharCursorTicks = 3; |
|||
private ValueNotifier<bool> _showCursor = new ValueNotifier<bool>(true); // todo
|
|||
private GlobalKey _editableKey = GlobalKey.key(); |
|||
private bool _didAutoFocus = false; |
|||
|
|||
TextInputConnection _textInputConnection; |
|||
private int _obscureShowCharTicksPending = 0; |
|||
private int _obscureLatestCharIndex; |
|||
|
|||
bool _textChangedSinceLastCaretUpdate = false; |
|||
public override void initState() |
|||
{ |
|||
base.initState(); |
|||
widget.controller.addListener(_didChangeTextEditingValue); |
|||
widget.focusNode.addListener(_handleFocusChanged); |
|||
} |
|||
|
|||
public override void didChangeDependencies() |
|||
{ |
|||
base.didChangeDependencies(); |
|||
if (!_didAutoFocus && widget.autofocus) |
|||
{ |
|||
FocusScope.of(context).autofocus(widget.focusNode); |
|||
_didAutoFocus = true; |
|||
} |
|||
} |
|||
|
|||
public override void didUpdateWidget(StatefulWidget old) |
|||
{ |
|||
EditableText oldWidget = (EditableText) old; |
|||
base.didUpdateWidget(oldWidget); |
|||
if (widget.controller != oldWidget.controller) |
|||
{ |
|||
oldWidget.controller.removeListener(_didChangeTextEditingValue); |
|||
widget.controller.addListener(_didChangeTextEditingValue); |
|||
_updateRemoteEditingValueIfNeeded(); |
|||
} |
|||
if (widget.focusNode != oldWidget.focusNode) { |
|||
oldWidget.focusNode.removeListener(_handleFocusChanged); |
|||
widget.focusNode.addListener(_handleFocusChanged); |
|||
} |
|||
} |
|||
|
|||
public override void dispose() |
|||
{ |
|||
widget.controller.removeListener(_didChangeTextEditingValue); |
|||
_closeInputConnectionIfNeeded(); |
|||
D.assert(!_hasInputConnection); |
|||
widget.focusNode.removeListener(_handleFocusChanged); |
|||
base.dispose(); |
|||
} |
|||
|
|||
TextEditingValue _lastKnownRemoteTextEditingValue; |
|||
|
|||
public void updateEditingValue(TextEditingValue value) { |
|||
if (value.text != _value.text) { |
|||
// _hideSelectionOverlayIfNeeded();
|
|||
if (widget.obscureText && value.text.Length == _value.text.Length + 1) { |
|||
_obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks; |
|||
_obscureLatestCharIndex = _value.selection.baseOffset; |
|||
} |
|||
} |
|||
|
|||
_lastKnownRemoteTextEditingValue = value; |
|||
_formatAndSetValue(value); |
|||
} |
|||
|
|||
void _updateRemoteEditingValueIfNeeded() |
|||
{ |
|||
if (!_hasInputConnection) |
|||
return; |
|||
var localValue = _value; |
|||
if (localValue == _lastKnownRemoteTextEditingValue) |
|||
return; |
|||
_lastKnownRemoteTextEditingValue = localValue; |
|||
_textInputConnection.setEditingState(localValue); |
|||
} |
|||
|
|||
|
|||
bool _hasInputConnection |
|||
{ |
|||
get |
|||
{ |
|||
return _textInputConnection != null && _textInputConnection.attached; |
|||
} |
|||
} |
|||
|
|||
|
|||
void _openInputConnection() { |
|||
if (!_hasInputConnection) { |
|||
TextEditingValue localValue = _value; |
|||
_lastKnownRemoteTextEditingValue = localValue; |
|||
_textInputConnection = Window.instance.textInput.attach(this); |
|||
_textInputConnection.setEditingState(localValue); |
|||
} |
|||
} |
|||
|
|||
void _closeInputConnectionIfNeeded() { |
|||
if (_hasInputConnection) { |
|||
_textInputConnection.close(); |
|||
_textInputConnection = null; |
|||
_lastKnownRemoteTextEditingValue = null; |
|||
} |
|||
} |
|||
|
|||
void _openOrCloseInputConnectionIfNeeded() { |
|||
if (_hasFocus && widget.focusNode.consumeKeyboardToken()) { |
|||
_openInputConnection(); |
|||
} else if (!_hasFocus) { |
|||
_closeInputConnectionIfNeeded(); |
|||
widget.controller.clearComposing(); |
|||
} |
|||
} |
|||
|
|||
public void requestKeyboard() |
|||
{ |
|||
if (_hasFocus) |
|||
{ |
|||
_openInputConnection(); |
|||
} |
|||
else |
|||
{ |
|||
FocusScope.of(context).requestFocus(widget.focusNode); |
|||
} |
|||
} |
|||
|
|||
private void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) { |
|||
widget.controller.selection = selection; |
|||
requestKeyboard(); |
|||
|
|||
if (widget.onSelectionChanged != null) |
|||
{ |
|||
widget.onSelectionChanged(selection, cause); |
|||
} |
|||
} |
|||
|
|||
void _handleCaretChanged(Rect caretRect) { |
|||
if (_textChangedSinceLastCaretUpdate) { |
|||
_textChangedSinceLastCaretUpdate = false; |
|||
// scheduleMicrotask(() { // todo
|
|||
// _scrollController.animateTo(
|
|||
// _getScrollOffsetForCaret(caretRect),
|
|||
// curve: Curves.fastOutSlowIn,
|
|||
// duration: const Duration(milliseconds: 50),
|
|||
// );
|
|||
// });
|
|||
} |
|||
} |
|||
private void _formatAndSetValue(TextEditingValue value) { |
|||
var textChanged = (_value == null ? null : _value.text) != (value == null ? null : value.text); |
|||
if (widget.inputFormatters != null && widget.inputFormatters.isNotEmpty()) { |
|||
foreach (var formatter in widget.inputFormatters) |
|||
{ |
|||
value = formatter.formatEditUpdate(_value, value); |
|||
} |
|||
_value = value; |
|||
_updateRemoteEditingValueIfNeeded(); |
|||
} else { |
|||
_value = value; |
|||
} |
|||
|
|||
if (textChanged && widget.onChanged != null) |
|||
{ |
|||
widget.onChanged(value.text); |
|||
} |
|||
} |
|||
|
|||
private TextEditingValue _value |
|||
{ |
|||
get { return widget.controller.value; } |
|||
set |
|||
{ |
|||
widget.controller.value = value; |
|||
} |
|||
} |
|||
|
|||
private bool _hasFocus |
|||
{ |
|||
get { return widget.focusNode.hasFocus; } |
|||
} |
|||
|
|||
private bool _isMultiline |
|||
{ |
|||
get { return widget.maxLines != 1; } |
|||
} |
|||
|
|||
private void _didChangeTextEditingValue() |
|||
{ |
|||
_updateRemoteEditingValueIfNeeded(); |
|||
_textChangedSinceLastCaretUpdate = true; |
|||
setState(() => {}); |
|||
} |
|||
|
|||
private void _handleFocusChanged() |
|||
{ |
|||
_openOrCloseInputConnectionIfNeeded(); |
|||
if (!_hasFocus) { |
|||
_value = new TextEditingValue(text: _value.text); |
|||
} else if (!_value.selection.isValid) { |
|||
widget.controller.selection = TextSelection.collapsed(offset: _value.text.Length); |
|||
} |
|||
} |
|||
|
|||
|
|||
private TextDirection? _textDirection |
|||
{ |
|||
get |
|||
{ |
|||
TextDirection? result = widget.textDirection ?? Directionality.of(context); |
|||
D.assert(result != null, |
|||
string.Format("{0} created without a textDirection and with no ambient Directionality.", GetType().FullName)); |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
public RenderEditable renderEditable |
|||
{ |
|||
get { return (RenderEditable)_editableKey.currentContext.findRenderObject(); } |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
FocusScope.of(context).reparentIfNeeded(widget.focusNode); |
|||
// todo base.build(context); See AutomaticKeepAliveClientMixin.
|
|||
return new _Editable( |
|||
key: _editableKey, |
|||
textSpan: buildTextSpan(), |
|||
value: _value, |
|||
cursorColor: widget.cursorColor, |
|||
showCursor: _showCursor, |
|||
hasFocus: _hasFocus, |
|||
maxLines: widget.maxLines, |
|||
selectionColor: widget.selectionColor, |
|||
textScaleFactor: 1.0, // todo widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
|||
textAlign: widget.textAlign, |
|||
textDirection: _textDirection, |
|||
obscureText: widget.obscureText, |
|||
autocorrect: widget.autocorrect, |
|||
offset: new _FixedViewportOffset(0.0), |
|||
onSelectionChanged: _handleSelectionChanged, |
|||
onCaretChanged: _handleCaretChanged, |
|||
rendererIgnoresPointer: widget.rendererIgnoresPointer |
|||
); |
|||
|
|||
} |
|||
|
|||
public TextSpan buildTextSpan() { |
|||
if (!widget.obscureText && _value.composing.isValid) { |
|||
TextStyle composingStyle = widget.style.merge( |
|||
new TextStyle(decoration: TextDecoration.underline) |
|||
); |
|||
|
|||
return new TextSpan( |
|||
style: widget.style, |
|||
children: new List<TextSpan> |
|||
{ |
|||
new TextSpan(text: _value.composing.textBefore(_value.text)), |
|||
new TextSpan( |
|||
style: composingStyle, |
|||
text: _value.composing.textInside(_value.text) |
|||
), |
|||
new TextSpan(text: _value.composing.textAfter(_value.text)), |
|||
}); |
|||
} |
|||
|
|||
var text = _value.text; |
|||
if (widget.obscureText) { |
|||
text = new string(RenderEditable.obscuringCharacter, text.Length); |
|||
int o = |
|||
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : -1; |
|||
if (o >= 0 && o < text.Length) |
|||
text = text.Substring(0, o) + _value.text.Substring(o, 1) + text.Substring(o + 1); |
|||
} |
|||
return new TextSpan(style: widget.style, text: text); |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
internal class _Editable : LeafRenderObjectWidget |
|||
{ |
|||
public readonly TextSpan textSpan; |
|||
public readonly TextEditingValue value; |
|||
public readonly Color cursorColor; |
|||
public readonly ValueNotifier<bool> showCursor; |
|||
public readonly bool hasFocus; |
|||
public readonly int maxLines; |
|||
public readonly Color selectionColor; |
|||
public readonly double textScaleFactor; |
|||
public readonly TextAlign textAlign; |
|||
public readonly TextDirection? textDirection; |
|||
public readonly bool obscureText; |
|||
public readonly bool autocorrect; |
|||
public readonly ViewportOffset offset; |
|||
public readonly SelectionChangedHandler onSelectionChanged; |
|||
public readonly CaretChangedHandler onCaretChanged; |
|||
public readonly bool rendererIgnoresPointer; |
|||
|
|||
|
|||
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, |
|||
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, Key key = null) : base(key) |
|||
{ |
|||
|
|||
this.textSpan = textSpan; |
|||
this.value = value; |
|||
this.cursorColor = cursorColor; |
|||
this.showCursor = showCursor; |
|||
this.hasFocus = hasFocus; |
|||
this.maxLines = maxLines; |
|||
this.selectionColor = selectionColor; |
|||
this.textScaleFactor = textScaleFactor; |
|||
this.textAlign = textAlign; |
|||
this.textDirection = textDirection; |
|||
this.obscureText = obscureText; |
|||
this.autocorrect = autocorrect; |
|||
this.offset = offset; |
|||
this.onSelectionChanged = onSelectionChanged; |
|||
this.onCaretChanged = onCaretChanged; |
|||
this.rendererIgnoresPointer = rendererIgnoresPointer; |
|||
} |
|||
|
|||
public override RenderObject createRenderObject(BuildContext context) |
|||
{ |
|||
return new RenderEditable( |
|||
text: textSpan, |
|||
textDirection: textDirection??TextDirection.ltr, |
|||
offset: offset, |
|||
showCursor: showCursor, |
|||
cursorColor: cursorColor, |
|||
hasFocus: hasFocus, |
|||
maxLines: maxLines, |
|||
selectionColor: selectionColor, |
|||
textScaleFactor: textScaleFactor, |
|||
textAlign: textAlign, |
|||
selection: value.selection, |
|||
obscureText: obscureText, |
|||
onSelectionChanged: onSelectionChanged, |
|||
onCaretChanged: onCaretChanged, |
|||
ignorePointer: rendererIgnoresPointer |
|||
); |
|||
} |
|||
|
|||
public override void updateRenderObject(BuildContext context, RenderObject renderObject) |
|||
{ |
|||
var edit = (RenderEditable) renderObject; |
|||
edit.text = textSpan; |
|||
edit.cursorColor = cursorColor; |
|||
edit.showCursor = showCursor; |
|||
edit.hasFocus = hasFocus; |
|||
edit.maxLines = maxLines; |
|||
edit.selectionColor = selectionColor; |
|||
edit.textScaleFactor = textScaleFactor; |
|||
edit.textAlign = textAlign; |
|||
edit.textDirection = textDirection; |
|||
edit.selection = value.selection; |
|||
edit.offset = offset; |
|||
edit.onSelectionChanged = onSelectionChanged; |
|||
edit.onCaretChanged = onCaretChanged; |
|||
edit.ignorePointer = rendererIgnoresPointer; |
|||
edit.obscureText = obscureText; |
|||
} |
|||
} |
|||
|
|||
|
|||
class _FixedViewportOffset : ViewportOffset { |
|||
internal _FixedViewportOffset(double _pixels) { |
|||
this._pixels = _pixels; |
|||
} |
|||
|
|||
internal new static _FixedViewportOffset zero() { |
|||
return new _FixedViewportOffset(0.0); |
|||
} |
|||
|
|||
double _pixels; |
|||
|
|||
public override double pixels { |
|||
get { return this._pixels; } |
|||
} |
|||
|
|||
public override bool applyViewportDimension(double viewportDimension) { |
|||
return true; |
|||
} |
|||
|
|||
public override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { |
|||
return true; |
|||
} |
|||
|
|||
public override void correctBy(double correction) { |
|||
this._pixels += correction; |
|||
} |
|||
|
|||
public override void jumpTo(double pixels) { |
|||
} |
|||
|
|||
public override IPromise animateTo(double to, TimeSpan duration, Curve curve) { |
|||
return Promise.Resolved(); |
|||
} |
|||
|
|||
public override ScrollDirection userScrollDirection { |
|||
get { return ScrollDirection.idle; } |
|||
} |
|||
|
|||
public override bool allowImplicitScrolling { |
|||
get { return false; } |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: b400bbae381e45f8b7242bb9e881bcd5 |
|||
timeCreated: 1537163865 |
|
|||
using UIWidgets.foundation; |
|||
|
|||
namespace UIWidgets.widgets |
|||
{ |
|||
|
|||
class _FocusScopeMarker: InheritedWidget { |
|||
|
|||
public _FocusScopeMarker( FocusScopeNode node, Widget child, Key key = null) : base(key, child) |
|||
{ |
|||
D.assert(node != null); |
|||
this.node = node; |
|||
} |
|||
|
|||
public readonly FocusScopeNode node; |
|||
|
|||
public override bool updateShouldNotify(InheritedWidget oldWidget) |
|||
{ |
|||
return node != ((_FocusScopeMarker)oldWidget).node; |
|||
} |
|||
} |
|||
|
|||
public class FocusScope : StatefulWidget |
|||
{ |
|||
public FocusScope(FocusScopeNode node, Widget child, Key key = null, bool autofocus = false) : base(key) |
|||
{ |
|||
this.node = node; |
|||
this.child = child; |
|||
this.autofocus = autofocus; |
|||
} |
|||
|
|||
public readonly FocusScopeNode node; |
|||
|
|||
public readonly bool autofocus; |
|||
|
|||
public readonly Widget child; |
|||
|
|||
public static FocusScopeNode of(BuildContext context) { |
|||
var scope = (_FocusScopeMarker)context.inheritFromWidgetOfExactType(typeof(_FocusScopeMarker)); |
|||
if (scope != null && scope.node != null) |
|||
{ |
|||
return scope.node; |
|||
} |
|||
return context.owner.focusManager.rootScope; |
|||
} |
|||
|
|||
public override State createState() |
|||
{ |
|||
return new _FocusScopeState(); |
|||
} |
|||
} |
|||
|
|||
class _FocusScopeState : State<FocusScope> |
|||
{ |
|||
private bool _didAutofocus = false; |
|||
|
|||
public override void didChangeDependencies() |
|||
{ |
|||
base.didChangeDependencies(); |
|||
if (!_didAutofocus && widget.autofocus) |
|||
{ |
|||
FocusScope.of(context).setFirstFocus(widget.node); |
|||
_didAutofocus = true; |
|||
} |
|||
} |
|||
|
|||
public override void dispose() { |
|||
widget.node.detach(); |
|||
base.dispose(); |
|||
} |
|||
|
|||
public override Widget build(BuildContext context) |
|||
{ |
|||
FocusScope.of(context).reparentScopeIfNeeded(widget.node); |
|||
return new _FocusScopeMarker(node:widget.node, child:widget.child); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: c5976c3bfa824d30a0fba367accca614 |
|||
timeCreated: 1536895629 |
撰写
预览
正在加载...
取消
保存
Reference in new issue