浏览代码

Complete todos in editable text.

/main
Yuncong Zhang 6 年前
当前提交
2c274328
共有 5 个文件被更改,包括 221 次插入95 次删除
  1. 77
      Runtime/rendering/editable.cs
  2. 79
      Runtime/service/text_input.cs
  3. 114
      Runtime/widgets/editable_text.cs
  4. 22
      Runtime/widgets/focus_scope.cs
  5. 24
      Runtime/widgets/text_selection.cs

77
Runtime/rendering/editable.cs


Offset _lastTapDownPosition;
public RenderEditable(
TextSpan text,
TextDirection textDirection,
TextSpan text,
TextDirection textDirection,
TextAlign textAlign = TextAlign.left,
float textScaleFactor = 1.0f,
TextAlign textAlign = TextAlign.left,
float textScaleFactor = 1.0f,
bool? hasFocus = null,
int? maxLines = 1,
bool? hasFocus = null,
int? maxLines = 1,
TextSelection selection = null,
bool obscureText = false,
TextSelection selection = null,
bool obscureText = false,
CaretChangedHandler onCaretChanged = null,
CaretChangedHandler onCaretChanged = null,
bool ignorePointer = false,
float cursorWidth = 1.0f,
Radius cursorRadius = null,

this._textPainter = new TextPainter(text: text, textAlign: textAlign, textDirection: textDirection,
textScaleFactor: textScaleFactor);
this._cursorColor = cursorColor;
this._showCursor = showCursor ?? new ValueNotifier<bool>(false);
this._hasFocus = hasFocus ?? false;
this._maxLines = maxLines;

this.markNeedsTextLayout();
}
}
float _devicePixelRatio;
public Color backgroundCursorColor {

this.markNeedsPaint();
}
}
Color _backgroundCursorColor;
public bool paintCursorAboveText {

this.markNeedsLayout();
}
}
bool _paintCursorOnTop;
public Offset cursorOffset {

this.markNeedsLayout();
}
}
Offset _cursorOffset;
public EdgeInsets floatingCursorAddedMargin {

this.markNeedsPaint();
}
}
EdgeInsets _floatingCursorAddedMargin;
bool _floatingCursorOn = false;

public bool selectionEnabled {
get { return this.enableInteractiveSelection ?? !this.obscureText; }
}
public bool obscureText {
get { return this._obscureText; }
set {

baseOffset: firstWord.baseOffset,
extentOffset: lastWord.extentOffset,
affinity: firstWord.affinity),
this,
this,
cause.Value);
}
}

