Commit 3c95ce62 authored by Hixie's avatar Hixie

Transform alignment

For those times where you want to spin something around a point relative
to the size of your box, but you don't know the size of your box.
parent 2032d3e4
...@@ -862,15 +862,41 @@ class RenderDecoratedBox extends RenderProxyBox { ...@@ -862,15 +862,41 @@ class RenderDecoratedBox extends RenderProxyBox {
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}decoration:\n${_painter.decoration.toString(prefix + " ")}\n'; String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}decoration:\n${_painter.decoration.toString(prefix + " ")}\n';
} }
/// An offset that's expressed as a fraction of a Size.
///
/// FractionalOffset(0.0, 0.0) represents the top left of the Size,
/// FractionalOffset(1.0, 1.0) represents the bottom right of the Size.
class FractionalOffset {
const FractionalOffset(this.x, this.y);
final double x;
final double y;
bool operator ==(dynamic other) {
if (other is! FractionalOffset)
return false;
final FractionalOffset typedOther = other;
return x == typedOther.x &&
y == typedOther.y;
}
int get hashCode {
int value = 373;
value = 37 * value + x.hashCode;
value = 37 * value + y.hashCode;
return value;
}
}
/// Applies a transformation before painting its child /// Applies a transformation before painting its child
class RenderTransform extends RenderProxyBox { class RenderTransform extends RenderProxyBox {
RenderTransform({ RenderTransform({
Matrix4 transform, Matrix4 transform,
Offset origin, Offset origin,
FractionalOffset alignment,
RenderBox child RenderBox child
}) : super(child) { }) : super(child) {
assert(transform != null); assert(transform != null);
assert(alignment == null || (alignment.x != null && alignment.y != null));
this.transform = transform; this.transform = transform;
this.alignment = alignment;
this.origin = origin; this.origin = origin;
} }
...@@ -888,6 +914,20 @@ class RenderTransform extends RenderProxyBox { ...@@ -888,6 +914,20 @@ class RenderTransform extends RenderProxyBox {
markNeedsPaint(); markNeedsPaint();
} }
/// The alignment of the origin, relative to the size of the box.
///
/// This is equivalent to setting an origin based on the size of the box.
/// If it is specificed at the same time as an offset, both are applied.
FractionalOffset get alignment => _alignment;
FractionalOffset _alignment;
void set alignment (FractionalOffset newAlignment) {
assert(newAlignment == null || (newAlignment.x != null && newAlignment.y != null));
if (_alignment == newAlignment)
return;
_alignment = newAlignment;
markNeedsPaint();
}
// Note the lack of a getter for transform because Matrix4 is not immutable // Note the lack of a getter for transform because Matrix4 is not immutable
Matrix4 _transform; Matrix4 _transform;
...@@ -937,13 +977,19 @@ class RenderTransform extends RenderProxyBox { ...@@ -937,13 +977,19 @@ class RenderTransform extends RenderProxyBox {
} }
Matrix4 get _effectiveTransform { Matrix4 get _effectiveTransform {
if (_origin == null) if (_origin == null && _alignment == null)
return _transform; return _transform;
return new Matrix4 Matrix4 result = new Matrix4.identity();
.identity() if (_origin != null)
.translate(_origin.dx, _origin.dy) result.translate(_origin.dx, _origin.dy);
.multiply(_transform) if (_alignment != null)
.translate(-_origin.dx, -_origin.dy); result.translate(_alignment.x * size.width, _alignment.y * size.height);
result.multiply(_transform);
if (_alignment != null)
result.translate(-_alignment.x * size.width, -_alignment.y * size.height);
if (_origin != null)
result.translate(-_origin.dx, -_origin.dy);
return result;
} }
bool hitTest(HitTestResult result, { Point position }) { bool hitTest(HitTestResult result, { Point position }) {
......
...@@ -23,6 +23,7 @@ export 'package:flutter/rendering.dart' show ...@@ -23,6 +23,7 @@ export 'package:flutter/rendering.dart' show
FlexAlignItems, FlexAlignItems,
FlexDirection, FlexDirection,
FlexJustifyContent, FlexJustifyContent,
FractionalOffset,
Matrix4, Matrix4,
Offset, Offset,
Paint, Paint,
...@@ -171,19 +172,21 @@ class ClipOval extends OneChildRenderObjectWidget { ...@@ -171,19 +172,21 @@ class ClipOval extends OneChildRenderObjectWidget {
// POSITIONING AND SIZING NODES // POSITIONING AND SIZING NODES
class Transform extends OneChildRenderObjectWidget { class Transform extends OneChildRenderObjectWidget {
Transform({ Key key, this.transform, this.origin, Widget child }) Transform({ Key key, this.transform, this.origin, this.alignment, Widget child })
: super(key: key, child: child) { : super(key: key, child: child) {
assert(transform != null); assert(transform != null);
} }
final Matrix4 transform; final Matrix4 transform;
final Offset origin; final Offset origin;
final FractionalOffset alignment;
RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin); RenderTransform createRenderObject() => new RenderTransform(transform: transform, origin: origin, alignment: alignment);
void updateRenderObject(RenderTransform renderObject, Transform oldWidget) { void updateRenderObject(RenderTransform renderObject, Transform oldWidget) {
renderObject.transform = transform; renderObject.transform = transform;
renderObject.origin = origin; renderObject.origin = origin;
renderObject.alignment = alignment;
} }
} }
......
...@@ -48,4 +48,95 @@ void main() { ...@@ -48,4 +48,95 @@ void main() {
expect(didReceiveTap, isTrue); expect(didReceiveTap, isTrue);
}); });
}); });
test('Transform alignment', () {
testWidgets((WidgetTester tester) {
bool didReceiveTap = false;
tester.pumpWidget(
new Stack([
new Positioned(
top: 100.0,
left: 100.0,
child: new Container(
width: 100.0,
height: 100.0,
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF0000FF)
)
)
),
new Positioned(
top: 100.0,
left: 100.0,
child: new Container(
width: 100.0,
height: 100.0,
child: new Transform(
transform: new Matrix4.identity().scale(0.5, 0.5),
alignment: new FractionalOffset(1.0, 0.5),
child: new GestureDetector(
onTap: () {
didReceiveTap = true;
},
child: new Container()
)
)
)
)
])
);
expect(didReceiveTap, isFalse);
tester.tapAt(new Point(110.0, 110.0));
expect(didReceiveTap, isFalse);
tester.tapAt(new Point(190.0, 150.0));
expect(didReceiveTap, isTrue);
});
});
test('Transform offset + alignment', () {
testWidgets((WidgetTester tester) {
bool didReceiveTap = false;
tester.pumpWidget(
new Stack([
new Positioned(
top: 100.0,
left: 100.0,
child: new Container(
width: 100.0,
height: 100.0,
decoration: new BoxDecoration(
backgroundColor: new Color(0xFF0000FF)
)
)
),
new Positioned(
top: 100.0,
left: 100.0,
child: new Container(
width: 100.0,
height: 100.0,
child: new Transform(
transform: new Matrix4.identity().scale(0.5, 0.5),
origin: new Offset(100.0, 0.0),
alignment: new FractionalOffset(0.0, 0.5),
child: new GestureDetector(
onTap: () {
didReceiveTap = true;
},
child: new Container()
)
)
)
)
])
);
expect(didReceiveTap, isFalse);
tester.tapAt(new Point(110.0, 110.0));
expect(didReceiveTap, isFalse);
tester.tapAt(new Point(190.0, 150.0));
expect(didReceiveTap, isTrue);
});
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment