GitHub
6 年前
当前提交
a75d1ec6
共有 23 个文件被更改,包括 1673 次插入 和 9 次删除
-
5Assets/UIWidgets/Tests/Menu.cs
-
107Assets/UIWidgets/painting/text_painter.cs
-
13Assets/UIWidgets/painting/text_span.cs
-
2Assets/UIWidgets/rendering/box.cs
-
14Assets/UIWidgets/rendering/box.mixin.gen.cs
-
5Assets/UIWidgets/rendering/flex.cs
-
9Assets/UIWidgets/rendering/proxy_box.mixin.gen.cs
-
9Assets/UIWidgets/rendering/proxy_box.mixin.njk
-
16Assets/UIWidgets/rendering/shifted_box.cs
-
85Assets/UIWidgets/ui/text.cs
-
232Assets/UIWidgets/ui/txt/paragraph.cs
-
203Assets/UIWidgets/Tests/RenderEditable.cs
-
3Assets/UIWidgets/Tests/RenderEditable.cs.meta
-
703Assets/UIWidgets/rendering/editable.cs
-
3Assets/UIWidgets/rendering/editable.cs.meta
-
3Assets/UIWidgets/service.meta
-
70Assets/UIWidgets/ui/txt/word_separate.cs
-
3Assets/UIWidgets/ui/txt/word_separate.cs.meta
-
191Assets/UIWidgets/service/text_editing.cs
-
3Assets/UIWidgets/service/text_editing.cs.meta
-
3Assets/UIWidgets/math.meta
|
|||
using UnityEditor; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using RSG; |
|||
using UIWidgets.animation; |
|||
using UIWidgets.editor; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.painting; |
|||
using UIWidgets.rendering; |
|||
using UIWidgets.service; |
|||
using UIWidgets.ui; |
|||
using UnityEditor; |
|||
using UnityEngine; |
|||
using Color = UIWidgets.ui.Color; |
|||
using FontStyle = UIWidgets.ui.FontStyle; |
|||
|
|||
namespace UIWidgets.Tests |
|||
{ |
|||
public class RenderEditable: EditorWindow |
|||
{ |
|||
private readonly Func<RenderBox>[] _options; |
|||
|
|||
private readonly string[] _optionStrings; |
|||
|
|||
private int _selected; |
|||
|
|||
class _FixedViewportOffset : ViewportOffset { |
|||
internal _FixedViewportOffset(double _pixels) { |
|||
this._pixels = _pixels; |
|||
} |
|||
|
|||
internal new static _FixedViewportOffset zero() { |
|||
return new _FixedViewportOffset(0.0); |
|||
} |
|||
|
|||
double _pixels; |
|||
|
|||
public override double pixels { |
|||
get { return this._pixels; } |
|||
} |
|||
|
|||
public override bool applyViewportDimension(double viewportDimension) { |
|||
return true; |
|||
} |
|||
|
|||
public override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { |
|||
return true; |
|||
} |
|||
|
|||
public override void correctBy(double correction) { |
|||
this._pixels += correction; |
|||
} |
|||
|
|||
public override void jumpTo(double pixels) { |
|||
} |
|||
|
|||
public override IPromise<object> animateTo(double to, TimeSpan duration, Curve curve) { |
|||
return Promise<object>.Resolved(null); |
|||
} |
|||
|
|||
public override ScrollDirection userScrollDirection { |
|||
get { return ScrollDirection.idle; } |
|||
} |
|||
|
|||
public override bool allowImplicitScrolling { |
|||
get { return false; } |
|||
} |
|||
} |
|||
|
|||
RenderEditable() { |
|||
this._options = new Func<RenderBox>[] { |
|||
this.textEditable, |
|||
}; |
|||
this._optionStrings = this._options.Select(x => x.Method.Name).ToArray(); |
|||
this._selected = 0; |
|||
this.titleContent = new GUIContent("RenderEditable"); |
|||
} |
|||
|
|||
private WindowAdapter windowAdapter; |
|||
|
|||
private RendererBindings rendererBindings; |
|||
|
|||
[NonSerialized] private bool hasInvoked = false; |
|||
|
|||
void OnGUI() { |
|||
var selected = EditorGUILayout.Popup("test case", this._selected, this._optionStrings); |
|||
if (selected != this._selected || !this.hasInvoked) { |
|||
this._selected = selected; |
|||
this.hasInvoked = true; |
|||
|
|||
var renderBox = this._options[this._selected](); |
|||
this.rendererBindings.setRoot(renderBox); |
|||
} |
|||
|
|||
if (this.windowAdapter != null) { |
|||
this.windowAdapter.OnGUI(); |
|||
} |
|||
} |
|||
|
|||
void Update() { |
|||
if (this.windowAdapter != null) { |
|||
this.windowAdapter.Update(); |
|||
} |
|||
} |
|||
|
|||
private void OnEnable() { |
|||
this.windowAdapter = new WindowAdapter(this); |
|||
this.rendererBindings = new RendererBindings(this.windowAdapter); |
|||
} |
|||
|
|||
void OnDestroy() { |
|||
this.windowAdapter = null; |
|||
this.rendererBindings = null; |
|||
} |
|||
|
|||
private RenderBox box(RenderBox p, int width = 400, int height = 400) |
|||
{ |
|||
return new RenderConstrainedOverflowBox( |
|||
minWidth: width, |
|||
maxWidth: width, |
|||
minHeight: height, |
|||
maxHeight: height, |
|||
alignment: Alignment.center, |
|||
child: p |
|||
) |
|||
; |
|||
} |
|||
|
|||
private RenderBox flexItemBox(RenderBox p, int width = 200, int height = 100) |
|||
{ |
|||
return new RenderConstrainedBox( |
|||
additionalConstraints: new BoxConstraints(minWidth: width, maxWidth: width, minHeight: height, |
|||
maxHeight: height), |
|||
child: new RenderDecoratedBox( |
|||
decoration: new BoxDecoration( |
|||
color: new Color(0xFFFFFFFF), |
|||
borderRadius: BorderRadius.all(3), |
|||
border: Border.all(Color.fromARGB(255, 255, 0, 0), 1) |
|||
), |
|||
child: new RenderPadding(EdgeInsets.all(10), p |
|||
) |
|||
)); |
|||
} |
|||
|
|||
RenderBox textEditable() |
|||
{ |
|||
var span = new TextSpan("", children: |
|||
new List<TextSpan> |
|||
{ |
|||
new TextSpan("Word Wrap:The ascent of the font is the distance from the baseline to the top line of the font, as defined in the font's original data file.", null), |
|||
}, style:new painting.TextStyle(height:1.0)); |
|||
|
|||
var flexbox = new RenderFlex( |
|||
direction: Axis.vertical, |
|||
mainAxisAlignment: MainAxisAlignment.spaceAround, |
|||
crossAxisAlignment: CrossAxisAlignment.center); |
|||
|
|||
flexbox.add(flexItemBox( |
|||
new rendering.RenderEditable(span, TextDirection.ltr, |
|||
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding, |
|||
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0), |
|||
maxLines: 100, |
|||
selectionColor: Color.fromARGB(255, 255, 0, 0)) |
|||
)); |
|||
|
|||
span = new TextSpan("", children: |
|||
new List<TextSpan> |
|||
{ |
|||
new TextSpan("Hard Break:The ascent of the font is the distance\nfrom the baseline to the top \nline of the font,\nas defined in", null), |
|||
}, style:new painting.TextStyle(height:1.0)); |
|||
flexbox.add(flexItemBox( |
|||
new rendering.RenderEditable(span, TextDirection.ltr, |
|||
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding, |
|||
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0), |
|||
maxLines: 100, |
|||
selectionColor: Color.fromARGB(255, 255, 0, 0)) |
|||
)); |
|||
|
|||
span = new TextSpan("", children: |
|||
new List<TextSpan> |
|||
{ |
|||
new TextSpan("Single Line:How to create mixin", null), |
|||
}, style:new painting.TextStyle(height:1.0)); |
|||
flexbox.add(flexItemBox( |
|||
new rendering.RenderEditable(span, TextDirection.ltr, |
|||
new _FixedViewportOffset(0.0), new ValueNotifier<bool>(true), this.rendererBindings.rendererBinding, |
|||
onSelectionChanged: selectionChanged, cursorColor: Color.fromARGB(255, 0, 0, 0), |
|||
selectionColor: Color.fromARGB(255, 255, 0, 0)) |
|||
, width:300)); |
|||
return flexbox; |
|||
} |
|||
|
|||
|
|||
private void selectionChanged(TextSelection selection, rendering.RenderEditable renderObject, |
|||
SelectionChangedCause cause) |
|||
{ |
|||
Debug.Log(string.Format("selection {0}", selection)); |
|||
renderObject.selection = selection; |
|||
} |
|||
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 3133671f82dc4c0e93d4dbeb9e50b55d |
|||
timeCreated: 1536660300 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.gestures; |
|||
using UIWidgets.painting; |
|||
using UIWidgets.service; |
|||
using UIWidgets.ui; |
|||
using UnityEngine; |
|||
using Canvas = UIWidgets.ui.Canvas; |
|||
using Color = UIWidgets.ui.Color; |
|||
using Rect = UIWidgets.ui.Rect; |
|||
|
|||
namespace UIWidgets.rendering |
|||
{ |
|||
public delegate void SelectionChangedHandler(TextSelection selection, RenderEditable renderObject, |
|||
SelectionChangedCause cause); |
|||
|
|||
public delegate void CaretChangedHandler(Rect caretRect); |
|||
|
|||
public enum SelectionChangedCause |
|||
{ |
|||
tap, |
|||
doubleTap, |
|||
longPress, |
|||
keyboard, |
|||
} |
|||
|
|||
public class TextSelectionPoint |
|||
{ |
|||
public readonly Offset point; |
|||
public readonly TextDirection? direction; |
|||
|
|||
public TextSelectionPoint(Offset point, TextDirection? direction) |
|||
{ |
|||
D.assert(point != null); |
|||
this.point = point; |
|||
this.direction = direction; |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Format("Point: {0}, Direction: {1}", point, direction); |
|||
} |
|||
} |
|||
/* |
|||
this._doubleTapGesture = new DoubleTapGestureRecognizer(this.rendererBindings.rendererBinding); |
|||
this._doubleTapGesture.onDoubleTap = () => { Debug.Log("onDoubleTap"); };*/ |
|||
|
|||
public class RenderEditable : RenderBox |
|||
{ |
|||
public static readonly string obscuringCharacter = "•"; |
|||
private static readonly double _kCaretGap = 1.0; |
|||
private static readonly double _kCaretHeightOffset = 2.0; |
|||
private static readonly double _kCaretWidth = 1.0; |
|||
|
|||
private TextPainter _textPainter; |
|||
private Color _cursorColor; |
|||
private bool _hasFocus; |
|||
private int _maxLines; |
|||
private Color _selectionColor; |
|||
private ViewportOffset _offset; |
|||
private ValueNotifier<bool> _showCursor; |
|||
private TextSelection _selection; |
|||
private bool _obscureText; |
|||
private TapGestureRecognizer _tap; |
|||
private DoubleTapGestureRecognizer _doubleTap; |
|||
private bool ignorePointer; |
|||
private SelectionChangedHandler onSelectionChanged; |
|||
private CaretChangedHandler onCaretChanged; |
|||
private Rect _lastCaretRect; |
|||
private double? _textLayoutLastWidth; |
|||
private List<TextBox> _selectionRects; |
|||
private Rect _caretPrototype; |
|||
private bool _hasVisualOverflow = false; |
|||
private Offset _lastTapDownPosition; |
|||
|
|||
public RenderEditable(TextSpan text, TextDirection textDirection, ViewportOffset offset, |
|||
ValueNotifier<bool> showCursor, |
|||
GestureBinding binding, |
|||
TextAlign textAlign = TextAlign.left, double textScaleFactor = 1.0, Color cursorColor = null, |
|||
bool? hasFocus = null, int maxLines = 1, Color selectionColor = null, |
|||
TextSelection selection = null, bool obscureText = false, SelectionChangedHandler onSelectionChanged = null, |
|||
CaretChangedHandler onCaretChanged = null, bool ignorePointer = false) |
|||
{ |
|||
_textPainter = new TextPainter(text: text, textAlign: textAlign, textDirection: textDirection, |
|||
textScaleFactor: textScaleFactor); |
|||
_cursorColor = cursorColor; |
|||
_showCursor = showCursor ?? new ValueNotifier<bool>(false); |
|||
_hasFocus = hasFocus ?? false; |
|||
_maxLines = maxLines; |
|||
_selectionColor = selectionColor; |
|||
_selection = selection; |
|||
_obscureText = obscureText; |
|||
_offset = offset; |
|||
this.ignorePointer = ignorePointer; |
|||
this.onCaretChanged = onCaretChanged; |
|||
this.onSelectionChanged = onSelectionChanged; |
|||
|
|||
D.assert(_showCursor != null); |
|||
D.assert(!_showCursor.value || cursorColor != null); |
|||
|
|||
_tap = new TapGestureRecognizer(binding, this); |
|||
_doubleTap = new DoubleTapGestureRecognizer(binding, this); |
|||
_tap.onTapDown = this._handleTapDown; |
|||
_tap.onTap = this._handleTap; |
|||
_doubleTap.onDoubleTap = this._handleDoubleTap; |
|||
} |
|||
|
|||
public bool obscureText |
|||
{ |
|||
get { return _obscureText; } |
|||
set |
|||
{ |
|||
if (_obscureText == value) |
|||
return; |
|||
_obscureText = value; |
|||
markNeedsSemanticsUpdate(); |
|||
} |
|||
} |
|||
|
|||
public TextSpan text |
|||
{ |
|||
get { return _textPainter.text; } |
|||
set |
|||
{ |
|||
if (_textPainter.text == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_textPainter.text = value; |
|||
markNeedsTextLayout(); |
|||
markNeedsSemanticsUpdate(); |
|||
} |
|||
} |
|||
|
|||
public TextAlign textAlign |
|||
{ |
|||
get { return _textPainter.textAlign; } |
|||
set |
|||
{ |
|||
if (_textPainter.textAlign == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_textPainter.textAlign = value; |
|||
markNeedsPaint(); |
|||
} |
|||
} |
|||
|
|||
public TextDirection? textDirection |
|||
{ |
|||
get { return _textPainter.textDirection; } |
|||
set |
|||
{ |
|||
if (_textPainter.textDirection == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_textPainter.textDirection = value; |
|||
markNeedsTextLayout(); |
|||
markNeedsSemanticsUpdate(); |
|||
} |
|||
} |
|||
|
|||
public Color cursorColor |
|||
{ |
|||
get { return _cursorColor; } |
|||
set |
|||
{ |
|||
if (_cursorColor == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_cursorColor = value; |
|||
markNeedsPaint(); |
|||
} |
|||
} |
|||
|
|||
public ValueNotifier<bool> ShowCursor |
|||
{ |
|||
get { return _showCursor; } |
|||
set |
|||
{ |
|||
D.assert(value != null); |
|||
if (_showCursor == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (attached) |
|||
{ |
|||
_showCursor.removeListener(markNeedsPaint); |
|||
} |
|||
|
|||
_showCursor = value; |
|||
if (attached) |
|||
{ |
|||
_showCursor.addListener(markNeedsPaint); |
|||
} |
|||
|
|||
markNeedsPaint(); |
|||
} |
|||
} |
|||
|
|||
public bool hasFocus |
|||
{ |
|||
get { return _hasFocus; } |
|||
set |
|||
{ |
|||
if (_hasFocus == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
hasFocus = value; |
|||
markNeedsSemanticsUpdate(); |
|||
} |
|||
} |
|||
|
|||
public int maxLines |
|||
{ |
|||
get { return _maxLines; } |
|||
set |
|||
{ |
|||
D.assert(value > 0); |
|||
if (_maxLines == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_maxLines = value; |
|||
markNeedsTextLayout(); |
|||
} |
|||
} |
|||
|
|||
public Color selectionColor |
|||
{ |
|||
get { return _selectionColor; } |
|||
set |
|||
{ |
|||
if (_selectionColor == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_selectionColor = value; |
|||
markNeedsPaint(); |
|||
} |
|||
} |
|||
|
|||
public double textScaleFactor |
|||
{ |
|||
get { return _textPainter.textScaleFactor; } |
|||
set |
|||
{ |
|||
if (_textPainter.textScaleFactor == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_textPainter.textScaleFactor = value; |
|||
markNeedsTextLayout(); |
|||
} |
|||
} |
|||
|
|||
public TextSelection selection |
|||
{ |
|||
get { return _selection; } |
|||
set |
|||
{ |
|||
if (_selection == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_selection = value; |
|||
_selectionRects = null; |
|||
markNeedsPaint(); |
|||
markNeedsSemanticsUpdate(); |
|||
} |
|||
} |
|||
|
|||
public ViewportOffset offset |
|||
{ |
|||
get { return _offset; } |
|||
set |
|||
{ |
|||
D.assert(offset != null); |
|||
if (_offset == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (attached) |
|||
{ |
|||
_offset.removeListener(markNeedsPaint); |
|||
} |
|||
|
|||
_offset = value; |
|||
if (attached) |
|||
{ |
|||
_offset.addListener(markNeedsPaint); |
|||
} |
|||
|
|||
markNeedsLayout(); |
|||
} |
|||
} |
|||
|
|||
public ValueNotifier<bool> showCursor |
|||
{ |
|||
get { return _showCursor; } |
|||
set |
|||
{ |
|||
D.assert(value != null); |
|||
if (_showCursor == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (attached) |
|||
{ |
|||
_showCursor.removeListener(markNeedsPaint); |
|||
} |
|||
_showCursor = value; |
|||
if (attached) |
|||
{ |
|||
_showCursor.addListener(markNeedsPaint); |
|||
} |
|||
|
|||
markNeedsPaint(); |
|||
} |
|||
} |
|||
|
|||
public double preferredLineHeight |
|||
{ |
|||
get { return _textPainter.preferredLineHeight; } |
|||
} |
|||
|
|||
|
|||
public override void attach(object ownerObject) |
|||
{ |
|||
base.attach(ownerObject); |
|||
_offset.addListener(markNeedsLayout); |
|||
_showCursor.addListener(markNeedsPaint); |
|||
} |
|||
|
|||
public override void detach() |
|||
{ |
|||
_offset.removeListener(markNeedsLayout); |
|||
_showCursor.removeListener(markNeedsPaint); |
|||
base.detach(); |
|||
} |
|||
|
|||
/// Returns the local coordinates of the endpoints of the given selection.
|
|||
///
|
|||
/// If the selection is collapsed (and therefore occupies a single point), the
|
|||
/// returned list is of length one. Otherwise, the selection is not collapsed
|
|||
/// and the returned list is of length two. In this case, however, the two
|
|||
/// points might actually be co-located (e.g., because of a bidirectional
|
|||
/// selection that contains some text but whose ends meet in the middle).
|
|||
///
|
|||
public List<TextSelectionPoint> getEndpointsForSelection(TextSelection selection) |
|||
{ |
|||
D.assert(constraints != null); |
|||
_layoutText(constraints.maxWidth); |
|||
var paintOffset = _paintOffset; |
|||
if (selection.isCollapsed) |
|||
{ |
|||
var caretOffset = _textPainter.getOffsetForCaret(selection.extendPos, _caretPrototype); |
|||
var start = new Offset(0.0, preferredLineHeight) + caretOffset + paintOffset; |
|||
return new List<TextSelectionPoint>{new TextSelectionPoint(start, null)}; |
|||
} |
|||
else |
|||
{ |
|||
var boxes = _textPainter.getBoxesForSelection(selection); |
|||
var start = new Offset(boxes[0].start, boxes[0].bottom) + paintOffset; |
|||
var last = boxes.Count - 1; |
|||
var end = new Offset(boxes[last].end, boxes[last].bottom) + paintOffset; |
|||
return new List<TextSelectionPoint> |
|||
{ |
|||
new TextSelectionPoint(start, boxes[0].direction), |
|||
new TextSelectionPoint(end, boxes[last].direction), |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public TextPosition getPositionForPoint(Offset globalPosition) |
|||
{ |
|||
_layoutText(constraints.maxWidth); |
|||
globalPosition -= _paintOffset; |
|||
return _textPainter.getPositionForOffset(globalPosition); |
|||
} |
|||
|
|||
public Rect getLocalRectForCaret(TextPosition caretPosition) |
|||
{ |
|||
_layoutText(constraints.maxWidth); |
|||
var caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype); |
|||
return Rect.fromLTWH(0.0, 0.0, _kCaretWidth, preferredLineHeight).shift(caretOffset + _paintOffset); |
|||
} |
|||
|
|||
public override double computeMinIntrinsicWidth(double height) { |
|||
_layoutText(double.PositiveInfinity); |
|||
return _textPainter.minIntrinsicWidth; |
|||
} |
|||
|
|||
public override double computeMaxIntrinsicWidth(double height) { |
|||
_layoutText(double.PositiveInfinity); |
|||
return _textPainter.maxIntrinsicWidth; |
|||
} |
|||
|
|||
public override double computeMinIntrinsicHeight(double width) { |
|||
return _preferredHeight(width); |
|||
} |
|||
|
|||
public override double computeMaxIntrinsicHeight(double width) { |
|||
return _preferredHeight(width); |
|||
} |
|||
|
|||
public override double? computeDistanceToActualBaseline(TextBaseline baseline) { |
|||
_layoutText(constraints.maxWidth); |
|||
return _textPainter.computeDistanceToActualBaseline(baseline); |
|||
} |
|||
|
|||
public override void handleEvent(PointerEvent evt, HitTestEntry entry) { |
|||
if (ignorePointer) |
|||
return; |
|||
D.assert(debugHandleEvent(evt, entry)); |
|||
if (evt is PointerDownEvent && onSelectionChanged != null) { |
|||
_tap.addPointer((PointerDownEvent)evt); |
|||
_doubleTap.addPointer((PointerDownEvent)evt); |
|||
// todo long press
|
|||
} |
|||
} |
|||
|
|||
public void handleTapDown(TapDownDetails details) |
|||
{ |
|||
_lastTapDownPosition = details.globalPosition - _paintOffset; |
|||
} |
|||
|
|||
public void handleTap() |
|||
{ |
|||
_layoutText(constraints.maxWidth); |
|||
D.assert(_lastTapDownPosition != null); |
|||
if (onSelectionChanged != null) |
|||
{ |
|||
var position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition)); |
|||
onSelectionChanged(TextSelection.fromPosition(position), this, SelectionChangedCause.tap); |
|||
} |
|||
} |
|||
|
|||
public void handleDoubleTap() |
|||
{ |
|||
_layoutText(constraints.maxWidth); |
|||
D.assert(_lastTapDownPosition != null); |
|||
if (onSelectionChanged != null) |
|||
{ |
|||
var position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition)); |
|||
onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.doubleTap); |
|||
} |
|||
} |
|||
|
|||
|
|||
public override void performLayout() { |
|||
_layoutText(constraints.maxWidth); |
|||
_caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, |
|||
preferredLineHeight - 2.0 * _kCaretHeightOffset); |
|||
_selectionRects = null; |
|||
|
|||
var textPainterSize = _textPainter.size; |
|||
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth))); |
|||
var contentSize = new Size(textPainterSize.width + _kCaretGap + _kCaretWidth, textPainterSize.height); |
|||
var _maxScrollExtent = _getMaxScrollExtend(contentSize); |
|||
_hasVisualOverflow = _maxScrollExtent > 0.0; |
|||
offset.applyViewportDimension(_viewportExtend); |
|||
offset.applyContentDimensions(0.0, _maxScrollExtent); |
|||
} |
|||
|
|||
public override void paint(PaintingContext context, Offset offset) |
|||
{ |
|||
_layoutText(constraints.maxWidth); |
|||
if (_hasVisualOverflow) |
|||
{ |
|||
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents); |
|||
} |
|||
else |
|||
{ |
|||
_paintContents(context, offset); |
|||
} |
|||
} |
|||
|
|||
protected override bool hitTestSelf(Offset position) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
protected void markNeedsTextLayout() |
|||
{ |
|||
_textLayoutLastWidth = null; |
|||
markNeedsLayout(); |
|||
} |
|||
|
|||
// describeSemanticsConfiguration todo
|
|||
|
|||
|
|||
protected internal override void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
|||
base.debugFillProperties(properties); |
|||
properties.add(new DiagnosticsProperty<Color>("cursorColor", cursorColor)); |
|||
properties.add(new DiagnosticsProperty<ValueNotifier<bool>>("showCursor", showCursor)); |
|||
properties.add(new DiagnosticsProperty<int>("maxLines", maxLines)); |
|||
properties.add(new DiagnosticsProperty<Color>("selectionColor", selectionColor)); |
|||
properties.add(new DiagnosticsProperty<double>("textScaleFactor", textScaleFactor)); |
|||
properties.add(new DiagnosticsProperty<TextSelection>("selection", selection)); |
|||
properties.add(new DiagnosticsProperty<ViewportOffset>("offset", offset)); |
|||
} |
|||
|
|||
private void _paintCaret(Canvas canvas, Offset effectiveOffset) |
|||
{ |
|||
D.assert(_textLayoutLastWidth == constraints.maxWidth); |
|||
var caretOffset = _textPainter.getOffsetForCaret(_selection.extendPos, _caretPrototype); |
|||
var paint = new Paint() {color = _cursorColor}; |
|||
var caretRec = _caretPrototype.shift(caretOffset + effectiveOffset); |
|||
canvas.drawRect(caretRec, BorderWidth.zero, BorderRadius.zero, paint); |
|||
if (!caretRec.Equals(_lastCaretRect)) |
|||
{ |
|||
_lastCaretRect = caretRec; |
|||
if (onCaretChanged != null) |
|||
{ |
|||
onCaretChanged(caretRec); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void _paintSelection(Canvas canvas, Offset effectiveOffset) |
|||
{ |
|||
D.assert(_textLayoutLastWidth == constraints.maxWidth); |
|||
D.assert(_selectionRects != null); |
|||
var paint = new Paint() {color = _selectionColor}; |
|||
|
|||
foreach (var box in _selectionRects) |
|||
{ |
|||
Debug.Log(string.Format("draw box {0}", box.toRect().shift(effectiveOffset))); |
|||
canvas.drawRect(box.toRect().shift(effectiveOffset), BorderWidth.zero, BorderRadius.zero, paint); |
|||
} |
|||
} |
|||
|
|||
void _paintContents(PaintingContext context, Offset offset) |
|||
{ |
|||
D.assert(_textLayoutLastWidth == constraints.maxWidth); |
|||
var effectiveOffset = offset + _paintOffset; |
|||
|
|||
if (_selection != null) { |
|||
if (_selection.isCollapsed && _showCursor.value && cursorColor != null) { |
|||
_paintCaret(context.canvas, effectiveOffset); |
|||
} else if (!_selection.isCollapsed && _selectionColor != null) { |
|||
_selectionRects = _selectionRects??_textPainter.getBoxesForSelection(_selection); |
|||
_paintSelection(context.canvas, effectiveOffset); |
|||
} |
|||
} |
|||
|
|||
_textPainter.paint(context.canvas, effectiveOffset); |
|||
} |
|||
|
|||
private void _handleSetSelection(TextSelection selection) |
|||
{ |
|||
onSelectionChanged(selection, this, SelectionChangedCause.keyboard); |
|||
} |
|||
|
|||
private void _handleTapDown(TapDownDetails details) |
|||
{ |
|||
D.assert(!ignorePointer); |
|||
handleTapDown(details); |
|||
} |
|||
|
|||
private void _handleTap() |
|||
{ |
|||
D.assert(!ignorePointer); |
|||
handleTap(); |
|||
} |
|||
|
|||
private void _handleDoubleTap() |
|||
{ |
|||
D.assert(!ignorePointer); |
|||
handleDoubleTap(); |
|||
} |
|||
|
|||
private void markNeedsSemanticsUpdate() |
|||
{ |
|||
// todo
|
|||
} |
|||
|
|||
private double _preferredHeight(double width) |
|||
{ |
|||
if (maxLines <= 0) |
|||
{ |
|||
return preferredLineHeight * maxLines; |
|||
} |
|||
|
|||
if (double.IsInfinity(width)) |
|||
{ |
|||
var text = _textPainter.text.text; |
|||
int lines = 1; |
|||
for (int index = 0; index < text.Length; ++index) |
|||
{ |
|||
if (text[index] == 0x0A) |
|||
{ |
|||
lines += 1; |
|||
} |
|||
} |
|||
|
|||
return preferredLineHeight * lines; |
|||
} |
|||
|
|||
_layoutText(width); |
|||
return Math.Max(preferredLineHeight, _textPainter.height); |
|||
} |
|||
|
|||
private void _layoutText(double constraintWidth) |
|||
{ |
|||
if (_textLayoutLastWidth == constraintWidth) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var caretMargin = _kCaretGap + _kCaretWidth; |
|||
var avialableWidth = Math.Max(0.0, constraintWidth - caretMargin); |
|||
var maxWidth = _isMultiline ? avialableWidth : double.PositiveInfinity; |
|||
_textPainter.layout(minWidth: avialableWidth, maxWidth: maxWidth); |
|||
_textLayoutLastWidth = constraintWidth; |
|||
} |
|||
|
|||
TextSelection _selectWordAtOffset(TextPosition position) |
|||
{ |
|||
D.assert(_textLayoutLastWidth == constraints.maxWidth); |
|||
var word = _textPainter.getWordBoundary(position); |
|||
if (position.offset >= word.end) |
|||
{ |
|||
return TextSelection.fromPosition(position); |
|||
} |
|||
return new TextSelection(baseOffset: word.start, extendOffset: word.end); |
|||
} |
|||
|
|||
private bool _isMultiline |
|||
{ |
|||
get { return _maxLines != 1; } |
|||
} |
|||
|
|||
private Axis _viewportAxis |
|||
{ |
|||
get { return _isMultiline ? Axis.vertical : Axis.horizontal; } |
|||
} |
|||
|
|||
private Offset _paintOffset |
|||
{ |
|||
get |
|||
{ |
|||
switch (_viewportAxis) |
|||
{ |
|||
case Axis.horizontal: |
|||
return new Offset(-offset.pixels, 0.0); |
|||
case Axis.vertical: |
|||
return new Offset(0.0, -offset.pixels); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
|||
|
|||
private double _viewportExtend |
|||
{ |
|||
get |
|||
{ |
|||
D.assert(hasSize); |
|||
switch (_viewportAxis) |
|||
{ |
|||
case Axis.horizontal: |
|||
return size.width; |
|||
case Axis.vertical: |
|||
return size.height; |
|||
} |
|||
|
|||
return 0.0; |
|||
} |
|||
} |
|||
|
|||
private double _getMaxScrollExtend(Size contentSize) |
|||
{ |
|||
D.assert(hasSize); |
|||
switch (_viewportAxis) |
|||
{ |
|||
case Axis.horizontal: |
|||
return Math.Max(0.0, contentSize.width - size.width); |
|||
case Axis.vertical: |
|||
return Math.Max(0.0, contentSize.height - size.height); |
|||
} |
|||
|
|||
return 0.0; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f171ccb5173340cf99e2ddd274a3cbd6 |
|||
timeCreated: 1536565172 |
|
|||
fileFormatVersion: 2 |
|||
guid: 355cc83db152457b8eb6da935c588fbc |
|||
timeCreated: 1536566821 |
|
|||
using System; |
|||
|
|||
namespace UIWidgets.ui |
|||
{ |
|||
public class WordSeparate |
|||
{ |
|||
|
|||
enum Direction |
|||
{ |
|||
Forward, |
|||
Backward, |
|||
} |
|||
|
|||
enum characterType |
|||
{ |
|||
LetterLike, |
|||
Symbol, |
|||
WhiteSpace |
|||
} |
|||
|
|||
private string _text; |
|||
|
|||
public WordSeparate(string text) |
|||
{ |
|||
this._text = text; |
|||
} |
|||
|
|||
public IndexRange findWordRange(int index) |
|||
{ |
|||
var t = classifyChar(index); |
|||
int start = index; |
|||
for (int i = index; i >= 0; --i) |
|||
{ |
|||
if (!char.IsLowSurrogate(_text[start])) |
|||
{ |
|||
if (classifyChar(i) != t) |
|||
{ |
|||
break; |
|||
} |
|||
start = i; |
|||
} |
|||
} |
|||
|
|||
int end = index; |
|||
for (int i = index; i < _text.Length; ++i) |
|||
{ |
|||
if (!char.IsLowSurrogate(_text[i])) |
|||
{ |
|||
if (classifyChar(i) != t) |
|||
{ |
|||
break; |
|||
} |
|||
end = i; |
|||
} |
|||
} |
|||
return new IndexRange(start, end + 1); |
|||
} |
|||
|
|||
|
|||
private characterType classifyChar(int index) |
|||
{ |
|||
if (char.IsWhiteSpace(_text, index)) |
|||
return characterType.WhiteSpace; |
|||
if (char.IsLetterOrDigit(_text, index) || _text[index] == '\'') |
|||
return characterType.LetterLike; |
|||
return characterType.Symbol; |
|||
} |
|||
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1c8f1db7a67b4775942ad83def1ce873 |
|||
timeCreated: 1536654844 |
|
|||
using System; |
|||
using UIWidgets.foundation; |
|||
using UIWidgets.ui; |
|||
|
|||
namespace UIWidgets.service |
|||
{ |
|||
public class TextRange: IEquatable<TextRange> |
|||
{ |
|||
public readonly int start; |
|||
public readonly int end; |
|||
|
|||
public static TextRange collapsed(int offset) |
|||
{ |
|||
D.assert(offset >= -1); |
|||
return new TextRange(offset, offset); |
|||
} |
|||
|
|||
public static readonly TextRange empty = new TextRange(-1, -1); |
|||
|
|||
public TextRange(int start, int end) |
|||
{ |
|||
D.assert(start >= -1); |
|||
D.assert(end >= -1); |
|||
this.start = start; |
|||
this.end = end; |
|||
} |
|||
|
|||
public bool isValid |
|||
{ |
|||
get { return start >= 0 && end >= 0; } |
|||
} |
|||
|
|||
public bool isCollapsed |
|||
{ |
|||
get { return start == end; } |
|||
} |
|||
|
|||
public bool isNormalized |
|||
{ |
|||
get { return start <= end; } |
|||
} |
|||
|
|||
public string textBefore(string text) |
|||
{ |
|||
D.assert(isNormalized); |
|||
return text.Substring(0, start); |
|||
} |
|||
|
|||
public string textAfter(string text) |
|||
{ |
|||
D.assert(isNormalized); |
|||
return text.Substring(end); |
|||
} |
|||
|
|||
public string textInside(string text) |
|||
{ |
|||
D.assert(isNormalized); |
|||
return text.Substring(start, end - start); |
|||
} |
|||
|
|||
public bool Equals(TextRange other) |
|||
{ |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return start == other.start && end == other.end; |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) return false; |
|||
if (ReferenceEquals(this, obj)) return true; |
|||
if (obj.GetType() != this.GetType()) return false; |
|||
return Equals((TextRange) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
return (start * 397) ^ end; |
|||
} |
|||
} |
|||
|
|||
public static bool operator ==(TextRange left, TextRange right) |
|||
{ |
|||
return Equals(left, right); |
|||
} |
|||
|
|||
public static bool operator !=(TextRange left, TextRange right) |
|||
{ |
|||
return !Equals(left, right); |
|||
} |
|||
|
|||
public override string ToString() |
|||
{ |
|||
return string.Format("TextRange Start: {0}, End: {1}", start, end); |
|||
} |
|||
} |
|||
|
|||
public class TextSelection : TextRange, IEquatable<TextSelection> |
|||
{ |
|||
public readonly int baseOffset; |
|||
public readonly int extendOffset; |
|||
public readonly TextAffinity affinity; |
|||
public readonly bool isDirectional; |
|||
|
|||
public TextSelection(int baseOffset, int extendOffset, TextAffinity affinity = TextAffinity.downstream, |
|||
bool isDirectional = false):base(baseOffset < extendOffset ? baseOffset: extendOffset, |
|||
baseOffset < extendOffset ? extendOffset : baseOffset ) |
|||
{ |
|||
this.baseOffset = baseOffset; |
|||
this.extendOffset = extendOffset; |
|||
this.affinity = affinity; |
|||
this.isDirectional = isDirectional; |
|||
} |
|||
|
|||
public static TextSelection collapsed(int offset, TextAffinity affinity = TextAffinity.downstream) |
|||
{ |
|||
return new TextSelection(offset, offset, affinity, false); |
|||
} |
|||
|
|||
public static TextSelection fromPosition(TextPosition position) |
|||
{ |
|||
return collapsed(position.offset, position.affinity); |
|||
} |
|||
|
|||
public TextPosition basePos |
|||
{ |
|||
get |
|||
{ |
|||
return new TextPosition(offset: baseOffset, affinity: affinity); |
|||
} |
|||
} |
|||
|
|||
public TextPosition extendPos |
|||
{ |
|||
get |
|||
{ |
|||
return new TextPosition(offset: extendOffset, affinity: affinity); |
|||
} |
|||
} |
|||
|
|||
public bool Equals(TextSelection other) |
|||
{ |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return baseOffset == other.baseOffset && extendOffset == other.extendOffset && |
|||
affinity == other.affinity && isDirectional == other.isDirectional; |
|||
} |
|||
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
if (ReferenceEquals(null, obj)) return false; |
|||
if (ReferenceEquals(this, obj)) return true; |
|||
if (obj.GetType() != this.GetType()) return false; |
|||
return Equals((TextSelection) obj); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hashCode = 0; |
|||
hashCode = (hashCode * 397) ^ baseOffset; |
|||
hashCode = (hashCode * 397) ^ extendOffset; |
|||
hashCode = (hashCode * 397) ^ (int) affinity; |
|||
hashCode = (hashCode * 397) ^ isDirectional.GetHashCode(); |
|||
return hashCode; |
|||
} |
|||
} |
|||
|
|||
public static bool operator ==(TextSelection left, TextSelection right) |
|||
{ |
|||
return Equals(left, right); |
|||
} |
|||
|
|||
public static bool operator !=(TextSelection left, TextSelection right) |
|||
{ |
|||
return !Equals(left, right); |
|||
} |
|||
|
|||
public TextSelection copyWith(int? baseOffset = null, int? extendOffset = null, TextAffinity? affinity = null, |
|||
bool? isDirectional = null) |
|||
{ |
|||
return new TextSelection( |
|||
baseOffset??this.baseOffset, extendOffset??this.extendOffset, affinity??this.affinity, |
|||
isDirectional??this.isDirectional |
|||
); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 75983962965c4fd8bf21cc55ba91bde0 |
|||
timeCreated: 1536566838 |
|
|||
fileFormatVersion: 2 |
|||
guid: c53bae489a2f499ea5b9000edb31b0bb |
|||
timeCreated: 1535421229 |
撰写
预览
正在加载...
取消
保存
Reference in new issue