if (this._cursorOffset != null) {
caretRect = caretRect.shift(this._cursorOffset);
}
if (this.cursorRadius == null) {
canvas.drawRect(caretRect, paint);
}

}
}
public void setFloatingCursor(FloatingCursorDragState? state, Offset boundedOffset, TextPosition lastTextPosition,
float? resetLerpValue = null) {
D.assert(boundedOffset != null);
D.assert(lastTextPosition != null);
if (state == FloatingCursorDragState.Start) {
this._relativeOrigin = new Offset(0, 0);
this._previousOffset = null;
this._resetOriginOnBottom = false;
this._resetOriginOnTop = false;
this._resetOriginOnRight = false;
this._resetOriginOnBottom = false;
}
this._floatingCursorOn = state != FloatingCursorDragState.End;
this._resetFloatingCursorAnimationValue = resetLerpValue;
if (this._floatingCursorOn) {
this._floatingCursorOffset = boundedOffset;
this._floatingCursorTextPosition = lastTextPosition;
}
this.markNeedsPaint();
}
void _paintFloatingCaret(Canvas canvas, Offset effectiveOffset) {
D.assert(this._textLayoutLastWidth == this.constraints.maxWidth);
D.assert(this._floatingCursorOn);

float sizeAdjustmentY = _kFloatingCaretSizeIncrease.dy;
if (this._resetFloatingCursorAnimationValue != null) {
sizeAdjustmentX = MathUtils.lerpFloat(sizeAdjustmentX, 0f, this._resetFloatingCursorAnimationValue.Value);
sizeAdjustmentY = MathUtils.lerpFloat(sizeAdjustmentY, 0f, this._resetFloatingCursorAnimationValue.Value);
sizeAdjustmentX =
MathUtils.lerpFloat(sizeAdjustmentX, 0f, this._resetFloatingCursorAnimationValue.Value);
sizeAdjustmentY =
MathUtils.lerpFloat(sizeAdjustmentY, 0f, this._resetFloatingCursorAnimationValue.Value);
}
Rect floatingCaretPrototype = Rect.fromLTRB(

bool _resetOriginOnBottom = false;
float? _resetFloatingCursorAnimationValue;
Offset calculateBoundedFloatingCursorOffset(Offset rawCursorOffset) {
public Offset calculateBoundedFloatingCursorOffset(Offset rawCursorOffset) {
Offset deltaPosition = new Offset(0f, 0f);
float topBound = -this.floatingCursorAddedMargin.top;
float bottomBound = this._textPainter.height - this.preferredLineHeight +

if (this._resetOriginOnLeft && deltaPosition.dx > 0) {
this._relativeOrigin = new Offset(rawCursorOffset.dx - leftBound, this._relativeOrigin.dy);
this._resetOriginOnLeft = false;
} else if (this._resetOriginOnRight && deltaPosition.dx < 0) {
}
else if (this._resetOriginOnRight && deltaPosition.dx < 0) {
this._relativeOrigin = new Offset(rawCursorOffset.dx - rightBound, this._relativeOrigin.dy);
this._resetOriginOnRight = false;
}

this._resetOriginOnTop = false;
} else if (this._resetOriginOnBottom && deltaPosition.dy < 0) {
}
else if (this._resetOriginOnBottom && deltaPosition.dy < 0) {
this._relativeOrigin = new Offset(this._relativeOrigin.dx, rawCursorOffset.dy - bottomBound);
this._resetOriginOnBottom = false;
}

if (currentX < leftBound && deltaPosition.dx < 0) {
this._resetOriginOnLeft = true;
} else if (currentX > rightBound && deltaPosition.dx > 0) {
}
else if (currentX > rightBound && deltaPosition.dx > 0) {
} else if (currentY > bottomBound && deltaPosition.dy > 0) {
}
else if (currentY > bottomBound && deltaPosition.dy > 0) {
this._resetOriginOnBottom = true;
}

if (this._selection != null && !this._floatingCursorOn) {
if (this._selection.isCollapsed && this._showCursor.value && this.cursorColor != null) {
showCaret = true;
} else if (!this._selection.isCollapsed && this._selectionColor != null) {
}
else if (!this._selection.isCollapsed && this._selectionColor != null) {
showSelection = true;
}
}

79
Runtime/service/text_input.cs


using UnityEngine;
namespace Unity.UIWidgets.service {
public enum FloatingCursorDragState {
Start,
Update,
End
}
public class RawFloatingCursorPoint {
public RawFloatingCursorPoint(
Offset offset = null,
FloatingCursorDragState? state = null
) {
D.assert(state != null);
D.assert(state == FloatingCursorDragState.Update ? offset != null : true);
this.offset = offset;
this.state = state;
}
public readonly Offset offset;
public readonly FloatingCursorDragState? state;
}
public class TextInputType : IEquatable<TextInputType> {
public readonly int index;
public readonly bool? signed;

case "TextAffinity.upstream":
return TextAffinity.upstream;
}
return null;
}

case "TextInputAction.newline":
return TextInputAction.newline;
}
public static FloatingCursorDragState _toTextCursorAction(string state) {
switch (state) {
case "FloatingCursorDragState.start":
return FloatingCursorDragState.Start;
case "FloatingCursorDragState.update":
return FloatingCursorDragState.Update;
case "FloatingCursorDragState.end":
return FloatingCursorDragState.End;
}
throw new UIWidgetsError("Unknown text cursor action: $state");
}
public static RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state,
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,
"You must provide a value for the vertical location of the floating cursor.");
Offset offset = state == FloatingCursorDragState.Update
? new Offset(encoded["X"] ?? 0.0f, encoded["Y"] ?? 0.0f)
: new Offset(0, 0);
return new RawFloatingCursorPoint(offset: offset, state: state);
}
public TextEditingValue(string text = "", TextSelection selection = null, TextRange composing = null) {
this.text = text;
this.selection = selection ?? TextSelection.collapsed(-1);

)
);
}
public TextEditingValue copyWith(string text = null, TextSelection selection = null,
TextRange composing = null) {
return new TextEditingValue(

if (selection.start < 0) {
selection = TextSelection.collapsed(0, this.selection.affinity);
}
newText = selection.textBefore(this.text) + text + selection.textAfter(this.text);
newSelection = TextSelection.collapsed(selection.start + text.Length);
return new TextEditingValue(

}
return this.copyWith(text: this.text.Substring(0, this.selection.start) +
this.text.Substring(this.selection.start + 1),
this.text.Substring(this.selection.start + 1),
return this.copyWith(text: newText, selection: TextSelection.collapsed(this.selection.start),
return this.copyWith(text: newText, selection: TextSelection.collapsed(this.selection.start),
composing: TextRange.empty);
}
}

void updateEditingValue(TextEditingValue value);
void performAction(TextInputAction action);
void updateFloatingCursor(RawFloatingCursorPoint point);
}
public enum TextInputAction {

class TextInputConfiguration {
public TextInputConfiguration(TextInputType inputType = null,
bool obscureText = false, bool autocorrect = true, TextInputAction inputAction = TextInputAction.done,
Brightness keyboardAppearance = Brightness.light, TextCapitalization textCapitalization = TextCapitalization.none,
Brightness keyboardAppearance = Brightness.light,
TextCapitalization textCapitalization = TextCapitalization.none,
bool unityTouchKeyboard = false) {
this.inputType = inputType ?? TextInputType.text;
this.inputAction = inputAction;

D.assert(this.imeRequired());
TextInput.keyboardDelegate.setIMEPos(imeGlobalPos);
}
public bool imeRequired() {
return TextInput.keyboardDelegate != null && TextInput.keyboardDelegate.imeRequired();
}

if (Application.isEditor) {
keyboardDelegate = new DefaultKeyboardDelegate();
} else {
}
else {
#if UNITY_IOS || UNITY_ANDROID
if (configuration.unityTouchKeyboard) {
keyboardDelegate = new UnityTouchScreenKeyboardDelegate();

keyboardDelegate = new DefaultKeyboardDelegate();
#endif
}
keyboardDelegate.setClient(connection._id, configuration);
return connection;
}

(keyboardDelegate as TextInputUpdateListener)?.Update();
}
}
internal static void _updateEditingState(int client, TextEditingValue value) {
if (_currentConnection == null) {
return;

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

keyboardDelegate.hide();
}
});
}
}
}
}

