浏览代码

Merge pull request #313 from UnityTech/fix_editable

Fix editable overflow issue (#312)
/main
GitHub 5 年前
当前提交
251adcb1
共有 2 个文件被更改,包括 262 次插入232 次删除
  1. 476
      Runtime/rendering/editable.cs
  2. 18
      Tests/Editor/RenderEditable.cs

476
Runtime/rendering/editable.cs


using System.Collections.Generic;
using System.Xml.Schema;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.painting;

using Rect = Unity.UIWidgets.ui.Rect;
namespace Unity.UIWidgets.rendering {
class EditableUtils {
public static readonly float _kCaretGap = 1.0f;
public static readonly float _kCaretHeightOffset = 2.0f;
public static readonly Offset _kFloatingCaretSizeIncrease = new Offset(0.5f, 1.0f);
public static readonly float _kFloatingCaretRadius = 1.0f;
}
public delegate void SelectionChangedHandler(TextSelection selection, RenderEditable renderObject,
SelectionChangedCause cause);

}
public class RenderEditable : RenderBox {
public static readonly char obscuringCharacter = '•';
static readonly float _kCaretGap = 1.0f;
static readonly float _kCaretHeightOffset = 2.0f;
static readonly Offset _kFloatingCaretSizeIncrease = new Offset(0.5f, 1.0f);
static readonly float _kFloatingCaretRadius = 1.0f;
TextPainter _textPainter;
Color _cursorColor;
int? _maxLines;
int? _minLines;
bool _expands;
Color _selectionColor;
ViewportOffset _offset;
ValueNotifier<bool> _showCursor;
TextSelection _selection;
bool _obscureText;
TapGestureRecognizer _tap;
LongPressGestureRecognizer _longPress;
DoubleTapGestureRecognizer _doubleTap;
public bool ignorePointer;
public SelectionChangedHandler onSelectionChanged;
public CaretChangedHandler onCaretChanged;
Rect _lastCaretRect;
float? _textLayoutLastWidth;
List<TextBox> _selectionRects;
Rect _caretPrototype;
bool _hasVisualOverflow = false;
Offset _lastTapDownPosition;
ViewportOffset offset,
ValueNotifier<bool> showCursor,
float textScaleFactor = 1.0f,
ValueNotifier<bool> showCursor = null,
bool? hasFocus = null,
int? maxLines = 1,
int? minLines = null,

float textScaleFactor = 1.0f,
bool obscureText = false,
ViewportOffset offset = null,
bool obscureText = false,
float cursorWidth = 1.0f,
Radius cursorRadius = null,
bool paintCursorAboveText = false,

floatingCursorAddedMargin = floatingCursorAddedMargin ?? EdgeInsets.fromLTRB(4, 4, 4, 5);
D.assert(textSelectionDelegate != null);
D.assert(minLines == null || minLines > 0);
D.assert((minLines == null) || maxLines >= minLines, () => "minLines can't be greater than maxLines");
this._textPainter = new TextPainter(text: text, textAlign: textAlign, textDirection: textDirection,
textScaleFactor: textScaleFactor, strutStyle: strutStyle);
D.assert(maxLines == null || maxLines > 0);
D.assert((maxLines == null) || (minLines == null) || maxLines >= minLines,
() => "minLines can't be greater than maxLines");
D.assert(offset != null);
D.assert(cursorWidth >= 0.0f);
this._textPainter = new TextPainter(
text: text,
textAlign: textAlign,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
strutStyle: strutStyle);
this._backgroundCursorColor = backgroundCursorColor;
this._showCursor = showCursor ?? new ValueNotifier<bool>(false);
this._hasFocus = hasFocus ?? false;
this._maxLines = maxLines;

this._longPress = new LongPressGestureRecognizer(debugOwner: this);
this._longPress.onLongPress = this._handleLongPress;
this._backgroundCursorColor = backgroundCursorColor;
public static readonly char obscuringCharacter = '•';
public SelectionChangedHandler onSelectionChanged;
float? _textLayoutLastWidth;
public CaretChangedHandler onCaretChanged;
public bool ignorePointer;
float _devicePixelRatio;
public float devicePixelRatio {
get { return this._devicePixelRatio; }

}
}
float _devicePixelRatio;
public Color backgroundCursorColor {
get { return this._backgroundCursorColor; }
set {
if (this.backgroundCursorColor == value) {
return;
}
this._backgroundCursorColor = value;
this.markNeedsPaint();
}
}
Color _backgroundCursorColor;
public bool paintCursorAboveText {
get { return this._paintCursorOnTop; }
set {
if (this._paintCursorOnTop == value) {
return;
}
this._paintCursorOnTop = value;
this.markNeedsLayout();
}
}
bool _paintCursorOnTop;
public Offset cursorOffset {
get { return this._cursorOffset; }
set {
if (this._cursorOffset == value) {
return;
}
this._cursorOffset = value;
this.markNeedsLayout();
}
}
Offset _cursorOffset;
public EdgeInsets floatingCursorAddedMargin {
get { return this._floatingCursorAddedMargin; }
set {
if (this._floatingCursorAddedMargin == value) {
return;
}
this._floatingCursorAddedMargin = value;
this.markNeedsPaint();
}
}
EdgeInsets _floatingCursorAddedMargin;
bool _floatingCursorOn = false;
Offset _floatingCursorOffset;
TextPosition _floatingCursorTextPosition;
public bool selectionEnabled {
get { return this.enableInteractiveSelection ?? !this.obscureText; }
}
bool _obscureText;
public bool obscureText {
get { return this._obscureText; }

}
public TextSelectionDelegate textSelectionDelegate;
Rect _lastCaretRect;
public ValueListenable<bool> selectionStartInViewport {
get { return this._selectionStartInViewport; }

readonly ValueNotifier<bool> _selectionEndInViewport = new ValueNotifier<bool>(true);
DoubleTapGestureRecognizer _doubleTap;
void _updateSelectionExtentsVisibility(Offset effectiveOffset) {
Rect visibleRegion = Offset.zero & this.size;
Offset startOffset = this._textPainter.getOffsetForCaret(

.contains(startOffset + effectiveOffset);
Offset endOffset = this._textPainter.getOffsetForCaret(
new TextPosition(offset: this._selection.end, affinity: this._selection.affinity),
new TextPosition(offset: this._selection.end, affinity: this._selection.affinity),
Rect.zero
);
this._selectionEndInViewport.value = visibleRegion

int _baseOffset = -1;
int _previousCursorLocation;
int _previousCursorLocation = -1;
bool _resetCursor = false;

this.markNeedsLayout();
}
TextPainter _textPainter;
public TextSpan text {
get { return this._textPainter.text; }
set {

}
}
Color _cursorColor;
public Color cursorColor {
get { return this._cursorColor; }
set {

}
}
Color _backgroundCursorColor;
public Color backgroundCursorColor {
get { return this._backgroundCursorColor; }
set {
if (this.backgroundCursorColor == value) {
return;
}
this._backgroundCursorColor = value;
this.markNeedsPaint();
}
}
ValueNotifier<bool> _showCursor;
public ValueNotifier<bool> showCursor {
get { return this._showCursor; }
set {

}
}
int? _maxLines;
public int? maxLines {
get { return this._maxLines; }
set {

}
}
int? _minLines;
public int? minLines {
get { return this._minLines; }
set {

}
}
bool _expands;
public bool expands {
get { return this._expands; }
set {

this.markNeedsTextLayout();
}
}
Color _selectionColor;
public Color selectionColor {
get { return this._selectionColor; }

}
}
List<TextBox> _selectionRects;
TextSelection _selection;
public TextSelection selection {
get { return this._selection; }
set {

}
}
ViewportOffset _offset;
public ViewportOffset offset {
get { return this._offset; }
set {

}
this._cursorWidth = value;
this.markNeedsLayout();
}
}
bool _paintCursorOnTop;
public bool paintCursorAboveText {
get { return this._paintCursorOnTop; }
set {
if (this._paintCursorOnTop == value) {
return;
}
this._paintCursorOnTop = value;
this.markNeedsLayout();
}
}
Offset _cursorOffset;
public Offset cursorOffset {
get { return this._cursorOffset; }
set {
if (this._cursorOffset == value) {
return;
}
this._cursorOffset = value;
this.markNeedsLayout();
}
}

}
}
public EdgeInsets floatingCursorAddedMargin {
get { return this._floatingCursorAddedMargin; }
set {
if (this._floatingCursorAddedMargin == value) {
return;
}
this._floatingCursorAddedMargin = value;
this.markNeedsPaint();
}
}
EdgeInsets _floatingCursorAddedMargin;
bool _floatingCursorOn = false;
Offset _floatingCursorOffset;
TextPosition _floatingCursorTextPosition;
bool? _enableInteractiveSelection;
public bool? enableInteractiveSelection {

}
}
public float preferredLineHeight {
get { return this._textPainter.preferredLineHeight; }
public bool selectionEnabled {
get { return this.enableInteractiveSelection ?? !this.obscureText; }
}

base.detach();
}
bool _isMultiline {
get { return this._maxLines != 1; }
}
Axis _viewportAxis {
get { return this._isMultiline ? Axis.vertical : Axis.horizontal; }
}
Offset _paintOffset {
get {
switch (this._viewportAxis) {
case Axis.horizontal:
return new Offset(-this.offset.pixels, 0.0f);
case Axis.vertical:
return new Offset(0.0f, -this.offset.pixels);
}
return null;
}
}
float _viewportExtent {
get {
D.assert(this.hasSize);
switch (this._viewportAxis) {
case Axis.horizontal:
return this.size.width;
case Axis.vertical:
return this.size.height;
}
return 0.0f;
}
}
float _getMaxScrollExtent(Size contentSize) {
D.assert(this.hasSize);
switch (this._viewportAxis) {
case Axis.horizontal:
return Mathf.Max(0.0f, contentSize.width - this.size.width);
case Axis.vertical:
return Mathf.Max(0.0f, contentSize.height - this.size.height);
}
return 0.0f;
}
float _maxScrollExtent = 0;
bool _hasVisualOverflow {
get { return this._maxScrollExtent > 0 || this._paintOffset != Offset.zero; }
}
/// Returns the local coordinates of the endpoints of the given selection.
///
/// If the selection is collapsed (and therefore occupies a single point), the

