浏览代码

Merge pull request #8 from UnityTech/text

editable render object(text selection part)
/main
GitHub 6 年前
当前提交
a75d1ec6
共有 23 个文件被更改,包括 1673 次插入9 次删除
  1. 5
      Assets/UIWidgets/Tests/Menu.cs
  2. 107
      Assets/UIWidgets/painting/text_painter.cs
  3. 13
      Assets/UIWidgets/painting/text_span.cs
  4. 2
      Assets/UIWidgets/rendering/box.cs
  5. 14
      Assets/UIWidgets/rendering/box.mixin.gen.cs
  6. 5
      Assets/UIWidgets/rendering/flex.cs
  7. 9
      Assets/UIWidgets/rendering/proxy_box.mixin.gen.cs
  8. 9
      Assets/UIWidgets/rendering/proxy_box.mixin.njk
  9. 16
      Assets/UIWidgets/rendering/shifted_box.cs
  10. 85
      Assets/UIWidgets/ui/text.cs
  11. 232
      Assets/UIWidgets/ui/txt/paragraph.cs
  12. 203
      Assets/UIWidgets/Tests/RenderEditable.cs
  13. 3
      Assets/UIWidgets/Tests/RenderEditable.cs.meta
  14. 703
      Assets/UIWidgets/rendering/editable.cs
  15. 3
      Assets/UIWidgets/rendering/editable.cs.meta
  16. 3
      Assets/UIWidgets/service.meta
  17. 70
      Assets/UIWidgets/ui/txt/word_separate.cs
  18. 3
      Assets/UIWidgets/ui/txt/word_separate.cs.meta
  19. 191
      Assets/UIWidgets/service/text_editing.cs
  20. 3
      Assets/UIWidgets/service/text_editing.cs.meta
  21. 3
      Assets/UIWidgets/math.meta

5
Assets/UIWidgets/Tests/Menu.cs


public static void gestures() {
EditorWindow.GetWindow(typeof(Gestures));
}
[MenuItem("UIWidgetsTests/RenderEditable")]
public static void renderEditable() {
EditorWindow.GetWindow(typeof(RenderEditable));
}
}
}

107
Assets/UIWidgets/painting/text_painter.cs


using System;
using System.Runtime.ConstrainedExecution;
using UIWidgets.math;
using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.service;
using Rect = UIWidgets.ui.Rect;
namespace UIWidgets.painting
{

if (minWidth != maxWidth)
{
var newWidth = MathUtil.Clamp(maxIntrinsicWidth, minWidth, maxWidth);
var newWidth = MathUtils.clamp(maxIntrinsicWidth, minWidth, maxWidth);
if (newWidth != width)
{
_paragraph.layout(new ParagraphConstraints(newWidth));

_paragraph.paint(canvas, offset.dx, offset.dy);
}
public Offset getOffsetForCaret(TextPosition position, Rect caretPrototype)
{
D.assert(!_needsLayout);
var offset = position.offset;
switch (position.affinity)
{
case TextAffinity.upstream:
return _getOffsetFromUpstream(offset, caretPrototype) ??
_getOffsetFromDownstream(offset, caretPrototype) ?? _emptyOffset;
case TextAffinity.downstream:
return _getOffsetFromDownstream(offset, caretPrototype) ??
_getOffsetFromUpstream(offset, caretPrototype) ?? _emptyOffset;
}
return null;
}
public List<TextBox> getBoxesForSelection(TextSelection selection)
{
D.assert(!_needsLayout);
var results = _paragraph.getRectsForRange(selection.start, selection.end);
if (results.Count > 0)
{
Debug.Log(string.Format(" getBoxesForSelection {0}", results[0]));
}
return results;
}
public TextPosition getPositionForOffset(Offset offset) {
D.assert(!_needsLayout);
var result = _paragraph.getGlyphPositionAtCoordinate(offset.dx, offset.dy);
return new TextPosition(result.position, result.affinity);
}
public TextRange getWordBoundary(TextPosition position)
{
D.assert(!_needsLayout);
var range = _paragraph.getWordBoundary(position.offset);
return new TextRange(range.start, range.end);
}
private ParagraphStyle _createParagraphStyle(TextDirection defaultTextDirection = TextDirection.ltr)
{
if (_text.style == null)

{
return Math.Ceiling(layoutValue);
}
private Offset _getOffsetFromUpstream(int offset, Rect caretPrototype) {
var prevCodeUnit = _text.codeUnitAt(offset - 1);
if (prevCodeUnit == null)
return null;
var prevRuneOffset = _isUtf16Surrogate((int)prevCodeUnit) ? offset - 2 : offset - 1;
var boxes = _paragraph.getRectsForRange(prevRuneOffset, offset);
if (boxes.Count == 0)
return null;
var box = boxes[0];
var caretEnd = box.end;
var dx = box.direction == TextDirection.rtl ? caretEnd : caretEnd - caretPrototype.width;
return new Offset(dx, box.top);
}
private Offset _getOffsetFromDownstream(int offset, Rect caretPrototype) {
var nextCodeUnit = _text.codeUnitAt(offset);
if (nextCodeUnit == null)
return null;
var nextRuneOffset = _isUtf16Surrogate((int)nextCodeUnit) ? offset + 2 : offset + 1;
var boxes = _paragraph.getRectsForRange(offset, nextRuneOffset);
if (boxes.Count == 0)
return null;
var box = boxes[0];
var caretStart = box.start;
var dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart;
return new Offset(dx, box.top);
}
private Offset _emptyOffset
{
get
{
D.assert(!_needsLayout);
switch (textAlign)
{
case TextAlign.left:
return Offset.zero;
case TextAlign.right:
return new Offset(width, 0.0);
case TextAlign.center:
return new Offset(width / 2.0, 0.0);
case TextAlign.justify:
if (textDirection == TextDirection.rtl)
{
return new Offset(width, 0.0);
}
return Offset.zero;
}
return null;
}
}
private static bool _isUtf16Surrogate(int value)
{
return (value & 0xF800) == 0xD800;
}
}
}

13
Assets/UIWidgets/painting/text_span.cs


return sb.ToString();
}
public int codeUnitAt(int index)
public int? codeUnitAt(int index)
return -1;
return null;
}
var offset = 0;

