|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using Unity.UIWidgets.gestures; |
|
|
|
using Unity.UIWidgets.painting; |
|
|
|
using Unity.UIWidgets.rendering; |
|
|
|
public static readonly Color _kScrollbarColor = new Color(0x99777777); |
|
|
|
public const float _kScrollbarThickness = 2.5f; |
|
|
|
public const float _kScrollbarMainAxisMargin = 4.0f; |
|
|
|
public const float _kScrollbarCrossAxisMargin = 2.5f; |
|
|
|
public static readonly Radius _kScrollbarRadius = Radius.circular(1.25f); |
|
|
|
public static readonly TimeSpan _kScrollbarTimeToFade = new TimeSpan(0, 0, 0, 0, 50); |
|
|
|
public static readonly TimeSpan _kScrollbarFadeDuration = new TimeSpan(0, 0, 0, 0, 250); |
|
|
|
|
|
|
|
public static readonly TimeSpan _kScrollbarTimeToFade = TimeSpan.FromMilliseconds(50); |
|
|
|
public static readonly TimeSpan _kScrollbarFadeDuration = TimeSpan.FromMilliseconds(250); |
|
|
|
public static readonly TimeSpan _kScrollbarResizeDuration = TimeSpan.FromMilliseconds(100); |
|
|
|
|
|
|
|
public static readonly Color _kScrollbarColor = CupertinoDynamicColor.withBrightness( |
|
|
|
color: new Color(0x59000000), |
|
|
|
darkColor: new Color(0x80FFFFFF) |
|
|
|
); |
|
|
|
public const float _kScrollbarThickness = 3f; |
|
|
|
public const float _kScrollbarThicknessDragging = 8.0f; |
|
|
|
public static Radius _kScrollbarRadius = Radius.circular(1.5f); |
|
|
|
public static Radius _kScrollbarRadiusDragging = Radius.circular(4.0f); |
|
|
|
public const float _kScrollbarMainAxisMargin = 3.0f; |
|
|
|
public const float _kScrollbarCrossAxisMargin = 3.0f; |
|
|
|
|
|
|
|
public static bool _hitTestInteractive(GlobalKey customPaintKey, Offset offset) { |
|
|
|
if (customPaintKey.currentContext == null) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
CustomPaint customPaint = customPaintKey.currentContext.widget as CustomPaint; |
|
|
|
ScrollbarPainter painter = customPaint.foregroundPainter as ScrollbarPainter; |
|
|
|
RenderBox renderBox = customPaintKey.currentContext.findRenderObject() as RenderBox; |
|
|
|
Offset localOffset = renderBox.globalToLocal(offset); |
|
|
|
return painter.hitTestInteractive(localOffset); |
|
|
|
} |
|
|
|
Widget child, |
|
|
|
Key key = null |
|
|
|
Key key = null, |
|
|
|
ScrollController controller = null, |
|
|
|
bool isAlwaysShown = false, |
|
|
|
Widget child = null |
|
|
|
this.controller = controller; |
|
|
|
this.isAlwaysShown = isAlwaysShown; |
|
|
|
} |
|
|
|
|
|
|
|
public readonly Widget child; |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
class _CupertinoScrollbarState : TickerProviderStateMixin<CupertinoScrollbar> { |
|
|
|
|
|
|
|
GlobalKey _customPaintKey = GlobalKey.key(); |
|
|
|
ScrollbarPainter _painter; |
|
|
|
|
|
|
|
TextDirection _textDirection; |
|
|
|
|
|
|
AnimationController _thicknessAnimationController; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Drag _drag; |
|
|
|
|
|
|
|
float _thickness { |
|
|
|
get { |
|
|
|
return CupertinoScrollbarUtils._kScrollbarThickness + _thicknessAnimationController.value * (CupertinoScrollbarUtils._kScrollbarThicknessDragging - CupertinoScrollbarUtils._kScrollbarThickness); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
Radius _radius { |
|
|
|
get { |
|
|
|
return Radius.lerp(CupertinoScrollbarUtils._kScrollbarRadius, CupertinoScrollbarUtils._kScrollbarRadiusDragging, _thicknessAnimationController.value); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
ScrollController _currentController; |
|
|
|
ScrollController _controller { |
|
|
|
get { |
|
|
|
return widget.controller ?? PrimaryScrollController.of(context); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override void initState() { |
|
|
|
base.initState(); |
|
|
|
|
|
|
parent: _fadeoutAnimationController, |
|
|
|
curve: Curves.fastOutSlowIn |
|
|
|
); |
|
|
|
_thicknessAnimationController = new AnimationController( |
|
|
|
vsync: this, |
|
|
|
duration: CupertinoScrollbarUtils._kScrollbarResizeDuration |
|
|
|
); |
|
|
|
_thicknessAnimationController.addListener(() => { |
|
|
|
_painter.updateThickness(_thickness, _radius); |
|
|
|
}); |
|
|
|
public override void didChangeDependencies() { |
|
|
|
base.didChangeDependencies(); |
|
|
|
_textDirection = Directionality.of(context); |
|
|
|
_painter = _buildCupertinoScrollbarPainter(); |
|
|
|
} |
|
|
|
|
|
|
|
ScrollbarPainter _buildCupertinoScrollbarPainter() { |
|
|
|
public ScrollbarPainter _buildCupertinoScrollbarPainter(BuildContext context) { |
|
|
|
color: CupertinoScrollbarUtils._kScrollbarColor, |
|
|
|
textDirection: _textDirection, |
|
|
|
thickness: CupertinoScrollbarUtils._kScrollbarThickness, |
|
|
|
color: CupertinoDynamicColor.resolve(CupertinoScrollbarUtils._kScrollbarColor, context), |
|
|
|
textDirection: Directionality.of(context), |
|
|
|
thickness: _thickness, |
|
|
|
radius: CupertinoScrollbarUtils._kScrollbarRadius, |
|
|
|
radius: _radius, |
|
|
|
padding: MediaQuery.of(context).padding, |
|
|
|
|
|
|
|
void _dragScrollbar(float primaryDelta) { |
|
|
|
D.assert(_currentController != null); |
|
|
|
float scrollOffsetLocal = _painter.getTrackToScroll(primaryDelta); |
|
|
|
float scrollOffsetGlobal = scrollOffsetLocal + _currentController.position.pixels; |
|
|
|
|
|
|
|
if (_drag == null) { |
|
|
|
_drag = _currentController.position.drag( |
|
|
|
new DragStartDetails( |
|
|
|
globalPosition: new Offset(0.0f, scrollOffsetGlobal) |
|
|
|
), |
|
|
|
() =>{} |
|
|
|
); |
|
|
|
} else { |
|
|
|
|
|
|
|
_drag.update( |
|
|
|
new DragUpdateDetails( |
|
|
|
delta: new Offset(0.0f, -scrollOffsetLocal), |
|
|
|
primaryDelta: (float?) -1f * scrollOffsetLocal, |
|
|
|
globalPosition: new Offset(0.0f, scrollOffsetGlobal) |
|
|
|
) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void _startFadeoutTimer() { |
|
|
|
if (!widget.isAlwaysShown) { |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_fadeoutTimer = Timer.create(CupertinoScrollbarUtils._kScrollbarTimeToFade, () => { |
|
|
|
_fadeoutAnimationController.reverse(); |
|
|
|
_fadeoutTimer = null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bool _checkVertical() { |
|
|
|
return _currentController.position.axis() == Axis.vertical; |
|
|
|
} |
|
|
|
|
|
|
|
float _pressStartY = 0.0f; |
|
|
|
|
|
|
|
void _handleLongPressStart(LongPressStartDetails details) { |
|
|
|
_currentController = _controller; |
|
|
|
if (!_checkVertical()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
_pressStartY = details.localPosition.dy; |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_fadeoutAnimationController.forward(); |
|
|
|
_dragScrollbar(details.localPosition.dy); |
|
|
|
_dragScrollbarPositionY = details.localPosition.dy; |
|
|
|
} |
|
|
|
|
|
|
|
void _handleLongPress() { |
|
|
|
if (!_checkVertical()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_thicknessAnimationController.forward().then( |
|
|
|
(_) => { return; } |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { |
|
|
|
if (!_checkVertical()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
_dragScrollbar(details.localPosition.dy - _dragScrollbarPositionY); |
|
|
|
_dragScrollbarPositionY = details.localPosition.dy; |
|
|
|
} |
|
|
|
|
|
|
|
void _handleLongPressEnd(LongPressEndDetails details) { |
|
|
|
if (!_checkVertical()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
_handleDragScrollEnd(details.velocity.pixelsPerSecond.dy); |
|
|
|
/*if (details.velocity.pixelsPerSecond.dy.abs() < 10 && |
|
|
|
(details.localPosition.dy - _pressStartY).abs() > 0) { |
|
|
|
//HapticFeedback.mediumImpact();
|
|
|
|
}*/ |
|
|
|
_currentController = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _handleDragScrollEnd(float trackVelocityY) { |
|
|
|
_startFadeoutTimer(); |
|
|
|
_thicknessAnimationController.reverse(); |
|
|
|
_dragScrollbarPositionY = 0.0f; |
|
|
|
float scrollVelocityY = _painter.getTrackToScroll(trackVelocityY); |
|
|
|
_drag?.end(new DragEndDetails( |
|
|
|
primaryVelocity: -scrollVelocityY, |
|
|
|
velocity: new Velocity( |
|
|
|
pixelsPerSecond: new Offset( |
|
|
|
0.0f, |
|
|
|
-scrollVelocityY |
|
|
|
) |
|
|
|
) |
|
|
|
)); |
|
|
|
_drag = null; |
|
|
|
} |
|
|
|
ScrollMetrics metrics = notification.metrics; |
|
|
|
if (metrics.maxScrollExtent <= metrics.minScrollExtent) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
if (_fadeoutAnimationController.status != AnimationStatus.forward) { |
|
|
|
_fadeoutAnimationController.forward(); |
|
|
|
} |
|
|
|
// Any movements always makes the scrollbar start showing up.
|
|
|
|
if (_fadeoutAnimationController.status != AnimationStatus.forward) { |
|
|
|
_fadeoutAnimationController.forward(); |
|
|
|
} |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_painter.update(notification.metrics, notification.metrics.axisDirection); |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_painter.update(notification.metrics, notification.metrics.axisDirection); |
|
|
|
} else if (notification is ScrollEndNotification) { |
|
|
|
// On iOS, the scrollbar can only go away once the user lifted the finger.
|
|
|
|
if (_dragScrollbarPositionY == null) { |
|
|
|
_startFadeoutTimer(); |
|
|
|
} |
|
|
|
else if (notification is ScrollEndNotification) { |
|
|
|
if (_dragScrollbarPositionY.Equals(0f)) { |
|
|
|
_startFadeoutTimer(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
Dictionary<Type, GestureRecognizerFactory> _gestures { |
|
|
|
get { |
|
|
|
Dictionary<Type, GestureRecognizerFactory> gestures = |
|
|
|
new Dictionary<Type, GestureRecognizerFactory>(); |
|
|
|
|
|
|
|
gestures[typeof(_ThumbPressGestureRecognizer)] = |
|
|
|
new GestureRecognizerFactoryWithHandlers<_ThumbPressGestureRecognizer>( |
|
|
|
() => new _ThumbPressGestureRecognizer( |
|
|
|
debugOwner: this, |
|
|
|
customPaintKey: _customPaintKey |
|
|
|
), |
|
|
|
(_ThumbPressGestureRecognizer instance)=> { |
|
|
|
instance.onLongPressStart = _handleLongPressStart; |
|
|
|
instance.onLongPress = _handleLongPress; |
|
|
|
instance.onLongPressMoveUpdate = _handleLongPressMoveUpdate; |
|
|
|
instance.onLongPressEnd = _handleLongPressEnd; |
|
|
|
); |
|
|
|
/*_fadeoutTimer?.cancel(); |
|
|
|
_fadeoutTimer = Window.instance.run(CupertinoScrollbarUtils._kScrollbarTimeToFade, () => { |
|
|
|
_fadeoutAnimationController.reverse(); |
|
|
|
_fadeoutTimer = null; |
|
|
|
});*/ |
|
|
|
return gestures; |
|
|
|
} |
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ScrollbarPainter _buildCupertinoScrollbarPainter() { |
|
|
|
return new ScrollbarPainter( |
|
|
|
color: CupertinoScrollbarUtils._kScrollbarColor, |
|
|
|
textDirection: _textDirection, |
|
|
|
thickness: CupertinoScrollbarUtils._kScrollbarThickness, |
|
|
|
fadeoutOpacityAnimation: _fadeoutOpacityAnimation, |
|
|
|
mainAxisMargin: CupertinoScrollbarUtils._kScrollbarMainAxisMargin, |
|
|
|
crossAxisMargin: CupertinoScrollbarUtils._kScrollbarCrossAxisMargin, |
|
|
|
radius: CupertinoScrollbarUtils._kScrollbarRadius, |
|
|
|
minLength: CupertinoScrollbarUtils._kScrollbarMinLength, |
|
|
|
minOverscrollLength: CupertinoScrollbarUtils._kScrollbarMinOverscrollLength |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
_thicknessAnimationController.dispose(); |
|
|
|
void _startFadeoutTimer() { |
|
|
|
if (!widget.isAlwaysShown) { |
|
|
|
_fadeoutTimer?.cancel(); |
|
|
|
_fadeoutTimer = Timer.create(CupertinoScrollbarUtils._kScrollbarTimeToFade, () => { |
|
|
|
_fadeoutAnimationController.reverse(); |
|
|
|
_fadeoutTimer = null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
child: new CustomPaint( |
|
|
|
foregroundPainter: _painter, |
|
|
|
child: new RepaintBoundary( |
|
|
|
child: widget.child |
|
|
|
child: new RawGestureDetector( |
|
|
|
gestures: _gestures, |
|
|
|
child: new CustomPaint( |
|
|
|
key: _customPaintKey, |
|
|
|
foregroundPainter: _painter, |
|
|
|
child: new RepaintBoundary(child: widget.child) |
|
|
|
) |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
public class _ThumbPressGestureRecognizer : LongPressGestureRecognizer { |
|
|
|
public _ThumbPressGestureRecognizer( |
|
|
|
float? postAcceptSlopTolerance = null, |
|
|
|
PointerDeviceKind kind = default, |
|
|
|
object debugOwner = null, |
|
|
|
GlobalKey customPaintKey = null |
|
|
|
) : base( |
|
|
|
postAcceptSlopTolerance: postAcceptSlopTolerance, |
|
|
|
kind: kind, |
|
|
|
debugOwner: debugOwner, |
|
|
|
duration: TimeSpan.FromMilliseconds(100) |
|
|
|
) { |
|
|
|
_customPaintKey = customPaintKey; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public readonly GlobalKey _customPaintKey; |
|
|
|
|
|
|
|
protected override bool isPointerAllowed(PointerDownEvent _event) { |
|
|
|
if (!CupertinoScrollbarUtils._hitTestInteractive(_customPaintKey, _event.position)) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return base.isPointerAllowed(_event); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |