| <!-- |
| // 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="sky-element.sky" /> |
| <import src="sky-scrollable.sky" /> |
| |
| <sky-element> |
| <template> |
| <style> |
| #mask { |
| background-color: black; |
| will-change: opacity; |
| position: absolute; |
| top: 0; |
| left: 0; |
| bottom: 0; |
| right: 0; |
| } |
| #content { |
| background-color: white; |
| will-change: transform; |
| position: absolute; |
| 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() { |
| _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> |