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 | |
5 | import 'dart:math' as math; |
6 | |
7 | import 'package:flutter/foundation.dart'; |
8 | import 'package:vector_math/vector_math_64.dart' ; |
9 | |
10 | import 'box.dart'; |
11 | import 'layer.dart'; |
12 | import 'object.dart'; |
13 | |
14 | const 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. |
21 | class 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 | |