114
Runtime/widgets/editable_text.cs


this._cursorBlinkOpacityController = new AnimationController(vsync: this, duration: _fadeDuration);
this._cursorBlinkOpacityController.addListener(this._onCursorColorTick);
this._floatingCursorResetController = new AnimationController(vsync: this);
// this._floatingCursorResetController.addListener(_onFloatingCursorResetTick); // TODO: remove comment when _onFloatingCursorResetTick is ready
this._floatingCursorResetController.addListener(this._onFloatingCursorResetTick);
}
public override void didChangeDependencies() {

public override void dispose() {
this.widget.controller.removeListener(this._didChangeTextEditingValue);
this._cursorBlinkOpacityController.removeListener(this._onCursorColorTick);
// this._floatingCursorResetController.removeListener(this._onFloatingCursorResetTick); // TODO: remove comment when _onFloatingCursorResetTick is ready
this._floatingCursorResetController.removeListener(this._onFloatingCursorResetTick);
this._closeInputConnectionIfNeeded();
D.assert(!this._hasInputConnection);
this._stopCursorTimer();

get { return new Offset(0, this.renderEditable.preferredLineHeight / 2); }
}
// TODO: remove comment when renderEditable is updated and FloatingCursorDragState is ready
// void updateFloatingCursor(RawFloatingCursorPoint point) {
// switch(point.state){
// case FloatingCursorDragState.Start:
// TextPosition currentTextPosition = new TextPosition(offset: this.renderEditable.selection.baseOffset);
// this._startCaretRect = this.renderEditable.getLocalRectForCaret(currentTextPosition);
// this.renderEditable.setFloatingCursor(point.state, this._startCaretRect.center - this._floatingCursorOffset, currentTextPosition);
// break;
// case FloatingCursorDragState.Update:
// // We want to send in points that are centered around a (0,0) origin, so we cache the
// // position on the first update call.
// if (this._pointOffsetOrigin != null) {
// Offset centeredPoint = point.offset - this._pointOffsetOrigin;
// Offset rawCursorOffset = this._startCaretRect.center + centeredPoint - this._floatingCursorOffset;
// this._lastBoundedOffset = this.renderEditable.calculateBoundedFloatingCursorOffset(rawCursorOffset);
// this._lastTextPosition = this.renderEditable.getPositionForPoint(this.renderEditable.localToGlobal(this._lastBoundedOffset + this._floatingCursorOffset));
// this.renderEditable.setFloatingCursor(point.state, this._lastBoundedOffset, this._lastTextPosition);
// } else {
// this._pointOffsetOrigin = point.offset;
// }
// break;
// case FloatingCursorDragState.End:
// this._floatingCursorResetController.setValue(0.0f);
// this._floatingCursorResetController.animateTo(1.0f, duration: _floatingCursorResetTime, curve: Curves.decelerate);
// break;
// }
// }
public void updateFloatingCursor(RawFloatingCursorPoint point) {
switch (point.state) {
case FloatingCursorDragState.Start:
TextPosition currentTextPosition =
new TextPosition(offset: this.renderEditable.selection.baseOffset);
this._startCaretRect = this.renderEditable.getLocalRectForCaret(currentTextPosition);
this.renderEditable.setFloatingCursor(point.state,
this._startCaretRect.center - this._floatingCursorOffset, currentTextPosition);
break;
case FloatingCursorDragState.Update:
// We want to send in points that are centered around a (0,0) origin, so we cache the
// position on the first update call.
if (this._pointOffsetOrigin != null) {
Offset centeredPoint = point.offset - this._pointOffsetOrigin;
Offset rawCursorOffset =
this._startCaretRect.center + centeredPoint - this._floatingCursorOffset;
this._lastBoundedOffset =
this.renderEditable.calculateBoundedFloatingCursorOffset(rawCursorOffset);
this._lastTextPosition = this.renderEditable.getPositionForPoint(
this.renderEditable.localToGlobal(this._lastBoundedOffset + this._floatingCursorOffset));
this.renderEditable.setFloatingCursor(point.state, this._lastBoundedOffset,
this._lastTextPosition);
}
else {
this._pointOffsetOrigin = point.offset;
}
// TODO: remove comment when RenderEditable.setFloatingCursor, Floating CursorDragState, and force press is ready
// void _onFloatingCursorResetTick() {
// Offset finalPosition = this.renderEditable.getLocalRectForCaret(this._lastTextPosition).centerLeft - this._floatingCursorOffset;
// if (this._floatingCursorResetController.isCompleted) {
// this.renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, this._lastTextPosition);
// if (this._lastTextPosition.offset != this.renderEditable.selection.baseOffset)
// this._handleSelectionChanged(TextSelection.collapsed(offset: this._lastTextPosition.offset), this.renderEditable, SelectionChangedCause.forcePress);
// this._startCaretRect = null;
// this._lastTextPosition = null;
// this._pointOffsetOrigin = null;
// this._lastBoundedOffset = null;
// } else {
// float lerpValue = this._floatingCursorResetController.value;
// float lerpX = MathUtils.lerpFloat(this._lastBoundedOffset.dx, finalPosition.dx, lerpValue);
// float lerpY = MathUtils.lerpFloat(this._lastBoundedOffset.dy, finalPosition.dy, lerpValue);
//
// this.renderEditable.setFloatingCursor(FloatingCursorDragState.Update, new Offset(lerpX, lerpY), this._lastTextPosition, resetLerpValue: lerpValue);
// }
// }
break;
case FloatingCursorDragState.End:
this._floatingCursorResetController.setValue(0.0f);
this._floatingCursorResetController.animateTo(1.0f, duration: _floatingCursorResetTime,
curve: Curves.decelerate);
break;
}
}
void _onFloatingCursorResetTick() {
Offset finalPosition = this.renderEditable.getLocalRectForCaret(this._lastTextPosition).centerLeft - this._floatingCursorOffset;
if (this._floatingCursorResetController.isCompleted) {
this.renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, this._lastTextPosition);
if (this._lastTextPosition.offset != this.renderEditable.selection.baseOffset)
this._handleSelectionChanged(TextSelection.collapsed(offset: this._lastTextPosition.offset), this.renderEditable, SelectionChangedCause.forcePress);
this._startCaretRect = null;
this._lastTextPosition = null;
this._pointOffsetOrigin = null;
this._lastBoundedOffset = null;
} else {
float lerpValue = this._floatingCursorResetController.value;
float lerpX = MathUtils.lerpFloat(this._lastBoundedOffset.dx, finalPosition.dx, lerpValue);
float lerpY = MathUtils.lerpFloat(this._lastBoundedOffset.dy, finalPosition.dy, lerpValue);
this.renderEditable.setFloatingCursor(FloatingCursorDragState.Update, new Offset(lerpX, lerpY), this._lastTextPosition, resetLerpValue: lerpValue);
}
}
void _finalizeEditing(bool shouldUnfocus) {
if (this.widget.onEditingComplete != null) {

this._openInputConnection();
}
else {
// TODO: remove comment when FocusScope.ancestorsOf is ready
// List<FocusScopeNode> ancestorScopes = FocusScope.ancestorsOf(this.context);
// for (int i = ancestorScopes.Count - 1; i >= 1; i -= 1)
// ancestorScopes[i].setFirstFocus(ancestorScopes[i - 1]);
List<FocusScopeNode> ancestorScopes = FocusScope.ancestorsOf(this.context);
for (int i = ancestorScopes.Count - 1; i >= 1; i -= 1)
ancestorScopes[i].setFirstFocus(ancestorScopes[i - 1]);
FocusScope.of(this.context).requestFocus(this.widget.focusNode);
}
}

