浏览代码

Merge pull request #13 from UnityTech/text

editable widgets
/main
GitHub 6 年前
当前提交
34ce78c0
共有 37 个文件被更改,包括 2544 次插入205 次删除
  1. 39
      Assets/UIWidgets/Tests/CanvasAndLayers.cs
  2. 30
      Assets/UIWidgets/Tests/Paragraph.cs
  3. 12
      Assets/UIWidgets/editor/editor_window.cs
  4. 3
      Assets/UIWidgets/foundation/basic_types.cs
  5. 2
      Assets/UIWidgets/foundation/diagnostics.cs
  6. 1
      Assets/UIWidgets/painting/basic_types.cs
  7. 10
      Assets/UIWidgets/painting/text_painter.cs
  8. 2
      Assets/UIWidgets/painting/text_span.cs
  9. 297
      Assets/UIWidgets/painting/text_style.cs
  10. 18
      Assets/UIWidgets/rendering/editable.cs
  11. 27
      Assets/UIWidgets/service/text_editing.cs
  12. 12
      Assets/UIWidgets/ui/painting/canvas.cs
  13. 41
      Assets/UIWidgets/ui/painting/canvas_impl.cs
  14. 7
      Assets/UIWidgets/ui/painting/draw_cmd.cs
  15. 6
      Assets/UIWidgets/ui/painting/painting.cs
  16. 34
      Assets/UIWidgets/ui/painting/picture.cs
  17. 1
      Assets/UIWidgets/ui/painting/txt/font_manager.cs
  18. 2
      Assets/UIWidgets/ui/painting/txt/mesh_generator.cs
  19. 239
      Assets/UIWidgets/ui/text.cs
  20. 2
      Assets/UIWidgets/ui/txt/linebreaker.cs
  21. 18
      Assets/UIWidgets/ui/txt/paint_record.cs
  22. 135
      Assets/UIWidgets/ui/txt/paragraph.cs
  23. 16
      Assets/UIWidgets/ui/txt/paragraph_builder.cs
  24. 3
      Assets/UIWidgets/ui/window.cs
  25. 2
      Assets/UIWidgets/widgets/binding.cs
  26. 387
      Assets/UIWidgets/widgets/focus_manager.cs
  27. 4
      Assets/UIWidgets/widgets/focus_manager.cs.meta
  28. 66
      Assets/UIWidgets/Tests/EditableTextWiget.cs
  29. 3
      Assets/UIWidgets/Tests/EditableTextWiget.cs.meta
  30. 108
      Assets/UIWidgets/service/text_formatter.cs
  31. 3
      Assets/UIWidgets/service/text_formatter.cs.meta
  32. 544
      Assets/UIWidgets/service/text_input.cs
  33. 3
      Assets/UIWidgets/service/text_input.cs.meta
  34. 589
      Assets/UIWidgets/widgets/editable_text.cs
  35. 3
      Assets/UIWidgets/widgets/editable_text.cs.meta
  36. 77
      Assets/UIWidgets/widgets/focus_scope.cs
  37. 3
      Assets/UIWidgets/widgets/focus_scope.cs.meta

39
Assets/UIWidgets/Tests/CanvasAndLayers.cs


this.clipRect,
this.clipRRect,
this.saveLayer,
this.drawLine,
};
this._optionStrings = this._options.Select(x => x.Method.Name).ToArray();
this._selected = 0;

canvas.drawPloygon4(
new[] {new Offset(10, 150), new Offset(10, 160), new Offset(140, 120), new Offset(110, 180)},
paint);
}
void drawLine() {
var canvas = new CanvasImpl();
var paint = new Paint {
color = new Color(0xFFFF0000),
};
canvas.drawLine(
new Offset(10, 20),
new Offset(50, 20),
paint);
canvas.drawLine(
new Offset(10, 10),
new Offset(100, 100),
paint);
canvas.drawLine(
new Offset(10, 10),
new Offset(10, 50),
paint);
canvas.drawLine(
new Offset(40, 10),
new Offset(90, 10),
paint);
var widthPaint = new Paint {
color = new Color(0xFFFF0000),
strokeWidth = 4,
};
canvas.drawLine(
new Offset(40, 20),
new Offset(120, 190),
widthPaint);
}
void drawRect() {

30
Assets/UIWidgets/Tests/Paragraph.cs


this.textHeight,
this.textOverflow,
this.textAlign,
this.textDecoration,
};
this._optionStrings = this._options.Select(x => x.Method.Name).ToArray();
this._selected = 0;

})));
}
RenderBox textDecoration()
{
return box(
new RenderParagraph(new TextSpan(style: new painting.TextStyle(height:1.2), text: "", children:
new List<TextSpan>()
{
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.underline),
text: "Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.underline, decorationStyle: TextDecorationStyle.doubleLine),
text: "Double line Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.underline, fontSize: 24),
text: "Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.overline),
text: "Over line Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.overline, decorationStyle: TextDecorationStyle.doubleLine),
text: "Over line Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.lineThrough),
text: "Line through Real-time 3D revolution\n"),
new TextSpan(style: new painting.TextStyle(color: Color.fromARGB(255, 255, 0, 0),
decoration: TextDecoration.lineThrough, decorationColor:Color.fromARGB(255, 0, 255, 0)),
text: "Color Line through Real-time 3D revolution\n"),
})), width: 400);
}
RenderBox textAlign()
{

12
Assets/UIWidgets/editor/editor_window.cs


using System.Diagnostics;
using UIWidgets.async;
using UIWidgets.flow;
using UIWidgets.service;
using UIWidgets.rendering;
using UIWidgets.ui;
using UIWidgets.widgets;

readonly DateTime _epoch = new DateTime(Stopwatch.GetTimestamp());
readonly MicrotaskQueue _microtaskQueue = new MicrotaskQueue();
readonly TimerProvider _timerProvider = new TimerProvider();
readonly TextInput _textInput = new TextInput();
public void OnGUI() {
Window.instance = this;

}));
}
}
if (_textInput != null)
{
_textInput.OnGUI();
}
}
public void Update() {

Window.instance = null;
WidgetsBinding.instance = null;
}
}
public override TextInput textInput
{
get { return _textInput; }
}
}
}

3
Assets/UIWidgets/foundation/basic_types.cs


using System.Collections.Generic;
namespace UIWidgets.foundation {
public delegate void ValueChanged<T>(T value);
public delegate IEnumerable<T> EnumerableFilter<T>(IEnumerable<T> input);
public static class CollectionUtils {

2
Assets/UIWidgets/foundation/diagnostics.cs


return result.ToString();
}
string toStringDeep(
public string toStringDeep(
String prefixLineOne = "",
String prefixOtherLines = null,
DiagnosticLevel minLevel = DiagnosticLevel.debug

1
Assets/UIWidgets/painting/basic_types.cs


using System;
using System.ComponentModel;
using UIWidgets.foundation;
using UIWidgets.ui;
using UIWidgets.widgets;

10
Assets/UIWidgets/painting/text_painter.cs


{
Debug.Assert(text != null, "TextPainter.text must be set to a non-null value before using the TextPainter.");
Debug.Assert(textDirection != null, "TextPainter.textDirection must be set to a non-null value before using the TextPainter.");
if (_needsLayout && minWidth == _lastMaxWidth && maxWidth == _lastMaxWidth)
if (!_needsLayout && minWidth == _lastMinWidth && maxWidth == _lastMaxWidth)
_needsLayout = false;
if (_paragraph == null)
{

{
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;
}

); // direction doesn't matter, text is just a space
if (text != null && text.style != null)
{
builder.pushStyle(text.style.getTextStyle(textScaleFactor));
builder.pushStyle(text.style);
}
builder.addText(" ");

2
Assets/UIWidgets/painting/text_span.cs


var hasTyle = style != null;
if (hasTyle)
{
builder.pushStyle(style.getTextStyle(textScaleFactor));
builder.pushStyle(style);
}
if (!string.IsNullOrEmpty(text))
{

297
Assets/UIWidgets/painting/text_style.cs


using System;
using System.Collections.Generic;
using System.Linq;
using UIWidgets.foundation;
public class TextStyle
public class TextStyle : Diagnosticable, IEquatable<TextStyle>, ParagraphBuilder.ITextStyleProvider
public readonly bool? inherit;
public readonly bool inherit;
public readonly Color color;
public readonly double? fontSize;
public readonly FontWeight? fontWeight;

public readonly TextBaseline? textBaseline;
public readonly double? height;
public readonly TextDecoration decoration;
public readonly Color decorationColor;
public readonly TextDecorationStyle? decorationStyle;
public readonly Paint background;
public readonly string debugLabel;
public TextStyle(bool? inherit = null, Color color = null, double? fontSize = null, FontWeight? fontWeight = null,
FontStyle? fontStyle = null, double? letterSpacing = null, double? wordSpacing = null,
TextBaseline? textBaseline = null, double? height = null, TextDecoration decoration = null, string fontFamily = null)
const string _kDefaultDebugLabel = "unknown";
public TextStyle(bool inherit = true, Color color = null, double? fontSize = null,
FontWeight? fontWeight = null,
FontStyle? fontStyle = null, double? letterSpacing = null, double? wordSpacing = null,
TextBaseline? textBaseline = null, double? height = null, Paint background = null,
TextDecoration decoration = null,
Color decorationColor = null, TextDecorationStyle? decorationStyle = null,
string fontFamily = null, string debugLabel = null)
{
this.inherit = inherit;
this.color = color;

this.textBaseline = textBaseline;
this.height = height;
this.decoration = decoration;
this.decorationColor = decorationColor;
this.decorationStyle = decorationStyle;
this.debugLabel = debugLabel;
this.background = background;
public ui.TextStyle getTextStyle(double textScaleFactor = 1.0)
public ui.TextStyle getTextStyle(ui.TextStyle currentStyle = null)
if (currentStyle != null)
{
return new ui.TextStyle(
color: color??currentStyle.color,
fontSize: fontSize??currentStyle.fontSize,
fontWeight: fontWeight??currentStyle.fontWeight,
fontStyle: fontStyle??currentStyle.fontStyle,
letterSpacing: letterSpacing??currentStyle.letterSpacing,
wordSpacing: wordSpacing??currentStyle.wordSpacing,
textBaseline: textBaseline??currentStyle.textBaseline,
height: height??currentStyle.height,
decoration: decoration??currentStyle.decoration,
decorationColor: decorationColor??currentStyle.decorationColor,
fontFamily: fontFamily??currentStyle.fontFamily,
background: background??currentStyle.background
);
}
color: color,
decoration: decoration,
color: color,
fontSize: fontSize,
fontSize: fontSize == null ? null : fontSize * textScaleFactor,
fontFamily: fontFamily
);
decoration: decoration,
decorationColor: decorationColor,
fontFamily: fontFamily,
background: background
);
}
public RenderComparison compareTo(TextStyle other)

|| fontStyle != other.fontStyle || letterSpacing != other.letterSpacing
|| wordSpacing != other.wordSpacing || textBaseline != other.textBaseline
|| height != other.height)
|| height != other.height || background != other.background)
if (color != other.color || decoration != other.decoration)
if (color != other.color || decoration != other.decoration || decorationColor != other.decorationColor
|| decorationStyle != other.decorationStyle)
public ParagraphStyle getParagraphStyle(TextAlign textAlign,
TextDirection textDirection, string ellipsis, int maxLines, double textScaleFactor = 1.0)
public ParagraphStyle getParagraphStyle(TextAlign textAlign,
TextDirection textDirection, string ellipsis, int maxLines, double textScaleFactor = 1.0)
textAlign, textDirection, fontWeight, fontStyle,
maxLines, (fontSize ?? _defaultFontSize) * textScaleFactor,
fontFamily, height, ellipsis
);
textAlign, textDirection, fontWeight, fontStyle,
maxLines, (fontSize ?? _defaultFontSize) * textScaleFactor,
fontFamily, height, ellipsis
);
}
public TextStyle merge(TextStyle other)
{
if (other == null)
{
return this;
}
if (!other.inherit)
{
return other;
}
string mergedDebugLabel = null;
D.assert(() =>
{
if (other.debugLabel != null || debugLabel != null)
{
mergedDebugLabel = string.Format("({0}).merge({1})", debugLabel ?? _kDefaultDebugLabel,
other.debugLabel ?? _kDefaultDebugLabel);
}
return true;
});
return copyWith(
color: other.color,
fontFamily: other.fontFamily,
fontSize: other.fontSize,
fontWeight: other.fontWeight,
fontStyle: other.fontStyle,
letterSpacing: other.letterSpacing,
wordSpacing: other.wordSpacing,
textBaseline: other.textBaseline,
height: other.height,
decoration: other.decoration,
decorationColor: other.decorationColor,
decorationStyle: other.decorationStyle,
debugLabel: mergedDebugLabel
);
}
public TextStyle copyWith(Color color,
String fontFamily,
double? fontSize,
FontWeight? fontWeight,
FontStyle? fontStyle,
double? letterSpacing,
double? wordSpacing,
TextBaseline? textBaseline = null,
double? height = null,
Paint background = null,
TextDecoration decoration = null,
Color decorationColor = null,
TextDecorationStyle? decorationStyle = null,
string debugLabel = null)
{
string newDebugLabel = null;
D.assert(() =>
{
if (this.debugLabel != null)
{
newDebugLabel = debugLabel ?? string.Format("({0}).copyWith", this.debugLabel);
}
return true;
});
return new TextStyle(
inherit: inherit,
color: color ?? this.color,
fontFamily: fontFamily ?? this.fontFamily,
fontSize: fontSize ?? this.fontSize,
fontWeight: fontWeight ?? this.fontWeight,
fontStyle: fontStyle ?? this.fontStyle,
letterSpacing: letterSpacing ?? this.letterSpacing,
wordSpacing: wordSpacing ?? this.wordSpacing,
textBaseline: textBaseline ?? this.textBaseline,
height: height ?? this.height,
decoration: decoration ?? this.decoration,
decorationColor: decorationColor ?? this.decorationColor,
decorationStyle: decorationStyle ?? this.decorationStyle,
background: background ?? this.background,
debugLabel: newDebugLabel
);
public override void debugFillProperties(DiagnosticPropertiesBuilder properties)
{
base.debugFillProperties(properties);
List<DiagnosticsNode> styles = new List<DiagnosticsNode>();
styles.Add(new DiagnosticsProperty<Color>("color", color, defaultValue: null));
styles.Add(new StringProperty("family", fontFamily, defaultValue: null, quoted: false));
styles.Add(new DiagnosticsProperty<double?>("size", fontSize, defaultValue: null));
string weightDescription = "";
if (fontWeight != null)
{
switch (fontWeight)
{
case FontWeight.w400:
weightDescription = "400";
break;
case FontWeight.w700:
weightDescription = "700";
break;
}
}
styles.Add(new DiagnosticsProperty<FontWeight?>(
"weight",
fontWeight,
description: weightDescription,
defaultValue: null
));
styles.Add(new EnumProperty<FontStyle?>("style", fontStyle, defaultValue: null));
styles.Add(new DiagnosticsProperty<double?>("letterSpacing", letterSpacing, defaultValue: null));
styles.Add(new DiagnosticsProperty<double?>("wordSpacing", wordSpacing, defaultValue: null));
styles.Add(new EnumProperty<TextBaseline?>("baseline", textBaseline, defaultValue: null));
styles.Add(new DiagnosticsProperty<double?>("height", height, defaultValue: null));
styles.Add(new StringProperty("background", background == null ? null : background.ToString(), defaultValue: null, quoted: false));
if (decoration != null)
{
List<string> decorationDescription = new List<string>();
if (decorationStyle != null)
{
decorationDescription.Add(decorationStyle.ToString());
}
styles.Add(new DiagnosticsProperty<Color>("decorationColor", decorationColor, defaultValue: null,
level: DiagnosticLevel.fine));
if (decorationColor != null)
{
decorationDescription.Add(decorationColor.ToString());
}
styles.Add(new DiagnosticsProperty<TextDecoration>("decoration", decoration, defaultValue: null,
level: DiagnosticLevel.hidden));
if (decoration != null)
decorationDescription.Add("$decoration");
D.assert(decorationDescription.isNotEmpty);
styles.Add(new MessageProperty("decoration", string.Join(" ", decorationDescription.ToArray())));
}
bool styleSpecified = styles.Any((DiagnosticsNode n) => !n.isFiltered(DiagnosticLevel.info));
properties.add(new DiagnosticsProperty<bool>("inherit", inherit,
level: (!styleSpecified && inherit) ? DiagnosticLevel.fine : DiagnosticLevel.info));
foreach (var style in styles)
{
properties.add(style);
}
if (!styleSpecified)
properties.add(new FlagProperty("inherit", value: inherit, ifTrue: "<all styles inherited>",
ifFalse: "<no style specified>"));
}
public bool Equals(TextStyle other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return inherit == other.inherit && Equals(color, other.color) && fontSize.Equals(other.fontSize) &&
fontWeight == other.fontWeight && fontStyle == other.fontStyle &&
letterSpacing.Equals(other.letterSpacing) && wordSpacing.Equals(other.wordSpacing) &&
textBaseline == other.textBaseline && height.Equals(other.height) &&
Equals(decoration, other.decoration) && Equals(decorationColor, other.decorationColor) &&
decorationStyle == other.decorationStyle && Equals(background, other.background) &&
string.Equals(fontFamily, other.fontFamily);
}
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((TextStyle) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = inherit.GetHashCode();
hashCode = (hashCode * 397) ^ (color != null ? color.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ fontSize.GetHashCode();
hashCode = (hashCode * 397) ^ fontWeight.GetHashCode();
hashCode = (hashCode * 397) ^ fontStyle.GetHashCode();
hashCode = (hashCode * 397) ^ letterSpacing.GetHashCode();
hashCode = (hashCode * 397) ^ wordSpacing.GetHashCode();
hashCode = (hashCode * 397) ^ textBaseline.GetHashCode();
hashCode = (hashCode * 397) ^ height.GetHashCode();
hashCode = (hashCode * 397) ^ (decoration != null ? decoration.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (decorationColor != null ? decorationColor.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ decorationStyle.GetHashCode();
hashCode = (hashCode * 397) ^ (background != null ? background.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (fontFamily != null ? fontFamily.GetHashCode() : 0);
return hashCode;
}
}
public static bool operator ==(TextStyle left, TextStyle right)
{
return Equals(left, right);
}
public static bool operator !=(TextStyle left, TextStyle right)
{
return !Equals(left, right);
}
public override string toStringShort()
{
return this.GetType().FullName;
}
}
}

18
Assets/UIWidgets/rendering/editable.cs


public class RenderEditable : RenderBox
{
public static readonly string obscuringCharacter = "•";
public static readonly char obscuringCharacter = '•';
private static readonly double _kCaretGap = 1.0;
private static readonly double _kCaretHeightOffset = 2.0;
private static readonly double _kCaretWidth = 1.0;

private bool _obscureText;
private TapGestureRecognizer _tap;
private DoubleTapGestureRecognizer _doubleTap;
private bool ignorePointer;
private SelectionChangedHandler onSelectionChanged;
private CaretChangedHandler onCaretChanged;
public bool ignorePointer;
public SelectionChangedHandler onSelectionChanged;
public CaretChangedHandler onCaretChanged;
private Rect _lastCaretRect;
private double? _textLayoutLastWidth;
private List<TextBox> _selectionRects;

return;
}
hasFocus = value;
_hasFocus = value;
markNeedsSemanticsUpdate();
}
}

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);
}
}

}
}
if (_hasFocus) {
var caretOffset = _textPainter.getOffsetForCaret(_selection.extendPos, Rect.fromLTWH(0, 0, 1, preferredLineHeight));
var caretRec = _caretPrototype.shift(caretOffset + effectiveOffset);
Input.compositionCursorPos = new Vector2((float)caretRec.left, (float)caretRec.bottom);
}
_textPainter.paint(context.canvas, effectiveOffset);
}

{
return TextSelection.fromPosition(position);
}
return new TextSelection(baseOffset: word.start, extendOffset: word.end);
return new TextSelection(baseOffset: word.start, extentOffset: word.end);
}
private bool _isMultiline

27
Assets/UIWidgets/service/text_editing.cs


public class TextSelection : TextRange, IEquatable<TextSelection>
{
public readonly int baseOffset;
public readonly int extendOffset;
public readonly int extentOffset;
public TextSelection(int baseOffset, int extendOffset, TextAffinity affinity = TextAffinity.downstream,
bool isDirectional = false):base(baseOffset < extendOffset ? baseOffset: extendOffset,
baseOffset < extendOffset ? extendOffset : baseOffset )
public TextSelection(int baseOffset, int extentOffset, TextAffinity affinity = TextAffinity.downstream,
bool isDirectional = false):base(baseOffset < extentOffset ? baseOffset: extentOffset,
baseOffset < extentOffset ? extentOffset : baseOffset )
this.extendOffset = extendOffset;
this.extentOffset = extentOffset;
this.affinity = affinity;
this.isDirectional = isDirectional;
}

{
get
{
return new TextPosition(offset: extendOffset, affinity: affinity);
return new TextPosition(offset: extentOffset, affinity: affinity);
}
}

if (ReferenceEquals(this, other)) return true;
return baseOffset == other.baseOffset && extendOffset == other.extendOffset &&
return baseOffset == other.baseOffset && extentOffset == other.extentOffset &&
affinity == other.affinity && isDirectional == other.isDirectional;
}

{
unchecked
{
int hashCode = 0;
var hashCode = base.GetHashCode();
hashCode = (hashCode * 397) ^ extendOffset;
hashCode = (hashCode * 397) ^ extentOffset;
hashCode = (hashCode * 397) ^ (int) affinity;
hashCode = (hashCode * 397) ^ isDirectional.GetHashCode();
return hashCode;

return !Equals(left, right);
}
public TextSelection copyWith(int? baseOffset = null, int? extendOffset = null, TextAffinity? affinity = null,
public TextSelection copyWith(int? baseOffset = null, int? extentOffset = null, TextAffinity? affinity = null,
baseOffset??this.baseOffset, extendOffset??this.extendOffset, affinity??this.affinity,
baseOffset??this.baseOffset, extentOffset??this.extentOffset, affinity??this.affinity,
}
public override string ToString()
{
return string.Format("{0}, BaseOffset: {1}, ExtentOffset: {2}, Affinity: {3}, IsDirectional: {4}", base.ToString(), baseOffset, extentOffset, affinity, isDirectional);
}
}
}

12
Assets/UIWidgets/ui/painting/canvas.cs


void drawImageRect(Rect src, Rect dst, Paint paint, Image image);
void drawLine(Offset from, Offset to, Paint paint);
void concat(Matrix4x4 transform);
void save();

image = image,
src = src,
dst = dst,
});
}
public void drawLine(Offset from, Offset to, Paint paint)
{
this._recorder.addDrawCmd(new DrawLine
{
from = from,
to = to,
paint = paint,
});
}

41
Assets/UIWidgets/ui/painting/canvas_impl.cs


else if (drawCmd is DrawRect) {
var drawRect = (DrawRect) drawCmd;
this.drawRect(drawRect.rect, drawRect.borderWidth, drawRect.borderRadius, drawRect.paint);
} else if (drawCmd is DrawLine)
{
var drawLine = (DrawLine) drawCmd;
this.drawLine(drawLine.from, drawLine.to, drawLine.paint);
}
else if (drawCmd is DrawRectShadow) {
var drawRectShadow = (DrawRectShadow) drawCmd;

}
}
public void drawLine(Offset from, Offset to, Paint paint)
{
var color = paint.color;
Offset vect = to - from;
var distance = vect.distance;
if (color.alpha > 0 && distance > 0)
{
var halfWidth = paint.strokeWidth * 0.5;
var diff = vect / distance * halfWidth;
diff = new Offset(diff.dy, -diff.dx);
this.prepareGL(CanvasImpl.linesMat);
CanvasImpl.linesMat.SetPass(0);
var points = new[]
{
(from + diff).toVector(),
(from - diff).toVector(),
(to - diff).toVector(),
(to + diff).toVector(),
};
GL.Begin(GL.QUADS);
GL.Color(color.toColor());
for (int i = 0; i < points.Length; ++i)
{
GL.Vertex(points[i]);
}
GL.End();
}
}
public void concat(Matrix4x4 transform) {
this._transform = transform * this._transform;
}

