浏览代码

Merge branch 'text_edit' into 'master'

text selection overlay

See merge request upm-packages/ui-widgets/com.unity.uiwidgets!27
/main
Shenhua Gu 6 年前
当前提交
6d294bbd
共有 26 个文件被更改,包括 1421 次插入51 次删除
  1. 2
      Runtime/debugger/inspector_treeview.cs
  2. 17
      Runtime/gestures/multitap.cs
  3. 35
      Runtime/painting/matrix_utils.cs
  4. 35
      Runtime/rendering/editable.cs
  5. 198
      Runtime/rendering/layer.cs
  6. 2
      Runtime/rendering/object.cs
  7. 142
      Runtime/rendering/proxy_box.cs
  8. 98
      Runtime/rendering/shifted_box.cs
  9. 9
      Runtime/service/text_input.cs
  10. 75
      Runtime/widgets/basic.cs
  11. 4
      Runtime/widgets/custom_paint.cs
  12. 111
      Runtime/widgets/editable_text.cs
  13. 2
      Runtime/widgets/framework.cs
  14. 1
      Runtime/widgets/page_view.cs
  15. 2
      Samples/ReduxSample/ObjectFinder/ObjectFinderApp.cs
  16. 2
      Samples/ReduxSample/ObjectFinder/Reducer.cs
  17. 2
      Samples/UIWidgetSample/AsScreenCanvas.cs
  18. 13
      Samples/UIWidgetSample/TextInputCanvas.cs
  19. 2
      Samples/UIWidgetSample/ToDoAppCanvas.cs
  20. 2
      Tests/Editor/EditableTextWiget.cs
  21. 2
      Tests/Editor/Gestures.cs
  22. 14
      Tests/Editor/Widgets.cs
  23. 42
      Runtime/service/clipboard.cs
  24. 439
      Runtime/widgets/text_selection.cs
  25. 221
      Runtime/material/text_selection.cs

2
Runtime/debugger/inspector_treeview.cs


}
xoffset += iconSize;
}
this.labelGUI(xoffset, rect, node.description);
this.labelGUI(xoffset, rect, node.description.Replace("\n", " "));
}
protected override void SelectionChanged(IList<int> selectedIds) {

17
Runtime/gestures/multitap.cs


using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.gestures {
public delegate void GestureDoubleTapCallback();
public delegate void GestureDoubleTapCallback(DoubleTapDetails details);
public class DoubleTapDetails {
public DoubleTapDetails(Offset firstGlobalPosition = null) {
this.firstGlobalPosition = firstGlobalPosition ?? Offset.zero;
}
public readonly Offset firstGlobalPosition;
}
class _TapTracker {
internal _TapTracker(
PointerDownEvent evt = null,

public readonly int pointer;
public readonly GestureArenaEntry entry;
readonly Offset _initialPosition;
internal readonly Offset _initialPosition;
bool _isTrackingPointer = false;

return offset.distance <= tolerance;
}
}
public class DoubleTapGestureRecognizer : GestureRecognizer {
public DoubleTapGestureRecognizer(object debugOwner = null)

}
void _registerSecondTap(_TapTracker tracker) {
var initialPosition = tracker._initialPosition;
this._firstTap.entry.resolve(GestureDisposition.accepted);
tracker.entry.resolve(GestureDisposition.accepted);
this._freezeTracker(tracker);

this.onDoubleTap();
this.onDoubleTap(new DoubleTapDetails(initialPosition));
return null;
});
}

35
Runtime/painting/matrix_utils.cs


using Unity.UIWidgets.ui;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
}
public static List<string> debugDescribeTransform(Matrix3 transform) {
if (transform == null)
return new List<string> {"null"};
List<string> result = new List<string>(3);
for (int i = 0; i < 3; i++) {
result.Add($"[{i}] {transform[i * 3]}, {transform[i * 3 + 1]}, {transform[i * 3 + 2]}");
}
return result;
}
}
public class TransformProperty : DiagnosticsProperty<Matrix3> {
public TransformProperty(string name, Matrix3 value,
bool showName = true,
object defaultValue = null,
DiagnosticLevel level = DiagnosticLevel.info
): base(name, value, showName: showName, defaultValue: defaultValue??Diagnostics.kNoDefaultValue, level: level) {
}
protected override string valueToString(TextTreeConfiguration parentConfiguration = null) {
if (parentConfiguration != null && !parentConfiguration.lineBreakProperties) {
return this.value == null ? "null" : this.value.ToString();
}
return string.Join("\n", MatrixUtils.debugDescribeTransform(this.value));
}
}
}

35
Runtime/rendering/editable.cs


TextSelection _selection;
bool _obscureText;
TapGestureRecognizer _tap;
LongPressGestureRecognizer _longPress;
DoubleTapGestureRecognizer _doubleTap;
public bool ignorePointer;
public SelectionChangedHandler onSelectionChanged;