return this._textPainter.maxIntrinsicWidth + this.cursorWidth;
}
public float preferredLineHeight {
get { return this._textPainter.preferredLineHeight; }
}
float _preferredHeight(float width) {
bool lockedMax = this.maxLines != null && this.minLines == null;
bool lockedBoth = this.maxLines != null && this.minLines == this.maxLines;
bool singleLine = this.maxLines == 1;
if (singleLine || lockedMax || lockedBoth) {
return this.preferredLineHeight * this.maxLines.Value;
}
bool minLimited = this.minLines != null && this.minLines > 1;
bool maxLimited = this.maxLines != null;
if (minLimited || maxLimited) {
this._layoutText(width);
if (minLimited && this._textPainter.height < this.preferredLineHeight * this.minLines.Value) {
return this.preferredLineHeight * this.minLines.Value;
}
if (maxLimited && this._textPainter.height > this.preferredLineHeight * this.maxLines.Value) {
return this.preferredLineHeight * this.maxLines.Value;
}
}
if (!width.isFinite()) {
var text = this._textPainter.text.text;
int lines = 1;
for (int index = 0; index < text.Length; ++index) {
if (text[index] == 0x0A) {
lines += 1;
}
}
return this.preferredLineHeight * lines;
}
this._layoutText(width);
return Mathf.Max(this.preferredLineHeight, this._textPainter.height);
}
protected override float computeMinIntrinsicHeight(float width) {
return this._preferredHeight(width);
}

