您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
485 行
17 KiB
485 行
17 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.UIWidgets.async2;
|
|
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);
|
|
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;
|
|
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) _richTextKey.currentContext.findRenderObject(); }
|
|
}
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
widget.focusNode.addListener(_handleFocusChanged);
|
|
}
|
|
|
|
|
|
public override void didUpdateWidget(StatefulWidget old) {
|
|
SelectableText oldWidget = (SelectableText) old;
|
|
base.didUpdateWidget(oldWidget);
|
|
|
|
if (oldWidget.focusNode != widget.focusNode) {
|
|
oldWidget.focusNode.removeListener(_handleFocusChanged);
|
|
widget.focusNode.addListener(_handleFocusChanged);
|
|
}
|
|
}
|
|
|
|
public override void dispose() {
|
|
widget.focusNode.removeListener(_handleFocusChanged);
|
|
base.dispose();
|
|
}
|
|
|
|
bool _hasFocus {
|
|
get { return widget.focusNode.hasFocus; }
|
|
}
|
|
|
|
void _handleFocusChanged() {
|
|
if (_hasFocus) {
|
|
WidgetsBinding.instance.addObserver(this);
|
|
_renderParagragh.hasFocus = true;
|
|
}
|
|
else {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
_renderParagragh.hasFocus = false;
|
|
}
|
|
}
|
|
|
|
|
|
public void didChangeMetrics() {
|
|
}
|
|
|
|
public void didChangeTextScaleFactor() {
|
|
}
|
|
|
|
public void didChangePlatformBrightness() {
|
|
}
|
|
|
|
public void didChangeLocales(List<Locale> locale) {
|
|
}
|
|
|
|
public Future<bool> didPopRoute() {
|
|
return Future<bool>.value(false).to<bool>();
|
|
}
|
|
|
|
public Future<bool> didPushRoute(string route) {
|
|
return Future<bool>.value(false).to<bool>();
|
|
}
|
|
|
|
void _handleTapDown(TapDownDetails details) {
|
|
widget.onTapDown?.Invoke(details);
|
|
}
|
|
|
|
void _handleSingleTapUp(TapUpDetails details) {
|
|
widget.onTapUp?.Invoke(details);
|
|
}
|
|
|
|
void _handleSingleTapCancel() {
|
|
widget.onTapCancel?.Invoke();
|
|
}
|
|
|
|
void _handleLongPress() {
|
|
}
|
|
|
|
void _handleDragSelectionStart(DragStartDetails details) {
|
|
_renderParagragh.selectPositionAt(
|
|
from: details.globalPosition,
|
|
cause: SelectionChangedCause.drag);
|
|
}
|
|
|
|
void _handleDragSelectionUpdate(DragStartDetails startDetails,
|
|
DragUpdateDetails updateDetails) {
|
|
_renderParagragh.selectPositionAt(
|
|
from: startDetails.globalPosition,
|
|
to: updateDetails.globalPosition,
|
|
cause: SelectionChangedCause.drag);
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
|
|
|
|
DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
|
TextStyle effectiveTextStyle = widget.style;
|
|
if (widget.style == null || widget.style.inherit) {
|
|
effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
|
|
}
|
|
|
|
Widget child = new RichText(
|
|
key: _richTextKey,
|
|
textAlign: widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.left,
|
|
softWrap: widget.softWrap ?? defaultTextStyle.softWrap,
|
|
overflow: widget.overflow ?? defaultTextStyle.overflow,
|
|
textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
|
|
maxLines: widget.maxLines ?? defaultTextStyle.maxLines,
|
|
text: new TextSpan(
|
|
style: effectiveTextStyle,
|
|
text: widget.data,
|
|
children: widget.textSpan != null ? new List<TextSpan> {widget.textSpan} : null
|
|
),
|
|
onSelectionChanged: () => {
|
|
if (_hasFocus) {
|
|
return;
|
|
}
|
|
|
|
FocusScope.of(this.context).requestFocus(widget.focusNode);
|
|
},
|
|
selectionColor: widget.selectionColor ?? Colors.blue);
|
|
|
|
return new IgnorePointer(
|
|
ignoring: false,
|
|
child: new RichTextSelectionGestureDetector(
|
|
onTapDown: _handleTapDown,
|
|
onSingleTapUp: _handleSingleTapUp,
|
|
onSingleTapCancel: _handleSingleTapCancel,
|
|
onSingleLongTapStart: _handleLongPress,
|
|
onDragSelectionStart: _handleDragSelectionStart,
|
|
onDragSelectionUpdate: _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() {
|
|
_doubleTapTimer?.cancel();
|
|
_dragUpdateThrottleTimer?.cancel();
|
|
base.dispose();
|
|
}
|
|
|
|
void _handleTapDown(TapDownDetails details) {
|
|
if (widget.onTapDown != null) {
|
|
widget.onTapDown(details);
|
|
}
|
|
|
|
if (_doubleTapTimer != null &&
|
|
_isWithinDoubleTapTolerance(details.globalPosition)) {
|
|
if (widget.onDoubleTapDown != null) {
|
|
widget.onDoubleTapDown(details);
|
|
}
|
|
|
|
_doubleTapTimer.cancel();
|
|
_doubleTapTimeout();
|
|
_isDoubleTap = true;
|
|
}
|
|
}
|
|
|
|
void _handleTapUp(TapUpDetails details) {
|
|
if (!_isDoubleTap) {
|
|
if (widget.onSingleTapUp != null) {
|
|
widget.onSingleTapUp(details);
|
|
}
|
|
|
|
_lastTapOffset = details.globalPosition;
|
|
_doubleTapTimer = Timer.create(Constants.kDoubleTapTimeout, _doubleTapTimeout);
|
|
}
|
|
|
|
_isDoubleTap = false;
|
|
}
|
|
|
|
void _handleTapCancel() {
|
|
if (widget.onSingleTapCancel != null) {
|
|
widget.onSingleTapCancel();
|
|
}
|
|
}
|
|
|
|
DragStartDetails _lastDragStartDetails;
|
|
DragUpdateDetails _lastDragUpdateDetails;
|
|
Timer _dragUpdateThrottleTimer;
|
|
|
|
void _handleDragStart(DragStartDetails details) {
|
|
D.assert(_lastDragStartDetails == null);
|
|
_lastDragStartDetails = details;
|
|
if (widget.onDragSelectionStart != null) {
|
|
widget.onDragSelectionStart(details);
|
|
}
|
|
}
|
|
|
|
void _handleDragUpdate(DragUpdateDetails details) {
|
|
_lastDragUpdateDetails = details;
|
|
_dragUpdateThrottleTimer = _dragUpdateThrottleTimer ??
|
|
Timer.create(TextSelectionUtils._kDragSelectionUpdateThrottle,
|
|
_handleDragUpdateThrottled);
|
|
}
|
|
|
|
object _handleDragUpdateThrottled() {
|
|
D.assert(_lastDragStartDetails != null);
|
|
D.assert(_lastDragUpdateDetails != null);
|
|
if (widget.onDragSelectionUpdate != null) {
|
|
widget.onDragSelectionUpdate(_lastDragStartDetails, _lastDragUpdateDetails);
|
|
}
|
|
|
|
_dragUpdateThrottleTimer = null;
|
|
_lastDragUpdateDetails = null;
|
|
return null;
|
|
}
|
|
|
|
void _handleDragEnd(DragEndDetails details) {
|
|
D.assert(_lastDragStartDetails != null);
|
|
if (_lastDragUpdateDetails != null) {
|
|
_dragUpdateThrottleTimer.cancel();
|
|
_handleDragUpdateThrottled();
|
|
}
|
|
|
|
if (widget.onDragSelectionEnd != null) {
|
|
widget.onDragSelectionEnd(details);
|
|
}
|
|
|
|
_dragUpdateThrottleTimer = null;
|
|
_lastDragStartDetails = null;
|
|
_lastDragUpdateDetails = null;
|
|
}
|
|
|
|
void _handleLongPressStart() {
|
|
if (!_isDoubleTap && widget.onSingleLongTapStart != null) {
|
|
widget.onSingleLongTapStart();
|
|
}
|
|
}
|
|
|
|
object _doubleTapTimeout() {
|
|
_doubleTapTimer = null;
|
|
_lastTapOffset = null;
|
|
return null;
|
|
}
|
|
|
|
bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
|
|
D.assert(secondTapOffset != null);
|
|
if (_lastTapOffset == null) {
|
|
return false;
|
|
}
|
|
|
|
Offset difference = secondTapOffset - _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 = _handleTapDown;
|
|
instance.onTapUp = _handleTapUp;
|
|
instance.onTapCancel = _handleTapCancel;
|
|
}
|
|
)
|
|
);
|
|
|
|
if (widget.onSingleLongTapStart != null) {
|
|
gestures[typeof(LongPressGestureRecognizer)] =
|
|
new GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
|
() => new LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
|
|
instance => { instance.onLongPress = _handleLongPressStart; });
|
|
}
|
|
|
|
if (widget.onDragSelectionStart != null ||
|
|
widget.onDragSelectionUpdate != null ||
|
|
widget.onDragSelectionEnd != null) {
|
|
gestures.Add(typeof(PanGestureRecognizer),
|
|
new GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
|
|
() => new PanGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.mouse),
|
|
instance => {
|
|
instance.dragStartBehavior = DragStartBehavior.down;
|
|
instance.onStart = _handleDragStart;
|
|
instance.onUpdate = _handleDragUpdate;
|
|
instance.onEnd = _handleDragEnd;
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
return new RawGestureDetector(
|
|
gestures: gestures,
|
|
behavior: widget.behavior,
|
|
child: widget.child
|
|
);
|
|
}
|
|
}
|
|
}
|