您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
484 行
18 KiB
484 行
18 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using RSG;
|
|
using Unity.UIWidgets.async;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.material;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.ui;
|
|
using Color = Unity.UIWidgets.ui.Color;
|
|
using Constants = Unity.UIWidgets.gestures.Constants;
|
|
using TextStyle = Unity.UIWidgets.painting.TextStyle;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public class SelectableText : StatefulWidget {
|
|
public SelectableText(string data,
|
|
Key key = null,
|
|
TextStyle style = null,
|
|
TextAlign? textAlign = null,
|
|
bool? softWrap = null,
|
|
TextOverflow? overflow = null,
|
|
float? textScaleFactor = null,
|
|
int? maxLines = null,
|
|
FocusNode focusNode = null,
|
|
Color selectionColor = null,
|
|
GestureTapDownCallback onTapDown = null,
|
|
GestureTapUpCallback onTapUp = null,
|
|
GestureTapCancelCallback onTapCancel = null) : base(key) {
|
|
D.assert(data != null);
|
|
this.textSpan = null;
|
|
this.data = data;
|
|
this.style = style;
|
|
this.textAlign = textAlign;
|
|
this.softWrap = softWrap;
|
|
this.overflow = overflow;
|
|
this.textScaleFactor = textScaleFactor;
|
|
this.maxLines = maxLines;
|
|
this.focusNode = focusNode ?? new FocusNode();
|
|
this.selectionColor = selectionColor;
|
|
this.onTapDown = onTapDown;
|
|
this.onTapUp = onTapUp;
|
|
this.onTapCancel = onTapCancel;
|
|
}
|
|
|
|
public SelectableText(TextSpan textSpan,
|
|
Key key = null,
|
|
TextStyle style = null,
|
|
TextAlign? textAlign = null,
|
|
bool? softWrap = null,
|
|
TextOverflow? overflow = null,
|
|
float? textScaleFactor = null,
|
|
int? maxLines = null,
|
|
FocusNode focusNode = null,
|
|
Color selectionColor = null,
|
|
GestureTapDownCallback onTapDown = null,
|
|
GestureTapUpCallback onTapUp = null,
|
|
GestureTapCancelCallback onTapCancel = null) : base(key) {
|
|
D.assert(textSpan != null);
|
|
this.textSpan = textSpan;
|
|
this.data = null;
|
|
this.style = style;
|
|
this.textAlign = textAlign;
|
|
this.softWrap = softWrap;
|
|
this.overflow = overflow;
|
|
this.textScaleFactor = textScaleFactor;
|
|
this.maxLines = maxLines;
|
|
this.focusNode = focusNode ?? new FocusNode();
|
|
this.selectionColor = selectionColor;
|
|
this.onTapDown = onTapDown;
|
|
this.onTapUp = onTapUp;
|
|
this.onTapCancel = onTapCancel;
|
|
}
|
|
|
|
public static SelectableText rich(TextSpan textSpan,
|
|
Key key = null,
|
|
TextStyle style = null,
|
|
TextAlign? textAlign = null,
|
|
bool? softWrap = null,
|
|
TextOverflow? overflow = null,
|
|
float? textScaleFactor = null,
|
|
int? maxLines = null,
|
|
FocusNode focusNode = null,
|
|
Color selectionColor = null,
|
|
GestureTapDownCallback onTapDown = null,
|
|
GestureTapUpCallback onTapUp = null,
|
|
GestureTapCancelCallback onTapCancel = null) {
|
|
return new SelectableText(
|
|
textSpan, key,
|
|
style,
|
|
textAlign,
|
|
softWrap,
|
|
overflow,
|
|
textScaleFactor,
|
|
maxLines,
|
|
focusNode,
|
|
selectionColor,
|
|
onTapDown,
|
|
onTapUp,
|
|
onTapCancel);
|
|
}
|
|
|
|
public readonly string data;
|
|
|
|
public readonly FocusNode focusNode;
|
|
|
|
public readonly TextSpan textSpan;
|
|
|
|
public readonly TextStyle style;
|
|
|
|
public readonly TextAlign? textAlign;
|
|
|
|
public readonly bool? softWrap;
|
|
|
|
public readonly TextOverflow? overflow;
|
|
|
|
public readonly float? textScaleFactor;
|
|
|
|
public readonly int? maxLines;
|
|
|
|
public readonly Color selectionColor;
|
|
|
|
public readonly GestureTapDownCallback onTapDown;
|
|
|
|
public readonly GestureTapUpCallback onTapUp;
|
|
|
|
public readonly GestureTapCancelCallback onTapCancel;
|
|
|
|
public override State createState() {
|
|
return new _SelectableTextState();
|
|
}
|
|
}
|
|
|
|
|
|
class _SelectableTextState : State<SelectableText>, WidgetsBindingObserver {
|
|
readonly GlobalKey _richTextKey = GlobalKey.key();
|
|
|
|
RenderParagraph _renderParagragh {
|
|
get { return (RenderParagraph) this._richTextKey.currentContext.findRenderObject(); }
|
|
}
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
this.widget.focusNode.addListener(this._handleFocusChanged);
|
|
}
|
|
|
|
|
|
public override void didUpdateWidget(StatefulWidget old) {
|
|
SelectableText oldWidget = (SelectableText) old;
|
|
base.didUpdateWidget(oldWidget);
|
|
|
|
if (oldWidget.focusNode != this.widget.focusNode) {
|
|
oldWidget.focusNode.removeListener(this._handleFocusChanged);
|
|
this.widget.focusNode.addListener(this._handleFocusChanged);
|
|
}
|
|
}
|
|
|
|
public override void dispose() {
|
|
this.widget.focusNode.removeListener(this._handleFocusChanged);
|
|
base.dispose();
|
|
}
|
|
|
|
bool _hasFocus {
|
|
get { return this.widget.focusNode.hasFocus; }
|
|
}
|
|
|
|
void _handleFocusChanged() {
|
|
if (this._hasFocus) {
|
|
WidgetsBinding.instance.addObserver(this);
|
|
this._renderParagragh.hasFocus = true;
|
|
}
|
|
else {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
this._renderParagragh.hasFocus = false;
|
|
}
|
|
}
|
|
|
|
|
|
public void didChangeMetrics() {
|
|
}
|
|
|
|
public void didChangeTextScaleFactor() {
|
|
}
|
|
|
|
public void didChangePlatformBrightness() {
|
|
}
|
|
|
|
public void didChangeLocales(List<Locale> locale) {
|
|
}
|
|
|
|
public IPromise<bool> didPopRoute() {
|
|
return Promise<bool>.Resolved(false);
|
|
}
|
|
|
|
public IPromise<bool> didPushRoute(string route) {
|
|
return Promise<bool>.Resolved(false);
|
|
}
|
|
|
|
void _handleTapDown(TapDownDetails details) {
|
|
this.widget.onTapDown?.Invoke(details);
|
|
}
|
|
|
|
void _handleSingleTapUp(TapUpDetails details) {
|
|
this.widget.onTapUp?.Invoke(details);
|
|
}
|
|
|
|
void _handleSingleTapCancel() {
|
|
this.widget.onTapCancel?.Invoke();
|
|
}
|
|
|
|
void _handleLongPress() {
|
|
}
|
|
|
|
void _handleDragSelectionStart(DragStartDetails details) {
|
|
this._renderParagragh.selectPositionAt(
|
|
from: details.globalPosition,
|
|
cause: SelectionChangedCause.drag);
|
|
}
|
|
|
|
void _handleDragSelectionUpdate(DragStartDetails startDetails,
|
|
DragUpdateDetails updateDetails) {
|
|
this._renderParagragh.selectPositionAt(
|
|
from: startDetails.globalPosition,
|
|
to: updateDetails.globalPosition,
|
|
cause: SelectionChangedCause.drag);
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
FocusScope.of(context).reparentIfNeeded(this.widget.focusNode);
|
|
|
|
DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
|
TextStyle effectiveTextStyle = this.widget.style;
|
|
if (this.widget.style == null || this.widget.style.inherit) {
|
|
effectiveTextStyle = defaultTextStyle.style.merge(this.widget.style);
|
|
}
|
|
|
|
Widget child = new RichText(
|
|
key: this._richTextKey,
|
|
textAlign: this.widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.left,
|
|
softWrap: this.widget.softWrap ?? defaultTextStyle.softWrap,
|
|
overflow: this.widget.overflow ?? defaultTextStyle.overflow,
|
|
textScaleFactor: this.widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
|
maxLines: this.widget.maxLines ?? defaultTextStyle.maxLines,
|
|
text: new TextSpan(
|
|
style: effectiveTextStyle,
|
|
text: this.widget.data,
|
|
children: this.widget.textSpan != null ? new List<TextSpan> {this.widget.textSpan} : null
|
|
),
|
|
onSelectionChanged: () => {
|
|
if (this._hasFocus) {
|
|
return;
|
|
}
|
|
|
|
FocusScope.of(this.context).requestFocus(this.widget.focusNode);
|
|
},
|
|
selectionColor: this.widget.selectionColor ?? Colors.blue);
|
|
|
|
return new IgnorePointer(
|
|
ignoring: false,
|
|
child: new RichTextSelectionGestureDetector(
|
|
onTapDown: this._handleTapDown,
|
|
onSingleTapUp: this._handleSingleTapUp,
|
|
onSingleTapCancel: this._handleSingleTapCancel,
|
|
onSingleLongTapStart: this._handleLongPress,
|
|
onDragSelectionStart: this._handleDragSelectionStart,
|
|
onDragSelectionUpdate: this._handleDragSelectionUpdate,
|
|
behavior: HitTestBehavior.translucent,
|
|
child: child
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public class RichTextSelectionGestureDetector : StatefulWidget {
|
|
public RichTextSelectionGestureDetector(
|
|
Key key = null,
|
|
GestureTapDownCallback onTapDown = null,
|
|
GestureTapUpCallback onSingleTapUp = null,
|
|
GestureTapCancelCallback onSingleTapCancel = null,
|
|
GestureLongPressCallback onSingleLongTapStart = null,
|
|
GestureTapDownCallback onDoubleTapDown = null,
|
|
GestureDragStartCallback onDragSelectionStart = null,
|
|
DragSelectionUpdateCallback onDragSelectionUpdate = null,
|
|
GestureDragEndCallback onDragSelectionEnd = null,
|
|
HitTestBehavior? behavior = null,
|
|
Widget child = null
|
|
) : base(key: key) {
|
|
D.assert(child != null);
|
|
this.onTapDown = onTapDown;
|
|
this.onSingleTapUp = onSingleTapUp;
|
|
this.onSingleTapCancel = onSingleTapCancel;
|
|
this.onSingleLongTapStart = onSingleLongTapStart;
|
|
this.onDoubleTapDown = onDoubleTapDown;
|
|
this.onDragSelectionStart = onDragSelectionStart;
|
|
this.onDragSelectionUpdate = onDragSelectionUpdate;
|
|
this.onDragSelectionEnd = onDragSelectionEnd;
|
|
this.behavior = behavior;
|
|
this.child = child;
|
|
}
|
|
|
|
public readonly GestureTapDownCallback onTapDown;
|
|
|
|
public readonly GestureTapUpCallback onSingleTapUp;
|
|
|
|
public readonly GestureTapCancelCallback onSingleTapCancel;
|
|
|
|
public readonly GestureLongPressCallback onSingleLongTapStart;
|
|
|
|
public readonly GestureTapDownCallback onDoubleTapDown;
|
|
|
|
public readonly GestureDragStartCallback onDragSelectionStart;
|
|
|
|
public readonly DragSelectionUpdateCallback onDragSelectionUpdate;
|
|
|
|
public readonly GestureDragEndCallback onDragSelectionEnd;
|
|
|
|
public HitTestBehavior? behavior;
|
|
|
|
public readonly Widget child;
|
|
|
|
public override State createState() {
|
|
return new _RichTextSelectionGestureDetectorState();
|
|
}
|
|
}
|
|
|
|
class _RichTextSelectionGestureDetectorState : State<RichTextSelectionGestureDetector> {
|
|
Timer _doubleTapTimer;
|
|
Offset _lastTapOffset;
|
|
|
|
bool _isDoubleTap = false;
|
|
|
|
public override void dispose() {
|
|
this._doubleTapTimer?.cancel();
|
|
this._dragUpdateThrottleTimer?.cancel();
|
|
base.dispose();
|
|
}
|
|
|
|
void _handleTapDown(TapDownDetails details) {
|
|
if (this.widget.onTapDown != null) {
|
|
this.widget.onTapDown(details);
|
|
}
|
|
|
|
if (this._doubleTapTimer != null &&
|
|
this._isWithinDoubleTapTolerance(details.globalPosition)) {
|
|
if (this.widget.onDoubleTapDown != null) {
|
|
this.widget.onDoubleTapDown(details);
|
|
}
|
|
|
|
this._doubleTapTimer.cancel();
|
|
this._doubleTapTimeout();
|
|
this._isDoubleTap = true;
|
|
}
|
|
}
|
|
|
|
void _handleTapUp(TapUpDetails details) {
|
|
if (!this._isDoubleTap) {
|
|
if (this.widget.onSingleTapUp != null) {
|
|
this.widget.onSingleTapUp(details);
|
|
}
|
|
|
|
this._lastTapOffset = details.globalPosition;
|
|
this._doubleTapTimer = Window.instance.run(Constants.kDoubleTapTimeout, this._doubleTapTimeout);
|
|
}
|
|
|
|
this._isDoubleTap = false;
|
|
}
|
|
|
|
void _handleTapCancel() {
|
|
if (this.widget.onSingleTapCancel != null) {
|
|
this.widget.onSingleTapCancel();
|
|
}
|
|
}
|
|
|
|
DragStartDetails _lastDragStartDetails;
|
|
DragUpdateDetails _lastDragUpdateDetails;
|
|
Timer _dragUpdateThrottleTimer;
|
|
|
|
void _handleDragStart(DragStartDetails details) {
|
|
D.assert(this._lastDragStartDetails == null);
|
|
this._lastDragStartDetails = details;
|
|
if (this.widget.onDragSelectionStart != null) {
|
|
this.widget.onDragSelectionStart(details);
|
|
}
|
|
}
|
|
|
|
void _handleDragUpdate(DragUpdateDetails details) {
|
|
this._lastDragUpdateDetails = details;
|
|
this._dragUpdateThrottleTimer = this._dragUpdateThrottleTimer ??
|
|
Window.instance.run(TextSelectionUtils._kDragSelectionUpdateThrottle,
|
|
this._handleDragUpdateThrottled);
|
|
}
|
|
|
|
void _handleDragUpdateThrottled() {
|
|
D.assert(this._lastDragStartDetails != null);
|
|
D.assert(this._lastDragUpdateDetails != null);
|
|
if (this.widget.onDragSelectionUpdate != null) {
|
|
this.widget.onDragSelectionUpdate(this._lastDragStartDetails, this._lastDragUpdateDetails);
|
|
}
|
|
|
|
this._dragUpdateThrottleTimer = null;
|
|
this._lastDragUpdateDetails = null;
|
|
}
|
|
|
|
void _handleDragEnd(DragEndDetails details) {
|
|
D.assert(this._lastDragStartDetails != null);
|
|
if (this._lastDragUpdateDetails != null) {
|
|
this._dragUpdateThrottleTimer.cancel();
|
|
this._handleDragUpdateThrottled();
|
|
}
|
|
|
|
if (this.widget.onDragSelectionEnd != null) {
|
|
this.widget.onDragSelectionEnd(details);
|
|
}
|
|
|
|
this._dragUpdateThrottleTimer = null;
|
|
this._lastDragStartDetails = null;
|
|
this._lastDragUpdateDetails = null;
|
|
}
|
|
|
|
void _handleLongPressStart() {
|
|
if (!this._isDoubleTap && this.widget.onSingleLongTapStart != null) {
|
|
this.widget.onSingleLongTapStart();
|
|
}
|
|
}
|
|
|
|
void _doubleTapTimeout() {
|
|
this._doubleTapTimer = null;
|
|
this._lastTapOffset = null;
|
|
}
|
|
|
|
bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
|
|
D.assert(secondTapOffset != null);
|
|
if (this._lastTapOffset == null) {
|
|
return false;
|
|
}
|
|
|
|
Offset difference = secondTapOffset - this._lastTapOffset;
|
|
return difference.distance <= Constants.kDoubleTapSlop;
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Dictionary<Type, GestureRecognizerFactory> gestures = new Dictionary<Type, GestureRecognizerFactory>();
|
|
|
|
gestures.Add(typeof(TapGestureRecognizer), new GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
|
() => new TapGestureRecognizer(debugOwner: this),
|
|
instance => {
|
|
instance.onTapDown = this._handleTapDown;
|
|
instance.onTapUp = this._handleTapUp;
|
|
instance.onTapCancel = this._handleTapCancel;
|
|
}
|
|
)
|
|
);
|
|
|
|
if (this.widget.onSingleLongTapStart != null) {
|
|
gestures[typeof(LongPressGestureRecognizer)] =
|
|
new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
|
() => new LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
|
|
instance => { instance.onLongPress = this._handleLongPressStart; });
|
|
}
|
|
|
|
if (this.widget.onDragSelectionStart != null ||
|
|
this.widget.onDragSelectionUpdate != null ||
|
|
this.widget.onDragSelectionEnd != null) {
|
|
gestures.Add(typeof(PanGestureRecognizer),
|
|
new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
|
() => new PanGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.mouse),
|
|
instance => {
|
|
instance.dragStartBehavior = DragStartBehavior.down;
|
|
instance.onStart = this._handleDragStart;
|
|
instance.onUpdate = this._handleDragUpdate;
|
|
instance.onEnd = this._handleDragEnd;
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
return new RawGestureDetector(
|
|
gestures: gestures,
|
|
behavior: this.widget.behavior,
|
|
child: this.widget.child
|
|
);
|
|
}
|
|
}
|
|
}
|