this._tap.onTapDown = this._handleTapDown;
this._tap.onTap = this._handleTap;
this._doubleTap.onDoubleTap = this._handleDoubleTap;
this._longPress = new LongPressGestureRecognizer(debugOwner: this);
this._longPress.onLongPress = this._handleLongPress;
}
public bool obscureText {

public TextPosition getPositionForPoint(Offset globalPosition) {
this._layoutText(this.constraints.maxWidth);
globalPosition -= this._paintOffset;
return this._textPainter.getPositionForOffset(globalPosition);
return this._textPainter.getPositionForOffset(this.globalToLocal(globalPosition));
}
public Rect getLocalRectForCaret(TextPosition caretPosition) {

if (evt is PointerDownEvent && this.onSelectionChanged != null) {
this._tap.addPointer((PointerDownEvent) evt);
this._doubleTap.addPointer((PointerDownEvent) evt);
// todo long press
this._longPress.addPointer((PointerDownEvent) evt);
}
}

}
}
public void handleDoubleTap() {
public void handleDoubleTap(DoubleTapDetails details) {
this._lastTapDownPosition = details.firstGlobalPosition - this._paintOffset;
this.selectWord(cause: SelectionChangedCause.doubleTap);
}
public void handleLongPress() {
this.selectWord(cause: SelectionChangedCause.longPress);
}
void selectWord(SelectionChangedCause? cause = null) {
var position = this._textPainter.getPositionForOffset(this.globalToLocal(this._lastTapDownPosition));
this.onSelectionChanged(this._selectWordAtOffset(position), this, SelectionChangedCause.doubleTap);
TextPosition position =
this._textPainter.getPositionForOffset(this.globalToLocal(this._lastTapDownPosition));
this.onSelectionChanged(this._selectWordAtOffset(position), this, cause.Value);
protected override void performLayout() {
this._layoutText(this.constraints.maxWidth);
this._caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth,

this.handleTap();
}
void _handleDoubleTap() {
void _handleDoubleTap(DoubleTapDetails details) {
D.assert(!this.ignorePointer);
this.handleDoubleTap(details);
}
void _handleLongPress() {
this.handleDoubleTap();
this.handleLongPress();
}
void markNeedsSemanticsUpdate() {

198
Runtime/rendering/layer.cs


using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.rendering {
public abstract class Layer : AbstractNodeMixinDiagnosticableTree {

while (node.parent != null) {
node = node.parent;
}
D.assert(node != newLayer);
return true;
});

while (node.parent != null) {
node = node.parent;
}
D.assert(node != child);
return true;
});

if (child._previousSibling == null) {
D.assert(this.firstChild == child);
this._firstChild = child.nextSibling;
} else {
}
else {
child._previousSibling._nextSibling = child.nextSibling;
}

} else {
}
else {
child._nextSibling._previousSibling = child.previousSibling;
}

properties.add(new IntProperty("alpha", this.alpha));
}
}
public class LayerLink {
public LeaderLayer leader => this._leader;
internal LeaderLayer _leader;
public override string ToString() {
return $"{Diagnostics.describeIdentity(this)}({(this._leader != null ? "<linked>" : "<dangling>")})";
}
}
public class LeaderLayer : ContainerLayer {
public readonly LayerLink link;
public Offset offset;
public LeaderLayer(LayerLink link, Offset offset = null) {
D.assert(link != null);
offset = offset ?? Offset.zero;
this.link = link;
this.offset = offset;
}
public override void attach(object owner) {
base.attach(owner);
D.assert(this.link.leader == null);
this._lastOffset = null;
this.link._leader = this;
}
public override void detach() {
D.assert(this.link.leader == this);
this.link._leader = null;
this._lastOffset = null;
base.detach();
}
internal Offset _lastOffset;
public override void addToScene(SceneBuilder builder, Offset layerOffset) {
D.assert(this.offset != null);
this._lastOffset = this.offset + layerOffset;
if (this._lastOffset != Offset.zero)
builder.pushTransform(Matrix3.makeTrans(this._lastOffset));
this.addChildrenToScene(builder, Offset.zero);
if (this._lastOffset != Offset.zero)
builder.pop();
}
public override void applyTransform(Layer child, Matrix3 transform) {
D.assert(this._lastOffset != null);
if (this._lastOffset != Offset.zero)
transform.preTranslate((float)this._lastOffset.dx, (float)this._lastOffset.dy);
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<Offset>("offset", this.offset));
properties.add(new DiagnosticsProperty<LayerLink>("link", this.link));
}
}
public class FollowerLayer : ContainerLayer {
public FollowerLayer(
LayerLink link = null,
bool showWhenUnlinked = true,
Offset unlinkedOffset = null,
Offset linkedOffset = null
) {
D.assert(link != null);
this.link = link;
this.showWhenUnlinked = showWhenUnlinked;
this.unlinkedOffset = unlinkedOffset;
this.linkedOffset = linkedOffset;
}
public readonly LayerLink link;
public readonly bool showWhenUnlinked;
public readonly Offset unlinkedOffset;
public readonly Offset linkedOffset;
Offset _lastOffset;
Matrix3 _lastTransform;
Matrix3 _invertedTransform;
public Matrix3 getLastTransform() {
if (this._lastTransform == null)
return null;
Matrix3 result = Matrix3.makeTrans((float)-this._lastOffset.dx, (float)-this._lastOffset.dy);
result.preConcat(this._lastTransform);
return result;
}
Matrix3 _collectTransformForLayerChain(List<ContainerLayer> layers) {
Matrix3 result = Matrix3.I();
for (int index = layers.Count - 1; index > 0; index -= 1)
layers[index].applyTransform(layers[index - 1], result);
return result;
}
void _establishTransform() {
D.assert(this.link != null);
this._lastTransform = null;
if (this.link._leader == null) {
return;
}
D.assert(this.link.leader.owner == this.owner,
"Linked LeaderLayer anchor is not in the same layer tree as the FollowerLayer.");
D.assert(this.link.leader._lastOffset != null, "LeaderLayer anchor must come before FollowerLayer in paint order, but the reverse was true.");
HashSet<Layer> ancestors = new HashSet<Layer>();
Layer ancestor = this.parent;
while (ancestor != null) {
ancestors.Add(ancestor);
ancestor = ancestor.parent;
}
ContainerLayer layer = this.link.leader;
List<ContainerLayer> forwardLayers = new List<ContainerLayer> {null, layer};
do {
layer = layer.parent;
forwardLayers.Add(layer);
} while (!ancestors.Contains(layer));
ancestor = layer;
layer = this;
List<ContainerLayer> inverseLayers = new List<ContainerLayer>();
do {
layer = layer.parent;
inverseLayers.Add(layer);
} while (layer != ancestor);
Matrix3 forwardTransform = this._collectTransformForLayerChain(forwardLayers);
Matrix3 inverseTransform = this._collectTransformForLayerChain(inverseLayers);
var inverse = Matrix3.I();
var invertible = inverseTransform.invert(inverseTransform);
if (!invertible) {
return;
}
inverseTransform = inverse;
inverseTransform.preConcat(forwardTransform);
inverseTransform.preTranslate((float)this.linkedOffset.dx, (float)this.linkedOffset.dy);
this._lastTransform = inverseTransform;
}
public override void addToScene(SceneBuilder builder, Offset layerOffset) {
D.assert(this.link != null);
if (this.link.leader == null && !this.showWhenUnlinked) {
this._lastTransform = null;
this._lastOffset = null;
return;
}
this._establishTransform();
if (this._lastTransform != null) {
builder.pushTransform(this._lastTransform);
this.addChildrenToScene(builder, Offset.zero);
builder.pop();
this._lastOffset = this.unlinkedOffset + layerOffset;
}
else {
this._lastOffset = null;
var matrix = Matrix3.makeTrans((float)this.unlinkedOffset.dx, (float)this.unlinkedOffset.dy);
builder.pushTransform(matrix);
this.addChildrenToScene(builder, Offset.zero);
builder.pop();
}
}
public override void applyTransform(Layer child, Matrix3 transform) {
D.assert(child != null);
D.assert(transform != null);
if (this._lastTransform != null) {
transform.preConcat(this._lastTransform);
} else {
transform.preConcat(Matrix3.makeTrans((float)this.unlinkedOffset.dx, (float)this.unlinkedOffset.dy));
}
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<LayerLink>("link", this.link));
properties.add(new TransformProperty("transform", this.getLastTransform(), defaultValue: null));
}
}
}

2
Runtime/rendering/object.cs


this.appendLayer(layer);
}
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset,
public void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset,
Rect childPaintBounds = null) {
D.assert(!childLayer.attached);
D.assert(childLayer.parent == null);

142
Runtime/rendering/proxy_box.cs


properties.add(new DiagnosticsProperty<object>("metaData", this.metaData));
}
}
public class RenderLeaderLayer : RenderProxyBox {
public RenderLeaderLayer(
LayerLink link = null,
RenderBox child = null
) : base(child:child) {
D.assert(link != null);
this.link = link;
}
public LayerLink link {
get => this._link;
set {
D.assert(value != null);
if (this._link == value)
return;
this._link = value;
this.markNeedsPaint();
}
}
LayerLink _link;
protected override bool alwaysNeedsCompositing => true;
public override void paint(PaintingContext context, Offset offset) {
context.pushLayer(new LeaderLayer(link: this.link, offset: offset), base.paint, Offset.zero);
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<LayerLink>("link", this.link));
}
}
class RenderFollowerLayer : RenderProxyBox {
public RenderFollowerLayer(LayerLink link,
bool showWhenUnlinked = true,
Offset offset = null,
RenderBox child = null
): base(child) {
this.link = link;
this.showWhenUnlinked = showWhenUnlinked;
this.offset = offset;
}
public LayerLink link {
get => this._link;
set {
D.assert(value != null);
if (this._link == value)
return;
this._link = value;
this.markNeedsPaint();
}
}
LayerLink _link;
public bool showWhenUnlinked {
get => this._showWhenUnlinked;
set {
if (this._showWhenUnlinked == value)
return;
this._showWhenUnlinked = value;
this.markNeedsPaint();
}
}
bool _showWhenUnlinked;
public Offset offset {
get => this._offset;
set {
D.assert(value != null);
if (this._offset == value)
return;
this._offset = value;
this.markNeedsPaint();
}
}
Offset _offset;
public override void detach() {
this._layer = null;
base.detach();
}
protected override bool alwaysNeedsCompositing => true;
new FollowerLayer _layer;
Matrix3 getCurrentTransform() {
return this._layer?.getLastTransform() ?? Matrix3.I();
}
public override bool hitTest(HitTestResult result, Offset position) {
return this.hitTestChildren(result, position: position);
}
protected override bool hitTestChildren(HitTestResult result, Offset position) {
Matrix3 inverse = Matrix3.I();
if (!this.getCurrentTransform().invert(inverse)) {
return false;
}
position = inverse.mapPoint(position);
return base.hitTestChildren(result, position: position);
}
public override void paint(PaintingContext context, Offset offset) {
this._layer = new FollowerLayer(
link: this.link,
showWhenUnlinked: this.showWhenUnlinked,
linkedOffset: this.offset,
unlinkedOffset: offset
);
context.pushLayer(this._layer,
base.paint,
Offset.zero,
childPaintBounds: Rect.fromLTRB(
double.NegativeInfinity,
double.NegativeInfinity,
double.PositiveInfinity,
double.PositiveInfinity
)
);
}
public override void applyPaintTransform(RenderObject child, Matrix3 transform) {
transform.preConcat(this.getCurrentTransform());
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<LayerLink>("link", this.link));
properties.add(new DiagnosticsProperty<bool>("showWhenUnlinked", this.showWhenUnlinked));
properties.add(new DiagnosticsProperty<Offset>("offset", this.offset));
properties.add(new TransformProperty("current transform matrix", this.getCurrentTransform()));
}
}
}