if (ReferenceEquals(this, other)) return true;
return Equals(style, other.style) && string.Equals(text, other.text) && childEquals(children, other.children);
}
public static bool operator ==(TextSpan left, TextSpan right)
{
return Equals(left, right);
}
public static bool operator !=(TextSpan left, TextSpan right)
{
return !Equals(left, right);
}
private int childHash()
{
unchecked

2
Assets/UIWidgets/rendering/box.cs


return false;
}
protected bool hitTestChildren(HitTestResult result, Offset position = null) {
protected virtual bool hitTestChildren(HitTestResult result, Offset position = null) {
return false;
}

14
Assets/UIWidgets/rendering/box.mixin.gen.cs


using System;
using System.Collections.Generic;
using UIWidgets.gestures;
using UIWidgets.ui;
namespace UIWidgets.rendering {

context.paintChild(child, childParentData.offset + offset);
child = childParentData.nextSibling;
}
}
public bool defaultHitTestChildren(HitTestResult result, Offset position) {
// the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild;
while (child != null)
{
ParentDataType childParentData = (ParentDataType) child.parentData;
if (child.hitTest(result, position: position - childParentData.offset))
return true;
child = childParentData.previousSibling;
}
return false;
}
public List<ChildType> getChildrenAsList() {

5
Assets/UIWidgets/rendering/flex.cs


using System;
using System.Collections.Generic;
using UIWidgets.gestures;
using UIWidgets.painting;
using UIWidgets.ui;

return;
context.pushClipRect(this.needsCompositing, offset, Offset.zero & this.size, this.defaultPaint);
}
protected override bool hitTestChildren(HitTestResult result, Offset position = null) {
return defaultHitTestChildren(result, position: position);
}
}
}

9
Assets/UIWidgets/rendering/proxy_box.mixin.gen.cs


using UIWidgets.ui;
using UIWidgets.gestures;
using UnityEngine;
namespace UIWidgets.rendering {

if (this.child != null) {
context.paintChild(this.child, offset);
}
}
protected override bool hitTestChildren(HitTestResult result, Offset position = null) {
if (this.child != null) {
return this.child.hitTest(result, position);
}
return false;
}
}

9
Assets/UIWidgets/rendering/proxy_box.mixin.njk


using UIWidgets.ui;
using UIWidgets.gestures;
using UnityEngine;
namespace UIWidgets.rendering {

if (this.child != null) {
context.paintChild(this.child, offset);
}
}
protected override bool hitTestChildren(HitTestResult result, Offset position = null) {
if (this.child != null) {
return this.child.hitTest(result, position);
}
return false;
}
}
{% endmacro %}

16
Assets/UIWidgets/rendering/shifted_box.cs


using System;
using UIWidgets.gestures;
using UIWidgets.painting;
using UIWidgets.ui;

var childParentData = (BoxParentData) this.child.parentData;
context.paintChild(this.child, childParentData.offset + offset);
}
}
protected override bool hitTestChildren(HitTestResult result, Offset position = null)
{
if (child != null)
{
var childParentData = child.parentData as BoxParentData;
if (childParentData != null)
{
position = position - childParentData.offset;
}
return child.hitTest(result, position);
}
return false;
}
}

85
Assets/UIWidgets/ui/text.cs


}
}
}
public class TextBox: IEquatable<TextBox>
{
public readonly double left;
public readonly double top;
public readonly double right;
public readonly double bottom;
public readonly TextDirection direction;
private TextBox(double left, double top, double right, double bottom, TextDirection direction)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
this.direction = direction;
}
public static TextBox fromLTBD(double left, double top, double right, double bottom, TextDirection direction)
{
return new TextBox(left, top, right, bottom, direction);
}
public Rect toRect()
{
return Rect.fromLTRB(left, top, right, bottom);
}
public double start
{
get { return direction == TextDirection.ltr ? left : right; }
}
public double end
{
get { return direction == TextDirection.ltr ? right : left; }
}
public bool Equals(TextBox other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return left.Equals(other.left) && top.Equals(other.top) && right.Equals(other.right) && bottom.Equals(other.bottom) && direction == other.direction;
}
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((TextBox) obj);
}
public override string ToString()
{
return string.Format("Left: {0}, Top: {1}, Right: {2}, Bottom: {3}, Direction: {4}", left, top, right, bottom, direction);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = left.GetHashCode();
hashCode = (hashCode * 397) ^ top.GetHashCode();
hashCode = (hashCode * 397) ^ right.GetHashCode();
hashCode = (hashCode * 397) ^ bottom.GetHashCode();
hashCode = (hashCode * 397) ^ (int) direction;
return hashCode;
}
}
public static bool operator ==(TextBox left, TextBox right)
{
return Equals(left, right);
}
public static bool operator !=(TextBox left, TextBox right)
{
return !Equals(left, right);
}
}
}

232
Assets/UIWidgets/ui/txt/paragraph.cs


using System;
using System.Collections.Generic;
using UIWidgets.painting;
using UIWidgets.ui.txt;
using UnityEngine;