layerLink: this._layerLink,
renderObject: renderObject,
selectionControls: this.widget.selectionControls,
selectionDelegate: this
// dragStartBehavior: this.widget.dragStartBehavior // TODO: remove comment when TextSelectionOverlay is updated
selectionDelegate: this,
dragStartBehavior: this.widget.dragStartBehavior
);
bool longPress = cause == SelectionChangedCause.longPress;
if (cause != SelectionChangedCause.keyboard && (this._value.text.isNotEmpty() || longPress)) {

22
Runtime/widgets/focus_scope.cs


using Unity.UIWidgets.foundation;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
namespace Unity.UIWidgets.widgets {
class _FocusScopeMarker : InheritedWidget {

public readonly Widget child;
public static FocusScopeNode of(BuildContext context) {
D.assert(context != null);
var scope = (_FocusScopeMarker) context.inheritFromWidgetOfExactType(typeof(_FocusScopeMarker));
if (scope != null && scope.node != null) {
return scope.node;

}
public static List<FocusScopeNode> ancestorsOf(BuildContext context) {
D.assert(context != null);
List<FocusScopeNode> ancestors = new List<FocusScopeNode> { };
while (true) {
context = context.ancestorInheritedElementForWidgetOfExactType(typeof(_FocusScopeMarker));
if (context == null) {
return ancestors;
}
_FocusScopeMarker scope = (_FocusScopeMarker) context.widget;
ancestors.Add(scope.node);
context.visitAncestorElements((Element parent) => {
context = parent;
return false;
});
}
}
public override State createState() {

24
Runtime/widgets/text_selection.cs


LayerLink layerLink = null,
RenderEditable renderObject = null,
TextSelectionControls selectionControls = null,
TextSelectionDelegate selectionDelegate = null) {
TextSelectionDelegate selectionDelegate = null,
DragStartBehavior? dragStartBehavior = null) {
D.assert(value != null);
D.assert(context != null);
this.context = context;

D.assert(overlay != null);
this._handleController = new AnimationController(duration: _fadeDuration, vsync: overlay);
this._toolbarController = new AnimationController(duration: _fadeDuration, vsync: overlay);
this.dragStartBehavior = dragStartBehavior;
}
public readonly BuildContext context;

public readonly TextSelectionControls selectionControls;
public readonly TextSelectionDelegate selectionDelegate;
public readonly DragStartBehavior? dragStartBehavior;
public static TimeSpan _fadeDuration = TimeSpan.FromMilliseconds(150);
AnimationController _handleController;

renderObject: this.renderObject,
selection: this._selection,
selectionControls: this.selectionControls,
position: position
position: position,
dragStartBehavior: this.dragStartBehavior ?? DragStartBehavior.down
)
);
}

RenderEditable renderObject = null,
ValueChanged<TextSelection> onSelectionHandleChanged = null,
VoidCallback onSelectionHandleTapped = null,
TextSelectionControls selectionControls = null
TextSelectionControls selectionControls = null,
DragStartBehavior dragStartBehavior = DragStartBehavior.down
) : base(key: key) {
this.selection = selection;
this.position = position;

this.onSelectionHandleTapped = onSelectionHandleTapped;
this.selectionControls = selectionControls;
this.dragStartBehavior = dragStartBehavior;
}
public readonly TextSelection selection;

public readonly ValueChanged<TextSelection> onSelectionHandleChanged;
public readonly VoidCallback onSelectionHandleTapped;
public readonly TextSelectionControls selectionControls;
public readonly DragStartBehavior dragStartBehavior;
public override State createState() {
return new _TextSelectionHandleOverlayState();

link: this.widget.layerLink,
showWhenUnlinked: false,
child: new GestureDetector(
dragStartBehavior: this.widget.dragStartBehavior,
onPanStart: this._handleDragStart,
onPanUpdate: this._handleDragUpdate,
onTap: this._handleTap,

);
if (this.widget.onSingleLongTapStart != null) {
gestures[typeof(LongPressGestureRecognizer)] = new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => new LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
instance => {
instance.onLongPress = this._handleLongPressStart;
});
gestures[typeof(LongPressGestureRecognizer)] =
new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
() => new LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
instance => { instance.onLongPress = this._handleLongPressStart; });
}
if (this.widget.onDragSelectionStart != null ||

正在加载...
取消
保存