blob: 803c85b2b884b7e1d962cfa6940fd2f753dd3fc5 [file] [log] [blame]
// 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 'node.dart';
import 'dart:sky' as sky;
// GENERIC BOX RENDERING
// Anything that has a concept of x, y, width, height is going to derive from this
class EdgeDims {
// used for e.g. padding
const EdgeDims(this.top, this.right, this.bottom, this.left);
const EdgeDims.all(double value)
: top = value, right = value, bottom = value, left = value;
const EdgeDims.onlyLeft(double value)
: top = 0.0, right = 0.0, bottom = 0.0, left = value;
const EdgeDims.onlyRight(double value)
: top = 0.0, right = value, bottom = 0.0, left = 0.0;
const EdgeDims.onlyTop(double value)
: top = value, right = 0.0, bottom = 0.0, left = 0.0;
const EdgeDims.onlyBottom(double value)
: top = 0.0, right = 0.0, bottom = value, left = 0.0;
final double top;
final double right;
final double bottom;
final double left;
operator ==(EdgeDims other) => (top == other.top) ||
(right == other.right) ||
(bottom == other.bottom) ||
(left == other.left);
}
class BoxConstraints {
const BoxConstraints({
this.minWidth: 0.0,
this.maxWidth: double.INFINITY,
this.minHeight: 0.0,
this.maxHeight: double.INFINITY});
BoxConstraints.tight(sky.Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;
BoxConstraints deflate(EdgeDims edges) {
assert(edges != null);
return new BoxConstraints(
minWidth: minWidth,
maxWidth: maxWidth - (edges.left + edges.right),
minHeight: minHeight,
maxHeight: maxHeight - (edges.top + edges.bottom)
);
}
final double minWidth;
final double maxWidth;
final double minHeight;
final double maxHeight;
double constrainWidth(double width) {
return clamp(min: minWidth, max: maxWidth, value: width);
}
double constrainHeight(double height) {
return clamp(min: minHeight, max: maxHeight, value: height);
}
sky.Size constrain(sky.Size size) {
return new sky.Size(constrainWidth(size.width), constrainHeight(size.height));
}
bool get isInfinite => maxWidth >= double.INFINITY || maxHeight >= double.INFINITY;
}
class BoxParentData extends ParentData {
sky.Point position = new sky.Point(0.0, 0.0);
}
abstract class RenderBox extends RenderNode {
void setParentData(RenderNode child) {
if (child.parentData is! BoxParentData)
child.parentData = new BoxParentData();
}
// override this to report what dimensions you would have if you
// were laid out with the given constraints this can walk the tree
// if it must, but it should be as cheap as possible; just get the
// dimensions and nothing else (e.g. don't calculate hypothetical
// child positions if they're not needed to determine dimensions)
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
return constraints.constrain(new sky.Size(0.0, 0.0));
}
BoxConstraints get constraints => super.constraints as BoxConstraints;
void performResize() {
// default behaviour for subclasses that have sizedByParent = true
size = constraints.constrain(new sky.Size(0.0, 0.0));
assert(size.height < double.INFINITY);
assert(size.width < double.INFINITY);
}
void performLayout() {
// descendants have to either override performLayout() to set both
// width and height and lay out children, or, set sizedByParent to
// true so that performResize()'s logic above does its thing.
assert(sizedByParent);
}
bool hitTest(HitTestResult result, { sky.Point position }) {
hitTestChildren(result, position: position);
result.add(this);
return true;
}
void hitTestChildren(HitTestResult result, { sky.Point position }) { }
sky.Size size = new sky.Size(0.0, 0.0);
}
abstract class RenderProxyBox extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
RenderProxyBox(RenderBox child) {
this.child = child;
}
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
if (child != null)
return child.getIntrinsicDimensions(constraints);
return super.getIntrinsicDimensions(constraints);
}
void performLayout() {
if (child != null) {
child.layout(constraints, parentUsesSize: true);
size = child.size;
} else {
performResize();
}
}
void hitTestChildren(HitTestResult result, { sky.Point position }) {
if (child != null)
child.hitTest(result, position: position);
else
super.hitTestChildren(result, position: position);
}
void paint(RenderNodeDisplayList canvas) {
if (child != null)
child.paint(canvas);
}
}
class RenderSizedBox extends RenderProxyBox {
RenderSizedBox({
RenderBox child,
sky.Size desiredSize: const sky.Size.infinite()
}) : super(child) {
assert(desiredSize != null);
this.desiredSize = desiredSize;
}
sky.Size _desiredSize;
sky.Size get desiredSize => _desiredSize;
void set desiredSize (sky.Size value) {
assert(value != null);
if (_desiredSize == value)
return;
_desiredSize = value;
markNeedsLayout();
}
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
return constraints.constrain(_desiredSize);
}
void performLayout() {
size = constraints.constrain(_desiredSize);
child.layout(new BoxConstraints.tight(size));
}
}
class RenderPadding extends RenderBox with RenderNodeWithChildMixin<RenderBox> {
RenderPadding({ EdgeDims padding, RenderBox child }) {
assert(padding != null);
this.padding = padding;
this.child = child;
}
EdgeDims _padding;
EdgeDims get padding => _padding;
void set padding (EdgeDims value) {
assert(value != null);
if (_padding == value)
return;
_padding = value;
markNeedsLayout();
}
sky.Size getIntrinsicDimensions(BoxConstraints constraints) {
assert(padding != null);
constraints = constraints.deflate(padding);
if (child == null)
return super.getIntrinsicDimensions(constraints);
return child.getIntrinsicDimensions(constraints);
}
void performLayout() {
assert(padding != null);
BoxConstraints innerConstraints = constraints.deflate(padding);
if (child == null) {
size = innerConstraints.constrain(
new sky.Size(padding.left + padding.right, padding.top + padding.bottom));
return;
}
child.layout(innerConstraints, parentUsesSize: true);
assert(child.parentData is BoxParentData);
child.parentData.position = new sky.Point(padding.left, padding.top);
size = constraints.constrain(new sky.Size(padding.left + child.size.width + padding.right,
padding.top + child.size.height + padding.bottom));
}
void paint(RenderNodeDisplayList canvas) {
if (child != null)
canvas.paintChild(child, child.parentData.position);
}
void hitTestChildren(HitTestResult result, { sky.Point position }) {
if (child != null) {
assert(child.parentData is BoxParentData);
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
if (childBounds.contains(position)) {
child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
position.y - child.parentData.position.y));
}
}
}
}
// This must be immutable, because we won't notice when it changes
class BoxDecoration {
// TODO(mpcomplete): go through and change the users of this class to pass
// a Color object.
BoxDecoration({
backgroundColor
}) : backgroundColor = new sky.Color(backgroundColor);
final sky.Color backgroundColor;
}
class RenderDecoratedBox extends RenderProxyBox {
RenderDecoratedBox({
BoxDecoration decoration,
RenderBox child
}) : _decoration = decoration, super(child);
BoxDecoration _decoration;
BoxDecoration get decoration => _decoration;
void set decoration (BoxDecoration value) {
if (value == _decoration)
return;
_decoration = value;
markNeedsPaint();
}
void paint(RenderNodeDisplayList canvas) {
assert(size.width != null);
assert(size.height != null);
if (_decoration == null)
return;
if (_decoration.backgroundColor != null) {
sky.Paint paint = new sky.Paint()..color = _decoration.backgroundColor;
canvas.drawRect(new sky.Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint);
}
super.paint(canvas);
}
}
// RENDER VIEW LAYOUT MANAGER
class ViewConstraints {
const ViewConstraints({
this.width: 0.0, this.height: 0.0, this.orientation: null
});
final double width;
final double height;
final int orientation;
}
class RenderView extends RenderNode with RenderNodeWithChildMixin<RenderBox> {
RenderView({
RenderBox child,
this.timeForRotation: const Duration(microseconds: 83333)
}) {
this.child = child;
}
sky.Size _size = new sky.Size(0.0, 0.0);
double get width => _size.width;
double get height => _size.height;
int _orientation; // 0..3
int get orientation => _orientation;
Duration timeForRotation;
ViewConstraints get constraints => super.constraints as ViewConstraints;
bool get sizedByParent => true;
void performResize() {
if (constraints.orientation != _orientation) {
if (_orientation != null && child != null)
child.rotate(oldAngle: _orientation, newAngle: constraints.orientation, time: timeForRotation);
_orientation = constraints.orientation;
}
_size = new sky.Size(constraints.width, constraints.height);
assert(_size.height < double.INFINITY);
assert(_size.width < double.INFINITY);
}
void performLayout() {
if (child != null) {
child.layout(new BoxConstraints.tight(_size));
assert(child.size.width == width);
assert(child.size.height == height);
}
}
void rotate({ int oldAngle, int newAngle, Duration time }) {
assert(false); // nobody tells the screen to rotate, the whole rotate() dance is started from our performResize()
}
bool hitTest(HitTestResult result, { sky.Point position }) {
if (child != null) {
sky.Rect childBounds = new sky.Rect.fromSize(child.size);
if (childBounds.contains(position))
child.hitTest(result, position: position);
}
result.add(this);
return true;
}
void paint(RenderNodeDisplayList canvas) {
if (child != null)
canvas.paintChild(child, new sky.Point(0.0, 0.0));
}
void paintFrame() {
RenderNode.debugDoingPaint = true;
var canvas = new RenderNodeDisplayList(sky.view.width, sky.view.height);
paint(canvas);
sky.view.picture = canvas.endRecording();
RenderNode.debugDoingPaint = false;
}
}
// DEFAULT BEHAVIORS FOR RENDERBOX CONTAINERS
abstract class RenderBoxContainerDefaultsMixin<ChildType extends RenderBox, ParentDataType extends ContainerParentDataMixin<ChildType>> implements ContainerRenderNodeMixin<ChildType, ParentDataType> {
void defaultHitTestChildren(HitTestResult result, { sky.Point position }) {
// the x, y parameters have the top left of the node's box as the origin
ChildType child = lastChild;
while (child != null) {
assert(child.parentData is ParentDataType);
sky.Rect childBounds = new sky.Rect.fromPointAndSize(child.parentData.position, child.size);
if (childBounds.contains(position)) {
if (child.hitTest(result, position: new sky.Point(position.x - child.parentData.position.x,
position.y - child.parentData.position.y)))
break;
}
child = child.parentData.previousSibling;
}
}
void defaultPaint(RenderNodeDisplayList canvas) {
RenderBox child = firstChild;
while (child != null) {
assert(child.parentData is ParentDataType);
canvas.paintChild(child, child.parentData.position);
child = child.parentData.nextSibling;
}
}
}