1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/foundation.dart';
8import 'package:vector_math/vector_math_64.dart';
9
10import 'box.dart';
11import 'layer.dart';
12import 'object.dart';
13
14const double _kQuarterTurnsInRadians = math.pi / 2.0;
15
16/// Rotates its child by a integral number of quarter turns.
17///
18/// Unlike [RenderTransform], which applies a transform just prior to painting,
19/// this object applies its rotation prior to layout, which means the entire
20/// rotated box consumes only as much space as required by the rotated child.
21class RenderRotatedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
22 /// Creates a rotated render box.
23 RenderRotatedBox({
24 required int quarterTurns,
25 RenderBox? child,
26 }) : _quarterTurns = quarterTurns {
27 this.child = child;
28 }
29
30 /// The number of clockwise quarter turns the child should be rotated.
31 int get quarterTurns => _quarterTurns;
32 int _quarterTurns;
33 set quarterTurns(int value) {
34 if (_quarterTurns == value) {
35 return;
36 }
37 _quarterTurns = value;
38 markNeedsLayout();
39 }
40
41 bool get _isVertical => quarterTurns.isOdd;
42
43 @override
44 double computeMinIntrinsicWidth(double height) {
45 if (child == null) {
46 return 0.0;
47 }
48 return _isVertical ? child!.getMinIntrinsicHeight(height) : child!.getMinIntrinsicWidth(height);
49 }
50
51 @override
52 double computeMaxIntrinsicWidth(double height) {
53 if (child == null) {
54 return 0.0;
55 }
56 return _isVertical ? child!.getMaxIntrinsicHeight(height) : child!.getMaxIntrinsicWidth(height);
57 }
58
59 @override
60 double computeMinIntrinsicHeight(double width) {
61 if (child == null) {
62 return 0.0;
63 }
64 return _isVertical ? child!.getMinIntrinsicWidth(width) : child!.getMinIntrinsicHeight(width);
65 }
66
67 @override
68 double computeMaxIntrinsicHeight(double width) {
69 if (child == null) {
70 return 0.0;
71 }
72 return _isVertical ? child!.getMaxIntrinsicWidth(width) : child!.getMaxIntrinsicHeight(width);
73 }
74
75 Matrix4? _paintTransform;
76
77 @override
78 @protected
79 Size computeDryLayout(covariant BoxConstraints constraints) {
80 if (child == null) {
81 return constraints.smallest;
82 }
83 final Size childSize = child!.getDryLayout(_isVertical ? constraints.flipped : constraints);
84 return _isVertical ? Size(childSize.height, childSize.width) : childSize;
85 }
86
87 @override
88 void performLayout() {
89 _paintTransform = null;
90 if (child != null) {
91 child!.layout(_isVertical ? constraints.flipped : constraints, parentUsesSize: true);
92 size = _isVertical ? Size(child!.size.height, child!.size.width) : child!.size;
93 _paintTransform = Matrix4.identity()
94 ..translate(size.width / 2.0, size.height / 2.0)
95 ..rotateZ(_kQuarterTurnsInRadians * (quarterTurns % 4))
96 ..translate(-child!.size.width / 2.0, -child!.size.height / 2.0);
97 } else {
98 size = constraints.smallest;
99 }
100 }
101
102 @override
103 bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
104 assert(_paintTransform != null || debugNeedsLayout || child == null);
105 if (child == null || _paintTransform == null) {
106 return false;
107 }
108 return result.addWithPaintTransform(
109 transform: _paintTransform,
110 position: position,
111 hitTest: (BoxHitTestResult result, Offset position) {
112 return child!.hitTest(result, position: position);
113 },
114 );
115 }
116
117 void _paintChild(PaintingContext context, Offset offset) {
118 context.paintChild(child!, offset);
119 }
120
121 @override
122 void paint(PaintingContext context, Offset offset) {
123 if (child != null) {
124 _transformLayer.layer = context.pushTransform(
125 needsCompositing,
126 offset,
127 _paintTransform!,
128 _paintChild,
129 oldLayer: _transformLayer.layer,
130 );
131 } else {
132 _transformLayer.layer = null;
133 }
134 }
135
136 final LayerHandle<TransformLayer> _transformLayer = LayerHandle<TransformLayer>();
137
138 @override
139 void dispose() {
140 _transformLayer.layer = null;
141 super.dispose();
142 }
143
144 @override
145 void applyPaintTransform(RenderBox child, Matrix4 transform) {
146 if (_paintTransform != null) {
147 transform.multiply(_paintTransform!);
148 }
149 super.applyPaintTransform(child, transform);
150 }
151}
152