| // 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:ui' show Vertices; |
| import 'package:flutter/material.dart'; |
| import 'transformations_demo_board.dart'; |
| import 'transformations_demo_edit_board_point.dart'; |
| import 'transformations_demo_gesture_transformable.dart'; |
| |
| class TransformationsDemo extends StatefulWidget { |
| const TransformationsDemo({ Key? key }) : super(key: key); |
| |
| static const String routeName = '/transformations'; |
| |
| @override _TransformationsDemoState createState() => _TransformationsDemoState(); |
| } |
| class _TransformationsDemoState extends State<TransformationsDemo> { |
| // The radius of a hexagon tile in pixels. |
| static const double _kHexagonRadius = 32.0; |
| // The margin between hexagons. |
| static const double _kHexagonMargin = 1.0; |
| // The radius of the entire board in hexagons, not including the center. |
| static const int _kBoardRadius = 8; |
| |
| bool _reset = false; |
| Board _board = Board( |
| boardRadius: _kBoardRadius, |
| hexagonRadius: _kHexagonRadius, |
| hexagonMargin: _kHexagonMargin, |
| ); |
| |
| @override |
| Widget build (BuildContext context) { |
| final BoardPainter painter = BoardPainter( |
| board: _board, |
| ); |
| |
| // The scene is drawn by a CustomPaint, but user interaction is handled by |
| // the GestureTransformable parent widget. |
| return Scaffold( |
| appBar: AppBar( |
| title: const Text('2D Transformations'), |
| actions: <Widget>[ |
| IconButton( |
| icon: const Icon(Icons.help), |
| tooltip: 'Help', |
| onPressed: () { |
| showDialog<Column>( |
| context: context, |
| builder: (BuildContext context) => instructionDialog, |
| ); |
| }, |
| ), |
| ], |
| ), |
| body: LayoutBuilder( |
| builder: (BuildContext context, BoxConstraints constraints) { |
| // Draw the scene as big as is available, but allow the user to |
| // translate beyond that to a visibleSize that's a bit bigger. |
| final Size size = Size(constraints.maxWidth, constraints.maxHeight); |
| final Size visibleSize = Size(size.width * 3, size.height * 2); |
| return GestureTransformable( |
| reset: _reset, |
| onResetEnd: () { |
| setState(() { |
| _reset = false; |
| }); |
| }, |
| child: CustomPaint( |
| painter: painter, |
| ), |
| boundaryRect: Rect.fromLTWH( |
| -visibleSize.width / 2, |
| -visibleSize.height / 2, |
| visibleSize.width, |
| visibleSize.height, |
| ), |
| // Center the board in the middle of the screen. It's drawn centered |
| // at the origin, which is the top left corner of the |
| // GestureTransformable. |
| initialTranslation: Offset(size.width / 2, size.height / 2), |
| onTapUp: _onTapUp, |
| size: size, |
| ); |
| }, |
| ), |
| floatingActionButton: _board.selected == null ? resetButton : editButton, |
| ); |
| } |
| |
| Widget get instructionDialog { |
| return AlertDialog( |
| title: const Text('2D Transformations'), |
| content: Column( |
| crossAxisAlignment: CrossAxisAlignment.start, |
| mainAxisSize: MainAxisSize.min, |
| children: const <Widget>[ |
| Text('Tap to edit hex tiles, and use gestures to move around the scene:\n'), |
| Text('- Drag to pan.'), |
| Text('- Pinch to zoom.'), |
| Text('- Rotate with two fingers.'), |
| Text('\nYou can always press the home button to return to the starting orientation!'), |
| ], |
| ), |
| actions: <Widget>[ |
| TextButton( |
| child: const Text('OK'), |
| onPressed: () { |
| Navigator.of(context).pop(); |
| }, |
| ), |
| ], |
| ); |
| } |
| |
| FloatingActionButton get resetButton { |
| return FloatingActionButton( |
| onPressed: () { |
| setState(() { |
| _reset = true; |
| }); |
| }, |
| tooltip: 'Reset Transform', |
| backgroundColor: Theme.of(context).primaryColor, |
| child: const Icon(Icons.home), |
| ); |
| } |
| |
| FloatingActionButton get editButton { |
| return FloatingActionButton( |
| onPressed: () { |
| if (_board.selected == null) { |
| return; |
| } |
| showModalBottomSheet<Widget>(context: context, builder: (BuildContext context) { |
| return Container( |
| width: double.infinity, |
| height: 150, |
| padding: const EdgeInsets.all(12.0), |
| child: EditBoardPoint( |
| boardPoint: _board.selected!, |
| onColorSelection: (Color color) { |
| setState(() { |
| _board = _board.copyWithBoardPointColor(_board.selected!, color); |
| Navigator.pop(context); |
| }); |
| }, |
| ), |
| ); |
| }); |
| }, |
| tooltip: 'Edit Tile', |
| child: const Icon(Icons.edit), |
| ); |
| } |
| |
| void _onTapUp(TapUpDetails details) { |
| final Offset scenePoint = details.globalPosition; |
| final BoardPoint? boardPoint = _board.pointToBoardPoint(scenePoint); |
| setState(() { |
| _board = _board.copyWithSelected(boardPoint); |
| }); |
| } |
| } |
| |
| // CustomPainter is what is passed to CustomPaint and actually draws the scene |
| // when its `paint` method is called. |
| class BoardPainter extends CustomPainter { |
| const BoardPainter({ |
| this.board, |
| }); |
| |
| final Board? board; |
| |
| @override |
| void paint(Canvas canvas, Size size) { |
| void drawBoardPoint(BoardPoint? boardPoint) { |
| final Color color = boardPoint!.color.withOpacity( |
| board!.selected == boardPoint ? 0.2 : 1.0, |
| ); |
| final Vertices vertices = board!.getVerticesForBoardPoint(boardPoint, color); |
| canvas.drawVertices(vertices, BlendMode.color, Paint()); |
| } |
| |
| board!.forEach(drawBoardPoint); |
| } |
| |
| // We should repaint whenever the board changes, such as board.selected. |
| @override |
| bool shouldRepaint(BoardPainter oldDelegate) { |
| return oldDelegate.board != board; |
| } |
| } |