diff --git a/examples/widgets/progress_indicator.dart b/examples/widgets/progress_indicator.dart new file mode 100644 index 0000000000..df06c452b8 --- /dev/null +++ b/examples/widgets/progress_indicator.dart @@ -0,0 +1,123 @@ +// 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 'dart:sky' as sky; + +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/animated_value.dart'; +import 'package:sky/animation/curves.dart'; +import 'package:sky/theme/colors.dart'; +import 'package:sky/widgets/basic.dart'; +import 'package:sky/widgets/icon.dart'; +import 'package:sky/widgets/progress_indicator.dart'; +import 'package:sky/widgets/scaffold.dart'; +import 'package:sky/widgets/theme.dart'; +import 'package:sky/widgets/tool_bar.dart'; +import 'package:sky/widgets/framework.dart'; +import 'package:sky/widgets/task_description.dart'; +import 'package:sky/widgets/transitions.dart'; + +class ProgressIndicatorApp extends App { + + AnimationPerformance valueAnimation; + Direction valueAnimationDirection = Direction.forward; + + void initState() { + super.initState(); + valueAnimation = new AnimationPerformance() + ..duration = const Duration(milliseconds: 1500) + ..variable = new AnimatedValue( + 0.0, + end: 1.0, + curve: ease, + reverseCurve: ease, + interval: new Interval(0.0, 0.9) + ); + } + + void handleTap(sky.GestureEvent event) { + if (valueAnimation.isAnimating) + valueAnimation.stop(); + else + valueAnimation.resume(); + } + + void reverseValueAnimationDirection() { + setState(() { + valueAnimationDirection = (valueAnimationDirection == Direction.forward) + ? Direction.reverse + : Direction.forward; + }); + } + + Widget buildIndicators() { + List indicators = [ + new SizedBox( + width: 200.0, + child: new LinearProgressIndicator() + ), + new LinearProgressIndicator(), + new LinearProgressIndicator(), + new LinearProgressIndicator(value: valueAnimation.variable.value), + new CircularProgressIndicator(), + new SizedBox( + width: 20.0, + height: 20.0, + child: new CircularProgressIndicator(value: valueAnimation.variable.value) + ), + new SizedBox( + width: 50.0, + height: 30.0, + child: new CircularProgressIndicator(value: valueAnimation.variable.value) + ) + ]; + return new Flex( + indicators + .map((c) => new Container(child: c, margin: const EdgeDims.symmetric(vertical: 20.0))) + .toList(), + direction: FlexDirection.vertical, + justifyContent: FlexJustifyContent.center + ); + } + + Widget build() { + Widget body = new Listener( + onGestureTap: (e) { handleTap(e); }, + child: new Container( + padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), + decoration: new BoxDecoration(backgroundColor: Theme.of(this).cardColor), + child: new BuilderTransition( + variables: [valueAnimation.variable], + direction: valueAnimationDirection, + performance: valueAnimation, + onDismissed: reverseValueAnimationDirection, + onCompleted: reverseValueAnimationDirection, + builder: buildIndicators + ) + ) + ); + + return new IconTheme( + data: const IconThemeData(color: IconThemeColor.white), + child: new Theme( + data: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Blue, + accentColor: RedAccent[200] + ), + child: new TaskDescription( + label: 'Cards', + child: new Scaffold( + toolbar: new ToolBar(center: new Text('Progress Indicators')), + body: body + ) + ) + ) + ); + } +} + +void main() { + runApp(new ProgressIndicatorApp()); +} diff --git a/packages/flutter/lib/widgets/progress_indicator.dart b/packages/flutter/lib/widgets/progress_indicator.dart new file mode 100644 index 0000000000..437a9d6f14 --- /dev/null +++ b/packages/flutter/lib/widgets/progress_indicator.dart @@ -0,0 +1,153 @@ +// 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 'dart:math' as math; +import 'dart:sky' as sky; + +import 'package:sky/animation/animation_performance.dart'; +import 'package:sky/animation/animated_value.dart'; +import 'package:sky/animation/curves.dart'; +import 'package:sky/theme/colors.dart'; +import 'package:sky/widgets/basic.dart'; +import 'package:sky/widgets/icon.dart'; +import 'package:sky/widgets/scaffold.dart'; +import 'package:sky/widgets/theme.dart'; +import 'package:sky/widgets/tool_bar.dart'; +import 'package:sky/widgets/framework.dart'; +import 'package:sky/widgets/task_description.dart'; +import 'package:sky/widgets/transitions.dart'; + +const double _kLinearProgressIndicatorHeight = 6.0; +const double _kMinCircularProgressIndicatorSize = 15.0; +const double _kCircularProgressIndicatorStrokeWidth = 3.0; + +abstract class ProgressIndicator extends StatefulComponent { + ProgressIndicator({ + Key key, + this.value, + this.bufferValue + }) : super(key: key); + + double value; // Null for non-determinate progress indicator. + double bufferValue; // TODO(hansmuller) implement the support for this. + + AnimationPerformance _animation; + double get _animationValue => _animation.variable.value; + Color get _backgroundColor => Theme.of(this).primarySwatch[200]; + Color get _valueColor => Theme.of(this).primaryColor; + + void initState() { + _animation = new AnimationPerformance() + ..duration = const Duration(milliseconds: 1500) + ..variable = new AnimatedValue(0.0, end: 1.0, curve: ease); + } + + void syncFields(ProgressIndicator source) { + value = source.value; + bufferValue = source.bufferValue; + } + + void _restartAnimation() { + _animation.progress = 0.0; + _animation.play(); + } + + Widget build() { + if (value != null) + return _buildIndicator(); + + return new BuilderTransition( + variables: [_animation.variable], + direction: Direction.forward, + performance: _animation, + onCompleted: _restartAnimation, + builder: _buildIndicator + ); + } + + Widget _buildIndicator(); +} + +class LinearProgressIndicator extends ProgressIndicator { + LinearProgressIndicator({ + Key key, + double value, + double bufferValue + }) : super(key: key, value: value, bufferValue: bufferValue); + + void _paint(sky.Canvas canvas, Size size) { + Paint paint = new Paint() + ..color = _backgroundColor + ..setStyle(sky.PaintingStyle.fill); + canvas.drawRect(Point.origin & size, paint); + + paint.color = _valueColor; + if (value != null) { + double width = value.clamp(0.0, 1.0) * size.width; + canvas.drawRect(Point.origin & new Size(width, size.height), paint); + } else { + double startX = size.width * (1.5 * _animationValue - 0.5); + double endX = startX + 0.5 * size.width; + double x = startX.clamp(0.0, size.width); + double width = endX.clamp(0.0, size.width) - x; + canvas.drawRect(new Point(x, 0.0) & new Size(width, size.height), paint); + } + } + + Widget _buildIndicator() { + return new Container( + child: new CustomPaint(callback: _paint), + constraints: new BoxConstraints.tightFor( + width: double.INFINITY, + height: _kLinearProgressIndicatorHeight + ) + ); + } +} + +class CircularProgressIndicator extends ProgressIndicator { + static const _kTwoPI = math.PI * 2.0; + static const _kEpsilon = .0000001; + // Canavs.drawArc(r, 0, 2*PI) doesn't draw anything, so just get close. + static const _kSweep = _kTwoPI - _kEpsilon; + static const _kStartAngle = -math.PI / 2.0; + + CircularProgressIndicator({ + Key key, + double value, + double bufferValue + }) : super(key: key, value: value, bufferValue: bufferValue); + + void _paint(sky.Canvas canvas, Size size) { + Paint paint = new Paint() + ..color = _valueColor + ..strokeWidth = _kCircularProgressIndicatorStrokeWidth + ..setStyle(sky.PaintingStyle.stroke); + + if (value != null) { + double angle = value.clamp(0.0, 1.0) * _kSweep; + sky.Path path = new sky.Path() + ..arcTo(Point.origin & size, _kStartAngle, angle, false); + canvas.drawPath(path, paint); + } else { + double startAngle = _kTwoPI * (1.75 * _animationValue - 0.75); + double endAngle = startAngle + _kTwoPI * 0.75; + double arcAngle = startAngle.clamp(0.0, _kTwoPI); + double arcSweep = endAngle.clamp(0.0, _kTwoPI) - arcAngle; + sky.Path path = new sky.Path() + ..arcTo(Point.origin & size, _kStartAngle + arcAngle, arcSweep, false); + canvas.drawPath(path, paint); + } + } + + Widget _buildIndicator() { + return new Container( + child: new CustomPaint(callback: _paint), + constraints: new BoxConstraints( + minWidth: _kMinCircularProgressIndicatorSize, + minHeight: _kMinCircularProgressIndicatorSize + ) + ); + } +}