blob: 24c068776d7c66ea1e67ac82118d793055df2c7c [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:vector_math/vector_math_64.dart';
const double _kGemCornerRadius = 16.0;
const double _kGemOpacity = 1.0;
const double _kFaceRotation = math.pi / 2.0;
const double _kPerspectiveFieldOfViewRadians = math.pi / 6.0;
const double _kPerspectiveNearZ = 100.0;
const double _kPerspectiveAspectRatio = 1.0;
const double _kCubeScaleFactor = 50.0;
const double _kCubeAnimationYRotation = 2.0 * math.pi;
const double _kCubeAnimationXRotation = 6.0 * math.pi;
/// Creates a spinning unicolor cube with rounded corners.
class SpinningCubeGem extends StatelessWidget {
/// Controls the spinning animation.
final AnimationController controller;
/// The color of the cube faces.
final Color color;
/// Constructor.
const SpinningCubeGem({this.controller, this.color});
// The six cube faces are:
// 1. Placed in a stack and rotated and translated into different positions
// to form a cube.
// 2. Manipulated into a perspective view.
// 3. Rotated based on the animation.
@override
Widget build(BuildContext context) => new LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double gemSize = math.min(
constraints.maxWidth,
constraints.maxHeight,
);
double faceSize = gemSize;
double halfFaceSize = faceSize / 2.0;
double perspectiveFarZ = _kPerspectiveNearZ + (2.0 * faceSize);
double cubeZ = _kPerspectiveNearZ + faceSize;
/// We draw the cube with a 3D perspective. This matrix manipulates the cube
/// faces into this perspective.
Matrix4 cubePerspective = new Matrix4.diagonal3Values(
_kCubeScaleFactor,
_kCubeScaleFactor,
_kCubeScaleFactor,
) *
makePerspectiveMatrix(
_kPerspectiveFieldOfViewRadians,
_kPerspectiveAspectRatio,
_kPerspectiveNearZ,
perspectiveFarZ,
) *
new Matrix4.translationValues(0.0, 0.0, cubeZ);
return new Opacity(
opacity: _kGemOpacity,
child: new AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget child) => new Stack(
children: <Widget>[
// Right face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
halfFaceSize,
0.0,
0.0,
),
faceRotation: new Matrix4.rotationY(_kFaceRotation),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
// Left face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
-halfFaceSize, 0.0, 0.0),
faceRotation: new Matrix4.rotationY(_kFaceRotation),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
// Back face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
0.0,
0.0,
halfFaceSize,
),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
// Front face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
0.0, 0.0, -halfFaceSize),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
// Bottom face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
0.0,
halfFaceSize,
0.0,
),
faceRotation: new Matrix4.rotationX(_kFaceRotation),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
// Top face.
_createRoundedCubeFace(
faceTranslation: new Matrix4.translationValues(
0.0,
-halfFaceSize,
0.0,
),
faceRotation: new Matrix4.rotationX(_kFaceRotation),
cubePerspective: cubePerspective,
gemSize: gemSize,
),
],
),
),
);
},
);
/// Creates a cube face, rotated by [faceRotation] and translated by
/// [faceTranslation]. The face is then rotated with the rest of the ongoing
/// cube rotation as specified by [controller],
/// [_kCubeAnimationXRotation], and [_kCubeAnimationYRotation].
Widget _createRoundedCubeFace({
@required Matrix4 cubePerspective,
@required double gemSize,
Matrix4 faceTranslation,
Matrix4 faceRotation,
}) =>
new Transform(
alignment: FractionalOffset.center,
transform: cubePerspective *
new Matrix4.rotationY(
_kCubeAnimationYRotation * controller.value,
) *
new Matrix4.rotationX(
_kCubeAnimationXRotation * controller.value,
) *
(faceTranslation ?? new Matrix4.identity()) *
(faceRotation ?? new Matrix4.identity()),
child: new Container(
width: gemSize,
height: gemSize,
decoration: new BoxDecoration(
color: color,
borderRadius: new BorderRadius.circular(_kGemCornerRadius),
),
),
);
}