return new Vector2d(a.x - b.x, a.y - b.y);
}
}
public class CodeUnitRun
{
public int lineNumber;
public TextDirection direction;
public IndexRange codeUnits;
public FontMetrics fontMetrics;
public CodeUnitRun(IndexRange cu, int line, FontMetrics fontMetrics, TextDirection direction)
{
this.lineNumber = line;
this.codeUnits = cu;
this.fontMetrics = fontMetrics;
this.direction = direction;
}
}
public class FontMetrics
{
public readonly double ascent;
public readonly double descent;
public FontMetrics(double ascent, double descent)
{
this.ascent = ascent;
this.descent = descent;
}
}
public struct IndexRange :IEquatable<IndexRange>
{
public int start, end;
public IndexRange(int s, int e)
{
start = s;
end = e;
}
int width()
{
return end - start;
}
void shift(int delta)
{
start += delta;
end += delta;
}
public bool Equals(IndexRange other)
{
return start == other.start && end == other.end;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
return obj is IndexRange && Equals((IndexRange) obj);
}
public override int GetHashCode()
{
unchecked
{
return (start * 397) ^ end;
}
}
public static bool operator ==(IndexRange left, IndexRange right)
{
return left.Equals(right);
}
public static bool operator !=(IndexRange left, IndexRange right)
{
return !left.Equals(right);
}
}
public class PositionWithAffinity
{
public readonly int position;
public readonly TextAffinity affinity;
public PositionWithAffinity(int p, TextAffinity a)
{
position = p;
affinity = a;
}
}
public class Paragraph
{

private ParagraphStyle _paragraphStyle;
private List<LineRange> _lineRanges = new List<LineRange>();
private List<double> _lineWidths = new List<double>();
private List<double> _lineBaseLines = new List<double>();
private Vector2d[] _characterPositions;
private double _maxIntrinsicWidth;
private double _minIntrinsicWidth;

private List<double> _lineHeights = new List<double>();
private List<PaintRecord> _paintRecords = new List<PaintRecord>();
private List<CodeUnitRun> _codeUnitRuns = new List<CodeUnitRun>();
private bool _didExceedMaxLines;
// private double _characterWidth;

_paragraphStyle = style;
}
public List<TextBox> getRectsForRange(int start, int end)
{
var lineBoxes = new SortedDictionary<int, List<TextBox>>();
foreach (var run in _codeUnitRuns)
{
if (run.codeUnits.start >= end)
{
break;
}
if (run.codeUnits.end <= start)
{
continue;
}
var baseLine = _lineBaseLines[run.lineNumber];
double top = baseLine - run.fontMetrics.ascent;
double bottom = baseLine + run.fontMetrics.descent;
// double left, right;
var from = Math.Max(start, run.codeUnits.start);
var to = Math.Min(end, run.codeUnits.end);
if (from < to)
{
List<TextBox> boxs;
if (!lineBoxes.TryGetValue(run.lineNumber, out boxs))
{
boxs = new List<TextBox>();
lineBoxes.Add(run.lineNumber, boxs);
}
double left = _characterPositions[from].x;
double right = _characterPositions[to - 1].x + _characterWidths[to - 1];
boxs.Add(TextBox.fromLTBD(left, top, right, bottom, run.direction));
}
}
for (int lineNumber = 0; lineNumber < _lineRanges.Count; ++lineNumber)
{
var line = _lineRanges[lineNumber];
if (line.start >= end)
{
break;
}
if (line.endIncludingNewLine <= start)
{
continue;
}
if (!lineBoxes.ContainsKey(lineNumber))
{
if (line.end != line.endIncludingNewLine && line.end >= start && line.endIncludingNewLine <= end)
{
var x = _lineWidths[lineNumber];
var top = (lineNumber > 0) ? _lineHeights[lineNumber - 1] : 0;
var bottom = _lineHeights[lineNumber];
lineBoxes.Add(lineNumber, new List<TextBox>(){TextBox.fromLTBD(
x, top, x, bottom, TextDirection.ltr)});
}
}
}
var result = new List<TextBox>();
foreach (var keyValuePair in lineBoxes)
{
result.AddRange(keyValuePair.Value);
}
return result;
}
public PositionWithAffinity getGlyphPositionAtCoordinate(double dx, double dy)
{
if (_lineHeights.Count == 0)
{
return new PositionWithAffinity(0, TextAffinity.downstream);
}
int yIndex;
for (yIndex = 0; yIndex < _lineHeights.Count - 1; ++yIndex)
{
if (dy < _lineHeights[yIndex])
{
break;
}
}
var line = _lineRanges[yIndex];
if (line.start >= line.end)
{
return new PositionWithAffinity(line.start, TextAffinity.downstream);
}
int index;
for (index = line.start; index < line.end; ++index)
{
if (dx < _characterPositions[index].x + _characterWidths[index])
{
break;
}
}
if (index >= line.end)
{
return new PositionWithAffinity(line.end, TextAffinity.upstream);
}
TextDirection direction = TextDirection.ltr;
var codeUnit = _codeUnitRuns.Find((u) => u.codeUnits.start >= index && index < u.codeUnits.end);
if (codeUnit != null)
{
direction = codeUnit.direction;
}
double glyphCenter = (_characterPositions[index].x + _characterPositions[index].x + _characterWidths[index]) / 2;
if ((direction == TextDirection.ltr && dx < glyphCenter) || (direction == TextDirection.rtl && dx >= glyphCenter))
{
return new PositionWithAffinity(index, TextAffinity.downstream);
} else
{
return new PositionWithAffinity(index, TextAffinity.upstream);
}
}
public IndexRange getWordBoundary(int offset)
{
WordSeparate s = new WordSeparate(_text);
return s.findWordRange(offset);
}
public static void offsetCharacters(Vector2d offset, Vector2d[] characterPos, int start, int end)
{
if (characterPos != null)

}
}
}
private void computeWidthMetrics(double maxWordWidth)
{

_lineHeights.Clear();
_lineRanges.Clear();
_lineWidths.Clear();
_lineBaseLines.Clear();
_codeUnitRuns.Clear();
_characterWidths = new double[_text.Length];
for (int i = 0; i < _runs.size; ++i)
{

_paintRecords.Add(new PaintRecord(run.style, new TextBlob(
_text, start, end, _characterPositions, run.style, bounds),
lineNumber, width));
_codeUnitRuns.Add(new CodeUnitRun(
new IndexRange(start, end), lineNumber, new FontMetrics(ascent, descent), TextDirection.ltr));
}
}

