您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
329 行
11 KiB
329 行
11 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.ui;
|
|
using UnityEngine.Assertions;
|
|
|
|
namespace Unity.UIWidgets.painting {
|
|
public class TextSpan : DiagnosticableTree, IEquatable<TextSpan> {
|
|
public delegate bool Visitor(TextSpan span);
|
|
|
|
public readonly TextStyle style;
|
|
public readonly string text;
|
|
public List<string> splitedText;
|
|
public readonly List<TextSpan> children;
|
|
public readonly GestureRecognizer recognizer;
|
|
public readonly HoverRecognizer hoverRecognizer;
|
|
|
|
public TextSpan(string text = "", TextStyle style = null, List<TextSpan> children = null,
|
|
GestureRecognizer recognizer = null, HoverRecognizer hoverRecognizer = null) {
|
|
this.text = text;
|
|
this.splitedText = !string.IsNullOrEmpty(text) ? EmojiUtils.splitByEmoji(text) : null;
|
|
this.style = style;
|
|
this.children = children;
|
|
this.recognizer = recognizer;
|
|
this.hoverRecognizer = hoverRecognizer;
|
|
}
|
|
|
|
public void build(ParagraphBuilder builder, float textScaleFactor = 1.0f) {
|
|
var hasStyle = this.style != null;
|
|
|
|
if (hasStyle) {
|
|
builder.pushStyle(this.style, textScaleFactor);
|
|
}
|
|
|
|
if (this.splitedText != null) {
|
|
if (this.splitedText.Count == 1 && !char.IsHighSurrogate(this.splitedText[0][0]) &&
|
|
!EmojiUtils.isSingleCharEmoji(this.splitedText[0][0])) {
|
|
builder.addText(this.splitedText[0]);
|
|
}
|
|
else {
|
|
TextStyle style = this.style ?? new TextStyle();
|
|
for (int i = 0; i < this.splitedText.Count; i++) {
|
|
builder.pushStyle(style, textScaleFactor);
|
|
builder.addText(this.splitedText[i]);
|
|
builder.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (this.children != null) {
|
|
foreach (var child in this.children) {
|
|
Assert.IsNotNull(child);
|
|
child.build(builder, textScaleFactor);
|
|
}
|
|
}
|
|
|
|
if (hasStyle) {
|
|
builder.pop();
|
|
}
|
|
}
|
|
|
|
public bool hasHoverRecognizer {
|
|
get {
|
|
bool need = false;
|
|
this.visitTextSpan((text) => {
|
|
if (text.hoverRecognizer != null) {
|
|
need = true;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return need;
|
|
}
|
|
}
|
|
|
|
bool visitTextSpan(Visitor visitor) {
|
|
if (!string.IsNullOrEmpty(this.text)) {
|
|
if (!visitor.Invoke(this)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (this.children != null) {
|
|
foreach (var child in this.children) {
|
|
if (!child.visitTextSpan(visitor)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public TextSpan getSpanForPosition(TextPosition position) {
|
|
D.assert(this.debugAssertIsValid());
|
|
var offset = 0;
|
|
var targetOffset = position.offset;
|
|
var affinity = position.affinity;
|
|
TextSpan result = null;
|
|
this.visitTextSpan((span) => {
|
|
var endOffset = offset + span.text.Length;
|
|
if ((targetOffset == offset && affinity == TextAffinity.downstream) ||
|
|
(targetOffset > offset && targetOffset < endOffset) ||
|
|
(targetOffset == endOffset && affinity == TextAffinity.upstream)) {
|
|
result = span;
|
|
return false;
|
|
}
|
|
|
|
offset = endOffset;
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public string toPlainText() {
|
|
var sb = new StringBuilder();
|
|
this.visitTextSpan((span) => {
|
|
sb.Append(span.text);
|
|
return true;
|
|
});
|
|
return sb.ToString();
|
|
}
|
|
|
|
public int? codeUnitAt(int index) {
|
|
if (index < 0) {
|
|
return null;
|
|
}
|
|
|
|
var offset = 0;
|
|
int? result = null;
|
|
this.visitTextSpan(span => {
|
|
if (index - offset < span.text.Length) {
|
|
result = span.text[index - offset];
|
|
return false;
|
|
}
|
|
|
|
offset += span.text.Length;
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
bool debugAssertIsValid() {
|
|
D.assert(() => {
|
|
if (!this.visitTextSpan(span => {
|
|
if (span.children != null) {
|
|
foreach (TextSpan child in span.children) {
|
|
if (child == null) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
})) {
|
|
throw new UIWidgetsError(
|
|
"A TextSpan object with a non-null child list should not have any nulls in its child list.\n" +
|
|
"The full text in question was:\n" +
|
|
this.toStringDeep(prefixLineOne: " "));
|
|
}
|
|
|
|
return true;
|
|
});
|
|
return true;
|
|
}
|
|
|
|
public RenderComparison compareTo(TextSpan other) {
|
|
if (this.Equals(other)) {
|
|
return RenderComparison.identical;
|
|
}
|
|
|
|
if (other.text != this.text
|
|
|| ((this.children == null) != (other.children == null))
|
|
|| (this.children != null && other.children != null && this.children.Count != other.children.Count)
|
|
|| ((this.style == null) != (other.style != null))
|
|
) {
|
|
return RenderComparison.layout;
|
|
}
|
|
|
|
RenderComparison result = Equals(this.recognizer, other.recognizer)
|
|
? RenderComparison.identical
|
|
: RenderComparison.metadata;
|
|
|
|
if (!Equals(this.hoverRecognizer, other.hoverRecognizer)) {
|
|
result = RenderComparison.function > result ? RenderComparison.function : result;
|
|
}
|
|
|
|
if (this.style != null) {
|
|
var candidate = this.style.compareTo(other.style);
|
|
if (candidate > result) {
|
|
result = candidate;
|
|
}
|
|
|
|
if (result == RenderComparison.layout) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (this.children != null) {
|
|
for (var index = 0; index < this.children.Count; index++) {
|
|
var candidate = this.children[index].compareTo(other.children[index]);
|
|
if (candidate > result) {
|
|
result = candidate;
|
|
}
|
|
|
|
if (result == RenderComparison.layout) {
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != this.GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return this.Equals((TextSpan) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = (this.style != null ? this.style.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ (this.text != null ? this.text.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ (this.recognizer != null ? this.recognizer.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ (this.childHash());
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
public bool Equals(TextSpan other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return Equals(this.style, other.style) && string.Equals(this.text, other.text) &&
|
|
childEquals(this.children, other.children) && this.recognizer == other.recognizer;
|
|
}
|
|
|
|
public static bool operator ==(TextSpan left, TextSpan right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(TextSpan left, TextSpan right) {
|
|
return !Equals(left, right);
|
|
}
|
|
|
|
int childHash() {
|
|
unchecked {
|
|
var hashCode = 0;
|
|
if (this.children != null) {
|
|
foreach (var child in this.children) {
|
|
hashCode = (hashCode * 397) ^ (child != null ? child.GetHashCode() : 0);
|
|
}
|
|
}
|
|
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
static bool childEquals(List<TextSpan> left, List<TextSpan> right) {
|
|
if (ReferenceEquals(left, right)) {
|
|
return true;
|
|
}
|
|
|
|
if (left == null || right == null) {
|
|
return false;
|
|
}
|
|
|
|
return left.SequenceEqual(right);
|
|
}
|
|
|
|
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
base.debugFillProperties(properties);
|
|
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
|
|
// Properties on style are added as if they were properties directly on
|
|
// this TextSpan.
|
|
if (this.style != null) {
|
|
this.style.debugFillProperties(properties);
|
|
}
|
|
|
|
properties.add(new DiagnosticsProperty<GestureRecognizer>(
|
|
"recognizer", this.recognizer,
|
|
description: this.recognizer == null ? "" : this.recognizer.GetType().FullName,
|
|
defaultValue: Diagnostics.kNullDefaultValue
|
|
));
|
|
|
|
properties.add(new StringProperty("text", this.text, showName: false,
|
|
defaultValue: Diagnostics.kNullDefaultValue));
|
|
if (this.style == null && this.text == null && this.children == null) {
|
|
properties.add(DiagnosticsNode.message("(empty)"));
|
|
}
|
|
}
|
|
|
|
public override List<DiagnosticsNode> debugDescribeChildren() {
|
|
if (this.children == null) {
|
|
return new List<DiagnosticsNode>();
|
|
}
|
|
|
|
return this.children.Select((child) => {
|
|
if (child != null) {
|
|
return child.toDiagnosticsNode();
|
|
}
|
|
else {
|
|
return DiagnosticsNode.message("<null child>");
|
|
}
|
|
}).ToList();
|
|
}
|
|
}
|
|
}
|