flutter/dev/manual_tests/lib/material_arc.dart
Ian Hickson 449f4a6673
License update (#45373)
* Update project.pbxproj files to say Flutter rather than Chromium

Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright.

* Update the copyright notice checker to require a standard notice on all files

* Update copyrights on Dart files. (This was a mechanical commit.)

* Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine.

Some were already marked "The Flutter Authors", not clear why. Their
dates have been normalized. Some were missing the blank line after the
license. Some were randomly different in trivial ways for no apparent
reason (e.g. missing the trailing period).

* Clean up the copyrights in non-Dart files. (Manual edits.)

Also, make sure templates don't have copyrights.

* Fix some more ORGANIZATIONNAMEs
2019-11-27 15:04:02 -08:00

493 lines
14 KiB
Dart

// Copyright 2014 The Flutter 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:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
enum _DragTarget {
start,
end
}
// How close a drag's start position must be to the target point. This is
// a distance squared.
const double _kTargetSlop = 2500.0;
// Used by the Painter classes.
const double _kPointRadius = 6.0;
class _DragHandler extends Drag {
_DragHandler(this.onUpdate, this.onCancel, this.onEnd);
final GestureDragUpdateCallback onUpdate;
final GestureDragCancelCallback onCancel;
final GestureDragEndCallback onEnd;
@override
void update(DragUpdateDetails details) {
onUpdate(details);
}
@override
void cancel() {
onCancel();
}
@override
void end(DragEndDetails details) {
onEnd(details);
}
}
class _IgnoreDrag extends Drag {
}
class _PointDemoPainter extends CustomPainter {
_PointDemoPainter({
Animation<double> repaint,
this.arc,
}) : _repaint = repaint, super(repaint: repaint);
final MaterialPointArcTween arc;
final Animation<double> _repaint;
void drawPoint(Canvas canvas, Offset point, Color color) {
final Paint paint = Paint()
..color = color.withOpacity(0.25)
..style = PaintingStyle.fill;
canvas.drawCircle(point, _kPointRadius, paint);
paint
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawCircle(point, _kPointRadius + 1.0, paint);
}
@override
void paint(Canvas canvas, Size size) {
final Paint paint = Paint();
if (arc.center != null)
drawPoint(canvas, arc.center, Colors.grey.shade400);
paint
..isAntiAlias = false // Work-around for github.com/flutter/flutter/issues/5720
..color = Colors.green.withOpacity(0.25)
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
if (arc.center != null && arc.radius != null)
canvas.drawCircle(arc.center, arc.radius, paint);
else
canvas.drawLine(arc.begin, arc.end, paint);
drawPoint(canvas, arc.begin, Colors.green);
drawPoint(canvas, arc.end, Colors.red);
paint
..color = Colors.green
..style = PaintingStyle.fill;
canvas.drawCircle(arc.lerp(_repaint.value), _kPointRadius, paint);
}
@override
bool hitTest(Offset position) {
return (arc.begin - position).distanceSquared < _kTargetSlop
|| (arc.end - position).distanceSquared < _kTargetSlop;
}
@override
bool shouldRepaint(_PointDemoPainter oldPainter) => arc != oldPainter.arc;
}
class _PointDemo extends StatefulWidget {
const _PointDemo({ Key key, this.controller }) : super(key: key);
final AnimationController controller;
@override
_PointDemoState createState() => _PointDemoState();
}
class _PointDemoState extends State<_PointDemo> {
final GlobalKey _painterKey = GlobalKey();
CurvedAnimation _animation;
_DragTarget _dragTarget;
Size _screenSize;
Offset _begin;
Offset _end;
@override
void initState() {
super.initState();
_animation = CurvedAnimation(parent: widget.controller, curve: Curves.fastOutSlowIn);
}
@override
void dispose() {
widget.controller.value = 0.0;
super.dispose();
}
Drag _handleOnStart(Offset position) {
// TODO(hansmuller): allow the user to drag both points at the same time.
if (_dragTarget != null)
return _IgnoreDrag();
final RenderBox box = _painterKey.currentContext.findRenderObject();
final double startOffset = (box.localToGlobal(_begin) - position).distanceSquared;
final double endOffset = (box.localToGlobal(_end) - position).distanceSquared;
setState(() {
if (startOffset < endOffset && startOffset < _kTargetSlop)
_dragTarget = _DragTarget.start;
else if (endOffset < _kTargetSlop)
_dragTarget = _DragTarget.end;
else
_dragTarget = null;
});
return _DragHandler(_handleDragUpdate, _handleDragCancel, _handleDragEnd);
}
void _handleDragUpdate(DragUpdateDetails details) {
switch (_dragTarget) {
case _DragTarget.start:
setState(() {
_begin = _begin + details.delta;
});
break;
case _DragTarget.end:
setState(() {
_end = _end + details.delta;
});
break;
}
}
void _handleDragCancel() {
_dragTarget = null;
widget.controller.value = 0.0;
}
void _handleDragEnd(DragEndDetails details) {
_dragTarget = null;
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
if (_screenSize == null || _screenSize != screenSize) {
_screenSize = screenSize;
_begin = Offset(screenSize.width * 0.5, screenSize.height * 0.2);
_end = Offset(screenSize.width * 0.1, screenSize.height * 0.4);
}
final MaterialPointArcTween arc = MaterialPointArcTween(begin: _begin, end: _end);
return RawGestureDetector(
behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque,
gestures: <Type, GestureRecognizerFactory>{
ImmediateMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<ImmediateMultiDragGestureRecognizer>(
() => ImmediateMultiDragGestureRecognizer(),
(ImmediateMultiDragGestureRecognizer instance) {
instance
..onStart = _handleOnStart;
},
),
},
child: ClipRect(
child: CustomPaint(
key: _painterKey,
foregroundPainter: _PointDemoPainter(
repaint: _animation,
arc: arc,
),
// Watch out: if this IgnorePointer is left out, then gestures that
// fail _PointDemoPainter.hitTest() will still be recognized because
// they do overlap this child, which is as big as the CustomPaint.
child: IgnorePointer(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Tap the refresh button to run the animation. Drag the green '
"and red points to change the animation's path.",
style: Theme.of(context).textTheme.caption.copyWith(fontSize: 16.0),
),
),
),
),
),
);
}
}
class _RectangleDemoPainter extends CustomPainter {
_RectangleDemoPainter({
Animation<double> repaint,
this.arc,
}) : _repaint = repaint, super(repaint: repaint);
final MaterialRectArcTween arc;
final Animation<double> _repaint;
void drawPoint(Canvas canvas, Offset p, Color color) {
final Paint paint = Paint()
..color = color.withOpacity(0.25)
..style = PaintingStyle.fill;
canvas.drawCircle(p, _kPointRadius, paint);
paint
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawCircle(p, _kPointRadius + 1.0, paint);
}
void drawRect(Canvas canvas, Rect rect, Color color) {
final Paint paint = Paint()
..color = color.withOpacity(0.25)
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
canvas.drawRect(rect, paint);
drawPoint(canvas, rect.center, color);
}
@override
void paint(Canvas canvas, Size size) {
drawRect(canvas, arc.begin, Colors.green);
drawRect(canvas, arc.end, Colors.red);
drawRect(canvas, arc.lerp(_repaint.value), Colors.blue);
}
@override
bool hitTest(Offset position) {
return (arc.begin.center - position).distanceSquared < _kTargetSlop
|| (arc.end.center - position).distanceSquared < _kTargetSlop;
}
@override
bool shouldRepaint(_RectangleDemoPainter oldPainter) => arc != oldPainter.arc;
}
class _RectangleDemo extends StatefulWidget {
const _RectangleDemo({ Key key, this.controller }) : super(key: key);
final AnimationController controller;
@override
_RectangleDemoState createState() => _RectangleDemoState();
}
class _RectangleDemoState extends State<_RectangleDemo> {
final GlobalKey _painterKey = GlobalKey();
CurvedAnimation _animation;
_DragTarget _dragTarget;
Size _screenSize;
Rect _begin;
Rect _end;
@override
void initState() {
super.initState();
_animation = CurvedAnimation(parent: widget.controller, curve: Curves.fastOutSlowIn);
}
@override
void dispose() {
widget.controller.value = 0.0;
super.dispose();
}
Drag _handleOnStart(Offset position) {
// TODO(hansmuller): allow the user to drag both points at the same time.
if (_dragTarget != null)
return _IgnoreDrag();
final RenderBox box = _painterKey.currentContext.findRenderObject();
final double startOffset = (box.localToGlobal(_begin.center) - position).distanceSquared;
final double endOffset = (box.localToGlobal(_end.center) - position).distanceSquared;
setState(() {
if (startOffset < endOffset && startOffset < _kTargetSlop)
_dragTarget = _DragTarget.start;
else if (endOffset < _kTargetSlop)
_dragTarget = _DragTarget.end;
else
_dragTarget = null;
});
return _DragHandler(_handleDragUpdate, _handleDragCancel, _handleDragEnd);
}
void _handleDragUpdate(DragUpdateDetails details) {
switch (_dragTarget) {
case _DragTarget.start:
setState(() {
_begin = _begin.shift(details.delta);
});
break;
case _DragTarget.end:
setState(() {
_end = _end.shift(details.delta);
});
break;
}
}
void _handleDragCancel() {
_dragTarget = null;
widget.controller.value = 0.0;
}
void _handleDragEnd(DragEndDetails details) {
_dragTarget = null;
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
if (_screenSize == null || _screenSize != screenSize) {
_screenSize = screenSize;
_begin = Rect.fromLTWH(
screenSize.width * 0.5, screenSize.height * 0.2,
screenSize.width * 0.4, screenSize.height * 0.2,
);
_end = Rect.fromLTWH(
screenSize.width * 0.1, screenSize.height * 0.4,
screenSize.width * 0.3, screenSize.height * 0.3,
);
}
final MaterialRectArcTween arc = MaterialRectArcTween(begin: _begin, end: _end);
return RawGestureDetector(
behavior: _dragTarget == null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque,
gestures: <Type, GestureRecognizerFactory>{
ImmediateMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<ImmediateMultiDragGestureRecognizer>(
() => ImmediateMultiDragGestureRecognizer(),
(ImmediateMultiDragGestureRecognizer instance) {
instance
..onStart = _handleOnStart;
},
),
},
child: ClipRect(
child: CustomPaint(
key: _painterKey,
foregroundPainter: _RectangleDemoPainter(
repaint: _animation,
arc: arc,
),
// Watch out: if this IgnorePointer is left out, then gestures that
// fail _RectDemoPainter.hitTest() will still be recognized because
// they do overlap this child, which is as big as the CustomPaint.
child: IgnorePointer(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Tap the refresh button to run the animation. Drag the rectangles '
"to change the animation's path.",
style: Theme.of(context).textTheme.caption.copyWith(fontSize: 16.0),
),
),
),
),
),
);
}
}
typedef _DemoBuilder = Widget Function(_ArcDemo demo);
class _ArcDemo {
_ArcDemo(this.title, this.builder, TickerProvider vsync)
: controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: vsync),
key = GlobalKey(debugLabel: title);
final String title;
final _DemoBuilder builder;
final AnimationController controller;
final GlobalKey key;
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({ Key key }) : super(key: key);
@override
_AnimationDemoState createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> with TickerProviderStateMixin {
List<_ArcDemo> _allDemos;
@override
void initState() {
super.initState();
_allDemos = <_ArcDemo>[
_ArcDemo('POINT', (_ArcDemo demo) {
return _PointDemo(
key: demo.key,
controller: demo.controller,
);
}, this),
_ArcDemo('RECTANGLE', (_ArcDemo demo) {
return _RectangleDemo(
key: demo.key,
controller: demo.controller,
);
}, this),
];
}
Future<void> _play(_ArcDemo demo) async {
await demo.controller.forward();
if (demo.key.currentState != null && demo.key.currentState.mounted)
demo.controller.reverse();
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _allDemos.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Animation'),
bottom: TabBar(
tabs: _allDemos.map<Tab>((_ArcDemo demo) => Tab(text: demo.title)).toList(),
),
),
floatingActionButton: Builder(
builder: (BuildContext context) {
return FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
_play(_allDemos[DefaultTabController.of(context).index]);
},
);
},
),
body: TabBarView(
children: _allDemos.map<Widget>((_ArcDemo demo) => demo.builder(demo)).toList(),
),
),
);
}
}
// Sets a platform override for desktop to avoid exceptions. See
// https://flutter.dev/desktop#target-platform-override for more info.
// TODO(gspencergoog): Remove once TargetPlatform includes all desktop platforms.
void _enablePlatformOverrideForDesktop() {
if (!kIsWeb && (Platform.isWindows || Platform.isLinux)) {
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
}
}
void main() {
_enablePlatformOverrideForDesktop();
runApp(const MaterialApp(
home: AnimationDemo(),
));
}