mirror of
https://github.com/flutter/flutter.git
synced 2026-02-13 14:21:39 +08:00
The compositor backends we're planning to use can't handle a general-purpose paint layer and instead need lower-level operations. Fixes #707
602 lines
17 KiB
Dart
602 lines
17 KiB
Dart
// Copyright 2015 The Chromium 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:sky' as sky;
|
|
|
|
import 'package:sky/painting/box_painter.dart';
|
|
import 'package:sky/painting/text_style.dart';
|
|
import 'package:sky/rendering/object.dart';
|
|
import 'package:sky/rendering/box.dart';
|
|
import 'package:vector_math/vector_math.dart';
|
|
|
|
export 'package:sky/painting/box_painter.dart';
|
|
|
|
class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
|
|
|
|
// ProxyBox assumes the child will be at 0,0 and will have the same size
|
|
|
|
RenderProxyBox([RenderBox child = null]) {
|
|
this.child = child;
|
|
}
|
|
|
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMinIntrinsicWidth(constraints);
|
|
return super.getMinIntrinsicWidth(constraints);
|
|
}
|
|
|
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMaxIntrinsicWidth(constraints);
|
|
return super.getMaxIntrinsicWidth(constraints);
|
|
}
|
|
|
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMinIntrinsicHeight(constraints);
|
|
return super.getMinIntrinsicHeight(constraints);
|
|
}
|
|
|
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMaxIntrinsicHeight(constraints);
|
|
return super.getMaxIntrinsicHeight(constraints);
|
|
}
|
|
|
|
double computeDistanceToActualBaseline(TextBaseline baseline) {
|
|
if (child != null)
|
|
return child.getDistanceToActualBaseline(baseline);
|
|
return super.computeDistanceToActualBaseline(baseline);
|
|
}
|
|
|
|
void performLayout() {
|
|
if (child != null) {
|
|
child.layout(constraints, parentUsesSize: true);
|
|
size = child.size;
|
|
} else {
|
|
performResize();
|
|
}
|
|
}
|
|
|
|
void hitTestChildren(HitTestResult result, { Point position }) {
|
|
if (child != null)
|
|
child.hitTest(result, position: position);
|
|
else
|
|
super.hitTestChildren(result, position: position);
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null)
|
|
context.paintChild(child, offset.toPoint());
|
|
}
|
|
}
|
|
|
|
class RenderConstrainedBox extends RenderProxyBox {
|
|
RenderConstrainedBox({
|
|
RenderBox child,
|
|
BoxConstraints additionalConstraints
|
|
}) : super(child), _additionalConstraints = additionalConstraints {
|
|
assert(additionalConstraints != null);
|
|
}
|
|
|
|
BoxConstraints _additionalConstraints;
|
|
BoxConstraints get additionalConstraints => _additionalConstraints;
|
|
void set additionalConstraints (BoxConstraints value) {
|
|
assert(value != null);
|
|
if (_additionalConstraints == value)
|
|
return;
|
|
_additionalConstraints = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMinIntrinsicWidth(_additionalConstraints.apply(constraints));
|
|
return _additionalConstraints.apply(constraints).constrainWidth(0.0);
|
|
}
|
|
|
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMaxIntrinsicWidth(_additionalConstraints.apply(constraints));
|
|
return _additionalConstraints.apply(constraints).constrainWidth(0.0);
|
|
}
|
|
|
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMinIntrinsicHeight(_additionalConstraints.apply(constraints));
|
|
return _additionalConstraints.apply(constraints).constrainHeight(0.0);
|
|
}
|
|
|
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child != null)
|
|
return child.getMaxIntrinsicHeight(_additionalConstraints.apply(constraints));
|
|
return _additionalConstraints.apply(constraints).constrainHeight(0.0);
|
|
}
|
|
|
|
void performLayout() {
|
|
if (child != null) {
|
|
child.layout(_additionalConstraints.apply(constraints), parentUsesSize: true);
|
|
size = child.size;
|
|
} else {
|
|
size = _additionalConstraints.apply(constraints).constrain(Size.zero);
|
|
}
|
|
}
|
|
|
|
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}additionalConstraints: ${additionalConstraints}\n';
|
|
}
|
|
|
|
class RenderAspectRatio extends RenderProxyBox {
|
|
RenderAspectRatio({
|
|
RenderBox child,
|
|
double aspectRatio
|
|
}) : super(child), _aspectRatio = aspectRatio {
|
|
assert(_aspectRatio != null);
|
|
}
|
|
|
|
double _aspectRatio;
|
|
double get aspectRatio => _aspectRatio;
|
|
void set aspectRatio (double value) {
|
|
assert(value != null);
|
|
if (_aspectRatio == value)
|
|
return;
|
|
_aspectRatio = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
|
return _applyAspectRatio(constraints).height;
|
|
}
|
|
|
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
|
return _applyAspectRatio(constraints).height;
|
|
}
|
|
|
|
Size _applyAspectRatio(BoxConstraints constraints) {
|
|
double width = constraints.constrainWidth();
|
|
double height = constraints.constrainHeight(width / _aspectRatio);
|
|
return new Size(width, height);
|
|
}
|
|
|
|
bool get sizedByParent => true;
|
|
|
|
void performResize() {
|
|
size = _applyAspectRatio(constraints);
|
|
}
|
|
|
|
void performLayout() {
|
|
if (child != null)
|
|
child.layout(new BoxConstraints.tight(size));
|
|
}
|
|
|
|
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}aspectRatio: ${aspectRatio}\n';
|
|
}
|
|
|
|
class RenderShrinkWrapWidth extends RenderProxyBox {
|
|
|
|
// This class will attempt to size its child to the child's maximum
|
|
// intrinsic width, snapped to a multiple of the stepWidth, if one
|
|
// is provided, and given the provided constraints; and will then
|
|
// adopt the child's resulting dimensions.
|
|
|
|
// Note: laying out this class is relatively expensive. Avoid using
|
|
// it where possible.
|
|
|
|
RenderShrinkWrapWidth({
|
|
double stepWidth,
|
|
double stepHeight,
|
|
RenderBox child
|
|
}) : _stepWidth = stepWidth, _stepHeight = stepHeight, super(child);
|
|
|
|
double _stepWidth;
|
|
double get stepWidth => _stepWidth;
|
|
void set stepWidth(double value) {
|
|
if (value == _stepWidth)
|
|
return;
|
|
_stepWidth = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double _stepHeight;
|
|
double get stepHeight => _stepHeight;
|
|
void set stepHeight(double value) {
|
|
if (value == _stepHeight)
|
|
return;
|
|
_stepHeight = value;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
static double applyStep(double input, double step) {
|
|
if (step == null)
|
|
return input;
|
|
return (input / step).ceil() * step;
|
|
}
|
|
|
|
BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
|
|
if (constraints.hasTightWidth)
|
|
return constraints;
|
|
double width = child.getMaxIntrinsicWidth(constraints);
|
|
assert(width == constraints.constrainWidth(width));
|
|
return constraints.applyWidth(applyStep(width, _stepWidth));
|
|
}
|
|
|
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
|
return getMaxIntrinsicWidth(constraints);
|
|
}
|
|
|
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
|
if (child == null)
|
|
return constraints.constrainWidth(0.0);
|
|
double childResult = child.getMaxIntrinsicWidth(constraints);
|
|
return constraints.constrainWidth(applyStep(childResult, _stepWidth));
|
|
}
|
|
|
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child == null)
|
|
return constraints.constrainWidth(0.0);
|
|
double childResult = child.getMinIntrinsicHeight(_getInnerConstraints(constraints));
|
|
return constraints.constrainHeight(applyStep(childResult, _stepHeight));
|
|
}
|
|
|
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
|
if (child == null)
|
|
return constraints.constrainWidth(0.0);
|
|
double childResult = child.getMaxIntrinsicHeight(_getInnerConstraints(constraints));
|
|
return constraints.constrainHeight(applyStep(childResult, _stepHeight));
|
|
}
|
|
|
|
void performLayout() {
|
|
if (child != null) {
|
|
BoxConstraints childConstraints = _getInnerConstraints(constraints);
|
|
if (_stepHeight != null)
|
|
childConstraints.applyHeight(getMaxIntrinsicHeight(childConstraints));
|
|
child.layout(childConstraints, parentUsesSize: true);
|
|
size = child.size;
|
|
} else {
|
|
performResize();
|
|
}
|
|
}
|
|
|
|
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}stepWidth: ${stepWidth}\n${prefix}stepHeight: ${stepHeight}\n';
|
|
|
|
}
|
|
|
|
class RenderOpacity extends RenderProxyBox {
|
|
RenderOpacity({ RenderBox child, double opacity })
|
|
: this._opacity = opacity, super(child) {
|
|
assert(opacity >= 0.0 && opacity <= 1.0);
|
|
}
|
|
|
|
double _opacity;
|
|
double get opacity => _opacity;
|
|
void set opacity (double value) {
|
|
assert(value != null);
|
|
assert(value >= 0.0 && value <= 1.0);
|
|
if (_opacity == value)
|
|
return;
|
|
_opacity = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
int get _alpha => (_opacity * 255).round();
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null) {
|
|
int a = _alpha;
|
|
if (a == 0)
|
|
return;
|
|
if (a == 255)
|
|
context.paintChild(child, offset.toPoint());
|
|
else
|
|
context.paintChildWithOpacity(child, offset.toPoint(), null, a);
|
|
}
|
|
}
|
|
}
|
|
|
|
class RenderColorFilter extends RenderProxyBox {
|
|
RenderColorFilter({ RenderBox child, Color color, sky.TransferMode transferMode })
|
|
: _color = color, _transferMode = transferMode, super(child) {
|
|
}
|
|
|
|
Color _color;
|
|
Color get color => _color;
|
|
void set color (Color value) {
|
|
assert(value != null);
|
|
if (_color == value)
|
|
return;
|
|
_color = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
sky.TransferMode _transferMode;
|
|
sky.TransferMode get transferMode => _transferMode;
|
|
void set transferMode (sky.TransferMode value) {
|
|
assert(value != null);
|
|
if (_transferMode == value)
|
|
return;
|
|
_transferMode = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null)
|
|
context.paintChildWithColorFilter(child, offset.toPoint(), offset & size, _color, _transferMode);
|
|
}
|
|
}
|
|
|
|
class RenderClipRect extends RenderProxyBox {
|
|
RenderClipRect({ RenderBox child }) : super(child);
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null)
|
|
context.paintChildWithClipRect(child, offset.toPoint(), offset & size);
|
|
}
|
|
}
|
|
|
|
class RenderClipRRect extends RenderProxyBox {
|
|
RenderClipRRect({ RenderBox child, double xRadius, double yRadius })
|
|
: _xRadius = xRadius, _yRadius = yRadius, super(child) {
|
|
assert(_xRadius != null);
|
|
assert(_yRadius != null);
|
|
}
|
|
|
|
double _xRadius;
|
|
double get xRadius => _xRadius;
|
|
void set xRadius (double value) {
|
|
assert(value != null);
|
|
if (_xRadius == value)
|
|
return;
|
|
_xRadius = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
double _yRadius;
|
|
double get yRadius => _yRadius;
|
|
void set yRadius (double value) {
|
|
assert(value != null);
|
|
if (_yRadius == value)
|
|
return;
|
|
_yRadius = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null) {
|
|
Rect rect = offset & size;
|
|
sky.RRect rrect = new sky.RRect()..setRectXY(rect, xRadius, yRadius);
|
|
context.paintChildWithClipRRect(child, offset.toPoint(), rect, rrect);
|
|
}
|
|
}
|
|
}
|
|
|
|
class RenderClipOval extends RenderProxyBox {
|
|
RenderClipOval({ RenderBox child }) : super(child);
|
|
|
|
Rect _cachedRect;
|
|
Path _cachedPath;
|
|
|
|
Path _getPath(Rect rect) {
|
|
if (rect != _cachedRect) {
|
|
_cachedRect = rect;
|
|
_cachedPath = new Path()..addOval(_cachedRect);
|
|
}
|
|
return _cachedPath;
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null) {
|
|
Rect rect = offset & size;
|
|
context.paintChildWithClipPath(child, offset.toPoint(), rect, _getPath(rect));
|
|
}
|
|
}
|
|
}
|
|
|
|
enum BoxDecorationPosition {
|
|
background,
|
|
foreground,
|
|
}
|
|
|
|
class RenderDecoratedBox extends RenderProxyBox {
|
|
|
|
RenderDecoratedBox({
|
|
BoxDecoration decoration,
|
|
RenderBox child,
|
|
this.position: BoxDecorationPosition.background
|
|
}) : _painter = new BoxPainter(decoration), super(child);
|
|
|
|
BoxDecorationPosition position;
|
|
final BoxPainter _painter;
|
|
|
|
BoxDecoration get decoration => _painter.decoration;
|
|
void set decoration (BoxDecoration value) {
|
|
assert(value != null);
|
|
if (value == _painter.decoration)
|
|
return;
|
|
_removeBackgroundImageListenerIfNeeded();
|
|
_painter.decoration = value;
|
|
_addBackgroundImageListenerIfNeeded();
|
|
markNeedsPaint();
|
|
}
|
|
|
|
bool get _needsBackgroundImageListener {
|
|
return attached &&
|
|
_painter.decoration != null &&
|
|
_painter.decoration.backgroundImage != null;
|
|
}
|
|
|
|
void _addBackgroundImageListenerIfNeeded() {
|
|
if (_needsBackgroundImageListener)
|
|
_painter.decoration.backgroundImage.addChangeListener(markNeedsPaint);
|
|
}
|
|
|
|
void _removeBackgroundImageListenerIfNeeded() {
|
|
if (_needsBackgroundImageListener)
|
|
_painter.decoration.backgroundImage.removeChangeListener(markNeedsPaint);
|
|
}
|
|
|
|
void attach() {
|
|
super.attach();
|
|
_addBackgroundImageListenerIfNeeded();
|
|
}
|
|
|
|
void detach() {
|
|
_removeBackgroundImageListenerIfNeeded();
|
|
super.detach();
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
assert(size.width != null);
|
|
assert(size.height != null);
|
|
if (position == BoxDecorationPosition.background)
|
|
_painter.paint(context.canvas, offset & size);
|
|
super.paint(context, offset);
|
|
if (position == BoxDecorationPosition.foreground)
|
|
_painter.paint(context.canvas, offset & size);
|
|
}
|
|
|
|
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}decoration:\n${_painter.decoration.toString(prefix + " ")}\n';
|
|
}
|
|
|
|
class RenderTransform extends RenderProxyBox {
|
|
RenderTransform({
|
|
Matrix4 transform,
|
|
RenderBox child
|
|
}) : super(child) {
|
|
assert(transform != null);
|
|
this.transform = transform;
|
|
}
|
|
|
|
Matrix4 _transform;
|
|
|
|
void set transform(Matrix4 value) {
|
|
assert(value != null);
|
|
if (_transform == value)
|
|
return;
|
|
_transform = new Matrix4.copy(value);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void setIdentity() {
|
|
_transform.setIdentity();
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void rotateX(double radians) {
|
|
_transform.rotateX(radians);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void rotateY(double radians) {
|
|
_transform.rotateY(radians);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void rotateZ(double radians) {
|
|
_transform.rotateZ(radians);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void translate(x, [double y = 0.0, double z = 0.0]) {
|
|
_transform.translate(x, y, z);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void scale(x, [double y, double z]) {
|
|
_transform.scale(x, y, z);
|
|
markNeedsPaint();
|
|
}
|
|
|
|
bool hitTest(HitTestResult result, { Point position }) {
|
|
Matrix4 inverse = new Matrix4.zero();
|
|
// TODO(abarth): Check the determinant for degeneracy.
|
|
inverse.copyInverse(_transform);
|
|
|
|
Vector3 position3 = new Vector3(position.x, position.y, 0.0);
|
|
Vector3 transformed3 = inverse.transform3(position3);
|
|
Point transformed = new Point(transformed3.x, transformed3.y);
|
|
return super.hitTest(result, position: transformed);
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
if (child != null)
|
|
context.paintChildWithTransform(child, offset.toPoint(), _transform);
|
|
}
|
|
|
|
void applyPaintTransform(Matrix4 transform) {
|
|
super.applyPaintTransform(transform);
|
|
transform.multiply(_transform);
|
|
}
|
|
|
|
String debugDescribeSettings(String prefix) {
|
|
List<String> result = _transform.toString().split('\n').map((s) => '$prefix $s\n').toList();
|
|
result.removeLast();
|
|
return '${super.debugDescribeSettings(prefix)}${prefix}transform matrix:\n${result.join()}';
|
|
}
|
|
}
|
|
|
|
typedef void SizeChangedCallback(Size newSize);
|
|
|
|
class RenderSizeObserver extends RenderProxyBox {
|
|
RenderSizeObserver({
|
|
this.callback,
|
|
RenderBox child
|
|
}) : super(child) {
|
|
assert(callback != null);
|
|
}
|
|
|
|
SizeChangedCallback callback;
|
|
|
|
void performLayout() {
|
|
Size oldSize = size;
|
|
|
|
super.performLayout();
|
|
|
|
if (oldSize != size)
|
|
callback(size);
|
|
}
|
|
}
|
|
|
|
typedef void CustomPaintCallback(PaintingCanvas canvas, Size size);
|
|
|
|
class RenderCustomPaint extends RenderProxyBox {
|
|
|
|
RenderCustomPaint({
|
|
CustomPaintCallback callback,
|
|
RenderBox child
|
|
}) : super(child) {
|
|
assert(callback != null);
|
|
_callback = callback;
|
|
}
|
|
|
|
CustomPaintCallback _callback;
|
|
void set callback (CustomPaintCallback value) {
|
|
assert(value != null || !attached);
|
|
if (_callback == value)
|
|
return;
|
|
_callback = value;
|
|
markNeedsPaint();
|
|
}
|
|
|
|
void attach() {
|
|
assert(_callback != null);
|
|
super.attach();
|
|
}
|
|
|
|
void paint(PaintingContext context, Offset offset) {
|
|
assert(_callback != null);
|
|
context.canvas.translate(offset.dx, offset.dy);
|
|
_callback(context.canvas, size);
|
|
// TODO(abarth): We should translate back before calling super because in
|
|
// the future, super.paint might switch our compositing layer.
|
|
super.paint(context, Offset.zero);
|
|
context.canvas.translate(-offset.dx, -offset.dy);
|
|
}
|
|
}
|
|
|
|
class RenderIgnorePointer extends RenderProxyBox {
|
|
RenderIgnorePointer({ RenderBox child }) : super(child);
|
|
bool hitTest(HitTestResult result, { Point position }) {
|
|
return false;
|
|
}
|
|
}
|