|
| 1 | +import 'dart:ui' show Vertices; |
| 2 | +import 'package:flutter/material.dart'; |
| 3 | +import 'transformations_demo_board.dart'; |
| 4 | +import 'transformations_demo_edit_board_point.dart'; |
| 5 | +import 'transformations_demo_gesture_transformable.dart'; |
| 6 | + |
| 7 | +class TransformationsDemo extends StatefulWidget { |
| 8 | + const TransformationsDemo({ Key key }) : super(key: key); |
| 9 | + |
| 10 | + static const String routeName = '/transformations'; |
| 11 | + |
| 12 | + @override _TransformationsDemoState createState() => _TransformationsDemoState(); |
| 13 | +} |
| 14 | +class _TransformationsDemoState extends State<TransformationsDemo> { |
| 15 | + // The radius of a hexagon tile in pixels. |
| 16 | + static const double _kHexagonRadius = 32.0; |
| 17 | + // The margin between hexagons. |
| 18 | + static const double _kHexagonMargin = 1.0; |
| 19 | + // The radius of the entire board in hexagons, not including the center. |
| 20 | + static const int _kBoardRadius = 8; |
| 21 | + |
| 22 | + bool _reset = false; |
| 23 | + Board _board = Board( |
| 24 | + boardRadius: _kBoardRadius, |
| 25 | + hexagonRadius: _kHexagonRadius, |
| 26 | + hexagonMargin: _kHexagonMargin, |
| 27 | + ); |
| 28 | + |
| 29 | + @override |
| 30 | + Widget build (BuildContext context) { |
| 31 | + final BoardPainter painter = BoardPainter( |
| 32 | + board: _board, |
| 33 | + ); |
| 34 | + |
| 35 | + // The scene is drawn by a CustomPaint, but user interaction is handled by |
| 36 | + // the GestureTransformable parent widget. |
| 37 | + return Scaffold( |
| 38 | + appBar: AppBar(), |
| 39 | + body: LayoutBuilder( |
| 40 | + builder: (BuildContext context, BoxConstraints constraints) { |
| 41 | + // Draw the scene as big as is available, but allow the user to |
| 42 | + // translate beyond that to a visibleSize that's a bit bigger. |
| 43 | + final Size size = Size(constraints.maxWidth, constraints.maxHeight); |
| 44 | + final Size visibleSize = Size(size.width * 3, size.height * 2); |
| 45 | + return GestureTransformable( |
| 46 | + reset: _reset, |
| 47 | + onResetEnd: () { |
| 48 | + setState(() { |
| 49 | + _reset = false; |
| 50 | + }); |
| 51 | + }, |
| 52 | + child: CustomPaint( |
| 53 | + painter: painter, |
| 54 | + ), |
| 55 | + boundaryRect: Rect.fromLTWH( |
| 56 | + -visibleSize.width / 2, |
| 57 | + -visibleSize.height / 2, |
| 58 | + visibleSize.width, |
| 59 | + visibleSize.height, |
| 60 | + ), |
| 61 | + // Center the board in the middle of the screen. It's drawn centered |
| 62 | + // at the origin, which is the top left corner of the |
| 63 | + // GestureTransformable. |
| 64 | + initialTranslation: Offset(size.width / 2, size.height / 2), |
| 65 | + onTapUp: _onTapUp, |
| 66 | + size: size, |
| 67 | + ); |
| 68 | + }, |
| 69 | + ), |
| 70 | + floatingActionButton: _board.selected == null ? resetButton : editButton, |
| 71 | + ); |
| 72 | + } |
| 73 | + |
| 74 | + FloatingActionButton get resetButton { |
| 75 | + return FloatingActionButton( |
| 76 | + onPressed: () { |
| 77 | + setState(() { |
| 78 | + _reset = true; |
| 79 | + }); |
| 80 | + }, |
| 81 | + tooltip: 'Reset Transform', |
| 82 | + backgroundColor: Theme.of(context).primaryColor, |
| 83 | + child: const Icon(Icons.home), |
| 84 | + ); |
| 85 | + } |
| 86 | + |
| 87 | + FloatingActionButton get editButton { |
| 88 | + return FloatingActionButton( |
| 89 | + onPressed: () { |
| 90 | + if (_board.selected == null) { |
| 91 | + return; |
| 92 | + } |
| 93 | + showModalBottomSheet<Widget>(context: context, builder: (BuildContext context) { |
| 94 | + return Container( |
| 95 | + width: double.infinity, |
| 96 | + height: 150, |
| 97 | + padding: const EdgeInsets.all(12.0), |
| 98 | + child: EditBoardPoint( |
| 99 | + boardPoint: _board.selected, |
| 100 | + onColorSelection: (Color color) { |
| 101 | + setState(() { |
| 102 | + _board = _board.copyWithBoardPointColor(_board.selected, color); |
| 103 | + Navigator.pop(context); |
| 104 | + }); |
| 105 | + }, |
| 106 | + ), |
| 107 | + ); |
| 108 | + }); |
| 109 | + }, |
| 110 | + tooltip: 'Edit Tile', |
| 111 | + child: const Icon(Icons.edit), |
| 112 | + ); |
| 113 | + } |
| 114 | + |
| 115 | + void _onTapUp(TapUpDetails details) { |
| 116 | + final Offset scenePoint = details.globalPosition; |
| 117 | + final BoardPoint boardPoint = _board.pointToBoardPoint(scenePoint); |
| 118 | + setState(() { |
| 119 | + _board = _board.copyWithSelected(boardPoint); |
| 120 | + }); |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +// CustomPainter is what is passed to CustomPaint and actually draws the scene |
| 125 | +// when its `paint` method is called. |
| 126 | +class BoardPainter extends CustomPainter { |
| 127 | + const BoardPainter({ |
| 128 | + this.board, |
| 129 | + }); |
| 130 | + |
| 131 | + final Board board; |
| 132 | + |
| 133 | + @override |
| 134 | + void paint(Canvas canvas, Size size) { |
| 135 | + void drawBoardPoint(BoardPoint boardPoint) { |
| 136 | + final Color color = boardPoint.color.withOpacity( |
| 137 | + board.selected == boardPoint ? 0.2 : 1.0, |
| 138 | + ); |
| 139 | + final Vertices vertices = board.getVerticesForBoardPoint(boardPoint, color); |
| 140 | + canvas.drawVertices(vertices, BlendMode.color, Paint()); |
| 141 | + } |
| 142 | + |
| 143 | + board.forEach(drawBoardPoint); |
| 144 | + } |
| 145 | + |
| 146 | + // We should repaint whenever the board changes, such as board.selected. |
| 147 | + @override |
| 148 | + bool shouldRepaint(BoardPainter oldDelegate) { |
| 149 | + return oldDelegate.board != board; |
| 150 | + } |
| 151 | +} |
0 commit comments