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 {
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
class RenderTransform extends RenderProxyBox {
RenderTransform({
Matrix4 transform,
Offset origin,
FractionalOffset alignment,
RenderBox child
}) : super(child) {
assert(transform != null);
assert(alignment == null || (alignment.x != null && alignment.y != null));
this.transform = transform;
this.alignment = alignment;
this.origin = origin;
}
......@@ -888,6 +914,20 @@ class RenderTransform extends RenderProxyBox {
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
Matrix4 _transform;
......@@ -937,13 +977,19 @@ class RenderTransform extends RenderProxyBox {
}
Matrix4 get _effectiveTransform {
if (_origin == null)
if (_origin == null && _alignment == null)
return _transform;
return new Matrix4
.identity()
.translate(_origin.dx, _origin.dy)
.multiply(_transform)
.translate(-_origin.dx, -_origin.dy);
Matrix4 result = new Matrix4.identity();
if (_origin != null)
result.translate(_origin.dx, _origin.dy);
if (_alignment != null)
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 }) {
......
......@@ -23,6 +23,7 @@ export 'package:flutter/rendering.dart' show
FlexAlignItems,
FlexDirection,
FlexJustifyContent,
FractionalOffset,
Matrix4,
Offset,
Paint,
......@@ -171,19 +172,21 @@ class ClipOval extends OneChildRenderObjectWidget {
// POSITIONING AND SIZING NODES
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) {
assert(transform != null);
}
final Matrix4 transform;
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) {
renderObject.transform = transform;
renderObject.origin = origin;
renderObject.alignment = alignment;
}
}
......
......@@ -48,4 +48,95 @@ void main() {
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