| <!-- | 
 | // Copyright 2015 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 | --> | 
 | <import src="shadow.sky" as="shadow" /> | 
 | <import src="sky-element.sky" /> | 
 | <import src="sky-scrollable.sky" /> | 
 |  | 
 | <sky-element attributes="level:number"> | 
 | <template> | 
 |   <style> | 
 |     #mask { | 
 |       background-color: black; | 
 |       will-change: opacity; | 
 |       position: absolute; | 
 |       top: 0; | 
 |       left: 0; | 
 |       bottom: 0; | 
 |       right: 0; | 
 |     } | 
 |     #content { | 
 |       background-color: #FAFAFA; | 
 |       will-change: transform; | 
 |       position: absolute; | 
 |       z-index: 2; | 
 |       width: 256px; | 
 |       top: 0; | 
 |       left: 0; | 
 |       bottom: 0; | 
 |     } | 
 |   </style> | 
 |   <div id="mask" /> | 
 |   <div id="content"> | 
 |     <content/> | 
 |   </div> | 
 | </template> | 
 | <script> | 
 | import "animation/controller.dart"; | 
 | import "animation/curves.dart"; | 
 | import "animation/timer.dart"; | 
 | import "dart:math" as math; | 
 | import "dart:sky"; | 
 |  | 
 | const double _kWidth = 256.0; | 
 | const double _kMinFlingVelocity = 0.4; | 
 | const double _kMinAnimationDurationMS = 246.0; | 
 | const double _kMaxAnimationDurationMS = 600.0; | 
 | const Cubic _kAnimationCurve = easeOut; | 
 |  | 
 | @Tagname('sky-drawer') | 
 | class SkyDrawer extends SkyElement implements AnimationDelegate { | 
 |   Element _mask; | 
 |   Element _content; | 
 |   double _position = 0.0; | 
 |   AnimationController _animation; | 
 |  | 
 |   SkyDrawer() { | 
 |     _animation = new AnimationController(this); | 
 |  | 
 |     addEventListener('pointerdown', _handlePointerDown); | 
 |     addEventListener('pointermove', _handlePointerMove); | 
 |     addEventListener('pointerup', _handlePointerUp); | 
 |     addEventListener('pointercancel', _handlePointerCancel); | 
 |   } | 
 |  | 
 |   void shadowRootReady() { | 
 |     shadow.applyTo(shadowRoot); | 
 |     _mask = shadowRoot.getElementById('mask'); | 
 |     _mask.addEventListener('gesturetap', _handleMaskTap); | 
 |     _content = shadowRoot.getElementById('content'); | 
 |     _content.addEventListener('gestureflingstart', _handleFlingStart); | 
 |     position = -_kWidth; | 
 |   } | 
 |  | 
 |   void toggle() { | 
 |     if (isMostlyClosed) | 
 |       open(); | 
 |     else | 
 |       close(); | 
 |   } | 
 |  | 
 |   void open() { | 
 |     _animateToPosition(0.0); | 
 |   } | 
 |  | 
 |   void close() { | 
 |     _animateToPosition(-_kWidth); | 
 |   } | 
 |  | 
 |   bool get isClosed => _position <= -_kWidth; | 
 |   bool get isMostlyClosed => _position <= -_kWidth / 2; | 
 |   double get position => _position; | 
 |  | 
 |   set position(double value) { | 
 |     double newPosition = math.min(0.0, math.max(value, -_kWidth)); | 
 |     _position = newPosition; | 
 |     _content.style['transform'] = 'translateX(${newPosition}px)'; | 
 |     _mask.style['opacity'] = '${(newPosition / _kWidth + 1) * 0.25}'; | 
 |     style['display'] = isClosed ? 'none' : ''; | 
 |   } | 
 |  | 
 |   void _settle() { | 
 |     if (isMostlyClosed) | 
 |       close(); | 
 |     else | 
 |       open(); | 
 |   } | 
 |  | 
 |   void _animateToPosition(double targetPosition) { | 
 |     double currentPosition = _position; | 
 |     double distance = (targetPosition - currentPosition).abs(); | 
 |     double duration = _kMaxAnimationDurationMS * distance / _kWidth; | 
 |     _animation.start( | 
 |       begin: currentPosition, | 
 |       end: targetPosition, | 
 |       duration: math.max(_kMinAnimationDurationMS, duration), | 
 |       curve: _kAnimationCurve); | 
 |   } | 
 |  | 
 |   void updateAnimation(double p) { | 
 |     position = p; | 
 |   } | 
 |  | 
 |   void _handleMaskTap(_) { | 
 |     close(); | 
 |   } | 
 |  | 
 |   void _handlePointerDown(_) { | 
 |     _animation.stop(); | 
 |   } | 
 |  | 
 |   void _handlePointerMove(PointerEvent event) { | 
 |     position += event.dx; | 
 |   } | 
 |  | 
 |   void _handlePointerUp(_) { | 
 |     if (!_animation.isAnimating) | 
 |       _settle(); | 
 |   } | 
 |  | 
 |   void _handlePointerCancel(_) { | 
 |     if (!_animation.isAnimating) | 
 |       _settle(); | 
 |   } | 
 |  | 
 |   void _handleFlingStart(event) { | 
 |     double direction = event.velocityX.sign; | 
 |     double velocityX = event.velocityX.abs() / 1000; | 
 |     if (velocityX < _kMinFlingVelocity) | 
 |       return; | 
 |     double targetPosition = direction < 0.0 ? -kWidth : 0.0; | 
 |     double currentPosition = _position; | 
 |     double distance = (targetPosition - currentPosition).abs(); | 
 |     double duration = distance / velocityX; | 
 |     _animation.start( | 
 |       begin: currentPosition, | 
 |       end: targetPosition, | 
 |       duration: duration, | 
 |       curve: linear); | 
 |   } | 
 | } | 
 |  | 
 | _init(script) => register(script, SkyDrawer); | 
 | </script> | 
 | </sky-element> |