_lineHeights.Add((_lineHeights.Count == 0 ? 0 : _lineHeights[_lineHeights.Count - 1]) +
Math.Round(maxAscent + maxDescent));
_lineBaseLines.Add(yOffset);
}
}

203
Assets/UIWidgets/Tests/RenderEditable.cs


using UnityEditor;
using System;
using System.Collections.Generic;
using System.Linq;
using RSG;
using UIWidgets.animation;
using UIWidgets.editor;
using UIWidgets.foundation;
using UIWidgets.painting;
using UIWidgets.rendering;
using UIWidgets.service;
using UIWidgets.ui;
using UnityEditor;
using UnityEngine;
using Color = UIWidgets.ui.Color;
using FontStyle = UIWidgets.ui.FontStyle;
namespace UIWidgets.Tests
{
public class RenderEditable: EditorWindow
{
private readonly Func<RenderBox>[] _options;
private readonly string[] _optionStrings;
private int _selected;
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<object> animateTo(double to, TimeSpan duration, Curve curve) {
return Promise<object>.Resolved(null);
}
public override ScrollDirection userScrollDirection {
get { return ScrollDirection.idle; }
}
public override bool allowImplicitScrolling {
get { return false; }
}
}
RenderEditable() {
this._options = new Func<RenderBox>[] {
this.textEditable,
};
this._optionStrings = this._options.Select(x => x.Method.Name).ToArray();
this._selected = 0;
this.titleContent = new GUIContent("RenderEditable");
}
private WindowAdapter windowAdapter;
private RendererBindings rendererBindings;
[NonSerialized] private bool hasInvoked = false;
void OnGUI() {
var selected = EditorGUILayout.Popup("test case", this._selected, this._optionStrings);
if (selected != this._selected || !this.hasInvoked) {
this._selected = selected;
this.hasInvoked = true;
var renderBox = this._options[this._selected]();
this.rendererBindings.setRoot(renderBox);
}
if (this.windowAdapter != null) {
this.windowAdapter.OnGUI();
}
}
void Update() {
if (this.windowAdapter != null) {
this.windowAdapter.Update();
}
}
private void OnEnable() {
this.windowAdapter = new WindowAdapter(this);
this.rendererBindings = new RendererBindings(this.windowAdapter);
}
void OnDestroy() {
this.windowAdapter = null;
this.rendererBindings = null;
}
private RenderBox box(RenderBox p, int width = 400, int height = 400)
{
return new RenderConstrainedOverflowBox(
minWidth: width,
maxWidth: width,
minHeight: height,
maxHeight: height,
alignment: Alignment.center,
child: p
)
;
}
private RenderBox flexItemBox(RenderBox p, int width = 200, int height = 100)
{
return new RenderConstrainedBox(
additionalConstraints: new BoxConstraints(minWidth: width, maxWidth: width, minHeight: height,
maxHeight: height),
child: new RenderDecoratedBox(
decoration: new BoxDecoration(
color: new Color(0xFFFFFFFF),
borderRadius: BorderRadius.all(3),
border: Border.all(Color.fromARGB(255, 255, 0, 0), 1)
),
child: new RenderPadding(EdgeInsets.all(10), p
)
));
}
RenderBox textEditable()
{
var span = new TextSpan("", children:
new List<TextSpan>
{
new TextSpan("Word Wrap:The ascent of the font is the distance from the baseline to the top line of the font, as defined in the font's original data file.", null),
}, style:new painting.TextStyle(height:1.0));
var flexbox = new RenderFlex(
direction: Axis.vertical,
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center);
flexbox.add(flexItemBox(
new rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding,
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
maxLines: 100,
selectionColor: Color.fromARGB(255, 255, 0, 0))
));
span = new TextSpan("", children:
new List<TextSpan>
{
new TextSpan("Hard Break:The ascent of the font is the distance\nfrom the baseline to the top \nline of the font,\nas defined in", null),
}, style:new painting.TextStyle(height:1.0));
flexbox.add(flexItemBox(
new rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding,
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
maxLines: 100,
selectionColor: Color.fromARGB(255, 255, 0, 0))
));
span = new TextSpan("", children:
new List<TextSpan>
{
new TextSpan("Single Line:How to create mixin", null),
}, style:new painting.TextStyle(height:1.0));
flexbox.add(flexItemBox(
new rendering.RenderEditable(span, TextDirection.ltr,
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding,
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0),
selectionColor: Color.fromARGB(255, 255, 0, 0))
, width:300));
return flexbox;
}
private void selectionChanged(TextSelection selection, rendering.RenderEditable renderObject,
SelectionChangedCause cause)
{
Debug.Log(string.Format("selection {0}", selection));
renderObject.selection = selection;
}
}
}

3
Assets/UIWidgets/Tests/RenderEditable.cs.meta


fileFormatVersion: 2
guid: 3133671f82dc4c0e93d4dbeb9e50b55d
timeCreated: 1536660300