return this._textPainter.computeDistanceToActualBaseline(baseline);
}
protected override bool hitTestSelf(Offset position) {
return true;
}
TapGestureRecognizer _tap;
LongPressGestureRecognizer _longPress;
public override void handleEvent(PointerEvent evt, HitTestEntry entry) {
if (this.ignorePointer) {
return;

this._longPress.addPointer((PointerDownEvent) evt);
}
}
Offset _lastTapDownPosition;
public void handleTapDown(TapDownDetails details) {
this._lastTapDownPosition = details.globalPosition;

D.assert(this._lastTapDownPosition != null);
if (this.onSelectionChanged != null) {
TextPosition position =
this._textPainter.getPositionForOffset(this.globalToLocal(this._lastTapDownPosition - this._paintOffset));
this._textPainter.getPositionForOffset(
this.globalToLocal(this._lastTapDownPosition - this._paintOffset));
TextRange word = this._textPainter.getWordBoundary(position);
if (position.offset - word.start <= 1) {
this.onSelectionChanged(

return new TextSelection(baseOffset: word.start, extentOffset: word.end);
}
Rect _caretPrototype;
var caretMargin = _kCaretGap + this.cursorWidth;
var caretMargin = EditableUtils._kCaretGap + this.cursorWidth;
var avialableWidth = Mathf.Max(0.0f, constraintWidth - caretMargin);
var maxWidth = this._isMultiline ? avialableWidth : float.PositiveInfinity;
this._textPainter.layout(minWidth: avialableWidth, maxWidth: maxWidth);

get {
switch (Application.platform) {
case RuntimePlatform.IPhonePlayer:
return Rect.fromLTWH(0.0f, -_kCaretHeightOffset + 0.5f, this.cursorWidth,
return Rect.fromLTWH(0.0f, -EditableUtils._kCaretHeightOffset + 0.5f, this.cursorWidth,
return Rect.fromLTWH(0.0f, _kCaretHeightOffset, this.cursorWidth,
this.preferredLineHeight - 2.0f * _kCaretHeightOffset);
return Rect.fromLTWH(0.0f, EditableUtils._kCaretHeightOffset, this.cursorWidth,
this.preferredLineHeight - 2.0f * EditableUtils._kCaretHeightOffset);
}
}
}

var textPainterSize = this._textPainter.size;
this.size = new Size(this.constraints.maxWidth,
this.constraints.constrainHeight(this._preferredHeight(this.constraints.maxWidth)));
var contentSize = new Size(textPainterSize.width + _kCaretGap + this.cursorWidth,
var contentSize = new Size(textPainterSize.width + EditableUtils._kCaretGap + this.cursorWidth,
var _maxScrollExtent = this._getMaxScrollExtend(contentSize);
this._hasVisualOverflow = _maxScrollExtent > 0.0;
this.offset.applyViewportDimension(this._viewportExtend);
this.offset.applyContentDimensions(0.0f, _maxScrollExtent);
}
public override void paint(PaintingContext context, Offset offset) {
this._layoutText(this.constraints.maxWidth);
if (this._hasVisualOverflow) {
context.pushClipRect(this.needsCompositing, offset, Offset.zero & this.size, this._paintContents);
}
else {
this._paintContents(context, offset);
}
}
protected override bool hitTestSelf(Offset position) {
return true;
this._maxScrollExtent = this._getMaxScrollExtent(contentSize);
this.offset.applyViewportDimension(this._viewportExtent);
this.offset.applyContentDimensions(0.0f, this._maxScrollExtent);
// describeSemanticsConfiguration todo
Offset _getPixelPerfectCursorOffset(Rect caretRect) {
Offset caretPosition = this.localToGlobal(caretRect.topLeft);
float pixelMultiple = 1.0f / this._devicePixelRatio;

if (this._cursorOffset != null) {
caretRect = caretRect.shift(this._cursorOffset);
}
caretRect.top - _kCaretHeightOffset,
caretRect.top - EditableUtils._kCaretHeightOffset,
caretRect.width,
this._textPainter.getFullHeightForCaret(textPosition, this._caretPrototype).Value
);

this.markNeedsPaint();
}
// describeSemanticsConfiguration todo
void _paintFloatingCaret(Canvas canvas, Offset effectiveOffset) {
D.assert(this._textLayoutLastWidth == this.constraints.maxWidth);
D.assert(this._floatingCursorOn);

float sizeAdjustmentX = _kFloatingCaretSizeIncrease.dx;
float sizeAdjustmentY = _kFloatingCaretSizeIncrease.dy;
float sizeAdjustmentX = EditableUtils._kFloatingCaretSizeIncrease.dx;
float sizeAdjustmentY = EditableUtils._kFloatingCaretSizeIncrease.dy;
if (this._resetFloatingCursorAnimationValue != null) {
sizeAdjustmentX =

);
Rect caretRect = floatingCaretPrototype.shift(effectiveOffset);
Radius floatingCursorRadius = Radius.circular(_kFloatingCaretRadius);
Radius floatingCursorRadius = Radius.circular(EditableUtils._kFloatingCaretRadius);
RRect caretRRect = RRect.fromRectAndRadius(caretRect, floatingCursorRadius);
canvas.drawRRect(caretRRect, paint);
}

// todo
}
float _preferredHeight(float width) {
bool lockedMax = this.maxLines != null && this.minLines == null;
bool lockedBoth = this.maxLines != null && this.minLines == this.maxLines;
bool singleLine = this.maxLines == 1;
if (singleLine || lockedMax || lockedBoth) {
return this.preferredLineHeight * this.maxLines.Value;
}
bool minLimited = this.minLines != null && this.minLines > 1;
bool maxLimited = this.maxLines != null;
if (minLimited || maxLimited) {
this._layoutText(width);
if (minLimited && this._textPainter.height < this.preferredLineHeight * this.minLines.Value) {
return this.preferredLineHeight * this.minLines.Value;
}
if (maxLimited && this._textPainter.height > this.preferredLineHeight * this.maxLines.Value) {
return this.preferredLineHeight * this.maxLines.Value;
}
}
if (!width.isFinite()) {
var text = this._textPainter.text.text;
int lines = 1;
for (int index = 0; index < text.Length; ++index) {
if (text[index] == 0x0A) {
lines += 1;
}
}
return this.preferredLineHeight * lines;
}
this._layoutText(width);
return Mathf.Max(this.preferredLineHeight, this._textPainter.height);
public override Rect describeApproximatePaintClip(RenderObject child) {
return this._hasVisualOverflow ? Offset.zero & this.size : null;
bool _isMultiline {
get { return this._maxLines != 1; }
}
Axis _viewportAxis {
get { return this._isMultiline ? Axis.vertical : Axis.horizontal; }
}
Offset _paintOffset {
get {
switch (this._viewportAxis) {
case Axis.horizontal:
return new Offset(-this.offset.pixels, 0.0f);
case Axis.vertical:
return new Offset(0.0f, -this.offset.pixels);
}
return null;
public override void paint(PaintingContext context, Offset offset) {
this._layoutText(this.constraints.maxWidth);
if (this._hasVisualOverflow) {
context.pushClipRect(this.needsCompositing, offset, Offset.zero & this.size, this._paintContents);
}
float _viewportExtend {
get {
D.assert(this.hasSize);
switch (this._viewportAxis) {
case Axis.horizontal:
return this.size.width;
case Axis.vertical:
return this.size.height;
}
return 0.0f;
else {
this._paintContents(context, offset);
}
float _getMaxScrollExtend(Size contentSize) {
D.assert(this.hasSize);
switch (this._viewportAxis) {
case Axis.horizontal:
return Mathf.Max(0.0f, contentSize.width - this.size.width);
case Axis.vertical:
return Mathf.Max(0.0f, contentSize.height - this.size.height);
}
return 0.0f;
}
public override Rect describeApproximatePaintClip(RenderObject child) {
return this._hasVisualOverflow ? Offset.zero & this.size : null;
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {

18
Tests/Editor/RenderEditable.cs


using UnityEditor;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace UIWidgets.Tests {
public class RenderEditable : EditorWindow, TextSelectionDelegate {

this._pixels += correction;
}
public override void jumpTo(float pixels) {
}
public override void jumpTo(float pixels) { }
public override IPromise animateTo(float to, TimeSpan duration, Curve curve) {
return Promise.Resolved();

flexbox.add(this.flexItemBox(
new Unity.UIWidgets.rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0f), new ValueNotifier<bool>(true),
offset: new _FixedViewportOffset(0.0f), showCursor: new ValueNotifier<bool>(true),
onSelectionChanged: this.selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
maxLines: 100,
selectionColor: Color.fromARGB(255, 255, 0, 0),

}, style: new TextStyle(height: 1.0f));
flexbox.add(this.flexItemBox(
new Unity.UIWidgets.rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0f), new ValueNotifier<bool>(true),
offset: new _FixedViewportOffset(0.0f), showCursor: new ValueNotifier<bool>(true),
onSelectionChanged: this.selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
maxLines: 100,
selectionColor: Color.fromARGB(255, 255, 0, 0),

}, style: new TextStyle(height: 1.0f));
flexbox.add(this.flexItemBox(
new Unity.UIWidgets.rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0f), new ValueNotifier<bool>(true),
offset: new _FixedViewportOffset(0.0f), showCursor: new ValueNotifier<bool>(true),
onSelectionChanged: this.selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
selectionColor: Color.fromARGB(255, 255, 0, 0),
textSelectionDelegate: this)

}
public TextEditingValue textEditingValue { get; set; }
public void hideToolbar() {
}
public void hideToolbar() { }
public void bringIntoView(TextPosition textPosition) {
}
public void bringIntoView(TextPosition textPosition) { }
}
}
正在加载...
取消
保存