using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.animation; using Unity.UIWidgets.rendering; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using Unity.UIWidgets.scheduler2; using System; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; using Rect = Unity.UIWidgets.ui.Rect; using Transform = Unity.UIWidgets.widgets.Transform; namespace Unity.UIWidgets.cupertino { class CupertinoContextMenuUtils { public static float _kOpenScale = 1.1f; public static float kMinFlingVelocity = 50.0f; public static Rect _getRect(GlobalKey globalKey) { D.assert(globalKey.currentContext != null); RenderBox renderBoxContainer = globalKey.currentContext.findRenderObject() as RenderBox; Offset containerOffset = renderBoxContainer.localToGlobal( renderBoxContainer.paintBounds.topLeft ); return containerOffset & renderBoxContainer.paintBounds.size; } public static Rect fromCenter(Offset center, float width, float height) { return Rect.fromLTRB( center.dx - width / 2, center.dy - height / 2, center.dx + width / 2, center.dy + height / 2 ); } } public delegate void _DismissCallback( BuildContext context, float scale, float opacity ); public delegate Widget ContextMenuPreviewBuilder( BuildContext context, Animation animation, Widget child ); public delegate Widget _ContextMenuPreviewBuilderChildless ( BuildContext context, Animation animation ); public enum _ContextMenuLocation { center, left, right, } public class CupertinoContextMenu : StatefulWidget { public CupertinoContextMenu( Key key = null, List actions = null, Widget child = null, ContextMenuPreviewBuilder previewBuilder = null ) : base(key: key) { D.assert(actions != null && actions.isNotEmpty()); D.assert(child != null); this.actions = actions; this.child = child; this.previewBuilder = previewBuilder; } public readonly Widget child; public readonly List actions; public readonly ContextMenuPreviewBuilder previewBuilder; public override State createState() { return new _CupertinoContextMenuState(); } } public class _CupertinoContextMenuState : TickerProviderStateMixin { public readonly GlobalKey _childGlobalKey = GlobalKey>.key(); static readonly TimeSpan kLongPressTimeout = TimeSpan.FromMilliseconds(500);//new TimeSpan(0, 0, 0, 0, 500); public bool _childHidden = false; public AnimationController _openController; public Rect _decoyChildEndRect; public OverlayEntry _lastOverlayEntry; public _ContextMenuRoute _route; public override void initState() { base.initState(); _openController = new AnimationController( duration: kLongPressTimeout, vsync: this ); _openController.addStatusListener(_onDecoyAnimationStatusChange); } public _ContextMenuLocation _contextMenuLocation { get { Rect childRect = CupertinoContextMenuUtils._getRect(_childGlobalKey); float screenWidth = MediaQuery.of(context).size.width; float center = screenWidth / 2; bool centerDividesChild = childRect.left < center && childRect.right > center; float distanceFromCenter = (center - childRect.center.dx).abs(); if (centerDividesChild && distanceFromCenter <= childRect.width / 4) { return _ContextMenuLocation.center; } if (childRect.center.dx > center) { return _ContextMenuLocation.right; } return _ContextMenuLocation.left; } } public void _openContextMenu() { setState(()=>{ _childHidden = true; }); _route = new _ContextMenuRoute( actions: widget.actions, barrierLabel: "Dismiss", filter: ImageFilter.blur( sigmaX: 5.0f, sigmaY: 5.0f ), contextMenuLocation: _contextMenuLocation, previousChildRect: _decoyChildEndRect, builder: (BuildContext _context, Animation animation)=>{ if (widget.previewBuilder == null) { return widget.child; } return widget.previewBuilder(_context, animation, widget.child); } ); Navigator.of(context, rootNavigator: true).push(_route); _route.animation.addStatusListener(_routeAnimationStatusListener); } public void _onDecoyAnimationStatusChange(AnimationStatus animationStatus) { switch (animationStatus) { case AnimationStatus.dismissed: if (_route == null) { setState(()=> { _childHidden = false; }); } _lastOverlayEntry?.remove(); _lastOverlayEntry = null; break; case AnimationStatus.completed: setState(()=>{ _childHidden = true; }); _openContextMenu(); SchedulerBinding.instance.addPostFrameCallback((TimeSpan timestamp) =>{ _lastOverlayEntry?.remove(); _lastOverlayEntry = null; _openController.reset(); }); break; default: return; } } public void _routeAnimationStatusListener(AnimationStatus status) { if (status != AnimationStatus.dismissed) { return; } setState(()=>{ _childHidden = false; }); _route.animation.removeStatusListener(_routeAnimationStatusListener); _route = null; } public void _onTap() { if (_openController.isAnimating && _openController.value < 0.5f) { _openController.reverse(); } } public void _onTapCancel() { if (_openController.isAnimating && _openController.value < 0.5f) { _openController.reverse(); } } public void _onTapUp(TapUpDetails details) { if(_openController.isAnimating && _openController.value < 0.5f) { _openController.reverse(); } } public void _onTapDown(TapDownDetails details) { setState(()=>{ _childHidden = true; }); Rect childRect = CupertinoContextMenuUtils._getRect(_childGlobalKey); _decoyChildEndRect = CupertinoContextMenuUtils.fromCenter( center: childRect.center, width: childRect.width * CupertinoContextMenuUtils._kOpenScale, height: childRect.height * CupertinoContextMenuUtils._kOpenScale ); _lastOverlayEntry = new OverlayEntry( opaque: false, builder: (BuildContext context) => { return new _DecoyChild( beginRect: childRect, child: widget.child, controller: _openController, endRect: _decoyChildEndRect ); } ); Overlay.of(context).insert(_lastOverlayEntry); _openController.forward(); } public override Widget build(BuildContext context) { return new GestureDetector( onTapCancel: _onTapCancel, onTapDown: _onTapDown, onTapUp: _onTapUp, onTap: _onTap, child: new TickerMode( enabled: !_childHidden, child: new Opacity( key: _childGlobalKey, opacity: _childHidden ? 0.0f : 1.0f, child: widget.child ) ) ); } public override void dispose() { _openController.dispose(); base.dispose(); } } public class _DecoyChild : StatefulWidget { public _DecoyChild( Key key = null, Rect beginRect = null, AnimationController controller = null, Rect endRect = null, Widget child = null ) : base(key: key) { this.beginRect = beginRect; this.controller = controller; this.endRect = endRect; this.child = child; } public readonly Rect beginRect; public readonly AnimationController controller; public readonly Rect endRect; public readonly Widget child; public override State createState() { return new _DecoyChildState(); } } public class _DecoyChildState : TickerProviderStateMixin<_DecoyChild> { public static readonly Color _lightModeMaskColor = new Color(0xFF888888); public static readonly Color _masklessColor = new Color(0xFFFFFFFF); public readonly GlobalKey _childGlobalKey = GlobalKey>.key(); public Animation _mask; public Animation _rect; public override void initState() { base.initState(); _mask = new _OnOffAnimation( controller: widget.controller, onValue: _lightModeMaskColor, offValue: _masklessColor, intervalOn: 0.0f, intervalOff: 0.5f ); Rect midRect = widget.beginRect.deflate( widget.beginRect.width * (CupertinoContextMenuUtils._kOpenScale - 1.0f) / 2f ); List> tweenSequenceItems = new List>(); tweenSequenceItems.Add( new TweenSequenceItem( tween: new RectTween( begin: widget.beginRect, end: midRect ).chain(new CurveTween(curve: Curves.easeInOutCubic)), weight: 1.0f )); tweenSequenceItems.Add( new TweenSequenceItem( tween: new RectTween( begin: midRect, end: widget.endRect ).chain(new CurveTween(curve: Curves.easeOutCubic)), weight: 1.0f )); _rect = new TweenSequence(tweenSequenceItems).animate(widget.controller); _rect.addListener(_rectListener); } public void _rectListener() { if (widget.controller.value < 0.5f) { return; } //HapticFeedback.selectionClick();???? /// tbc ??? _rect.removeListener(_rectListener); } public override void dispose() { _rect.removeListener(_rectListener); base.dispose(); } public Widget _buildAnimation(BuildContext context, Widget child) { Color color = widget.controller.status == AnimationStatus.reverse ? _masklessColor : _mask.value; List colors = new List(); colors.Add(color); colors.Add(color); return Positioned.fromRect( rect: _rect.value, child: foundation_.kIsWeb ? (Widget) new Container(key: _childGlobalKey, child: widget.child) : new ShaderMask( key: _childGlobalKey, shaderCallback: (Rect bounds) => { return new LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: colors).createShader(bounds); }, child: widget.child ) ); } public override Widget build(BuildContext context) { List widgets = new List(); widgets.Add(new AnimatedBuilder( builder: _buildAnimation, animation: widget.controller) ); return new Stack( children: widgets ); } } public class _ContextMenuRoute : PopupRoute { public _ContextMenuRoute( List actions = null, _ContextMenuLocation contextMenuLocation = default, string barrierLabel = null, _ContextMenuPreviewBuilderChildless builder = null, ImageFilter filter = null, Rect previousChildRect = null, RouteSettings settings = null ) : base(filter: filter, settings: settings){ D.assert(actions != null && actions.isNotEmpty()); D.assert(contextMenuLocation != null); this.barrierLabel = barrierLabel; _actions = actions; _builder = builder; _contextMenuLocation = contextMenuLocation; _previousChildRect = previousChildRect; } public readonly static Color _kModalBarrierColor = new Color(0x6604040F); public readonly TimeSpan _kModalPopupTransitionDuration =new TimeSpan(0, 0, 0, 0, 335); public readonly List _actions; public readonly _ContextMenuPreviewBuilderChildless _builder; public readonly GlobalKey _childGlobalKey = new LabeledGlobalKey>(); public readonly _ContextMenuLocation _contextMenuLocation; public bool _externalOffstage = false; public bool _internalOffstage = false; public Orientation _lastOrientation; public readonly Rect _previousChildRect; public float _scale = 1.0f; public readonly GlobalKey _sheetGlobalKey = new LabeledGlobalKey>();//GlobalKey(); public readonly static CurveTween _curve = new CurveTween( curve: Curves.easeOutBack ); public readonly static CurveTween _curveReverse = new CurveTween( curve: Curves.easeInBack ); public readonly static RectTween _rectTween = new RectTween(); public readonly static Animatable _rectAnimatable = _rectTween.chain(_curve); public readonly static RectTween _rectTweenReverse = new RectTween(); public readonly static Animatable _rectAnimatableReverse = _rectTweenReverse.chain(_curveReverse); public readonly static RectTween _sheetRectTween = new RectTween(); public readonly Animatable _sheetRectAnimatable = _sheetRectTween.chain(_curve); public readonly Animatable _sheetRectAnimatableReverse = _sheetRectTween.chain(_curveReverse); public readonly static Tween< float> _sheetScaleTween = new FloatTween(0.0f,0.0f); public readonly static Animatable< float> _sheetScaleAnimatable = _sheetScaleTween.chain(_curve); public readonly static Animatable< float> _sheetScaleAnimatableReverse = _sheetScaleTween.chain(_curveReverse); public readonly Tween< float> _opacityTween = new FloatTween(begin: 0.0f, end: 1.0f); public Animation< float> _sheetOpacity; public readonly string barrierLabel; //public override string barrierLabel; public Color barrierColor { get { return _kModalBarrierColor; } } public bool barrierDismissible{ get { return false; } } public bool semanticsDismissible { get { return true; } } public TimeSpan transitionDuration { get { return _kModalPopupTransitionDuration; } } /*public static AlignmentDirectional getSheetAlignment(_ContextMenuLocation contextMenuLocation) { switch (contextMenuLocation) { case _ContextMenuLocation.center: return AlignmentDirectional.topCenter; case _ContextMenuLocation.right: return AlignmentDirectional.topEnd; default: return AlignmentDirectional.topStart; } }*/ public static Rect _getScaledRect(GlobalKey globalKey, float scale) { Rect childRect = CupertinoContextMenuUtils._getRect(globalKey); Size sizeScaled = childRect.size * scale; Offset offsetScaled = new Offset( childRect.left + (childRect.size.width - sizeScaled.width) / 2, childRect.top + (childRect.size.height - sizeScaled.height) / 2 ); return offsetScaled & sizeScaled; } public static Rect _getSheetRectBegin(Orientation orientation, _ContextMenuLocation contextMenuLocation, Rect childRect, Rect sheetRect) { switch (contextMenuLocation) { case _ContextMenuLocation.center: Offset target1 = (orientation == Orientation.portrait) ? childRect.bottomCenter : childRect.topCenter; Offset centered = target1 - new Offset(sheetRect.width / 2, 0.0f); return centered & sheetRect.size; case _ContextMenuLocation.right: Offset target2 = ((orientation == Orientation.portrait) ? childRect.bottomRight : childRect.topRight); return (target2 - new Offset(sheetRect.width, 0.0f)) & sheetRect.size; default: Offset target3 = orientation == Orientation.portrait ? childRect.bottomLeft : childRect.topLeft; return target3 & sheetRect.size; } } public void _onDismiss(BuildContext context, float scale, float opacity) { _scale = scale; _opacityTween.end = opacity; _sheetOpacity = _opacityTween.animate( new CurvedAnimation( parent: animation, curve: new Interval(0.9f, 1.0f) )); Navigator.of(context).pop(); } public void _updateTweenRects() { Rect childRect = _scale == null ? CupertinoContextMenuUtils._getRect(_childGlobalKey) : _getScaledRect(_childGlobalKey, _scale); _rectTween.begin = _previousChildRect; _rectTween.end = childRect; Rect childRectOriginal = CupertinoContextMenuUtils.fromCenter( center: _previousChildRect.center, width: _previousChildRect.width / CupertinoContextMenuUtils._kOpenScale, height: _previousChildRect.height / CupertinoContextMenuUtils._kOpenScale ); Rect sheetRect = CupertinoContextMenuUtils._getRect(_sheetGlobalKey); Rect sheetRectBegin = _getSheetRectBegin( _lastOrientation, _contextMenuLocation, childRectOriginal, sheetRect ); _sheetRectTween.begin = sheetRectBegin; _sheetRectTween.end = sheetRect; _sheetScaleTween.begin = 0.0f; _sheetScaleTween.end = _scale; _rectTweenReverse.begin = childRectOriginal; _rectTweenReverse.end = childRect; } public void _setOffstageInternally() { base.offstage = _externalOffstage || _internalOffstage; changedInternalState(); } public bool didPop(object result) { _updateTweenRects(); return base.didPop(result); } public bool offstage{ set{ _externalOffstage = value; _setOffstageInternally(); } } public TickerFuture didPush() { _internalOffstage = true; _setOffstageInternally(); SchedulerBinding.instance.addPostFrameCallback((TimeSpan timeSpan)=>{ _updateTweenRects(); _internalOffstage = false; _setOffstageInternally(); }); return base.didPush(); } public Animation createAnimation() { Animation< float> animation = base.createAnimation(); _sheetOpacity = _opacityTween.animate(new CurvedAnimation( parent: animation, curve: Curves.linear )); return animation; } public override Widget buildPage(BuildContext context, Animation< float> animation, Animation< float> secondaryAnimation) { return null; } public override Widget buildTransitions(BuildContext context1, Animation< float> animation, Animation< float> secondaryAnimation, Widget child) { return new OrientationBuilder( builder: (BuildContext context2, Orientation orientation)=>{ _lastOrientation = orientation; if (!animation.isCompleted) { bool reverse = animation.status == AnimationStatus.reverse; Rect rect = reverse ? _rectAnimatableReverse.evaluate(animation) : _rectAnimatable.evaluate(animation); Rect sheetRect = reverse ? _sheetRectAnimatableReverse.evaluate(animation) : _sheetRectAnimatable.evaluate(animation); float sheetScale = reverse ? _sheetScaleAnimatableReverse.evaluate(animation) : _sheetScaleAnimatable.evaluate(animation); List widgets = new List(); widgets.Add( Positioned.fromRect( rect: sheetRect, child: new Opacity( opacity: _sheetOpacity.value, child: Transform.scale( //alignment: getSheetAlignment(_contextMenuLocation), scale: sheetScale, child: new _ContextMenuSheet( key: _sheetGlobalKey, actions: _actions, contextMenuLocation: _contextMenuLocation, orientation: orientation ) ) ) ) ); widgets.Add( Positioned.fromRect( key: _childGlobalKey, rect: rect, child: _builder(context2, animation) )); return new Stack( children: widgets ); } return new _ContextMenuRouteStatic( actions: _actions, child: _builder(context1, animation), childGlobalKey: _childGlobalKey, contextMenuLocation: _contextMenuLocation, onDismiss: _onDismiss, orientation: orientation, sheetGlobalKey: _sheetGlobalKey ); } ); } } public class _ContextMenuRouteStatic : StatefulWidget { public _ContextMenuRouteStatic( Key key = null, List actions = null, Widget child = null, GlobalKey childGlobalKey = null, _ContextMenuLocation contextMenuLocation = default, _DismissCallback onDismiss =default, Orientation orientation = default, GlobalKey sheetGlobalKey = null ) : base(key: key) { D.assert(contextMenuLocation != default); D.assert(orientation != default); this.actions = actions; this.child = child; this.childGlobalKey = childGlobalKey; this.contextMenuLocation = contextMenuLocation; this.onDismiss = onDismiss; this.orientation = orientation; this.sheetGlobalKey = sheetGlobalKey; } public readonly List actions; public readonly Widget child; public readonly GlobalKey childGlobalKey; public readonly _ContextMenuLocation contextMenuLocation; public readonly _DismissCallback onDismiss; public readonly Orientation orientation; public readonly GlobalKey sheetGlobalKey; public override State createState() { return new _ContextMenuRouteStaticState(); } } public class _ContextMenuRouteStaticState : TickerProviderStateMixin<_ContextMenuRouteStatic> { public readonly static float _kMinScale = 0.8f; public readonly static float _kSheetScaleThreshold = 0.9f; public readonly static float _kPadding = 20.0f; public readonly static float _kDamping = 400.0f; public readonly static TimeSpan _kMoveControllerDuration = TimeSpan.FromMilliseconds(600); Offset _dragOffset; public float _lastScale = 1.0f; public AnimationController _moveController; public AnimationController _sheetController; public Animation _moveAnimation; public Animation< float> _sheetScaleAnimation; public Animation< float> _sheetOpacityAnimation; public static float _getScale(Orientation orientation, float maxDragDistance, float dy) { float dyDirectional = dy <= 0.0f ? dy : -dy; return Mathf.Max( _kMinScale, (maxDragDistance + dyDirectional) / maxDragDistance ); } void _onPanStart(DragStartDetails details) { _moveController.setValue(1.0f) ; _setDragOffset(Offset.zero); } void _onPanUpdate(DragUpdateDetails details) { _setDragOffset(_dragOffset + details.delta); } void _onPanEnd(DragEndDetails details) { if (details.velocity.pixelsPerSecond.dy.abs() >= CupertinoContextMenuUtils.kMinFlingVelocity) { bool flingIsAway = details.velocity.pixelsPerSecond.dy > 0; float finalPosition = flingIsAway ? _moveAnimation.value.dy + 100.0f : 0.0f; if (flingIsAway && _sheetController.status != AnimationStatus.forward) { _sheetController.forward(); } else if (!flingIsAway && _sheetController.status != AnimationStatus.reverse) { _sheetController.reverse(); } _moveAnimation = new OffsetTween( begin: new Offset(0.0f, _moveAnimation.value.dy), end:new Offset(0.0f, finalPosition) ).animate(_moveController); _moveController.reset(); _moveController.duration = TimeSpan.FromMilliseconds(64); _moveController.forward(); _moveController.addStatusListener(_flingStatusListener); return; } if (_lastScale == _kMinScale) { widget.onDismiss(context, _lastScale, _sheetOpacityAnimation.value); return; } _moveController.addListener(_moveListener); _moveController.reverse(); } void _moveListener() { if (_lastScale > _kSheetScaleThreshold) { _moveController.removeListener(_moveListener); if (_sheetController.status != AnimationStatus.dismissed) { _sheetController.reverse(); } } } void _flingStatusListener(AnimationStatus status) { if (status != AnimationStatus.completed) { return; } _moveController.duration = _kMoveControllerDuration; _moveController.removeStatusListener(_flingStatusListener); if (_moveAnimation.value.dy == 0.0) { return; } widget.onDismiss(context, _lastScale, _sheetOpacityAnimation.value); } Alignment _getChildAlignment(Orientation orientation, _ContextMenuLocation contextMenuLocation) { switch (contextMenuLocation) { case _ContextMenuLocation.center: return orientation == Orientation.portrait ? Alignment.bottomCenter : Alignment.topRight; case _ContextMenuLocation.right: return orientation == Orientation.portrait ? Alignment.bottomCenter : Alignment.topLeft; default: return orientation == Orientation.portrait ? Alignment.bottomCenter : Alignment.topRight; } } void _setDragOffset(Offset dragOffset) { float endX = _kPadding * dragOffset.dx / _kDamping; float endY = dragOffset.dy >= 0.0 ? dragOffset.dy : _kPadding * dragOffset.dy / _kDamping; setState(() =>{ _dragOffset = dragOffset; _moveAnimation = new OffsetTween( begin: Offset.zero, end: new Offset( endX.clamp(-_kPadding, _kPadding) , endY )).animate( new CurvedAnimation( parent: _moveController, curve: Curves.elasticIn ) ); if (_lastScale <= _kSheetScaleThreshold && _sheetController.status != AnimationStatus.forward && _sheetScaleAnimation.value != 0.0f) { _sheetController.forward(); } else if (_lastScale > _kSheetScaleThreshold && _sheetController.status != AnimationStatus.reverse && _sheetScaleAnimation.value != 1.0f) { _sheetController.reverse(); } }); } List _getChildren(Orientation orientation, _ContextMenuLocation contextMenuLocation) { Expanded child = new Expanded( child: new Align( alignment: _getChildAlignment( widget.orientation, widget.contextMenuLocation ), child: new AnimatedBuilder( animation: _moveController, builder: _buildChildAnimation, child: widget.child ) ) ); Container spacer = new Container( width: _kPadding, height: _kPadding ); Expanded sheet = new Expanded( child: new AnimatedBuilder( animation: _sheetController, builder: _buildSheetAnimation, child: new _ContextMenuSheet( key: widget.sheetGlobalKey, actions: widget.actions, contextMenuLocation: widget.contextMenuLocation, orientation: widget.orientation ) ) ); List centerWidgets = new List(); centerWidgets.Add(child); centerWidgets.Add(spacer); centerWidgets.Add(sheet); List rightWidgets = new List(); rightWidgets.Add(sheet); rightWidgets.Add(spacer); rightWidgets.Add(child); switch (contextMenuLocation) { case _ContextMenuLocation.center: return new List{child, spacer, sheet}; case _ContextMenuLocation.right: return orientation == Orientation.portrait ? new List{child, spacer, sheet} : new List{sheet, spacer, child}; default: return new List{child, spacer, sheet}; } } // Build the animation for the _ContextMenuSheet. Widget _buildSheetAnimation(BuildContext context, Widget child) { return Transform.scale( //alignment: _ContextMenuRoute.getSheetAlignment(widget.contextMenuLocation), scale: _sheetScaleAnimation.value, child: new Opacity( opacity: _sheetOpacityAnimation.value, child: child ) ); } // Build the animation for the child. Widget _buildChildAnimation(BuildContext context, Widget child) { _lastScale = _getScale( widget.orientation, MediaQuery.of(context).size.height, _moveAnimation.value.dy ); return Transform.scale( key: widget.childGlobalKey, scale: _lastScale, child: child ); } // Build the animation for the overall draggable dismissable content. Widget _buildAnimation(BuildContext context, Widget child) { return Transform.translate( offset: _moveAnimation.value, child: child ); } public override void initState() { base.initState(); _moveController = new AnimationController( duration: _kMoveControllerDuration, value: 1.0f, vsync: this ); _sheetController = new AnimationController( duration: TimeSpan.FromMilliseconds(100), reverseDuration: TimeSpan.FromMilliseconds(300),/// TBC ??? vsync: this ); _sheetScaleAnimation = new FloatTween( begin: 1.0f, end: 0.0f ).animate( new CurvedAnimation( parent: _sheetController, curve: Curves.linear, reverseCurve: Curves.easeInBack ) ); _sheetOpacityAnimation = new FloatTween( begin: 1.0f, end: 0.0f ).animate(_sheetController); _setDragOffset(Offset.zero); } public override void dispose() { _moveController.dispose(); _sheetController.dispose(); base.dispose(); } public override Widget build(BuildContext context) { List children = _getChildren( widget.orientation, widget.contextMenuLocation ); return new SafeArea( child: new Padding( padding: EdgeInsets.all(_kPadding), child: new Align( alignment: Alignment.topLeft, child: new GestureDetector( onPanEnd: _onPanEnd, onPanStart: _onPanStart, onPanUpdate: _onPanUpdate, child: new AnimatedBuilder( animation: _moveController, builder: _buildAnimation, child: widget.orientation == Orientation.portrait ? (Widget) new Column( crossAxisAlignment: CrossAxisAlignment.start, children: children ) : new Row( crossAxisAlignment: CrossAxisAlignment.start, children: children ) ) ) ) ) ); } } public class _ContextMenuSheet : StatelessWidget { public _ContextMenuSheet( Key key = null, List actions = null, _ContextMenuLocation contextMenuLocation = default, Orientation orientation = default ) : base(key: key) { D.assert(actions != null && actions.isNotEmpty()); D.assert(contextMenuLocation != null); D.assert(orientation != null); _contextMenuLocation = contextMenuLocation; _orientation = orientation; this.actions = actions; } public readonly List actions; public readonly _ContextMenuLocation _contextMenuLocation; public readonly Orientation _orientation; public List children { get{ Flexible menu = new Flexible( fit: FlexFit.tight, flex: 2, child: new IntrinsicHeight( child: new ClipRRect( borderRadius: BorderRadius.circular(13.0f), child: new Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: actions ) ) ) ); List spacers1 = new List(); List spacers2 = new List(); spacers1.Add(new Spacer( flex: 1 )); spacers1.Add(menu); spacers1.Add(new Spacer( flex: 1 )); spacers2.Add(menu); spacers2.Add(new Spacer( flex: 1 )); List spacers3 = new List(); List spacers4 = new List(); spacers3.Add(new Spacer( flex: 1 )); spacers3.Add(menu); spacers4.Add(menu); spacers4.Add(new Spacer( flex: 1 )); switch (_contextMenuLocation) { case _ContextMenuLocation.center: return _orientation == Orientation.portrait ? spacers1 : spacers2; case _ContextMenuLocation.right: return spacers3; default: return spacers4; } } } public override Widget build(BuildContext context) { return new Row( crossAxisAlignment: CrossAxisAlignment.start, children: children ); } } public class _OnOffAnimation : CompoundAnimation { public _OnOffAnimation( AnimationController controller = null, T onValue = default, T offValue = default, float? intervalOn = null, float? intervalOff = null ) : base( first: new Tween(begin: offValue, end: onValue).animate( new CurvedAnimation( parent: controller, curve: new Interval(intervalOn == null ? 0.0f : (float)intervalOn, intervalOn == null ? 0.0f : (float)intervalOn) ) ), next: new Tween(begin: onValue, end: offValue).animate( new CurvedAnimation( parent: controller, curve: new Interval(intervalOff == null ? 0.0f : (float)intervalOff, intervalOff == null ? 0.0f : (float)intervalOff) ) ) ) { D.assert(intervalOn != null && intervalOn >= 0.0 && intervalOn <= 1.0); D.assert(intervalOff !=null && intervalOff >= 0.0 && intervalOff <= 1.0); D.assert(intervalOn <= intervalOff); } public readonly T _offValue; public override T value { get { return next.value.Equals( _offValue) ? next.value : first.value; } } } }