703
Assets/UIWidgets/rendering/editable.cs


using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.gestures;
using UIWidgets.painting;
using UIWidgets.service;
using UIWidgets.ui;
using UnityEngine;
using Canvas = UIWidgets.ui.Canvas;
using Color = UIWidgets.ui.Color;
using Rect = UIWidgets.ui.Rect;
namespace UIWidgets.rendering
{
public delegate void SelectionChangedHandler(TextSelection selection, RenderEditable renderObject,
SelectionChangedCause cause);
public delegate void CaretChangedHandler(Rect caretRect);
public enum SelectionChangedCause
{
tap,
doubleTap,
longPress,
keyboard,
}
public class TextSelectionPoint
{
public readonly Offset point;
public readonly TextDirection? direction;
public TextSelectionPoint(Offset point, TextDirection? direction)
{
D.assert(point != null);
this.point = point;
this.direction = direction;
}
public override string ToString()
{
return string.Format("Point: {0}, Direction: {1}", point, direction);
}
}
/*
this._doubleTapGesture = new DoubleTapGestureRecognizer(this.rendererBindings.rendererBinding);
this._doubleTapGesture.onDoubleTap = () => { Debug.Log("onDoubleTap"); };*/
public class RenderEditable : RenderBox
{
public static readonly string obscuringCharacter = "•";
private static readonly double _kCaretGap = 1.0;
private static readonly double _kCaretHeightOffset = 2.0;
private static readonly double _kCaretWidth = 1.0;
private TextPainter _textPainter;
private Color _cursorColor;
private bool _hasFocus;
private int _maxLines;
private Color _selectionColor;
private ViewportOffset _offset;
private ValueNotifier<bool> _showCursor;
private TextSelection _selection;
private bool _obscureText;
private TapGestureRecognizer _tap;
private DoubleTapGestureRecognizer _doubleTap;
private bool ignorePointer;
private SelectionChangedHandler onSelectionChanged;
private CaretChangedHandler onCaretChanged;
private Rect _lastCaretRect;
private double? _textLayoutLastWidth;
private List<TextBox> _selectionRects;
private Rect _caretPrototype;
private bool _hasVisualOverflow = false;
private Offset _lastTapDownPosition;
public RenderEditable(TextSpan text, TextDirection textDirection, ViewportOffset offset,
ValueNotifier<bool> showCursor,
GestureBinding binding,
TextAlign textAlign = TextAlign.left, double textScaleFactor = 1.0, Color cursorColor = null,
bool? hasFocus = null, int maxLines = 1, Color selectionColor = null,
TextSelection selection = null, bool obscureText = false, SelectionChangedHandler onSelectionChanged = null,
CaretChangedHandler onCaretChanged = null, bool ignorePointer = false)
{
_textPainter = new TextPainter(text: text, textAlign: textAlign, textDirection: textDirection,
textScaleFactor: textScaleFactor);
_cursorColor = cursorColor;
_showCursor = showCursor ?? new ValueNotifier<bool>(false);
_hasFocus = hasFocus ?? false;
_maxLines = maxLines;
_selectionColor = selectionColor;
_selection = selection;
_obscureText = obscureText;
_offset = offset;
this.ignorePointer = ignorePointer;
this.onCaretChanged = onCaretChanged;
this.onSelectionChanged = onSelectionChanged;
D.assert(_showCursor != null);
D.assert(!_showCursor.value || cursorColor != null);
_tap = new TapGestureRecognizer(binding, this);
_doubleTap = new DoubleTapGestureRecognizer(binding, this);
_tap.onTapDown = this._handleTapDown;
_tap.onTap = this._handleTap;
_doubleTap.onDoubleTap = this._handleDoubleTap;
}
public bool obscureText
{
get { return _obscureText; }
set
{
if (_obscureText == value)
return;
_obscureText = value;
markNeedsSemanticsUpdate();
}
}
public TextSpan text
{
get { return _textPainter.text; }
set
{
if (_textPainter.text == value)
{
return;
}
_textPainter.text = value;
markNeedsTextLayout();
markNeedsSemanticsUpdate();
}
}
public TextAlign textAlign
{
get { return _textPainter.textAlign; }
set
{
if (_textPainter.textAlign == value)
{
return;
}
_textPainter.textAlign = value;
markNeedsPaint();
}
}
public TextDirection? textDirection
{
get { return _textPainter.textDirection; }
set
{
if (_textPainter.textDirection == value)
{
return;
}
_textPainter.textDirection = value;
markNeedsTextLayout();
markNeedsSemanticsUpdate();
}
}
public Color cursorColor
{
get { return _cursorColor; }
set
{
if (_cursorColor == value)
{
return;
}
_cursorColor = value;
markNeedsPaint();
}
}
public ValueNotifier<bool> ShowCursor
{
get { return _showCursor; }
set
{
D.assert(value != null);
if (_showCursor == value)
{
return;
}
if (attached)
{
_showCursor.removeListener(markNeedsPaint);
}
_showCursor = value;
if (attached)
{
_showCursor.addListener(markNeedsPaint);
}
markNeedsPaint();
}
}
public bool hasFocus
{
get { return _hasFocus; }
set
{
if (_hasFocus == value)
{
return;
}
hasFocus = value;
markNeedsSemanticsUpdate();
}
}
public int maxLines
{
get { return _maxLines; }
set
{
D.assert(value > 0);
if (_maxLines == value)
{
return;
}
_maxLines = value;
markNeedsTextLayout();
}
}
public Color selectionColor
{
get { return _selectionColor; }
set
{
if (_selectionColor == value)
{
return;
}
_selectionColor = value;
markNeedsPaint();
}
}
public double textScaleFactor
{
get { return _textPainter.textScaleFactor; }
set
{
if (_textPainter.textScaleFactor == value)
{
return;
}
_textPainter.textScaleFactor = value;
markNeedsTextLayout();
}
}
public TextSelection selection
{
get { return _selection; }
set
{
if (_selection == value)
{
return;
}
_selection = value;
_selectionRects = null;
markNeedsPaint();
markNeedsSemanticsUpdate();
}
}
public ViewportOffset offset
{
get { return _offset; }
set
{
D.assert(offset != null);
if (_offset == value)
{
return;
}
if (attached)
{
_offset.removeListener(markNeedsPaint);
}
_offset = value;
if (attached)
{
_offset.addListener(markNeedsPaint);
}
markNeedsLayout();
}
}
public ValueNotifier<bool> showCursor
{
get { return _showCursor; }
set
{
D.assert(value != null);
if (_showCursor == value)
{
return;
}
if (attached)
{
_showCursor.removeListener(markNeedsPaint);
}
_showCursor = value;
if (attached)
{
_showCursor.addListener(markNeedsPaint);
}
markNeedsPaint();
}
}
public double preferredLineHeight
{
get { return _textPainter.preferredLineHeight; }
}
public override void attach(object ownerObject)
{
base.attach(ownerObject);
_offset.addListener(markNeedsLayout);
_showCursor.addListener(markNeedsPaint);
}
public override void detach()
{
_offset.removeListener(markNeedsLayout);
_showCursor.removeListener(markNeedsPaint);
base.detach();
}
/// Returns the local coordinates of the endpoints of the given selection.
///
/// If the selection is collapsed (and therefore occupies a single point), the
/// returned list is of length one. Otherwise, the selection is not collapsed
/// and the returned list is of length two. In this case, however, the two
/// points might actually be co-located (e.g., because of a bidirectional
/// selection that contains some text but whose ends meet in the middle).
///
public List<TextSelectionPoint> getEndpointsForSelection(TextSelection selection)
{
D.assert(constraints != null);
_layoutText(constraints.maxWidth);
var paintOffset = _paintOffset;
if (selection.isCollapsed)
{
var caretOffset = _textPainter.getOffsetForCaret(selection.extendPos, _caretPrototype);
var start = new Offset(0.0, preferredLineHeight) + caretOffset + paintOffset;
return new List<TextSelectionPoint>{new TextSelectionPoint(start, null)};
}
else
{
var boxes = _textPainter.getBoxesForSelection(selection);
var start = new Offset(boxes[0].start, boxes[0].bottom) + paintOffset;
var last = boxes.Count - 1;
var end = new Offset(boxes[last].end, boxes[last].bottom) + paintOffset;
return new List<TextSelectionPoint>
{
new TextSelectionPoint(start, boxes[0].direction),
new TextSelectionPoint(end, boxes[last].direction),
};
}
}
public TextPosition getPositionForPoint(Offset globalPosition)
{
_layoutText(constraints.maxWidth);
globalPosition -= _paintOffset;
return _textPainter.getPositionForOffset(globalPosition);
}
public Rect getLocalRectForCaret(TextPosition caretPosition)
{
_layoutText(constraints.maxWidth);
var caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
return Rect.fromLTWH(0.0, 0.0, _kCaretWidth, preferredLineHeight).shift(caretOffset + _paintOffset);
}
public override double computeMinIntrinsicWidth(double height) {
_layoutText(double.PositiveInfinity);
return _textPainter.minIntrinsicWidth;
}
public override double computeMaxIntrinsicWidth(double height) {
_layoutText(double.PositiveInfinity);
return _textPainter.maxIntrinsicWidth;
}
public override double computeMinIntrinsicHeight(double width) {
return _preferredHeight(width);
}
public override double computeMaxIntrinsicHeight(double width) {
return _preferredHeight(width);
}
public override double? computeDistanceToActualBaseline(TextBaseline baseline) {
_layoutText(constraints.maxWidth);
return _textPainter.computeDistanceToActualBaseline(baseline);
}
public override void handleEvent(PointerEvent evt, HitTestEntry entry) {
if (ignorePointer)
return;
D.assert(debugHandleEvent(evt, entry));
if (evt is PointerDownEvent && onSelectionChanged != null) {
_tap.addPointer((PointerDownEvent)evt);
_doubleTap.addPointer((PointerDownEvent)evt);
// todo long press
}
}
public void handleTapDown(TapDownDetails details)
{
_lastTapDownPosition = details.globalPosition - _paintOffset;
}
public void handleTap()
{
_layoutText(constraints.maxWidth);
D.assert(_lastTapDownPosition != null);
if (onSelectionChanged != null)
{
var position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition));
onSelectionChanged(TextSelection.fromPosition(position), this, SelectionChangedCause.tap);
}
}
public void handleDoubleTap()
{
_layoutText(constraints.maxWidth);
D.assert(_lastTapDownPosition != null);
if (onSelectionChanged != null)
{
var position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition));
onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.doubleTap);
}
}
public override void performLayout() {
_layoutText(constraints.maxWidth);
_caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth,
preferredLineHeight - 2.0 * _kCaretHeightOffset);
_selectionRects = null;
var textPainterSize = _textPainter.size;
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
var contentSize = new Size(textPainterSize.width + _kCaretGap + _kCaretWidth, textPainterSize.height);
var _maxScrollExtent = _getMaxScrollExtend(contentSize);
_hasVisualOverflow = _maxScrollExtent > 0.0;
offset.applyViewportDimension(_viewportExtend);
offset.applyContentDimensions(0.0, _maxScrollExtent);
}
public override void paint(PaintingContext context, Offset offset)
{
_layoutText(constraints.maxWidth);
if (_hasVisualOverflow)
{
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
}
else
{
_paintContents(context, offset);
}
}
protected override bool hitTestSelf(Offset position)
{
return true;
}
protected void markNeedsTextLayout()
{
_textLayoutLastWidth = null;
markNeedsLayout();
}
// describeSemanticsConfiguration todo
protected internal override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<Color>("cursorColor", cursorColor));
properties.add(new DiagnosticsProperty<ValueNotifier<bool>>("showCursor", showCursor));
properties.add(new DiagnosticsProperty<int>("maxLines", maxLines));
properties.add(new DiagnosticsProperty<Color>("selectionColor", selectionColor));
properties.add(new DiagnosticsProperty<double>("textScaleFactor", textScaleFactor));
properties.add(new DiagnosticsProperty<TextSelection>("selection", selection));
properties.add(new DiagnosticsProperty<ViewportOffset>("offset", offset));
}
private void _paintCaret(Canvas canvas, Offset effectiveOffset)
{
D.assert(_textLayoutLastWidth == constraints.maxWidth);
var caretOffset = _textPainter.getOffsetForCaret(_selection.extendPos, _caretPrototype);
var paint = new Paint() {color = _cursorColor};
var caretRec = _caretPrototype.shift(caretOffset + effectiveOffset);
canvas.drawRect(caretRec, BorderWidth.zero, BorderRadius.zero, paint);
if (!caretRec.Equals(_lastCaretRect))
{
_lastCaretRect = caretRec;
if (onCaretChanged != null)
{
onCaretChanged(caretRec);
}
}
}
void _paintSelection(Canvas canvas, Offset effectiveOffset)
{
D.assert(_textLayoutLastWidth == constraints.maxWidth);
D.assert(_selectionRects != null);
var paint = new Paint() {color = _selectionColor};
foreach (var box in _selectionRects)
{
Debug.Log(string.Format("draw box {0}", box.toRect().shift(effectiveOffset)));
canvas.drawRect(box.toRect().shift(effectiveOffset), BorderWidth.zero, BorderRadius.zero, paint);
}
}
void _paintContents(PaintingContext context, Offset offset)
{
D.assert(_textLayoutLastWidth == constraints.maxWidth);
var effectiveOffset = offset + _paintOffset;
if (_selection != null) {
if (_selection.isCollapsed && _showCursor.value && cursorColor != null) {
_paintCaret(context.canvas, effectiveOffset);
} else if (!_selection.isCollapsed && _selectionColor != null) {
_selectionRects = _selectionRects??_textPainter.getBoxesForSelection(_selection);
_paintSelection(context.canvas, effectiveOffset);
}
}
_textPainter.paint(context.canvas, effectiveOffset);
}
private void _handleSetSelection(TextSelection selection)
{
onSelectionChanged(selection, this, SelectionChangedCause.keyboard);
}
private void _handleTapDown(TapDownDetails details)
{
D.assert(!ignorePointer);
handleTapDown(details);
}
private void _handleTap()
{
D.assert(!ignorePointer);
handleTap();
}
private void _handleDoubleTap()
{
D.assert(!ignorePointer);
handleDoubleTap();
}
private void markNeedsSemanticsUpdate()
{
// todo
}
private double _preferredHeight(double width)
{
if (maxLines <= 0)
{
return preferredLineHeight * maxLines;
}
if (double.IsInfinity(width))
{
var text = _textPainter.text.text;
int lines = 1;
for (int index = 0; index < text.Length; ++index)
{
if (text[index] == 0x0A)
{
lines += 1;
}
}
return preferredLineHeight * lines;
}
_layoutText(width);
return Math.Max(preferredLineHeight, _textPainter.height);
}
private void _layoutText(double constraintWidth)
{
if (_textLayoutLastWidth == constraintWidth)
{
return;
}
var caretMargin = _kCaretGap + _kCaretWidth;
var avialableWidth = Math.Max(0.0, constraintWidth - caretMargin);
var maxWidth = _isMultiline ? avialableWidth : double.PositiveInfinity;
_textPainter.layout(minWidth: avialableWidth, maxWidth: maxWidth);
_textLayoutLastWidth = constraintWidth;
}
TextSelection _selectWordAtOffset(TextPosition position)
{
D.assert(_textLayoutLastWidth == constraints.maxWidth);
var word = _textPainter.getWordBoundary(position);
if (position.offset >= word.end)
{
return TextSelection.fromPosition(position);
}
return new TextSelection(baseOffset: word.start, extendOffset: word.end);
}
private bool _isMultiline
{
get { return _maxLines != 1; }
}
private Axis _viewportAxis
{
get { return _isMultiline ? Axis.vertical : Axis.horizontal; }
}
private Offset _paintOffset
{
get
{
switch (_viewportAxis)
{
case Axis.horizontal:
return new Offset(-offset.pixels, 0.0);
case Axis.vertical:
return new Offset(0.0, -offset.pixels);
}
return null;
}
}
private double _viewportExtend
{
get
{
D.assert(hasSize);
switch (_viewportAxis)
{
case Axis.horizontal:
return size.width;
case Axis.vertical:
return size.height;
}
return 0.0;
}
}
private double _getMaxScrollExtend(Size contentSize)
{
D.assert(hasSize);
switch (_viewportAxis)
{
case Axis.horizontal:
return Math.Max(0.0, contentSize.width - size.width);
case Axis.vertical:
return Math.Max(0.0, contentSize.height - size.height);
}
return 0.0;
}
}
}