98
Runtime/rendering/shifted_box.cs


}
}
public abstract class SingleChildLayoutDelegate {
public SingleChildLayoutDelegate(Listenable _relayout = null) {
this._relayout = _relayout;
}
public readonly Listenable _relayout;
public virtual Size getSize(BoxConstraints constraints) => constraints.biggest;
public virtual BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
public virtual Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
public abstract bool shouldRelayout(SingleChildLayoutDelegate oldDelegate);
}
public class RenderCustomSingleChildLayoutBox : RenderShiftedBox {
public RenderCustomSingleChildLayoutBox(RenderBox child = null,
SingleChildLayoutDelegate layoutDelegate = null) : base(child) {
D.assert(layoutDelegate != null);
this._delegate = layoutDelegate;
}
public SingleChildLayoutDelegate layoutDelegate {
get => this._delegate;
set {
var newDelegate = value;
D.assert(newDelegate != null);
if (this._delegate == newDelegate)
return;
SingleChildLayoutDelegate oldDelegate = this._delegate;
if (newDelegate.GetType() != oldDelegate.GetType() || newDelegate.shouldRelayout(oldDelegate)) this.markNeedsLayout();
this._delegate = newDelegate;
if (this.attached) {
oldDelegate?._relayout?.removeListener(this.markNeedsLayout);
newDelegate?._relayout?.addListener(this.markNeedsLayout);
}
}
}
SingleChildLayoutDelegate _delegate;
public override void attach(object owner) {
base.attach(owner);
this._delegate?._relayout?.addListener(this.markNeedsLayout);
}
public override void detach() {
this._delegate?._relayout?.removeListener(this.markNeedsLayout);
base.detach();
}
Size _getSize(BoxConstraints constraints) {
return constraints.constrain(this._delegate.getSize(constraints));
}
protected override double computeMinIntrinsicWidth(double height) {
double width = this._getSize(BoxConstraints.tightForFinite(height: height)).width;
if (width.isFinite())
return width;
return 0.0;
}
protected override double computeMaxIntrinsicWidth(double height) {
double width = this._getSize(BoxConstraints.tightForFinite(height: height)).width;
if (width.isFinite())
return width;
return 0.0;
}
protected override double computeMinIntrinsicHeight(double width) {
double height = this._getSize(BoxConstraints.tightForFinite(width: width)).height;
if (height.isFinite())
return height;
return 0.0;
}
protected override double computeMaxIntrinsicHeight(double width) {
double height = this._getSize(BoxConstraints.tightForFinite(width: width)).height;
if (height.isFinite())
return height;
return 0.0;
}
protected override void performLayout() {
this.size = this._getSize(this.constraints);
if (this.child != null) {
BoxConstraints childConstraints = this.layoutDelegate.getConstraintsForChild(this.constraints);
D.assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
this.child.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
BoxParentData childParentData = (BoxParentData)this.child.parentData;
childParentData.offset = this.layoutDelegate.getPositionForChild(this.size,
childConstraints.isTight ? childConstraints.smallest : this.child.size);
}
}
}
public class RenderBaseline : RenderShiftedBox {
public RenderBaseline(
RenderBox child = null,

9
Runtime/service/text_input.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using UnityEngine;
namespace Unity.UIWidgets.service {

public override string ToString() {
return $"Text: {this.text}, Selection: {this.selection}, Composing: {this.composing}";
}
}
public interface TextSelectionDelegate {
TextEditingValue textEditingValue { get; set; }
void hideToolbar();
void bringIntoView(TextPosition textPosition);
}
public interface TextInputClient {

75
Runtime/widgets/basic.cs


bool _needTextDirection {
get {
D.assert(this.direction != null);
switch (this.direction) {
case Axis.horizontal:
return true;

}
}
public class CompositedTransformTarget : SingleChildRenderObjectWidget {
public CompositedTransformTarget(
Key key = null,
LayerLink link = null,
Widget child = null
) : base(key: key, child: child) {
D.assert(link != null);
this.link = link;
}
public readonly LayerLink link;
public override RenderObject createRenderObject(BuildContext context) {
return new RenderLeaderLayer(
link: this.link
);
}
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
((RenderLeaderLayer) renderObject).link = this.link;
}
}
public class CompositedTransformFollower : SingleChildRenderObjectWidget {
public CompositedTransformFollower(
Key key = null,
LayerLink link = null,
bool showWhenUnlinked = true,
Offset offset = null,
Widget child = null
) : base(key: key, child: child) {
D.assert(link != null);
this.showWhenUnlinked = showWhenUnlinked;
this.offset = offset ?? Offset.zero;
this.link = link;
}
public readonly LayerLink link;
public readonly bool showWhenUnlinked;
public readonly Offset offset;
public override RenderObject createRenderObject(BuildContext context) {
return new RenderFollowerLayer(
link: this.link,
showWhenUnlinked: this.showWhenUnlinked,
offset: this.offset
);
}
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
((RenderFollowerLayer) renderObject).link = this.link;
((RenderFollowerLayer) renderObject).showWhenUnlinked = this.showWhenUnlinked;
((RenderFollowerLayer) renderObject).offset = this.offset;
}
}
public class FractionalTranslation : SingleChildRenderObjectWidget {
public FractionalTranslation(Key key = null, Offset translation = null,
bool transformHitTests = true, Widget child = null) : base(key: key, child: child) {

widthFactor: widthFactor,
heightFactor: heightFactor,
child: child) {
}
}
public class CustomSingleChildLayout : SingleChildRenderObjectWidget {
public CustomSingleChildLayout(Key key = null,
SingleChildLayoutDelegate layoutDelegate = null, Widget child = null):base(key:key, child:child) {
D.assert(layoutDelegate != null);
this.layoutDelegate = layoutDelegate;
}
public readonly SingleChildLayoutDelegate layoutDelegate;
public override RenderObject createRenderObject(BuildContext context) {
return new RenderCustomSingleChildLayoutBox(layoutDelegate: this.layoutDelegate);
}
public override void updateRenderObject(BuildContext context, RenderObject renderObject) {
((RenderCustomSingleChildLayoutBox)renderObject).layoutDelegate = this.layoutDelegate;
}
}

4
Runtime/widgets/custom_paint.cs


namespace Unity.UIWidgets.widgets {
public abstract class CustomPainter : Listenable {
public CustomPainter(Listenable repaint) {
public CustomPainter(Listenable repaint = null) {
this._repaint = repaint;
}

public abstract bool shouldRepaint(CustomPainter oldDelegate);
public virtual bool hitTest(Offset position) {
return false;
return true;
}
public override string ToString() {

111
Runtime/widgets/editable_text.cs


public readonly bool autofocus;
public readonly Color selectionColor;
public readonly TextSelectionControls selectionControls;
public readonly ValueChanged<string> onChanged;
public readonly ValueChanged<string> onSubmitted;

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,
bool autofocus = false, Color selectionColor = null, TextSelectionControls selectionControls = null,
ValueChanged<string> onChanged = null,
ValueChanged<string> onSubmitted = null, SelectionChangedCallback onSelectionChanged = null,
List<TextInputFormatter> inputFormatters = null, bool rendererIgnoresPointer = false,
EdgeInsets scrollPadding = null,

this.onSubmitted = onSubmitted;
this.onSelectionChanged = onSelectionChanged;
this.rendererIgnoresPointer = rendererIgnoresPointer;
this.selectionControls = selectionControls;
if (maxLines == 1) {
this.inputFormatters = new List<TextInputFormatter>();
this.inputFormatters.Add(BlacklistingTextInputFormatter.singleLineFormatter);

}
}
public class EditableTextState : AutomaticKeepAliveClientMixin<EditableText>, TextInputClient {
public class EditableTextState : AutomaticKeepAliveClientMixin<EditableText>, TextInputClient, TextSelectionDelegate {
LayerLink _layerLink = new LayerLink();
TextSelectionOverlay _selectionOverlay;
int _obscureShowCharTicksPending = 0;
int _obscureLatestCharIndex;

base.initState();
this.widget.controller.addListener(this._didChangeTextEditingValue);
this.widget.focusNode.addListener(this._handleFocusChanged);
this._scrollController.addListener(() => { this._selectionOverlay?.updateForScroll(); });
}
public override void didChangeDependencies() {

D.assert(!this._hasInputConnection);
this._stopCursorTimer();
D.assert(this._cursorTimer == null);
this._selectionOverlay?.dispose();
this._selectionOverlay = null;
this.widget.focusNode.removeListener(this._handleFocusChanged);
base.dispose();
}

public void updateEditingValue(TextEditingValue value) {
if (value.text != this._value.text) {
// _hideSelectionOverlayIfNeeded();
this._hideSelectionOverlayIfNeeded();
this._showCaretOnScreen();
if (this.widget.obscureText && value.text.Length == this._value.text.Length + 1) {
this._obscureShowCharTicksPending = _kObscureShowLatestCharCursorTicks;
this._obscureLatestCharIndex = this._value.selection.baseOffset;

this._textInputConnection.setEditingState(localValue);
}
// Calculate the new scroll offset so the cursor remains visible.
double _getScrollOffsetForCaret(Rect caretRect) {
double caretStart = this._isMultiline ? caretRect.top : caretRect.left;

}
}
void _hideSelectionOverlayIfNeeded() {
this._selectionOverlay?.hide();
this._selectionOverlay = null;
}
void _updateOrDisposeSelectionOverlayIfNeeded() {
if (this._selectionOverlay != null) {
if (this._hasFocus) {
this._selectionOverlay.update(this._value);
} else {
this._selectionOverlay.dispose();
this._selectionOverlay = null;
}
}
}
this._hideSelectionOverlayIfNeeded();
if (this.widget.selectionControls != null) {
this._selectionOverlay = new TextSelectionOverlay(
context: this.context,
value: this._value,
debugRequiredFor: this.widget,
layerLink: this._layerLink,
renderObject: renderObject,
selectionControls: this.widget.selectionControls,
selectionDelegate: this
);
bool longPress = cause == SelectionChangedCause.longPress;
if (cause != SelectionChangedCause.keyboard && (this._value.text.isNotEmpty() || longPress)) this._selectionOverlay.showHandles();
if (longPress || cause == SelectionChangedCause.doubleTap) this._selectionOverlay.showToolbar();
}
if (this.widget.onSelectionChanged != null) {
this.widget.onSelectionChanged(selection, cause);
}

void _didChangeTextEditingValue() {
this._updateRemoteEditingValueIfNeeded();
this._startOrStopCursorTimerIfNeeded();
this._updateOrDisposeSelectionOverlayIfNeeded();
this._textChangedSinceLastCaretUpdate = true;
this.setState(() => { });
}

this._startOrStopCursorTimerIfNeeded();
this._updateOrDisposeSelectionOverlayIfNeeded();
if (!this._hasFocus) {
this._value = new TextEditingValue(text: this._value.text);
} else if (!this._value.selection.isValid) {

get { return (RenderEditable) this._editableKey.currentContext.findRenderObject(); }
}
public TextEditingValue textEditingValue {
get { return this._value; }
set {
this._selectionOverlay?.update(value);
this._formatAndSetValue(value);
}
}
public void bringIntoView(TextPosition position) {
this._scrollController.jumpTo(this._getScrollOffsetForCaret(this.renderEditable.getLocalRectForCaret(position)));
}
public void hideToolbar() {
this._selectionOverlay?.hide();
}
public override Widget build(BuildContext context) {
FocusScope.of(context).reparentIfNeeded(this.widget.focusNode);
base.build(context); // See AutomaticKeepAliveClientMixin.

controller: this._scrollController,
physics: new ClampingScrollPhysics(),
viewportBuilder: (BuildContext _context, ViewportOffset offset) =>
new _Editable(
key: this._editableKey,
textSpan: this.buildTextSpan(),
value: this._value,
cursorColor: this.widget.cursorColor,
showCursor: this._showCursor,
hasFocus: this._hasFocus,
maxLines: this.widget.maxLines,
selectionColor: this.widget.selectionColor,
textScaleFactor: Window.instance
.devicePixelRatio, // todo widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
textAlign: this.widget.textAlign,
textDirection: this._textDirection,
obscureText: this.widget.obscureText,
autocorrect: this.widget.autocorrect,
offset: offset,
onSelectionChanged: this._handleSelectionChanged,
onCaretChanged: this._handleCaretChanged,
rendererIgnoresPointer: this.widget.rendererIgnoresPointer
new CompositedTransformTarget(
link: this._layerLink,
child: new _Editable(
key: this._editableKey,
textSpan: this.buildTextSpan(),
value: this._value,
cursorColor: this.widget.cursorColor,
showCursor: this._showCursor,
hasFocus: this._hasFocus,
maxLines: this.widget.maxLines,
selectionColor: this.widget.selectionColor,
textScaleFactor: Window.instance
.devicePixelRatio, // todo widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
textAlign: this.widget.textAlign,
textDirection: this._textDirection,
obscureText: this.widget.obscureText,
autocorrect: this.widget.autocorrect,
offset: offset,
onSelectionChanged: this._handleSelectionChanged,
onCaretChanged: this._handleCaretChanged,
rendererIgnoresPointer: this.widget.rendererIgnoresPointer
)
);
}

2
Runtime/widgets/framework.cs


if (haveOldChildren) {
Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[key];
oldChild = oldKeyedChildren.getOrDefault(key);
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
oldKeyedChildren.Remove(key);

1
Runtime/widgets/page_view.cs


oldPosition: oldPosition
) {
D.assert(viewportFraction > 0.0);
this.initialPage = initialPage;
this._viewportFraction = viewportFraction;
this._pageToUseOnStartup = initialPage;
}

2
Samples/ReduxSample/ObjectFinder/ObjectFinderApp.cs


using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;

decoration: new BoxDecoration(border: Border.all(new Color(0xFF000000), 1)),
padding: EdgeInsets.only(left: 8, right: 8),
child: new EditableText(
selectionControls: MaterialUtils.materialTextSelectionControls,
controller: this._controller,
focusNode: this._focusNode,
style: new TextStyle(

2
Samples/ReduxSample/ObjectFinder/Reducer.cs


if (action is SearchResultAction) {
var resultAction = (SearchResultAction) action;
var selected = state.selected;
if (selected != null) {
if (selected != 0) {
var obj = resultAction.results.Find(o => o.id == selected);
if (obj == null) {
selected = 0;

2
Samples/UIWidgetSample/AsScreenCanvas.cs


using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;

margin: EdgeInsets.only(right: 4),
child: new EditableText(
maxLines: 1,
selectionControls: MaterialUtils.materialTextSelectionControls,
controller: new TextEditingController("Type here to search assets"),
focusNode: new FocusNode(),
style: new TextStyle(

13
Samples/UIWidgetSample/TextInputCanvas.cs


using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Unity.UIWidgets.material;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using TextStyle = Unity.UIWidgets.painting.TextStyle;

}
Widget title() {
return new Text(this.widget.title ?? "", textAlign: TextAlign.center,
style: new TextStyle(fontSize: 24, fontWeight: FontWeight.w700));
return new Container(child:new Text(this.widget.title ?? "", textAlign: TextAlign.center,
style: new TextStyle(fontSize: 24, fontWeight: FontWeight.w700)), margin:EdgeInsets.only(bottom:20));
}
Widget titleInput() {

padding: EdgeInsets.fromLTRB(8, 0, 8, 0),
child: new EditableText(maxLines: 1,
controller: this.titleController,
selectionControls: MaterialUtils.materialTextSelectionControls,
autofocus: true,
focusNode: new FocusNode(),
style: new TextStyle(

padding: EdgeInsets.fromLTRB(8, 0, 8, 0),
child: new EditableText(maxLines: 200,
controller: this.descController,
selectionControls: MaterialUtils.materialTextSelectionControls,
focusNode: new FocusNode(),
style: new TextStyle(
fontSize: 18,

}
}
}
}

2
Samples/UIWidgetSample/ToDoAppCanvas.cs


using Unity.UIWidgets.engine;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;

padding: EdgeInsets.fromLTRB(8, 0, 8, 0),
child: new EditableText(maxLines: 1,
controller: this.controller,
selectionControls: MaterialUtils.materialTextSelectionControls,
autofocus: true,
focusNode: new FocusNode(),
style: new TextStyle(

2
Tests/Editor/EditableTextWiget.cs


using Unity.UIWidgets.editor;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.widgets;
using UnityEditor;

color: Color.fromARGB(255, 244, 190, 85),
child: new EditableText(
maxLines: 100,
selectionControls: MaterialUtils.materialTextSelectionControls,
controller: new TextEditingController(this.txt),
focusNode: new FocusNode(),
style: new TextStyle(),

2
Tests/Editor/Gestures.cs


this._panRecognizer.onUpdate = (details) => { Debug.Log("onUpdate " + details); };
this._doubleTapGesture = new DoubleTapGestureRecognizer();
this._doubleTapGesture.onDoubleTap = () => { Debug.Log("onDoubleTap"); };
this._doubleTapGesture.onDoubleTap = (detail) => { Debug.Log("onDoubleTap"); };
}
void OnDisable() {

14
Tests/Editor/Widgets.cs


using Unity.UIWidgets.editor;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.material;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.ui;

}
void _attachRootWidget(Widget widget) {
this.windowAdapter.attachRootWidget(new WidgetsApp(window: this.windowAdapter, home: widget));
this.windowAdapter.attachRootWidget(new WidgetsApp(window: this.windowAdapter, home: widget,
pageRouteBuilder: (RouteSettings settings, WidgetBuilder builder) =>
new PageRouteBuilder(
settings: settings,
pageBuilder: (BuildContext context, Unity.UIWidgets.animation.Animation<double> animation,
Unity.UIWidgets.animation.Animation<double> secondaryAnimation) => builder(context)
)));
}
void Update() {

}
Widget asPage() {
return new WidgetsApp(home: new AsScreen(), window: this.windowAdapter);
return new AsScreen();
return new WidgetsApp(home: new MouseHoverWidget(), window: this.windowAdapter);
return new MouseHoverWidget();
}
}

margin: EdgeInsets.only(right: 4),
child: new EditableText(
maxLines: 1,
selectionControls: MaterialUtils.materialTextSelectionControls,
controller: new TextEditingController("Type here to search assets"),
focusNode: new FocusNode(),
style: new TextStyle(

42
Runtime/service/clipboard.cs


using RSG;
using UnityEngine;
namespace Unity.UIWidgets.service {
public class ClipboardData {
public ClipboardData(string text = null) {
this.text = text;
}
public readonly string text;
}
public abstract class Clipboard {
static readonly Clipboard _instance = new UnityGUIClipboard();
public static readonly string kTextPlain = "text/plain";
public static IPromise setData(ClipboardData data) {
return _instance.setClipboardData(data);
}
public static IPromise<ClipboardData> getData(string format) {
return _instance.getClipboardData(format);
}
protected abstract IPromise setClipboardData(ClipboardData data);
protected abstract IPromise<ClipboardData> getClipboardData(string format);
}
public class UnityGUIClipboard : Clipboard {
protected override IPromise setClipboardData(ClipboardData data) {
GUIUtility.systemCopyBuffer = data.text;
return Promise.Resolved();
}
protected override IPromise<ClipboardData> getClipboardData(string format) {
var data = new ClipboardData(text: GUIUtility.systemCopyBuffer);
return Promise<ClipboardData>.Resolved(data);
}
}
}

439
Runtime/widgets/text_selection.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.animation;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.scheduler;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.widgets {
public enum TextSelectionHandleType {
left,
right,
collapsed,
}
internal enum _TextSelectionHandlePosition {
start,
end
}
public delegate void TextSelectionOverlayChanged(TextEditingValue value, Rect caretRect);
public abstract class TextSelectionControls {
public abstract Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight);
public abstract Widget buildToolbar(BuildContext context, Rect globalEditableRegion, Offset position,
TextSelectionDelegate selectionDelegate);
public abstract Size handleSize { get; }
public virtual bool canCut(TextSelectionDelegate selectionDelegate) {
return !selectionDelegate.textEditingValue.selection.isCollapsed;
}
public virtual bool canCopy(TextSelectionDelegate selectionDelegate) {
return !selectionDelegate.textEditingValue.selection.isCollapsed;
}
public virtual bool canPaste(TextSelectionDelegate selectionDelegate) {
// TODO in flutter: return false when clipboard is empty
return true;
}
public virtual bool canSelectAll(TextSelectionDelegate selectionDelegate) {
return selectionDelegate.textEditingValue.text.isEmpty() &&
selectionDelegate.textEditingValue.selection.isCollapsed;
}
public void handleCut(TextSelectionDelegate selectionDelegate) {
TextEditingValue value = selectionDelegate.textEditingValue;
Clipboard.setData(new ClipboardData(
text: value.selection.textInside(value.text)
));
selectionDelegate.textEditingValue = new TextEditingValue(
text: value.selection.textBefore(value.text)
+ value.selection.textAfter(value.text),
selection: TextSelection.collapsed(
offset: value.selection.start
)
);
selectionDelegate.bringIntoView(selectionDelegate.textEditingValue.selection.extendPos);
selectionDelegate.hideToolbar();
}
public void handleCopy(TextSelectionDelegate selectionDelegate) {
TextEditingValue value = selectionDelegate.textEditingValue;
Clipboard.setData(new ClipboardData(
text: value.selection.textInside(value.text)
));
selectionDelegate.textEditingValue = new TextEditingValue(
text: value.text,
selection: TextSelection.collapsed(offset: value.selection.end)
);
selectionDelegate.bringIntoView(selectionDelegate.textEditingValue.selection.extendPos);
selectionDelegate.hideToolbar();
}
public void handlePaste(TextSelectionDelegate selectionDelegate) {
TextEditingValue value = selectionDelegate.textEditingValue; // Snapshot the input before using `await`.
Clipboard.getData(Clipboard.kTextPlain).Then((data) => {
if (data != null) {
selectionDelegate.textEditingValue = new TextEditingValue(
text: value.selection.textBefore(value.text)
+ data.text
+ value.selection.textAfter(value.text),
selection: TextSelection.collapsed(
offset: value.selection.start + data.text.Length
)
);
}
selectionDelegate.bringIntoView(selectionDelegate.textEditingValue.selection.extendPos);
selectionDelegate.hideToolbar();
});
}
public void handleSelectAll(TextSelectionDelegate selectionDelegate) {
selectionDelegate.textEditingValue = new TextEditingValue(
text: selectionDelegate.textEditingValue.text,
selection: new TextSelection(
baseOffset: 0,
extentOffset: selectionDelegate.textEditingValue.text.Length
)
);
selectionDelegate.bringIntoView(selectionDelegate.textEditingValue.selection.extendPos);
}
}
public class TextSelectionOverlay {
public TextSelectionOverlay(TextEditingValue value = null,
BuildContext context = null, Widget debugRequiredFor = null,
LayerLink layerLink = null,
RenderEditable renderObject = null,
TextSelectionControls selectionControls = null,
TextSelectionDelegate selectionDelegate = null) {
D.assert(value != null);
D.assert(context != null);
this.context = context;
this.debugRequiredFor = debugRequiredFor;
this.layerLink = layerLink;
this.renderObject = renderObject;
this.selectionControls = selectionControls;
this.selectionDelegate = selectionDelegate;
this._value = value;
OverlayState overlay = Overlay.of(context);
D.assert(overlay != null);
this._handleController = new AnimationController(duration: _fadeDuration, vsync: overlay);
this._toolbarController = new AnimationController(duration: _fadeDuration, vsync: overlay);
}
public readonly BuildContext context;
public readonly Widget debugRequiredFor;
public readonly LayerLink layerLink;
public readonly RenderEditable renderObject;
public readonly TextSelectionControls selectionControls;
public readonly TextSelectionDelegate selectionDelegate;
public static TimeSpan _fadeDuration = TimeSpan.FromMilliseconds(150);
AnimationController _handleController;
AnimationController _toolbarController;
Animation<double> _handleOpacity => this._handleController.view;
Animation<double> _toolbarOpacity => this._toolbarController.view;
TextEditingValue _value;
List<OverlayEntry> _handles;
OverlayEntry _toolbar;
TextSelection _selection => this._value.selection;
public void showHandles() {
D.assert(this._handles == null);
this._handles = new List<OverlayEntry> {
new OverlayEntry(builder: (BuildContext context) =>
this._buildHandle(context, _TextSelectionHandlePosition.start)),
new OverlayEntry(builder: (BuildContext context) =>
this._buildHandle(context, _TextSelectionHandlePosition.end)),
};
Overlay.of(this.context, debugRequiredFor: this.debugRequiredFor).insertAll(this._handles);
this._handleController.forward(from: 0.0);
}
public void showToolbar() {
D.assert(this._toolbar == null);
this._toolbar = new OverlayEntry(builder: this._buildToolbar);
Overlay.of(this.context, debugRequiredFor: this.debugRequiredFor).insert(this._toolbar);
this._toolbarController.forward(from: 0.0);
}
public void update(TextEditingValue newValue) {
if (this._value == newValue)
return;
this._value = newValue;
if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
SchedulerBinding.instance.addPostFrameCallback((duration) => this._markNeedsBuild());
}
else {
this._markNeedsBuild();
}
}
public void updateForScroll() {
this._markNeedsBuild();
}
void _markNeedsBuild() {
if (this._handles != null) {
this._handles[0].markNeedsBuild();
this._handles[1].markNeedsBuild();
}
this._toolbar?.markNeedsBuild();
}
public bool handlesAreVisible => this._handles != null;
public bool toolbarIsVisible => this._toolbar != null;
public void hide() {
if (this._handles != null) {
this._handles[0].remove();
this._handles[1].remove();
this._handles = null;
}
this._toolbar?.remove();
this._toolbar = null;
this._handleController.stop();
this._toolbarController.stop();
}
public void dispose() {
this.hide();
this._handleController.dispose();
this._toolbarController.dispose();
}
Widget _buildHandle(BuildContext context, _TextSelectionHandlePosition position) {
if ((this._selection.isCollapsed && position == _TextSelectionHandlePosition.end) ||
this.selectionControls == null)
return new Container(); // hide the second handle when collapsed
return new FadeTransition(
opacity: this._handleOpacity,
child: new _TextSelectionHandleOverlay(
onSelectionHandleChanged: (TextSelection newSelection) => {
this._handleSelectionHandleChanged(newSelection, position);
},
onSelectionHandleTapped: this._handleSelectionHandleTapped,
layerLink: this.layerLink,
renderObject: this.renderObject,
selection: this._selection,
selectionControls: this.selectionControls,
position: position
)
);
}
Widget _buildToolbar(BuildContext context) {
if (this.selectionControls == null)
return new Container();
// Find the horizontal midpoint, just above the selected text.
List<TextSelectionPoint> endpoints = this.renderObject.getEndpointsForSelection(this._selection);
Offset midpoint = new Offset(
(endpoints.Count == 1) ? endpoints[0].point.dx : (endpoints[0].point.dx + endpoints[1].point.dx) / 2.0,
endpoints[0].point.dy - this.renderObject.preferredLineHeight
);
Rect editingRegion = Rect.fromPoints(this.renderObject.localToGlobal(Offset.zero),
this.renderObject.localToGlobal(this.renderObject.size.bottomRight(Offset.zero))
);
return new FadeTransition(
opacity: this._toolbarOpacity,
child: new CompositedTransformFollower(
link: this.layerLink,
showWhenUnlinked: false,
offset: -editingRegion.topLeft,
child: this.selectionControls.buildToolbar(context, editingRegion, midpoint, this.selectionDelegate)
)
);
}
void _handleSelectionHandleChanged(TextSelection newSelection, _TextSelectionHandlePosition position) {
TextPosition textPosition = null;
switch (position) {
case _TextSelectionHandlePosition.start:
textPosition = newSelection.basePos;
break;
case _TextSelectionHandlePosition.end:
textPosition = newSelection.extendPos;
break;
}
this.selectionDelegate.textEditingValue =
this._value.copyWith(selection: newSelection, composing: TextRange.empty);
this.selectionDelegate.bringIntoView(textPosition);
}
void _handleSelectionHandleTapped() {
if (this._value.selection.isCollapsed) {
if (this._toolbar != null) {
this._toolbar?.remove();
this._toolbar = null;
}
else {
this.showToolbar();
}
}
}
}
internal class _TextSelectionHandleOverlay : StatefulWidget {
internal _TextSelectionHandleOverlay(
Key key = null,
TextSelection selection = null,
_TextSelectionHandlePosition position = _TextSelectionHandlePosition.start,
LayerLink layerLink = null,
RenderEditable renderObject = null,
ValueChanged<TextSelection> onSelectionHandleChanged = null,
VoidCallback onSelectionHandleTapped = null,
TextSelectionControls selectionControls = null
) : base(key: key) {
this.selection = selection;
this.position = position;
this.layerLink = layerLink;
this.renderObject = renderObject;
this.onSelectionHandleChanged = onSelectionHandleChanged;
this.onSelectionHandleTapped = onSelectionHandleTapped;
this.selectionControls = selectionControls;
}
public readonly TextSelection selection;
public readonly _TextSelectionHandlePosition position;
public readonly LayerLink layerLink;
public readonly RenderEditable renderObject;
public readonly ValueChanged<TextSelection> onSelectionHandleChanged;
public readonly VoidCallback onSelectionHandleTapped;
public readonly TextSelectionControls selectionControls;
public override State createState() {
return new _TextSelectionHandleOverlayState();
}
}
internal class _TextSelectionHandleOverlayState : State<_TextSelectionHandleOverlay> {
Offset _dragPosition;
void _handleDragStart(DragStartDetails details) {
this._dragPosition = details.globalPosition +
new Offset(0.0, -this.widget.selectionControls.handleSize.height);
}
void _handleDragUpdate(DragUpdateDetails details) {
this._dragPosition += details.delta;
TextPosition position = this.widget.renderObject.getPositionForPoint(this._dragPosition);
if (this.widget.selection.isCollapsed) {
this.widget.onSelectionHandleChanged(TextSelection.fromPosition(position));
return;
}
TextSelection newSelection = null;
switch (this.widget.position) {
case _TextSelectionHandlePosition.start:
newSelection = new TextSelection(
baseOffset: position.offset,
extentOffset: this.widget.selection.extentOffset
);
break;
case _TextSelectionHandlePosition.end:
newSelection = new TextSelection(
baseOffset: this.widget.selection.baseOffset,
extentOffset: position.offset
);
break;
}
if (newSelection.baseOffset >= newSelection.extentOffset)
return; // don't allow order swapping.
this.widget.onSelectionHandleChanged(newSelection);
}
void _handleTap() {
this.widget.onSelectionHandleTapped();
}
public override Widget build(BuildContext context) {
List<TextSelectionPoint> endpoints =
this.widget.renderObject.getEndpointsForSelection(this.widget.selection);
Offset point = null;
TextSelectionHandleType type = TextSelectionHandleType.left;
switch (this.widget.position) {
case _TextSelectionHandlePosition.start:
point = endpoints[0].point;
type = this._chooseType(endpoints[0], TextSelectionHandleType.left, TextSelectionHandleType.right);
break;
case _TextSelectionHandlePosition.end:
D.assert(endpoints.Count == 2);
point = endpoints[1].point;
type = this._chooseType(endpoints[1], TextSelectionHandleType.right, TextSelectionHandleType.left);
break;
}
return new CompositedTransformFollower(
link: this.widget.layerLink,
showWhenUnlinked: false,
child: new GestureDetector(
onPanStart: this._handleDragStart,
onPanUpdate: this._handleDragUpdate,
onTap: this._handleTap,
child: new Stack(
overflow: Overflow.visible,
children: new List<Widget>() {
new Positioned(
left: point.dx,
top: point.dy,
child: this.widget.selectionControls.buildHandle(context, type,
this.widget.renderObject.preferredLineHeight)
)
}
)
)
);
}
TextSelectionHandleType _chooseType(
TextSelectionPoint endpoint,
TextSelectionHandleType ltrType,
TextSelectionHandleType rtlType
) {
if (this.widget.selection.isCollapsed)
return TextSelectionHandleType.collapsed;
D.assert(endpoint.direction != null);
switch (endpoint.direction) {
case TextDirection.ltr:
return ltrType;
case TextDirection.rtl:
return rtlType;
}
D.assert(() => throw new UIWidgetsError($"invalid endpoint.direction {endpoint.direction}"));
return ltrType;
}
}
}

221
Runtime/material/text_selection.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.gestures;
using Unity.UIWidgets.painting;
using Unity.UIWidgets.rendering;
using Unity.UIWidgets.service;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
// todo using material components: FlatButton & Material ...
namespace Unity.UIWidgets.material {
public static class MaterialUtils {
public readonly static TextSelectionControls materialTextSelectionControls =
new _MaterialTextSelectionControls();
}
internal static class _TextSelectionUtils {
internal const double _kHandleSize = 22.0;
internal const double _kToolbarScreenPadding = 8.0;
}
internal class _TextSelectionToolbar : StatelessWidget {
public _TextSelectionToolbar(Key key = null, Action handleCut = null,
Action handleCopy = null, Action handlePaste = null, Action handleSelectAll = null) : base(key: key) {
this.handleCut = handleCut;
this.handleCopy = handleCopy;
this.handlePaste = handlePaste;
this.handleSelectAll = handleSelectAll;
}
public readonly Action handleCut;
public readonly Action handleCopy;
public readonly Action handlePaste;
public readonly Action handleSelectAll;
public override Widget build(BuildContext context) {
List<Widget> items = new List<Widget>();
if (this.handleCut != null) {
items.Add(new _TempButton(onPressed: () => this.handleCut(), child: new Text("Cut")));
}
if (this.handleCopy != null) {
items.Add(new _TempButton(onPressed: () => this.handleCopy(), child: new Text("Copy")));
}
if (this.handlePaste != null) {
items.Add(new _TempButton(onPressed: () => this.handlePaste(), child: new Text("Past")));
}
if (this.handleSelectAll != null) {
items.Add(new _TempButton(onPressed: () => this.handleSelectAll(), child: new Text("Select All")));
}
return new Container(
color: new Color(0xFFEFEFEF),
height: 44.0, child: new Row(mainAxisSize: MainAxisSize.min, children: items));
}
}
internal class _TextSelectionToolbarLayout : SingleChildLayoutDelegate {
internal _TextSelectionToolbarLayout(Size screenSize = null, Rect globalEditableRegion = null,
Offset position = null) {
this.screenSize = screenSize;
this.globalEditableRegion = globalEditableRegion;
this.position = position;
}
public readonly Size screenSize;
public readonly Rect globalEditableRegion;
public readonly Offset position;
public override BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
return constraints.loosen();
}
public override Offset getPositionForChild(Size size, Size childSize) {
Offset globalPosition = this.globalEditableRegion.topLeft + this.position;
double x = globalPosition.dx - childSize.width / 2.0;
double y = globalPosition.dy - childSize.height;
if (x < _TextSelectionUtils._kToolbarScreenPadding)
x = _TextSelectionUtils._kToolbarScreenPadding;
else if (x + childSize.width > this.screenSize.width - _TextSelectionUtils._kToolbarScreenPadding)
x = this.screenSize.width - childSize.width - _TextSelectionUtils._kToolbarScreenPadding;
if (y < _TextSelectionUtils._kToolbarScreenPadding)
y = _TextSelectionUtils._kToolbarScreenPadding;
else if (y + childSize.height > this.screenSize.height - _TextSelectionUtils._kToolbarScreenPadding)
y = this.screenSize.height - childSize.height - _TextSelectionUtils._kToolbarScreenPadding;
return new Offset(x, y);
}
public override bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
return this.position != ((_TextSelectionToolbarLayout) oldDelegate).position;
}
}
internal class _TextSelectionHandlePainter : CustomPainter {
internal _TextSelectionHandlePainter(Color color) {
this.color = color;
}
public readonly Color color;
public override void paint(Canvas canvas, Size size) {
Paint paint = new Paint();
paint.color = this.color;
double radius = size.width / 2.0;
canvas.drawCircle(new Offset(radius, radius), radius, paint);
canvas.drawRect(Rect.fromLTWH(0.0, 0.0, radius, radius), paint);
}
public override bool shouldRepaint(CustomPainter oldPainter) {
return this.color != ((_TextSelectionHandlePainter) oldPainter).color;
}
}
internal class _MaterialTextSelectionControls : TextSelectionControls {
public override Size handleSize {
get => new Size(_TextSelectionUtils._kHandleSize,
_TextSelectionUtils._kHandleSize);
}
public override Widget buildToolbar(BuildContext context, Rect globalEditableRegion, Offset position,
TextSelectionDelegate selectionDelegate) {
return new ConstrainedBox(
constraints: BoxConstraints.tight(globalEditableRegion.size),
child: new CustomSingleChildLayout(
layoutDelegate: new _TextSelectionToolbarLayout(
MediaQuery.of(context).size,
globalEditableRegion,
position
),
child: new _TextSelectionToolbar(
handleCut: this.canCut(selectionDelegate)
? () => this.handleCut(selectionDelegate)
: (Action) null,
handleCopy: this.canCopy(selectionDelegate)
? () => this.handleCopy(selectionDelegate)
: (Action) null,
handlePaste: this.canPaste(selectionDelegate)
? () => this.handlePaste(selectionDelegate)
: (Action) null,
handleSelectAll: this.canSelectAll(selectionDelegate)
? () => this.handleSelectAll(selectionDelegate)
: (Action) null
)
)
);
}
public override Widget buildHandle(BuildContext context, TextSelectionHandleType type, double textLineHeight) {
Widget handle = new Padding(
padding: EdgeInsets.only(right: 26.0, bottom: 26.0),
child: new SizedBox(
width: 20,
height: 20,
child: new CustomPaint(
painter: new _TextSelectionHandlePainter(
color: new Color(0xFFFF0000)
)
)
)
);
switch (type) {
case TextSelectionHandleType.left: // points up-right
return new Transform(
transform: Matrix3.makeRotate(90),
child: handle
);
case TextSelectionHandleType.right: // points up-left
return handle;
case TextSelectionHandleType.collapsed: // points up
return new Transform(
transform: Matrix3.makeRotate(45),
child: handle
);
}
return null;
}
}
public class _TempButton : StatelessWidget {
public _TempButton(
Key key = null,
GestureTapCallback onPressed = null,
EdgeInsets padding = null,
Color backgroundColor = null,
Widget child = null
) : base(key: key) {
this.onPressed = onPressed;
this.padding = padding ?? EdgeInsets.all(8.0);
this.backgroundColor = backgroundColor ?? new Color(0);
this.child = child;
}
public readonly GestureTapCallback onPressed;
public readonly EdgeInsets padding;
public readonly Widget child;
public readonly Color backgroundColor;
public override Widget build(BuildContext context) {
return new GestureDetector(
onTap: this.onPressed,
child: new Container(
padding: this.padding,
color: this.backgroundColor,
child: this.child
)
);
}
}
}
正在加载...
取消
保存