blob: ca7b37c9639dc7d79a7dbfb412a4996bc337d2ee [file] [log] [blame]
// 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:math' as math;
import 'package:vector_math/vector_math_64.dart';
import 'box.dart';
import 'layer.dart';
import 'object.dart';
const double _kQuarterTurnsInRadians = math.pi / 2.0;
/// Rotates its child by a integral number of quarter turns.
///
/// Unlike [RenderTransform], which applies a transform just prior to painting,
/// this object applies its rotation prior to layout, which means the entire
/// rotated box consumes only as much space as required by the rotated child.
class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
/// Creates a rotated render box.
///
/// The [quarterTurns] argument must not be null.
RenderRotatedBox({
required int quarterTurns,
RenderBox? child,
}) : assert(quarterTurns != null),
_quarterTurns = quarterTurns {
this.child = child;
}
/// The number of clockwise quarter turns the child should be rotated.
int get quarterTurns => _quarterTurns;
int _quarterTurns;
set quarterTurns(int value) {
assert(value != null);
if (_quarterTurns == value)
return;
_quarterTurns = value;
markNeedsLayout();
}
bool get _isVertical => quarterTurns.isOdd;
@override
double computeMinIntrinsicWidth(double height) {
if (child == null)
return 0.0;
return _isVertical ? child!.getMinIntrinsicHeight(height) : child!.getMinIntrinsicWidth(height);
}
@override
double computeMaxIntrinsicWidth(double height) {
if (child == null)
return 0.0;
return _isVertical ? child!.getMaxIntrinsicHeight(height) : child!.getMaxIntrinsicWidth(height);
}
@override
double computeMinIntrinsicHeight(double width) {
if (child == null)
return 0.0;
return _isVertical ? child!.getMinIntrinsicWidth(width) : child!.getMinIntrinsicHeight(width);
}
@override
double computeMaxIntrinsicHeight(double width) {
if (child == null)
return 0.0;
return _isVertical ? child!.getMaxIntrinsicWidth(width) : child!.getMaxIntrinsicHeight(width);
}
Matrix4? _paintTransform;
@override
Size computeDryLayout(BoxConstraints constraints) {
if (child == null) {
return constraints.smallest;
}
final Size childSize = child!.getDryLayout(_isVertical ? constraints.flipped : constraints);
return _isVertical ? Size(childSize.height, childSize.width) : childSize;
}
@override
void performLayout() {
_paintTransform = null;
if (child != null) {
child!.layout(_isVertical ? constraints.flipped : constraints, parentUsesSize: true);
size = _isVertical ? Size(child!.size.height, child!.size.width) : child!.size;
_paintTransform = Matrix4.identity()
..translate(size.width / 2.0, size.height / 2.0)
..rotateZ(_kQuarterTurnsInRadians * (quarterTurns % 4))
..translate(-child!.size.width / 2.0, -child!.size.height / 2.0);
} else {
size = constraints.smallest;
}
}
@override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
assert(_paintTransform != null || debugNeedsLayout || child == null);
if (child == null || _paintTransform == null)
return false;
return result.addWithPaintTransform(
transform: _paintTransform,
position: position,
hitTest: (BoxHitTestResult result, Offset position) {
return child!.hitTest(result, position: position);
},
);
}
void _paintChild(PaintingContext context, Offset offset) {
context.paintChild(child!, offset);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_transformLayer.layer = context.pushTransform(
needsCompositing,
offset,
_paintTransform!,
_paintChild,
oldLayer: _transformLayer.layer,
);
} else {
_transformLayer.layer = null;
}
}
final LayerHandle<TransformLayer> _transformLayer = LayerHandle<TransformLayer>();
@override
void dispose() {
_transformLayer.layer = null;
super.dispose();
}
@override
void applyPaintTransform(RenderBox child, Matrix4 transform) {
if (_paintTransform != null)
transform.multiply(_paintTransform!);
super.applyPaintTransform(child, transform);
}
}