3
Assets/UIWidgets/rendering/editable.cs.meta


fileFormatVersion: 2
guid: f171ccb5173340cf99e2ddd274a3cbd6
timeCreated: 1536565172

3
Assets/UIWidgets/service.meta


fileFormatVersion: 2
guid: 355cc83db152457b8eb6da935c588fbc
timeCreated: 1536566821

70
Assets/UIWidgets/ui/txt/word_separate.cs


using System;
namespace UIWidgets.ui
{
public class WordSeparate
{
enum Direction
{
Forward,
Backward,
}
enum characterType
{
LetterLike,
Symbol,
WhiteSpace
}
private string _text;
public WordSeparate(string text)
{
this._text = text;
}
public IndexRange findWordRange(int index)
{
var t = classifyChar(index);
int start = index;
for (int i = index; i >= 0; --i)
{
if (!char.IsLowSurrogate(_text[start]))
{
if (classifyChar(i) != t)
{
break;
}
start = i;
}
}
int end = index;
for (int i = index; i < _text.Length; ++i)
{
if (!char.IsLowSurrogate(_text[i]))
{
if (classifyChar(i) != t)
{
break;
}
end = i;
}
}
return new IndexRange(start, end + 1);
}
private characterType classifyChar(int index)
{
if (char.IsWhiteSpace(_text, index))
return characterType.WhiteSpace;
if (char.IsLetterOrDigit(_text, index) || _text[index] == '\'')
return characterType.LetterLike;
return characterType.Symbol;
}
}
}

