blob: 3a2e39bee754fe04a5b814a41d35a492f793f9e7 [file] [log] [blame]
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/gallery_localizations.dart';
import 'transformations_demo_board.dart';
import 'transformations_demo_edit_board_point.dart';
// BEGIN transformationsDemo#1
class TransformationsDemo extends StatefulWidget {
const TransformationsDemo({super.key});
@override
State<TransformationsDemo> createState() => _TransformationsDemoState();
}
class _TransformationsDemoState extends State<TransformationsDemo>
with TickerProviderStateMixin {
final GlobalKey _targetKey = GlobalKey();
// The radius of a hexagon tile in pixels.
static const _kHexagonRadius = 16.0;
// The margin between hexagons.
static const _kHexagonMargin = 1.0;
// The radius of the entire board in hexagons, not including the center.
static const _kBoardRadius = 8;
Board _board = Board(
boardRadius: _kBoardRadius,
hexagonRadius: _kHexagonRadius,
hexagonMargin: _kHexagonMargin,
);
final TransformationController _transformationController =
TransformationController();
Animation<Matrix4>? _animationReset;
late AnimationController _controllerReset;
Matrix4? _homeMatrix;
// Handle reset to home transform animation.
void _onAnimateReset() {
_transformationController.value = _animationReset!.value;
if (!_controllerReset.isAnimating) {
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
}
// Initialize the reset to home transform animation.
void _animateResetInitialize() {
_controllerReset.reset();
_animationReset = Matrix4Tween(
begin: _transformationController.value,
end: _homeMatrix,
).animate(_controllerReset);
_controllerReset.duration = const Duration(milliseconds: 400);
_animationReset!.addListener(_onAnimateReset);
_controllerReset.forward();
}
// Stop a running reset to home transform animation.
void _animateResetStop() {
_controllerReset.stop();
_animationReset?.removeListener(_onAnimateReset);
_animationReset = null;
_controllerReset.reset();
}
void _onScaleStart(ScaleStartDetails details) {
// If the user tries to cause a transformation while the reset animation is
// running, cancel the reset animation.
if (_controllerReset.status == AnimationStatus.forward) {
_animateResetStop();
}
}
void _onTapUp(TapUpDetails details) {
final renderBox =
_targetKey.currentContext!.findRenderObject() as RenderBox;
final offset =
details.globalPosition - renderBox.localToGlobal(Offset.zero);
final scenePoint = _transformationController.toScene(offset);
final boardPoint = _board.pointToBoardPoint(scenePoint);
setState(() {
_board = _board.copyWithSelected(boardPoint);
});
}
@override
void initState() {
super.initState();
_controllerReset = AnimationController(
vsync: this,
);
}
@override
Widget build(BuildContext context) {
// The scene is drawn by a CustomPaint, but user interaction is handled by
// the InteractiveViewer parent widget.
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.primary,
appBar: AppBar(
automaticallyImplyLeading: false,
title:
Text(GalleryLocalizations.of(context)!.demo2dTransformationsTitle),
),
body: Container(
color: backgroundColor,
child: LayoutBuilder(
builder: (context, 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 viewportSize = Size(
constraints.maxWidth,
constraints.maxHeight,
);
// Start the first render, start the scene centered in the viewport.
if (_homeMatrix == null) {
_homeMatrix = Matrix4.identity()
..translate(
viewportSize.width / 2 - _board.size.width / 2,
viewportSize.height / 2 - _board.size.height / 2,
);
_transformationController.value = _homeMatrix!;
}
return ClipRect(
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: _onTapUp,
child: InteractiveViewer(
key: _targetKey,
transformationController: _transformationController,
boundaryMargin: EdgeInsets.symmetric(
horizontal: viewportSize.width,
vertical: viewportSize.height,
),
minScale: 0.01,
onInteractionStart: _onScaleStart,
child: SizedBox.expand(
child: CustomPaint(
size: _board.size,
painter: _BoardPainter(
board: _board,
),
),
),
),
),
),
);
},
),
),
persistentFooterButtons: [resetButton, editButton],
);
}
IconButton get resetButton {
return IconButton(
onPressed: () {
setState(() {
_animateResetInitialize();
});
},
tooltip: 'Reset',
color: Theme.of(context).colorScheme.surface,
icon: const Icon(Icons.replay),
);
}
IconButton get editButton {
return IconButton(
onPressed: () {
if (_board.selected == null) {
return;
}
showModalBottomSheet<Widget>(
context: context,
builder: (context) {
return Container(
width: double.infinity,
height: 150,
padding: const EdgeInsets.all(12),
child: EditBoardPoint(
boardPoint: _board.selected!,
onColorSelection: (color) {
setState(() {
_board = _board.copyWithBoardPointColor(
_board.selected!, color);
Navigator.pop(context);
});
},
),
);
});
},
tooltip: 'Edit',
color: Theme.of(context).colorScheme.surface,
icon: const Icon(Icons.edit),
);
}
@override
void dispose() {
_controllerReset.dispose();
super.dispose();
}
}
// CustomPainter is what is passed to CustomPaint and actually draws the scene
// when its `paint` method is called.
class _BoardPainter extends CustomPainter {
const _BoardPainter({required this.board});
final Board board;
@override
void paint(Canvas canvas, Size size) {
void drawBoardPoint(BoardPoint? boardPoint) {
final color = boardPoint!.color.withOpacity(
board.selected == boardPoint ? 0.7 : 1,
);
final 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;
}
}
// END