public void drawTextBlob(TextBlob textBlob, double x, double y)
{
var mesh = MeshGenrator.generateMesh(textBlob, x, y);
var font = FontManager.instance.getOrCreate(textBlob.style.safeFontFamily, textBlob.style.UnityFontSize);
var font = FontManager.instance.getOrCreate(textBlob.style.fontFamily, textBlob.style.UnityFontSize);
prepareGL(font.material);
font.material.SetPass(0);
Graphics.DrawMeshNow(mesh, Matrix4x4.identity);

GL.MultMatrix(this._transform);
}
//
// private double PixelRound(double v)
// {
// return Math.Floor(v * EditorGUIUtility.pixelsPerPoint) EditorGUIUtility.pixelsPerPoint;
// }
private class ClipRec {
public ClipRec(Matrix4x4 transform, Rect rect = null, RRect rrect = null) {

7
Assets/UIWidgets/ui/painting/draw_cmd.cs


public double y;
}
public class DrawLine : DrawCmd
{
public Offset from;
public Offset to;
public Paint paint;
}
}

6
Assets/UIWidgets/ui/painting/painting.cs


public static bool operator !=(Color a, Color b) {
return !(a == b);
}
public override string ToString()
{
return string.Format("Color(0x{0:X8})", value);
}
public double strokeWidth = 1;
}
public static class Conversions {

34
Assets/UIWidgets/ui/painting/picture.cs


this._isInLayer,
null
));
} else if (drawCmd is DrawSaveLayer) {
} else if (drawCmd is DrawLine)
{
var drawLine = (DrawLine) drawCmd;
Offset vect = drawLine.to - drawLine.from;
var distance = vect.distance;
if (distance > 0)
{
var halfWidth = drawLine.paint.strokeWidth * 0.5;
var diff = vect / distance * halfWidth;
diff = new Offset(diff.dy, -diff.dx);
var offsets = new Offset[]
{
drawLine.from + diff,
drawLine.from - diff,
drawLine.to + diff,
drawLine.to - diff,
};
var minX = offsets[0].dx;
var maxX = offsets[0].dx;
var minY = offsets[0].dy;
var maxY = offsets[0].dy;
for (int i = 1; i < offsets.Length; i++)
{
minX = Math.Min(minX, offsets[i].dx);
maxX = Math.Max(maxX, offsets[i].dx);
minY = Math.Min(minY, offsets[i].dy);
maxY = Math.Min(maxY, offsets[i].dy);
}
this.addPaintBounds(Rect.fromLTRB(minX, minY, maxX, maxY));
}
}
else if (drawCmd is DrawSaveLayer) {
this.stack.Push(new CanvasRec(
this._transform,
this._clipRect,

1
Assets/UIWidgets/ui/painting/txt/font_manager.cs


return founded;
}
Debug.Log(string.Format("Create new Font names={0}, size={1}", names, fontSize));
var newFont = Font.CreateDynamicFontFromOSFont(names,
fontSize);
_fonts.Add(newFont);

2
Assets/UIWidgets/ui/painting/txt/mesh_generator.cs


public static Mesh generateMesh(TextBlob textBlob, double x, double y)
{
var style = textBlob.style;
var font = FontManager.instance.getOrCreate(style.safeFontFamily, style.UnityFontSize);
var font = FontManager.instance.getOrCreate(style.fontFamily, style.UnityFontSize);
var length = textBlob.end - textBlob.start;
var vertices = new Vector3[length * 4];
var triangles = new int[length * 6];

239
Assets/UIWidgets/ui/text.cs


using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using UIWidgets.foundation;
namespace UIWidgets.ui {
public enum FontStyle {
namespace UIWidgets.ui
{
public enum FontStyle
{
/// Use the upright glyphs
normal,

public enum TextBaseline {
public enum TextBaseline
{
public enum TextAlign {
public enum TextAlign
{
/// Align the text on the left edge of the container.
left,

justify,
}
public class ParagraphConstraints: IEquatable<ParagraphConstraints>
public class ParagraphConstraints : IEquatable<ParagraphConstraints>
public ParagraphConstraints(double width)
{
this.width = width;

}
}
public class TextStyle:IEquatable<TextStyle>
public class TextStyle : IEquatable<TextStyle>
public static readonly string defaultFontFamily = "Helvetica";
public static readonly double defaultFontSize = 14.0;
public static readonly FontWeight defaultFontWeight = FontWeight.w400;
public static readonly FontStyle defaultFontStyle = FontStyle.normal;
public static readonly Color defaultColor = Color.fromARGB(255, 0, 0, 0);
public Color color;
public double? fontSize;
public FontWeight? fontWeight;
public FontStyle? fontStyle;
public double? letterSpacing;
public double? wordSpacing;
public TextBaseline? textBaseline;
public double? height;
public TextDecoration decoration;
public string fontFamily;
public FontStyle safeFontStyle
{
get { return fontStyle ?? defaultFontStyle; }
}
public string safeFontFamily
{
get { return fontFamily ?? defaultFontFamily; }
}
public double safeFontSize
{
get { return fontSize ?? defaultFontSize; }
}
public FontWeight safeFontWeight
{
get { return fontWeight ?? defaultFontWeight; }
}
public readonly Color color = Color.fromARGB(255, 0, 0, 0);
public readonly double fontSize = 14.0;
public readonly FontWeight fontWeight = FontWeight.w400;
public readonly FontStyle fontStyle = FontStyle.normal;
public readonly double letterSpacing = 0.0;
public readonly double wordSpacing = 0.0;
public readonly TextBaseline textBaseline = TextBaseline.alphabetic;
public readonly double height = 1.0;
public readonly TextDecoration decoration = TextDecoration.none;
public readonly Color decorationColor;
public readonly TextDecorationStyle decorationStyle = TextDecorationStyle.solid;
public readonly string fontFamily = "Helvetica";
public readonly Paint background;
get { return (color ?? defaultColor).toColor(); }
get { return color.toColor(); }
if (safeFontStyle == FontStyle.italic)
if (fontStyle == FontStyle.italic)
if (safeFontWeight == FontWeight.w700)
if (fontWeight == FontWeight.w700)
{
return UnityEngine.FontStyle.BoldAndItalic;
}

}
} else if (safeFontWeight == FontWeight.w700)
}
else if (fontWeight == FontWeight.w700)
{
return UnityEngine.FontStyle.Bold;
}

public int UnityFontSize
{
get { return (int) safeFontSize; }
}
public TextStyle merge(TextStyle style)
{
var ret = new TextStyle();
ret.color = style.color??color;
ret.fontSize = style.fontSize??fontSize;
ret.fontWeight = style.fontWeight??fontWeight;
ret.fontStyle = style.fontStyle??fontStyle;
ret.letterSpacing = style.letterSpacing??letterSpacing;
ret.textBaseline = style.textBaseline??textBaseline;
ret.height = style.height??height;
ret.decoration = style.decoration??decoration;
ret.fontFamily = style.fontFamily??fontFamily;
return ret;
get { return (int) fontSize; }
return color == other.color && fontSize == other.fontSize && fontWeight == other.fontWeight &&
fontStyle == other.fontStyle && letterSpacing == other.letterSpacing &&
wordSpacing == other.wordSpacing && textBaseline == other.textBaseline &&
height == other.height && decoration == other.decoration && fontFamily == other.fontFamily;
return Equals(color, other.color) && fontSize.Equals(other.fontSize) && fontWeight == other.fontWeight &&
fontStyle == other.fontStyle && letterSpacing.Equals(other.letterSpacing) &&
wordSpacing.Equals(other.wordSpacing) && textBaseline == other.textBaseline &&
height.Equals(other.height) && Equals(decoration, other.decoration) &&
Equals(decorationColor, other.decorationColor) && decorationStyle == other.decorationStyle &&
string.Equals(fontFamily, other.fontFamily);
}
public override bool Equals(object obj)

hashCode = (hashCode * 397) ^ textBaseline.GetHashCode();
hashCode = (hashCode * 397) ^ height.GetHashCode();
hashCode = (hashCode * 397) ^ (decoration != null ? decoration.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (decorationColor != null ? decorationColor.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ decorationStyle.GetHashCode();
hashCode = (hashCode * 397) ^ (fontFamily != null ? fontFamily.GetHashCode() : 0);
return hashCode;
}

return !Equals(left, right);
}
public TextStyle(Color color = null, double? fontSize = default(double?), FontWeight? fontWeight = default(FontWeight?), FontStyle? fontStyle = default(FontStyle?), double? letterSpacing = default(double?), double? wordSpacing = default(double?), TextBaseline? textBaseline = default(TextBaseline?), double? height = default(double?), TextDecoration decoration = null, string fontFamily = null)
public TextStyle(Color color = null, double? fontSize = null,
FontWeight? fontWeight = null, FontStyle? fontStyle = null, double? letterSpacing = null,
double? wordSpacing = null, TextBaseline? textBaseline = null, double? height= null,
TextDecoration decoration = null, TextDecorationStyle? decorationStyle = null, Color decorationColor = null, string fontFamily = null,
Paint background = null
)
this.color = color;
this.fontSize = fontSize;
this.fontWeight = fontWeight;
this.fontStyle = fontStyle;
this.letterSpacing = letterSpacing;
this.wordSpacing = wordSpacing;
this.textBaseline = textBaseline;
this.height = height;
this.decoration = decoration;
this.fontFamily = fontFamily;
this.color = color ?? this.color;
this.fontSize = fontSize ?? this.fontSize;
this.fontWeight = fontWeight ?? this.fontWeight;
this.fontStyle = fontStyle ?? this.fontStyle;
this.letterSpacing = letterSpacing ?? this.letterSpacing;
this.wordSpacing = wordSpacing ?? this.wordSpacing;
this.fontSize = fontSize ?? this.fontSize;
this.textBaseline = textBaseline ?? this.textBaseline;
this.height = height ?? this.height;
this.decoration = decoration ?? this.decoration;
this.decorationStyle = decorationStyle ?? this.decorationStyle;
this.decorationColor = decorationColor ?? this.decorationColor;
this.fontFamily = fontFamily ?? this.fontFamily;
this.background = background ?? this.background;
public class ParagraphStyle: IEquatable<ParagraphStyle>
public class ParagraphStyle : IEquatable<ParagraphStyle>
public ParagraphStyle(TextAlign? textAlign = null,
TextDirection? textDirection = null,
FontWeight? fontWeight = null,
FontStyle? fontStyle = null,
int? maxLines = null,
double? fontSize = null,
string fontFamily = null,
public ParagraphStyle(TextAlign? textAlign = null,
TextDirection? textDirection = null,
FontWeight? fontWeight = null,
FontStyle? fontStyle = null,
int? maxLines = null,
double? fontSize = null,
string fontFamily = null,
double? lineHeight = null, // todo
string ellipsis = null)
{

{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return textAlign == other.textAlign && textDirection == other.textDirection && fontWeight == other.fontWeight && fontStyle == other.fontStyle && maxLines == other.maxLines && fontSize.Equals(other.fontSize) && string.Equals(fontFamily, other.fontFamily) && lineHeight.Equals(other.lineHeight) && string.Equals(ellipsis, other.ellipsis);
return textAlign == other.textAlign && textDirection == other.textDirection &&
fontWeight == other.fontWeight && fontStyle == other.fontStyle && maxLines == other.maxLines &&
fontSize.Equals(other.fontSize) && string.Equals(fontFamily, other.fontFamily) &&
lineHeight.Equals(other.lineHeight) && string.Equals(ellipsis, other.ellipsis);
}
public override bool Equals(object obj)

if (obj.GetType() != this.GetType()) return false;
return Equals((ParagraphStyle) obj);
}
public static bool operator ==(ParagraphStyle a, ParagraphStyle b) {
public static bool operator ==(ParagraphStyle a, ParagraphStyle b)
{
public static bool operator !=(ParagraphStyle a, ParagraphStyle b) {
public static bool operator !=(ParagraphStyle a, ParagraphStyle b)
{
return !(a == b);
}

fontFamily: fontFamily,
fontSize: fontSize,
height: lineHeight
);
);
}
public TextAlign TextAlign

public readonly double? lineHeight;
public readonly string ellipsis;
}
public class TextDecoration: IEquatable<TextDecoration>
public enum TextDecorationStyle
{
solid,
doubleLine,
}
public class TextDecoration : IEquatable<TextDecoration>
{
public bool Equals(TextDecoration other)
{

{
return mask;
}
public static bool operator ==(TextDecoration a, TextDecoration b) {
public static bool operator ==(TextDecoration a, TextDecoration b)
{
public static bool operator !=(TextDecoration a, TextDecoration b) {
public static bool operator !=(TextDecoration a, TextDecoration b)
{
public readonly int mask;
public TextDecoration(int mask)

bool contains(TextDecoration other) {
public bool contains(TextDecoration other)
{
public enum TextDirection {
public enum TextDirection
{
public enum TextAffinity {
public enum TextAffinity
{
upstream,
downstream,
}

}
}
public class TextBox: IEquatable<TextBox>
public class TextBox : IEquatable<TextBox>
public readonly double bottom;
public readonly TextDirection direction;

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);

{
get { return direction == TextDirection.ltr ? left : right; }
}
public double end
{
get { return direction == TextDirection.ltr ? right : left; }

{
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;
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)

public override string ToString()
{
return string.Format("Left: {0}, Top: {1}, Right: {2}, Bottom: {3}, Direction: {4}", left, top, right, bottom, direction);
return string.Format("Left: {0}, Top: {1}, Right: {2}, Bottom: {3}, Direction: {4}", left, top, right,
bottom, direction);
}
public override int GetHashCode()

2
Assets/UIWidgets/ui/txt/linebreaker.cs


{
runIterator.nextTo(charIndex);
var run = runIterator.run;
var font = FontManager.instance.getOrCreate(run.style.safeFontFamily, run.style.UnityFontSize);
var font = FontManager.instance.getOrCreate(run.style.fontFamily, run.style.UnityFontSize);
var style = run.style;
var charInfo = new CharacterInfo();

18
Assets/UIWidgets/ui/txt/paint_record.cs


{
public class PaintRecord
{
public PaintRecord(TextStyle style, TextBlob _text,
public PaintRecord(TextStyle style, Offset offset, TextBlob _text,
FontMetrics metrics,
int line, double runWidth)
{
this._style = style;

this._metrics = metrics;
this._offset = offset;
}
public TextBlob text

get { return _runWidth; }
}
public Offset offset
{
get { return _offset; }
set { _offset = value; }
}
public FontMetrics metrics
{
get { return _metrics; }
}
private Offset _offset;
private FontMetrics _metrics;
}
}

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


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

{
public readonly double ascent;
public readonly double descent;
public readonly double? underlineThickness;
public readonly double? underlinePosition;
public readonly double? strikeoutPosition;
public readonly double? fxHeight;
public FontMetrics(double ascent, double descent)
public FontMetrics(double ascent, double descent,
double? underlineThickness = null, double? underlinePosition = null, double? strikeoutPosition = null,
double? fxHeight = null)
this.underlineThickness = underlineThickness;
this.underlinePosition = underlinePosition;
this.strikeoutPosition = strikeoutPosition;
this.fxHeight = fxHeight;
}
public static FontMetrics fromFont(Font font, double? height)
{
var ascent = font.ascent * (height??1.0);
var descent = (font.lineHeight - font.ascent) * (height??1.0);
double? fxHeight = null;
font.RequestCharactersInTexture("x");
CharacterInfo charInfo;
if (font.GetCharacterInfo('x', out charInfo))
{
fxHeight = charInfo.glyphHeight;
}
return new FontMetrics(ascent, descent, fxHeight: fxHeight);
}
}

private double _width;
public const char CHAR_NBSP = '\u00A0';
private const double kDoubleDecorationSpacing = 3.0;
public static bool isWordSpace(char ch)
{
return ch == ' ' || ch == CHAR_NBSP;

public void paint(Canvas canvas, double x, double y)
{
var baseOffset = new Offset(x, y);
paintDecorations(canvas, paintRecord, baseOffset);
}
}

var run = _runs.getRun(i);
if (run.start < run.end)
{
var font = FontManager.instance.getOrCreate(run.style.safeFontFamily, run.style.UnityFontSize);
var font = FontManager.instance.getOrCreate(run.style.fontFamily, run.style.UnityFontSize);
font.RequestCharactersInTexture(_text.Substring(run.start, run.end - run.start), 0,
run.style.UnityFontStyle);
}

double yOffset = 0;
var runIndex = 0;
double lastDescent = 0.0f;
var linePaintRecords = new List<PaintRecord>();
linePaintRecords.Clear();
var run = _runs.getRun(runIndex);
if (run.start < run.end && run.start < line.end && run.end > line.start)
var run = runIndex < _runs.size ? _runs.getRun(runIndex) : null;
if (run != null && run.start < run.end && run.start < line.end && run.end > line.start)
var font = FontManager.instance.getOrCreate(run.style.safeFontFamily, run.style.UnityFontSize);
var ascent = font.ascent * (run.style.height??1.0);
var descent = (font.lineHeight - font.ascent) * (run.style.height??1.0);
if (ascent > maxAscent)
var font = FontManager.instance.getOrCreate(run.style.fontFamily, run.style.UnityFontSize);
var metrics = FontMetrics.fromFont(font, run.style.height);
var ascent = font.ascent * (run.style.height);
var descent = (font.lineHeight - font.ascent) * (run.style.height);
if (metrics.ascent > maxAscent)
maxAscent = ascent;
maxAscent = metrics.ascent;
if (descent > maxDescent)
if (metrics.descent > maxDescent)
maxDescent = descent;
maxDescent = metrics.descent;
}
int start = Math.Max(run.start, line.start);

_characterPositions[end - 1].x + _characterWidths[end - 1] -
_characterPositions[start].x,
descent);
_paintRecords.Add(new PaintRecord(run.style, new TextBlob(
_text, start, end, _characterPositions, run.style, bounds),
linePaintRecords.Add(new PaintRecord(run.style, new Offset(_characterPositions[start].x, yOffset)
, new TextBlob(
_text, start, end, _characterPositions, run.style, bounds), metrics,
new IndexRange(start, end), lineNumber, new FontMetrics(ascent, descent), TextDirection.ltr));
new IndexRange(start, end), lineNumber, metrics, TextDirection.ltr));
}
}

}
lastDescent = maxDescent;
yOffset = Utils.PixelCorrectRound(yOffset + maxAscent + lastDescent);
foreach (var record in linePaintRecords)
{
record.offset = new Offset(record.offset.dx, yOffset);
}
_paintRecords.AddRange(linePaintRecords);
for (var charIndex = line.start; charIndex < line.end; charIndex++)
{
_characterPositions[charIndex].y = yOffset;

}
return words;
}
private void paintDecorations(Canvas canvas, PaintRecord record, Offset baseOffset)
{
if (record.style.decoration == null || record.style.decoration == TextDecoration.none)
{
return;
}
var paint = new Paint();
if (record.style.decorationColor == null)
{
paint.color = record.style.color;
}
else
{
paint.color = record.style.decorationColor;
}
var width = record.runWidth;
var metrics = record.metrics;
double underLineThickness = metrics.underlineThickness ?? (record.style.fontSize / 14.0);
paint.strokeWidth = underLineThickness;
var recordOffset = baseOffset + record.offset;
var x = recordOffset.dx;
var y = recordOffset.dy;
int decorationCount = 1;
switch (record.style.decorationStyle)
{
case TextDecorationStyle.doubleLine:
decorationCount = 2;
break;
}
var decoration = record.style.decoration;
for (int i = 0; i < decorationCount; i++)
{
double yOffset = i * underLineThickness * kDoubleDecorationSpacing;
double yOffsetOriginal = yOffset;
if (decoration != null && decoration.contains(TextDecoration.underline))
{
// underline
yOffset += metrics.underlinePosition ?? underLineThickness;
canvas.drawLine(new Offset(x, y + yOffset), new Offset(x + width, y + yOffset), paint);
yOffset = yOffsetOriginal;
}
if (decoration != null && decoration.contains(TextDecoration.overline))
{
yOffset -= metrics.ascent;
canvas.drawLine(new Offset(x, y + yOffset), new Offset(x + width, y + yOffset), paint);
yOffset = yOffsetOriginal;
}
if (decoration != null && decoration.contains(TextDecoration.lineThrough))
{
yOffset += (decorationCount - 1.0) * underLineThickness * kDoubleDecorationSpacing / -2.0;
yOffset += metrics.strikeoutPosition ?? (metrics.fxHeight??0) / -2.0;
canvas.drawLine(new Offset(x, y + yOffset), new Offset(x + width, y + yOffset), paint);
yOffset = yOffsetOriginal;
}
}
}
}

16
Assets/UIWidgets/ui/txt/paragraph_builder.cs


private List<int> _styleStack = new List<int>();
private int _paragraph_style_index;
public interface ITextStyleProvider
{
TextStyle getTextStyle(TextStyle current = null);
}
public ParagraphBuilder(ParagraphStyle style)
{
setParagraphStyle(style);

return paragraph;
}
public void pushStyle(TextStyle style)
public void pushStyle(ITextStyleProvider style)
var newStyle = peekStyle().merge(style);
var newStyle = style.getTextStyle(peekStyle());
_styleStack.Add(styleIndex);
_runs.startRun(styleIndex, _text.Length);
}
public void pushStyle(TextStyle style)
{
var styleIndex = _runs.addStyle(style);
_styleStack.Add(styleIndex);
_runs.startRun(styleIndex, _text.Length);
}

3
Assets/UIWidgets/ui/window.cs


using System;
using UIWidgets.async;
using UIWidgets.service;
using UIWidgets.foundation;
namespace UIWidgets.ui {

public Timer run(Action callback) {
return this.run(TimeSpan.Zero, callback);
}
public abstract TextInput textInput { get; }
}
}

2
Assets/UIWidgets/widgets/binding.cs


get { return this._buildOwner; }
}
readonly BuildOwner _buildOwner = new BuildOwner();
private readonly BuildOwner _buildOwner = new BuildOwner();
public FocusManager focusManager {
get { return this._buildOwner.focusManager; }

387
Assets/UIWidgets/widgets/focus_manager.cs


namespace UIWidgets.widgets {
public class FocusManager {
using System;
using System.Collections.Generic;
using UIWidgets.foundation;
using UIWidgets.ui;
namespace UIWidgets.widgets
{
public class FocusNode : ChangeNotifier {
internal FocusScopeNode _parent;
internal FocusManager _manager;
internal bool _hasKeyboardToken = false;
public bool hasFocus
{
get
{
FocusNode node = null;
if (_manager != null)
{
node = _manager._currentFocus;
}
return node == this;
}
}
public bool consumeKeyboardToken()
{
if (!_hasKeyboardToken)
{
return false;
}
_hasKeyboardToken = false;
return true;
}
public void unfocus()
{
if (_parent != null)
{
_parent._resignFocus(this);
}
D.assert(_parent == null);
D.assert(_manager == null);
}
public override void dispose() {
if (_manager != null)
{
_manager._willDisposeFocusNode(this);
}
if (_parent != null)
{
_parent._resignFocus(this);
}
D.assert(_parent == null);
D.assert(_manager == null);
base.dispose();
}
internal void _notify()
{
notifyListeners();
}
public override string ToString()
{
return string.Format("{0} hasFocus: {1}", Diagnostics.describeIdentity(this), hasFocus);
}
}
public class FocusScopeNode:DiagnosticableTree
{
internal FocusManager _manager;
internal FocusScopeNode _parent;
internal FocusScopeNode _nextSibling;
internal FocusScopeNode _previousSibling;
internal FocusScopeNode _firstChild;
internal FocusScopeNode _lastChild;
internal FocusNode _focus;
public bool isFirstFocus
{
get { return _parent == null || _parent._firstChild == this; }
}
internal void _prepend(FocusScopeNode child)
{
D.assert(child != this);
D.assert(child != _firstChild);
D.assert(child != _lastChild);
D.assert(child == null);
D.assert(child._manager == null);
D.assert(child._nextSibling == null);
D.assert(child._previousSibling == null);
D.assert(() =>
{
var node = this;
while (node._parent != null)
{
node = node._parent;
}
D.assert(node != child);
return true;
});
child._parent = this;
child._nextSibling = _firstChild;
if (_firstChild != null)
{
_firstChild._previousSibling = child;
}
_firstChild = child;
_lastChild = _lastChild ?? child;
child._updateManager(_manager);
}
void _updateManager(FocusManager manager)
{
Action<FocusScopeNode> update = null;
update = (child) =>
{
if (child._manager == manager)
return;
child._manager = manager;
// We don't proactively null out the manager for FocusNodes because the
// manager holds the currently active focus node until the end of the
// microtask, even if that node is detached from the focus tree.
if (manager != null && child._focus != null)
{
child._focus._manager = manager;
}
child._visitChildren(update);
};
update(this);
}
void _visitChildren(Action<FocusScopeNode> vistor) {
FocusScopeNode child = _firstChild;
while (child != null) {
vistor.Invoke(child);
child = child._nextSibling;
}
}
private bool _debugUltimatePreviousSiblingOf(FocusScopeNode child, FocusScopeNode equals)
{
while (child._previousSibling != null)
{
D.assert(child._previousSibling != child);
child = child._previousSibling;
}
return child == equals;
}
private bool _debugUltimateNextSiblingOf(FocusScopeNode child, FocusScopeNode equals)
{
while (child._nextSibling != null)
{
D.assert(child._nextSibling != child);
child = child._nextSibling;
}
return child == equals;
}
internal void _remove(FocusScopeNode child)
{
D.assert(child._parent == this);
D.assert(child._manager == _manager);
D.assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
D.assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
if (child._previousSibling == null)
{
D.assert(_firstChild == child);
_firstChild = child._nextSibling;
}
else
{
child._previousSibling._nextSibling = child._nextSibling;
}
if (child._nextSibling == null) {
D.assert(_lastChild == child);
_lastChild = child._previousSibling;
} else {
child._nextSibling._previousSibling = child._previousSibling;
}
child._previousSibling = null;
child._nextSibling = null;
child._parent = null;
child._updateManager(null);
}
internal void _didChangeFocusChain() {
if (isFirstFocus && _manager != null)
_manager._markNeedsUpdate();
}
public void requestFocus(FocusNode node)
{
D.assert(node != null);
if (_focus == node)
{
return;
}
if (_focus != null)
{
_focus.unfocus();
}
node._hasKeyboardToken = true;
_setFocus(node);
}
public void autofocus(FocusNode node) {
D.assert(node != null);
if (_focus == null)
{
node._hasKeyboardToken = true;
_setFocus(node);
}
}
public void reparentIfNeeded(FocusNode node) {
D.assert(node != null);
if (node._parent == null || node._parent == this)
return;
node.unfocus();
D.assert(node._parent == null);
if (_focus == null)
_setFocus(node);
}
internal void _setFocus(FocusNode node) {
D.assert(node != null);
D.assert(node._parent == null);
D.assert(_focus == null);
_focus = node;
_focus._parent = this;
_focus._manager = _manager;
_focus._hasKeyboardToken = true;
_didChangeFocusChain();
}
internal void _resignFocus(FocusNode node) {
D.assert(node != null);
if (_focus != node)
{
return;
}
_focus._parent = null;
_focus._manager = null;
_focus = null;
_didChangeFocusChain();
}
public void setFirstFocus(FocusScopeNode child)
{
D.assert(child != null);
D.assert(child._parent == null || child._parent == this);
if (_firstChild == child)
{
return;
}
child.detach();
_prepend(child);
D.assert(child._parent == this);
_didChangeFocusChain();
}
public void reparentScopeIfNeeded(FocusScopeNode child) {
D.assert(child != null);
if (child._parent == null || child._parent == this)
return;
if (child.isFirstFocus)
setFirstFocus(child);
else
child.detach();
}
public void detach() {
_didChangeFocusChain();
if (_parent != null)
{
_parent._remove(this);
}
D.assert(_parent == null);
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
if (_focus != null)
properties.add(new DiagnosticsProperty<FocusNode>("focus", _focus));
}
public override List<DiagnosticsNode> debugDescribeChildren() {
var children = new List<DiagnosticsNode>();
if (_firstChild != null) {
FocusScopeNode child = _firstChild;
int count = 1;
while (true) {
children.Add(child.toDiagnosticsNode(name: string.Format("child {0}", count)));
if (child == _lastChild)
break;
child = child._nextSibling;
count += 1;
}
}
return children;
}
}
public class FocusManager
{
public FocusManager()
{
rootScope._manager = this;
D.assert(rootScope._firstChild == null);
D.assert(rootScope._lastChild == null);
}
public readonly FocusScopeNode rootScope = new FocusScopeNode();
internal FocusNode _currentFocus;
internal void _willDisposeFocusNode(FocusNode node) {
D.assert(node != null);
if (_currentFocus == node)
_currentFocus = null;
}
bool _haveScheduledUpdate = false;
internal void _markNeedsUpdate() {
if (_haveScheduledUpdate)
{
return;
}
_haveScheduledUpdate = true;
Window.instance.scheduleMicrotask(_update);
}
internal FocusNode _findNextFocus() {
FocusScopeNode scope = rootScope;
while (scope._firstChild != null)
scope = scope._firstChild;
return scope._focus;
}
internal void _update()
{
_haveScheduledUpdate = false;
var nextFocus = _findNextFocus();
if (_currentFocus == nextFocus)
return;
var previousFocus = _currentFocus;
_currentFocus = nextFocus;
if (previousFocus != null)
{
previousFocus._notify();
}
if (_currentFocus != null)
{
_currentFocus._notify();
}
}
public override string ToString()
{
var status = _haveScheduledUpdate ? " UPDATE SCHEDULED" : "";
var indent = " ";
return string.Format("{1}{2}\n{0}currentFocus: {3}\n{4}", indent, Diagnostics.describeIdentity(this),
status, _currentFocus, rootScope.toStringDeep(prefixLineOne: indent, prefixOtherLines: indent));
}
}
}

4
Assets/UIWidgets/widgets/focus_manager.cs.meta


fileFormatVersion: 2
guid: 78895a8736b34e4085f9324116273d59
timeCreated: 1536819300
guid: f645f061c05e472285a90ddf48ebeeed
timeCreated: 1536895704

66
Assets/UIWidgets/Tests/EditableTextWiget.cs


using UIWidgets.editor;
using UIWidgets.painting;
using UIWidgets.widgets;
using UnityEditor;
using UnityEngine;
using Color = UIWidgets.ui.Color;
namespace UIWidgets.Tests
{
public class EditableTextWiget: EditorWindow
{
private WindowAdapter windowAdapter;
private PaintingBinding paintingBinding;
private Widget root;
private Widget image;
[MenuItem("UIWidgetsTests/EditableTextWiget")]
public static void renderWidgets() {
EditorWindow.GetWindow(typeof(EditableTextWiget));
}
EditableTextWiget() {
}
void OnGUI() {
if (this.windowAdapter != null) {
this.windowAdapter.OnGUI();
}
}
private void Update() {
if (this.windowAdapter != null) {
this.windowAdapter.Update();
}
}
private void OnEnable() {
this.paintingBinding = new PaintingBinding(null);
paintingBinding.initInstances();
this.windowAdapter = new WindowAdapter(this);
this.root = new widgets.Container(
width: 200,
height: 200,
margin: EdgeInsets.all(30.0),
padding: EdgeInsets.all(15.0),
color: ui.Color.fromARGB(255, 244, 190, 85),
child: new EditableText(
maxLines: 100,
controller: new TextEditingController("click to edit"),
focusNode: new FocusNode(),
style: new TextStyle(),
selectionColor: Color.fromARGB(255, 255, 0, 0),
cursorColor: Color.fromARGB(255, 0, 0, 0)
)
);
this.windowAdapter.attachRootWidget(root);
this.titleContent = new GUIContent("EditableTextWiget");
}
void OnDestroy() {
this.windowAdapter = null;
}
}
}

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


fileFormatVersion: 2
guid: 1438fd697c854ab783eb666a6a62115c
timeCreated: 1537236780

108
Assets/UIWidgets/service/text_formatter.cs


using System;
using System.Text.RegularExpressions;
using UIWidgets.foundation;
namespace UIWidgets.service
{
public abstract class TextInputFormatter
{
public delegate TextEditingValue TextInputFormatFunction(TextEditingValue oldValue,
TextEditingValue newValue);
public abstract TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue);
static TextInputFormatter withFunction(TextInputFormatFunction formatFunction)
{
return new _SimpleTextInputFormatter(formatFunction);
}
}
internal class _SimpleTextInputFormatter : TextInputFormatter
{
public readonly TextInputFormatFunction formatFunction;
internal _SimpleTextInputFormatter(TextInputFormatFunction formatFunction)
{
D.assert(formatFunction != null);
this.formatFunction = formatFunction;
}
public override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue)
{
return formatFunction(oldValue, newValue);
}
}
public class BlacklistingTextInputFormatter : TextInputFormatter
{
public readonly Regex blacklistedPattern;
public readonly string replacementString;
public static readonly BlacklistingTextInputFormatter singleLineFormatter
= new BlacklistingTextInputFormatter(new Regex(@"\n"));
public BlacklistingTextInputFormatter(Regex blacklistedPattern, string replacementString = "")
{
D.assert(blacklistedPattern != null);
this.blacklistedPattern = blacklistedPattern;
this.replacementString = replacementString;
}
public override TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue)
{
return Util._selectionAwareTextManipulation(newValue,
(substring) => blacklistedPattern.Replace(substring, replacementString));
}
}
internal static class Util
{
internal static TextEditingValue _selectionAwareTextManipulation(TextEditingValue value,
Func<string, string> substringManipulation)
{
int selectionStartIndex = value.selection.start;
int selectionEndIndex = value.selection.end;
string manipulatedText;
TextSelection manipulatedSelection = null;
if (selectionStartIndex < 0 || selectionEndIndex < 0)
{
manipulatedText = substringManipulation(value.text);
}
else
{
var beforeSelection = substringManipulation(
value.text.Substring(0, selectionStartIndex)
);
var inSelection = substringManipulation(
value.text.Substring(selectionStartIndex, selectionEndIndex - selectionStartIndex)
);
var afterSelection = substringManipulation(
value.text.Substring(selectionEndIndex)
);
manipulatedText = beforeSelection + inSelection + afterSelection;
if (value.selection.baseOffset > value.selection.extentOffset)
{
manipulatedSelection = value.selection.copyWith(
baseOffset: beforeSelection.Length + inSelection.Length,
extentOffset: beforeSelection.Length
);
}
else
{
manipulatedSelection = value.selection.copyWith(
baseOffset: beforeSelection.Length,
extentOffset: beforeSelection.Length + inSelection.Length
);
}
}
return new TextEditingValue(
text: manipulatedText,
selection: manipulatedSelection ?? TextSelection.collapsed(offset: -1),
composing: manipulatedText == value.text ? value.composing : TextRange.empty
);
}
}
}

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


fileFormatVersion: 2
guid: 6fbc4090466b444f8423c68d5928dfa7
timeCreated: 1537166345

544
Assets/UIWidgets/service/text_input.cs


using System;
using System.Collections.Generic;
using System.Text;
using UIWidgets.foundation;
using UnityEngine;
namespace UIWidgets.service
{
public class TextEditingValue:IEquatable<TextEditingValue>
{
public readonly string text;
public readonly TextSelection selection;
public readonly TextRange composing;
public TextEditingValue(string text = "", TextSelection selection = null, TextRange composing = null)
{
this.text = text;
this.selection = selection ?? TextSelection.collapsed(-1);
this.composing = composing ?? TextRange.empty;
}
public TextEditingValue copyWith(string text = null, TextSelection selection = null, TextRange composing = null)
{
return new TextEditingValue(
text??this.text, selection??this.selection, composing??this.composing
);
}
public TextEditingValue insert(string text)
{
string newText;
TextSelection newSelection;
if (string.IsNullOrEmpty(text))
{
return this;
}
newText = selection.textBefore(this.text) + text + selection.textAfter(this.text);
newSelection = TextSelection.collapsed(selection.start + text.Length);
return new TextEditingValue(
text: newText, selection: newSelection,composing:TextRange.empty
);
}
public TextEditingValue deleteSelection()
{
if (selection.isCollapsed)
{
if (selection.start == 0)
{
return this;
}
return this.copyWith(text: text.Substring(0, selection.start - 1) + selection.textAfter(this.text),
selection: TextSelection.collapsed(selection.start - 1));
}
else
{
var newText = selection.textBefore(this.text) + selection.textAfter(this.text);
return this.copyWith(text: newText, selection: TextSelection.collapsed(selection.start));
}
}
public TextEditingValue moveLeft()
{
return moveSelection(-1);
}
public TextEditingValue moveRight()
{
return moveSelection(1);
}
public TextEditingValue extendLeft()
{
return moveExtent(-1);
}
public TextEditingValue extendRight()
{
return moveExtent(1);
}
public TextEditingValue moveExtent(int move)
{
int offset = selection.extentOffset + move;
offset = Math.Max(0, offset);
offset = Math.Min(offset, text.Length);
return this.copyWith(selection: selection.copyWith(extentOffset: offset));
}
public TextEditingValue moveSelection(int move)
{
int offset = selection.baseOffset + move;
offset = Math.Max(0, offset);
offset = Math.Min(offset, text.Length);
return this.copyWith(selection: TextSelection.collapsed(offset));
}
public TextEditingValue compose(string composeText)
{
D.assert(!string.IsNullOrEmpty(composeText));
var composeStart = composing == TextRange.empty ? selection.start : composing.start;
var lastComposeEnd =composing == TextRange.empty ? selection.end : composing.end;
var newText = text.Substring(0, composeStart) + composeText + text.Substring(lastComposeEnd);
var componseEnd = composeStart + composeText.Length;
return new TextEditingValue(
text: newText, selection: TextSelection.collapsed(componseEnd),
composing: new TextRange(composeStart, componseEnd)
);
}
public TextEditingValue clearCompose()
{
if (composing == TextRange.empty)
{
return this;
}
return new TextEditingValue(
text: text.Substring(0, composing.start) + text.Substring(composing.end),
selection: TextSelection.collapsed(composing.start),
composing: TextRange.empty
);
}
public static readonly TextEditingValue empty = new TextEditingValue();
public bool Equals(TextEditingValue other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(text, other.text) && Equals(selection, other.selection) && Equals(composing, other.composing);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((TextEditingValue) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (text != null ? text.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (selection != null ? selection.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (composing != null ? composing.GetHashCode() : 0);
return hashCode;
}
}
public static bool operator ==(TextEditingValue left, TextEditingValue right)
{
return Equals(left, right);
}
public static bool operator !=(TextEditingValue left, TextEditingValue right)
{
return !Equals(left, right);
}
public override string ToString()
{
return string.Format("Text: {0}, Selection: {1}, Composing: {2}", text, selection, composing);
}
}
public interface TextInputClient
{
void updateEditingValue(TextEditingValue value);
// void performAction(TextInputAction action);
}
public enum TextInputAction {
done,
newline,
}
public class TextInputConnection
{
internal TextInputConnection(TextInputClient client, TextInput textInput)
{
D.assert(client != null);
D.assert(textInput != null);
_client = client;
_textInput = textInput;
_id = _nextId++;
}
public bool attached
{
get { return _textInput._currentConnection == this; }
}
public void setEditingState(TextEditingValue value)
{
D.assert(attached);
_textInput._value = value;
}
public void setCompositionCursorPos(double x, double y)
{
D.assert(attached);
_textInput.setCompositionCursorPos(x, y);
}
public void close()
{
if (attached)
{
_textInput._currentConnection = null;
_textInput._value = null;
Input.imeCompositionMode = IMECompositionMode.Auto;
}
D.assert(!attached);
}
private static int _nextId = 1;
internal readonly int _id;
internal readonly TextInputClient _client;
internal readonly TextInput _textInput;
}
public class TextInput
{
internal TextInputConnection _currentConnection;
internal TextEditingValue _value;
static Dictionary<Event, TextEditOp> s_Keyactions;
private string _lastCompositionString;
public TextInputConnection attach(TextInputClient client)
{
D.assert(client != null);
var connection = new TextInputConnection(client, this);
_currentConnection = connection;
Input.imeCompositionMode = IMECompositionMode.On;
return connection;
}
public void OnGUI()
{
if (_currentConnection == null)
{
return;
}
var currentEvent = Event.current;
if (currentEvent.type == EventType.KeyDown)
{
bool handled = handleKeyEvent(currentEvent);
if (!handled)
{
if (currentEvent.keyCode == KeyCode.None)
{
_value = _value.clearCompose().insert(new string(currentEvent.character, 1));
_currentConnection._client.updateEditingValue(_value);
}
}
currentEvent.Use();
}
if (!string.IsNullOrEmpty(Input.compositionString) && _lastCompositionString != Input.compositionString)
{
_value = _value.compose(Input.compositionString);
_currentConnection._client.updateEditingValue(_value);
}
_lastCompositionString = Input.compositionString;
}
public void setCompositionCursorPos(double x, double y)
{
Input.compositionCursorPos = new Vector2((float)x, (float)y);
}
private bool handleKeyEvent(Event e)
{
initKeyActions();
EventModifiers m = e.modifiers;
e.modifiers &= ~EventModifiers.CapsLock;
if (s_Keyactions.ContainsKey(e))
{
TextEditOp op = s_Keyactions[e];
var newValue = performOperation(op);
if (_value != newValue)
{
_value = newValue;
_currentConnection._client.updateEditingValue(_value);
}
e.modifiers = m;
return true;
}
e.modifiers = m;
return false;
}
TextEditingValue performOperation(TextEditOp operation)
{
switch (operation)
{
case TextEditOp.MoveLeft:
return _value.moveLeft();
case TextEditOp.MoveRight:
return _value.moveRight();
// case TextEditOp.MoveUp: MoveUp(); break;
// case TextEditOp.MoveDown: MoveDown(); break;
// case TextEditOp.MoveLineStart: MoveLineStart(); break;
// case TextEditOp.MoveLineEnd: MoveLineEnd(); break;
// case TextEditOp.MoveWordRight: MoveWordRight(); break;
// case TextEditOp.MoveToStartOfNextWord: MoveToStartOfNextWord(); break;
// case TextEditOp.MoveToEndOfPreviousWord: MoveToEndOfPreviousWord(); break;
// case TextEditOp.MoveWordLeft: MoveWordLeft(); break;
// case TextEditOp.MoveTextStart: MoveTextStart(); break;
// case TextEditOp.MoveTextEnd: MoveTextEnd(); break;
// case TextEditOp.MoveParagraphForward: MoveParagraphForward(); break;
// case TextEditOp.MoveParagraphBackward: MoveParagraphBackward(); break;
// case TextEditOp.MoveGraphicalLineStart: MoveGraphicalLineStart(); break;
// case TextEditOp.MoveGraphicalLineEnd: MoveGraphicalLineEnd(); break;
case TextEditOp.SelectLeft:
return _value.extendLeft();
case TextEditOp.SelectRight:
return _value.extendRight();
// case TextEditOp.SelectUp: SelectUp(); break;
// case TextEditOp.SelectDown: SelectDown(); break;
// case TextEditOp.SelectWordRight: SelectWordRight(); break;
// case TextEditOp.SelectWordLeft: SelectWordLeft(); break;
// case TextEditOp.SelectToEndOfPreviousWord: SelectToEndOfPreviousWord(); break;
// case TextEditOp.SelectToStartOfNextWord: SelectToStartOfNextWord(); break;
//
// case TextEditOp.SelectTextStart: SelectTextStart(); break;
// case TextEditOp.SelectTextEnd: SelectTextEnd(); break;
// case TextEditOp.ExpandSelectGraphicalLineStart: ExpandSelectGraphicalLineStart(); break;
// case TextEditOp.ExpandSelectGraphicalLineEnd: ExpandSelectGraphicalLineEnd(); break;
// case TextEditOp.SelectParagraphForward: SelectParagraphForward(); break;
// case TextEditOp.SelectParagraphBackward: SelectParagraphBackward(); break;
// case TextEditOp.SelectGraphicalLineStart: SelectGraphicalLineStart(); break;
// case TextEditOp.SelectGraphicalLineEnd: SelectGraphicalLineEnd(); break;
// case TextEditOp.Delete: return Delete();
case TextEditOp.Backspace:
return _value.deleteSelection();
// _value.composing
// _value = _value.
// case TextEditOp.Cut: return Cut();
// case TextEditOp.Copy: Copy(); break;
// case TextEditOp.Paste: return Paste();
// case TextEditOp.SelectAll: SelectAll(); break;
// case TextEditOp.SelectNone: SelectNone(); break;
// case TextEditOp.DeleteWordBack: return DeleteWordBack(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
// case TextEditOp.DeleteLineBack: return DeleteLineBack();
// case TextEditOp.DeleteWordForward: return DeleteWordForward(); // break; // The uncoditional return makes the "break;" issue a warning about unreachable code
default:
Debug.Log("Unimplemented: " + operation);
break;
}
return _value;
}
static void initKeyActions()
{
if (s_Keyactions != null)
return;
s_Keyactions = new Dictionary<Event, TextEditOp>();
// key mappings shared by the platforms
mapKey("left", TextEditOp.MoveLeft);
mapKey("right", TextEditOp.MoveRight);
mapKey("up", TextEditOp.MoveUp);
mapKey("down", TextEditOp.MoveDown);
mapKey("#left", TextEditOp.SelectLeft);
mapKey("#right", TextEditOp.SelectRight);
mapKey("#up", TextEditOp.SelectUp);
mapKey("#down", TextEditOp.SelectDown);
mapKey("delete", TextEditOp.Delete);
mapKey("backspace", TextEditOp.Backspace);
mapKey("#backspace", TextEditOp.Backspace);
// OSX is the special case for input shortcuts
if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
{
// Keyboard mappings for mac
// TODO mapKey ("home", TextEditOp.ScrollStart);
// TODO mapKey ("end", TextEditOp.ScrollEnd);
// TODO mapKey ("page up", TextEditOp.ScrollPageUp);
// TODO mapKey ("page down", TextEditOp.ScrollPageDown);
mapKey("^left", TextEditOp.MoveGraphicalLineStart);
mapKey("^right", TextEditOp.MoveGraphicalLineEnd);
// TODO mapKey ("^up", TextEditOp.ScrollPageUp);
// TODO mapKey ("^down", TextEditOp.ScrollPageDown);
mapKey("&left", TextEditOp.MoveWordLeft);
mapKey("&right", TextEditOp.MoveWordRight);
mapKey("&up", TextEditOp.MoveParagraphBackward);
mapKey("&down", TextEditOp.MoveParagraphForward);
mapKey("%left", TextEditOp.MoveGraphicalLineStart);
mapKey("%right", TextEditOp.MoveGraphicalLineEnd);
mapKey("%up", TextEditOp.MoveTextStart);
mapKey("%down", TextEditOp.MoveTextEnd);
mapKey("#home", TextEditOp.SelectTextStart);
mapKey("#end", TextEditOp.SelectTextEnd);
// TODO mapKey ("#page up", TextEditOp.SelectPageUp);
// TODO mapKey ("#page down", TextEditOp.SelectPageDown);
mapKey("#^left", TextEditOp.ExpandSelectGraphicalLineStart);
mapKey("#^right", TextEditOp.ExpandSelectGraphicalLineEnd);
mapKey("#^up", TextEditOp.SelectParagraphBackward);
mapKey("#^down", TextEditOp.SelectParagraphForward);
mapKey("#&left", TextEditOp.SelectWordLeft);
mapKey("#&right", TextEditOp.SelectWordRight);
mapKey("#&up", TextEditOp.SelectParagraphBackward);
mapKey("#&down", TextEditOp.SelectParagraphForward);
mapKey("#%left", TextEditOp.ExpandSelectGraphicalLineStart);
mapKey("#%right", TextEditOp.ExpandSelectGraphicalLineEnd);
mapKey("#%up", TextEditOp.SelectTextStart);
mapKey("#%down", TextEditOp.SelectTextEnd);
mapKey("%a", TextEditOp.SelectAll);
mapKey("%x", TextEditOp.Cut);
mapKey("%c", TextEditOp.Copy);
mapKey("%v", TextEditOp.Paste);
// emacs-like keybindings
mapKey("^d", TextEditOp.Delete);
mapKey("^h", TextEditOp.Backspace);
mapKey("^b", TextEditOp.MoveLeft);
mapKey("^f", TextEditOp.MoveRight);
mapKey("^a", TextEditOp.MoveLineStart);
mapKey("^e", TextEditOp.MoveLineEnd);
mapKey("&delete", TextEditOp.DeleteWordForward);
mapKey("&backspace", TextEditOp.DeleteWordBack);
mapKey("%backspace", TextEditOp.DeleteLineBack);
}
else
{
// Windows/Linux keymappings
mapKey("home", TextEditOp.MoveGraphicalLineStart);
mapKey("end", TextEditOp.MoveGraphicalLineEnd);
// TODO mapKey ("page up", TextEditOp.MovePageUp);
// TODO mapKey ("page down", TextEditOp.MovePageDown);
mapKey("%left", TextEditOp.MoveWordLeft);
mapKey("%right", TextEditOp.MoveWordRight);
mapKey("%up", TextEditOp.MoveParagraphBackward);
mapKey("%down", TextEditOp.MoveParagraphForward);
mapKey("^left", TextEditOp.MoveToEndOfPreviousWord);
mapKey("^right", TextEditOp.MoveToStartOfNextWord);
mapKey("^up", TextEditOp.MoveParagraphBackward);
mapKey("^down", TextEditOp.MoveParagraphForward);
mapKey("#^left", TextEditOp.SelectToEndOfPreviousWord);
mapKey("#^right", TextEditOp.SelectToStartOfNextWord);
mapKey("#^up", TextEditOp.SelectParagraphBackward);
mapKey("#^down", TextEditOp.SelectParagraphForward);
mapKey("#home", TextEditOp.SelectGraphicalLineStart);
mapKey("#end", TextEditOp.SelectGraphicalLineEnd);
// TODO mapKey ("#page up", TextEditOp.SelectPageUp);
// TODO mapKey ("#page down", TextEditOp.SelectPageDown);
mapKey("^delete", TextEditOp.DeleteWordForward);
mapKey("^backspace", TextEditOp.DeleteWordBack);
mapKey("%backspace", TextEditOp.DeleteLineBack);
mapKey("^a", TextEditOp.SelectAll);
mapKey("^x", TextEditOp.Cut);
mapKey("^c", TextEditOp.Copy);
mapKey("^v", TextEditOp.Paste);
mapKey("#delete", TextEditOp.Cut);
mapKey("^insert", TextEditOp.Copy);
mapKey("#insert", TextEditOp.Paste);
}
}
static void mapKey(string key, TextEditOp action)
{
s_Keyactions[Event.KeyboardEvent(key)] = action;
}
}
internal enum TextEditOp
{
MoveLeft,
MoveRight,
MoveUp,
MoveDown,
MoveLineStart,
MoveLineEnd,
MoveTextStart,
MoveTextEnd,
MovePageUp,
MovePageDown,
MoveGraphicalLineStart,
MoveGraphicalLineEnd,
MoveWordLeft,
MoveWordRight,
MoveParagraphForward,
MoveParagraphBackward,
MoveToStartOfNextWord,
MoveToEndOfPreviousWord,
SelectLeft,
SelectRight,
SelectUp,
SelectDown,
SelectTextStart,
SelectTextEnd,
SelectPageUp,
SelectPageDown,
ExpandSelectGraphicalLineStart,
ExpandSelectGraphicalLineEnd,
SelectGraphicalLineStart,
SelectGraphicalLineEnd,
SelectWordLeft,
SelectWordRight,
SelectToEndOfPreviousWord,
SelectToStartOfNextWord,
SelectParagraphBackward,
SelectParagraphForward,
Delete,
Backspace,
DeleteWordBack,
DeleteWordForward,
DeleteLineBack,
Cut,
Copy,
Paste,
SelectAll,
SelectNone,
ScrollStart,
ScrollEnd,
ScrollPageUp,
ScrollPageDown,
}
}

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


fileFormatVersion: 2
guid: 74a6b923efea4cc6bc243bd2ee179330
timeCreated: 1537164543

589
Assets/UIWidgets/widgets/editable_text.cs


using System;
using System.Collections.Generic;
using RSG;
using UIWidgets.animation;
using UIWidgets.foundation;
using UIWidgets.gestures;
using UIWidgets.painting;
using UIWidgets.rendering;
using UIWidgets.service;
using UIWidgets.ui;
using UnityEngine;
using Color = UIWidgets.ui.Color;
using Rect = UIWidgets.ui.Rect;
using TextStyle = UIWidgets.painting.TextStyle;
namespace UIWidgets.widgets
{
public delegate void SelectionChangedCallback(TextSelection selection, SelectionChangedCause cause);
public class TextEditingController : ValueNotifier<TextEditingValue>
{
public TextEditingController(string text) : base(text == null
? TextEditingValue.empty
: new TextEditingValue(text))
{
}
private TextEditingController(TextEditingValue value) : base(value ?? TextEditingValue.empty)
{
}
public TextEditingController fromValue(TextEditingValue value)
{
return new TextEditingController(value);
}
public string text
{
get { return value.text; }
set
{
this.value = this.value.copyWith(text: value, selection: TextSelection.collapsed(-1),
composing: TextRange.empty);
}
}
public TextSelection selection
{
get { return value.selection; }
set
{
if (value.start > text.Length || value.end > text.Length)
{
throw new UIWidgetsError(string.Format("invalid text selection: {0}", value));
}
this.value = this.value.copyWith(selection: value, composing: TextRange.empty);
}
}
public void clear()
{
value = TextEditingValue.empty;
}
public void clearComposing()
{
value = value.copyWith(composing: TextRange.empty);
}
}
public class EditableText : StatefulWidget
{
public readonly TextEditingController controller;
public readonly FocusNode focusNode;
public readonly bool obscureText;
public readonly bool autocorrect;
public readonly TextStyle style;
public readonly TextAlign textAlign;
public readonly TextDirection? textDirection;
public readonly double textScaleFactor;
public readonly Color cursorColor;
public readonly int maxLines;
public readonly bool autofocus;
public readonly Color selectionColor;
public readonly ValueChanged<string> onChanged;
public readonly ValueChanged<string> onSubmitted;
public readonly SelectionChangedCallback onSelectionChanged;
public readonly List<TextInputFormatter> inputFormatters;
public readonly bool rendererIgnoresPointer;
public EditableText(TextEditingController controller, FocusNode focusNode, TextStyle style,
Color cursorColor, bool obscureText = false, bool autocorrect = false,
TextAlign textAlign = TextAlign.left, TextDirection? textDirection = null,
double textScaleFactor = 1.0, int maxLines = 1,
bool autofocus = false, Color selectionColor = null, ValueChanged<string> onChanged = null,
ValueChanged<string> onSubmitted = null, SelectionChangedCallback onSelectionChanged = null,
List<TextInputFormatter> inputFormatters = null, bool rendererIgnoresPointer = false,
Key key = null) : base(key)
{
D.assert(controller != null);
D.assert(focusNode != null);
D.assert(style != null);
D.assert(cursorColor != null);
this.controller = controller;
this.focusNode = focusNode;
this.obscureText = obscureText;
this.autocorrect = autocorrect;
this.style = style;
this.textAlign = textAlign;
this.textDirection = textDirection;
this.textScaleFactor = textScaleFactor;
this.cursorColor = cursorColor;
this.maxLines = maxLines;
this.autofocus = autofocus;
this.selectionColor = selectionColor;
this.onChanged = onChanged;
this.onSubmitted = onSubmitted;
this.onSelectionChanged = onSelectionChanged;
this.rendererIgnoresPointer = rendererIgnoresPointer;
if (maxLines == 1)
{
this.inputFormatters = new List<TextInputFormatter>();
this.inputFormatters.Add(BlacklistingTextInputFormatter.singleLineFormatter);
if (inputFormatters != null)
{
this.inputFormatters.AddRange(inputFormatters);
}
}
else
{
this.inputFormatters = inputFormatters;
}
}
public override State createState()
{
return new EditableTextState();
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<TextEditingController>("controller", controller));
properties.add(new DiagnosticsProperty<FocusNode>("focusNode", focusNode));
properties.add(new DiagnosticsProperty<bool>("obscureText", obscureText, defaultValue: false));
properties.add(new DiagnosticsProperty<bool>("autocorrect", autocorrect, defaultValue: true));
if (style != null)
{
style.debugFillProperties(properties);
}
properties.add(new EnumProperty<TextAlign>("textAlign", textAlign, defaultValue: null));
properties.add(new EnumProperty<TextDirection?>("textDirection", textDirection, defaultValue: null));
properties.add(new DiagnosticsProperty<double>("textScaleFactor", textScaleFactor, defaultValue: null));
properties.add(new DiagnosticsProperty<int>("maxLines", maxLines, defaultValue: 1));
properties.add(new DiagnosticsProperty<bool>("autofocus", autofocus, defaultValue: false));
}
}
public class EditableTextState: State<EditableText>, TextInputClient
{
const int _kObscureShowLatestCharCursorTicks = 3;
private ValueNotifier<bool> _showCursor = new ValueNotifier<bool>(true); // todo
private GlobalKey _editableKey = GlobalKey.key();
private bool _didAutoFocus = false;
TextInputConnection _textInputConnection;
private int _obscureShowCharTicksPending = 0;
private int _obscureLatestCharIndex;
bool _textChangedSinceLastCaretUpdate = false;
public override void initState()
{
base.initState();
widget.controller.addListener(_didChangeTextEditingValue);
widget.focusNode.addListener(_handleFocusChanged);
}
public override void didChangeDependencies()
{
base.didChangeDependencies();
if (!_didAutoFocus && widget.autofocus)
{
FocusScope.of(context).autofocus(widget.focusNode);
_didAutoFocus = true;
}
}
public override void didUpdateWidget(StatefulWidget old)
{
EditableText oldWidget = (EditableText) old;
base.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller)
{
oldWidget.controller.removeListener(_didChangeTextEditingValue);
widget.controller.addListener(_didChangeTextEditingValue);
_updateRemoteEditingValueIfNeeded();
}
if (widget.focusNode != oldWidget.focusNode) {
oldWidget.focusNode.removeListener(_handleFocusChanged);
widget.focusNode.addListener(_handleFocusChanged);
}
}
public override void dispose()
{
widget.controller.removeListener(_didChangeTextEditingValue);
_closeInputConnectionIfNeeded();
D.assert(!_hasInputConnection);
widget.focusNode.removeListener(_handleFocusChanged);
base.dispose();
}
TextEditingValue _lastKnownRemoteTextEditingValue;
public void updateEditingValue(TextEditingValue value) {
if (value.text != _value.text) {
// _hideSelectionOverlayIfNeeded();
if (widget.obscureText && value.text.Length == _value.text.Length + 1) {
_obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks;
_obscureLatestCharIndex = _value.selection.baseOffset;
}
}
_lastKnownRemoteTextEditingValue = value;
_formatAndSetValue(value);
}
void _updateRemoteEditingValueIfNeeded()
{
if (!_hasInputConnection)
return;
var localValue = _value;
if (localValue == _lastKnownRemoteTextEditingValue)
return;
_lastKnownRemoteTextEditingValue = localValue;
_textInputConnection.setEditingState(localValue);
}
bool _hasInputConnection
{
get
{
return _textInputConnection != null && _textInputConnection.attached;
}
}
void _openInputConnection() {
if (!_hasInputConnection) {
TextEditingValue localValue = _value;
_lastKnownRemoteTextEditingValue = localValue;
_textInputConnection = Window.instance.textInput.attach(this);
_textInputConnection.setEditingState(localValue);
}
}
void _closeInputConnectionIfNeeded() {
if (_hasInputConnection) {
_textInputConnection.close();
_textInputConnection = null;
_lastKnownRemoteTextEditingValue = null;
}
}
void _openOrCloseInputConnectionIfNeeded() {
if (_hasFocus && widget.focusNode.consumeKeyboardToken()) {
_openInputConnection();
} else if (!_hasFocus) {
_closeInputConnectionIfNeeded();
widget.controller.clearComposing();
}
}
public void requestKeyboard()
{
if (_hasFocus)
{
_openInputConnection();
}
else
{
FocusScope.of(context).requestFocus(widget.focusNode);
}
}
private void _handleSelectionChanged(TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
widget.controller.selection = selection;
requestKeyboard();
if (widget.onSelectionChanged != null)
{
widget.onSelectionChanged(selection, cause);
}
}
void _handleCaretChanged(Rect caretRect) {
if (_textChangedSinceLastCaretUpdate) {
_textChangedSinceLastCaretUpdate = false;
// scheduleMicrotask(() { // todo
// _scrollController.animateTo(
// _getScrollOffsetForCaret(caretRect),
// curve: Curves.fastOutSlowIn,
// duration: const Duration(milliseconds: 50),
// );
// });
}
}
private void _formatAndSetValue(TextEditingValue value) {
var textChanged = (_value == null ? null : _value.text) != (value == null ? null : value.text);
if (widget.inputFormatters != null && widget.inputFormatters.isNotEmpty()) {
foreach (var formatter in widget.inputFormatters)
{
value = formatter.formatEditUpdate(_value, value);
}
_value = value;
_updateRemoteEditingValueIfNeeded();
} else {
_value = value;
}
if (textChanged && widget.onChanged != null)
{
widget.onChanged(value.text);
}
}
private TextEditingValue _value
{
get { return widget.controller.value; }
set
{
widget.controller.value = value;
}
}
private bool _hasFocus
{
get { return widget.focusNode.hasFocus; }
}
private bool _isMultiline
{
get { return widget.maxLines != 1; }
}
private void _didChangeTextEditingValue()
{
_updateRemoteEditingValueIfNeeded();
_textChangedSinceLastCaretUpdate = true;
setState(() => {});
}
private void _handleFocusChanged()
{
_openOrCloseInputConnectionIfNeeded();
if (!_hasFocus) {
_value = new TextEditingValue(text: _value.text);
} else if (!_value.selection.isValid) {
widget.controller.selection = TextSelection.collapsed(offset: _value.text.Length);
}
}
private TextDirection? _textDirection
{
get
{
TextDirection? result = widget.textDirection ?? Directionality.of(context);
D.assert(result != null,
string.Format("{0} created without a textDirection and with no ambient Directionality.", GetType().FullName));
return result;
}
}
public RenderEditable renderEditable
{
get { return (RenderEditable)_editableKey.currentContext.findRenderObject(); }
}
public override Widget build(BuildContext context)
{
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
// todo base.build(context); See AutomaticKeepAliveClientMixin.
return new _Editable(
key: _editableKey,
textSpan: buildTextSpan(),
value: _value,
cursorColor: widget.cursorColor,
showCursor: _showCursor,
hasFocus: _hasFocus,
maxLines: widget.maxLines,
selectionColor: widget.selectionColor,
textScaleFactor: 1.0, // todo widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
textAlign: widget.textAlign,
textDirection: _textDirection,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
offset: new _FixedViewportOffset(0.0),
onSelectionChanged: _handleSelectionChanged,
onCaretChanged: _handleCaretChanged,
rendererIgnoresPointer: widget.rendererIgnoresPointer
);
}
public TextSpan buildTextSpan() {
if (!widget.obscureText && _value.composing.isValid) {
TextStyle composingStyle = widget.style.merge(
new TextStyle(decoration: TextDecoration.underline)
);
return new TextSpan(
style: widget.style,
children: new List<TextSpan>
{
new TextSpan(text: _value.composing.textBefore(_value.text)),
new TextSpan(
style: composingStyle,
text: _value.composing.textInside(_value.text)
),
new TextSpan(text: _value.composing.textAfter(_value.text)),
});
}
var text = _value.text;
if (widget.obscureText) {
text = new string(RenderEditable.obscuringCharacter, text.Length);
int o =
_obscureShowCharTicksPending > 0 ? _obscureLatestCharIndex : -1;
if (o >= 0 && o < text.Length)
text = text.Substring(0, o) + _value.text.Substring(o, 1) + text.Substring(o + 1);
}
return new TextSpan(style: widget.style, text: text);
}
}
internal class _Editable : LeafRenderObjectWidget
{
public readonly TextSpan textSpan;
public readonly TextEditingValue value;
public readonly Color cursorColor;
public readonly ValueNotifier<bool> showCursor;
public readonly bool hasFocus;
public readonly int maxLines;
public readonly Color selectionColor;
public readonly double textScaleFactor;
public readonly TextAlign textAlign;
public readonly TextDirection? textDirection;
public readonly bool obscureText;
public readonly bool autocorrect;
public readonly ViewportOffset offset;
public readonly SelectionChangedHandler onSelectionChanged;
public readonly CaretChangedHandler onCaretChanged;
public readonly bool rendererIgnoresPointer;
public _Editable(TextSpan textSpan = null, TextEditingValue value = null,
Color cursorColor = null, ValueNotifier<bool> showCursor = null, bool hasFocus = false,
int maxLines = 0, Color selectionColor = null, double textScaleFactor = 1.0,
TextDirection? textDirection = null, bool obscureText = false, TextAlign textAlign = TextAlign.left,
bool autocorrect = false, ViewportOffset offset = null, SelectionChangedHandler onSelectionChanged = null,
CaretChangedHandler onCaretChanged = null, bool rendererIgnoresPointer = false, Key key = null) : base(key)
{
this.textSpan = textSpan;
this.value = value;
this.cursorColor = cursorColor;
this.showCursor = showCursor;
this.hasFocus = hasFocus;
this.maxLines = maxLines;
this.selectionColor = selectionColor;
this.textScaleFactor = textScaleFactor;
this.textAlign = textAlign;
this.textDirection = textDirection;
this.obscureText = obscureText;
this.autocorrect = autocorrect;
this.offset = offset;
this.onSelectionChanged = onSelectionChanged;
this.onCaretChanged = onCaretChanged;
this.rendererIgnoresPointer = rendererIgnoresPointer;
}
public override RenderObject createRenderObject(BuildContext context)
{
return new RenderEditable(
text: textSpan,
textDirection: textDirection??TextDirection.ltr,
offset: offset,
showCursor: showCursor,
cursorColor: cursorColor,
hasFocus: hasFocus,
maxLines: maxLines,
selectionColor: selectionColor,
textScaleFactor: textScaleFactor,
textAlign: textAlign,
selection: value.selection,
obscureText: obscureText,
onSelectionChanged: onSelectionChanged,
onCaretChanged: onCaretChanged,
ignorePointer: rendererIgnoresPointer
);
}
public override void updateRenderObject(BuildContext context, RenderObject renderObject)
{
var edit = (RenderEditable) renderObject;
edit.text = textSpan;
edit.cursorColor = cursorColor;
edit.showCursor = showCursor;
edit.hasFocus = hasFocus;
edit.maxLines = maxLines;
edit.selectionColor = selectionColor;
edit.textScaleFactor = textScaleFactor;
edit.textAlign = textAlign;
edit.textDirection = textDirection;
edit.selection = value.selection;
edit.offset = offset;
edit.onSelectionChanged = onSelectionChanged;
edit.onCaretChanged = onCaretChanged;
edit.ignorePointer = rendererIgnoresPointer;
edit.obscureText = obscureText;
}
}
class _FixedViewportOffset : ViewportOffset {
internal _FixedViewportOffset(double _pixels) {
this._pixels = _pixels;
}
internal new static _FixedViewportOffset zero() {
return new _FixedViewportOffset(0.0);
}
double _pixels;
public override double pixels {
get { return this._pixels; }
}
public override bool applyViewportDimension(double viewportDimension) {
return true;
}
public override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
return true;
}
public override void correctBy(double correction) {
this._pixels += correction;
}
public override void jumpTo(double pixels) {
}
public override IPromise animateTo(double to, TimeSpan duration, Curve curve) {
return Promise.Resolved();
}
public override ScrollDirection userScrollDirection {
get { return ScrollDirection.idle; }
}
public override bool allowImplicitScrolling {
get { return false; }
}
}
}

3
Assets/UIWidgets/widgets/editable_text.cs.meta


fileFormatVersion: 2
guid: b400bbae381e45f8b7242bb9e881bcd5
timeCreated: 1537163865

77
Assets/UIWidgets/widgets/focus_scope.cs


using UIWidgets.foundation;
namespace UIWidgets.widgets
{
class _FocusScopeMarker: InheritedWidget {
public _FocusScopeMarker( FocusScopeNode node, Widget child, Key key = null) : base(key, child)
{
D.assert(node != null);
this.node = node;
}
public readonly FocusScopeNode node;
public override bool updateShouldNotify(InheritedWidget oldWidget)
{
return node != ((_FocusScopeMarker)oldWidget).node;
}
}
public class FocusScope : StatefulWidget
{
public FocusScope(FocusScopeNode node, Widget child, Key key = null, bool autofocus = false) : base(key)
{
this.node = node;
this.child = child;
this.autofocus = autofocus;
}
public readonly FocusScopeNode node;
public readonly bool autofocus;
public readonly Widget child;
public static FocusScopeNode of(BuildContext context) {
var scope = (_FocusScopeMarker)context.inheritFromWidgetOfExactType(typeof(_FocusScopeMarker));
if (scope != null && scope.node != null)
{
return scope.node;
}
return context.owner.focusManager.rootScope;
}
public override State createState()
{
return new _FocusScopeState();
}
}
class _FocusScopeState : State<FocusScope>
{
private bool _didAutofocus = false;
public override void didChangeDependencies()
{
base.didChangeDependencies();
if (!_didAutofocus && widget.autofocus)
{
FocusScope.of(context).setFirstFocus(widget.node);
_didAutofocus = true;
}
}
public override void dispose() {
widget.node.detach();
base.dispose();
}
public override Widget build(BuildContext context)
{
FocusScope.of(context).reparentScopeIfNeeded(widget.node);
return new _FocusScopeMarker(node:widget.node, child:widget.child);
}
}
}

3
Assets/UIWidgets/widgets/focus_scope.cs.meta


fileFormatVersion: 2
guid: c5976c3bfa824d30a0fba367accca614
timeCreated: 1536895629
正在加载...
取消
保存