3
Assets/UIWidgets/ui/txt/word_separate.cs.meta


fileFormatVersion: 2
guid: 1c8f1db7a67b4775942ad83def1ce873
timeCreated: 1536654844

191
Assets/UIWidgets/service/text_editing.cs


using System;
using UIWidgets.foundation;
using UIWidgets.ui;
namespace UIWidgets.service
{
public class TextRange: IEquatable<TextRange>
{
public readonly int start;
public readonly int end;
public static TextRange collapsed(int offset)
{
D.assert(offset >= -1);
return new TextRange(offset, offset);
}
public static readonly TextRange empty = new TextRange(-1, -1);
public TextRange(int start, int end)
{
D.assert(start >= -1);
D.assert(end >= -1);
this.start = start;
this.end = end;
}
public bool isValid
{
get { return start >= 0 && end >= 0; }
}
public bool isCollapsed
{
get { return start == end; }
}
public bool isNormalized
{
get { return start <= end; }
}
public string textBefore(string text)
{
D.assert(isNormalized);
return text.Substring(0, start);
}
public string textAfter(string text)
{
D.assert(isNormalized);
return text.Substring(end);
}
public string textInside(string text)
{
D.assert(isNormalized);
return text.Substring(start, end - start);
}
public bool Equals(TextRange other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return start == other.start && end == other.end;
}
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((TextRange) obj);
}
public override int GetHashCode()
{
unchecked
{
return (start * 397) ^ end;
}
}
public static bool operator ==(TextRange left, TextRange right)
{
return Equals(left, right);
}
public static bool operator !=(TextRange left, TextRange right)
{
return !Equals(left, right);
}
public override string ToString()
{
return string.Format("TextRange Start: {0}, End: {1}", start, end);
}
}
public class TextSelection : TextRange, IEquatable<TextSelection>
{
public readonly int baseOffset;
public readonly int extendOffset;
public readonly TextAffinity affinity;
public readonly bool isDirectional;
public TextSelection(int baseOffset, int extendOffset, TextAffinity affinity = TextAffinity.downstream,
bool isDirectional = false):base(baseOffset < extendOffset ? baseOffset: extendOffset,
baseOffset < extendOffset ? extendOffset : baseOffset )
{
this.baseOffset = baseOffset;
this.extendOffset = extendOffset;
this.affinity = affinity;
this.isDirectional = isDirectional;
}
public static TextSelection collapsed(int offset, TextAffinity affinity = TextAffinity.downstream)
{
return new TextSelection(offset, offset, affinity, false);
}
public static TextSelection fromPosition(TextPosition position)
{
return collapsed(position.offset, position.affinity);
}
public TextPosition basePos
{
get
{
return new TextPosition(offset: baseOffset, affinity: affinity);
}
}
public TextPosition extendPos
{
get
{
return new TextPosition(offset: extendOffset, affinity: affinity);
}
}
public bool Equals(TextSelection other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return baseOffset == other.baseOffset && extendOffset == other.extendOffset &&
affinity == other.affinity && isDirectional == other.isDirectional;
}
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((TextSelection) obj);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
hashCode = (hashCode * 397) ^ baseOffset;
hashCode = (hashCode * 397) ^ extendOffset;
hashCode = (hashCode * 397) ^ (int) affinity;
hashCode = (hashCode * 397) ^ isDirectional.GetHashCode();
return hashCode;
}
}
public static bool operator ==(TextSelection left, TextSelection right)
{
return Equals(left, right);
}
public static bool operator !=(TextSelection left, TextSelection right)
{
return !Equals(left, right);
}
public TextSelection copyWith(int? baseOffset = null, int? extendOffset = null, TextAffinity? affinity = null,
bool? isDirectional = null)
{
return new TextSelection(
baseOffset??this.baseOffset, extendOffset??this.extendOffset, affinity??this.affinity,
isDirectional??this.isDirectional
);
}
}
}

3
Assets/UIWidgets/service/text_editing.cs.meta


fileFormatVersion: 2
guid: 75983962965c4fd8bf21cc55ba91bde0
timeCreated: 1536566838

3
Assets/UIWidgets/math.meta


fileFormatVersion: 2
guid: c53bae489a2f499ea5b9000edb31b0bb
timeCreated: 1535421229
正在加载...
取消
保存