// 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 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; import 'package:sky/base/debug.dart'; import 'package:vector_math/vector_math.dart'; abstract class Layer { Layer({ this.offset: Offset.zero }); Offset offset; // From parent, in parent's coordinate system. ContainerLayer _parent; ContainerLayer get parent => _parent; Layer _nextSibling; Layer get nextSibling => _nextSibling; Layer _previousSibling; Layer get previousSibling => _previousSibling; void detach() { if (_parent != null) _parent.remove(this); } void replaceWith(Layer newLayer) { assert(_parent != null); assert(newLayer._parent == null); assert(newLayer._nextSibling == null); assert(newLayer._previousSibling == null); newLayer._nextSibling = _nextSibling; if (_nextSibling != null) newLayer._nextSibling._previousSibling = newLayer; newLayer._previousSibling = _previousSibling; if (_previousSibling != null) newLayer._previousSibling._nextSibling = newLayer; newLayer._parent = _parent; if (_parent._firstChild == this) _parent._firstChild = newLayer; if (_parent._lastChild == this) _parent._lastChild = newLayer; _nextSibling = null; _previousSibling = null; _parent = null; } // The paint() methods are temporary. Eventually, Layers won't have // a paint() method, the entire Layer hierarchy will be handed over // to the C++ side for processing. Until we implement that, though, // we instead have the layers paint themselves into a canvas at // paint time. void paint(sky.Canvas canvas); } class PictureLayer extends Layer { PictureLayer({ Offset offset: Offset.zero, this.paintBounds }) : super(offset: offset); Rect paintBounds; sky.Picture picture; bool _debugPaintLayerBorder(sky.Canvas canvas) { if (debugPaintLayerBordersEnabled) { Paint border = new Paint() ..color = debugPaintLayerBordersColor ..strokeWidth = 2.0 ..setStyle(sky.PaintingStyle.stroke); canvas.drawRect(paintBounds, border); } return true; } void paint(sky.Canvas canvas) { assert(picture != null); canvas.translate(offset.dx, offset.dy); canvas.drawPicture(picture); assert(_debugPaintLayerBorder(canvas)); canvas.translate(-offset.dx, -offset.dy); } } class ContainerLayer extends Layer { ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset); // TODO(ianh): hide firstChild since nobody uses it Layer _firstChild; Layer get firstChild => _firstChild; // TODO(ianh): remove _lastChild since nobody uses it Layer _lastChild; Layer get lastChild => _lastChild; bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) { while (child._previousSibling != null) { assert(child._previousSibling != child); child = child._previousSibling; } return child == equals; } bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) { while (child._nextSibling != null) { assert(child._nextSibling != child); child = child._nextSibling; } return child == equals; } // TODO(ianh): Remove 'before' and rename the function to 'append' since nobody uses 'before' void add(Layer child, { Layer before }) { assert(child != this); assert(before != this); assert(child != before); assert(child != _firstChild); assert(child != _lastChild); assert(child._parent == null); assert(child._nextSibling == null); assert(child._previousSibling == null); child._parent = this; if (before == null) { child._previousSibling = _lastChild; if (_lastChild != null) _lastChild._nextSibling = child; _lastChild = child; if (_firstChild == null) _firstChild = child; } else { assert(_firstChild != null); assert(_lastChild != null); assert(_debugUltimatePreviousSiblingOf(before, equals: _firstChild)); assert(_debugUltimateNextSiblingOf(before, equals: _lastChild)); if (before._previousSibling == null) { assert(before == _firstChild); child._nextSibling = before; before._previousSibling = child; _firstChild = child; } else { child._previousSibling = before._previousSibling; child._nextSibling = before; child._previousSibling._nextSibling = child; child._nextSibling._previousSibling = child; assert(before._previousSibling == child); } } } // TODO(ianh): Hide this function since only detach() uses it void remove(Layer child) { assert(child._parent == this); assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild)); assert(_debugUltimateNextSiblingOf(child, equals: _lastChild)); if (child._previousSibling == null) { assert(_firstChild == child); _firstChild = child._nextSibling; } else { child._previousSibling._nextSibling = child._nextSibling; } if (child._nextSibling == null) { assert(_lastChild == child); _lastChild = child._previousSibling; } else { child._nextSibling._previousSibling = child._previousSibling; } child._previousSibling = null; child._nextSibling = null; child._parent = null; } void removeAllChildren() { Layer child = _firstChild; while (child != null) { Layer next = child.nextSibling; child._previousSibling = null; child._nextSibling = null; child._parent = null; child = next; } _firstChild = null; _lastChild = null; } void paint(sky.Canvas canvas) { canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.translate(-offset.dx, -offset.dy); } void paintChildren(sky.Canvas canvas) { Layer child = firstChild; while (child != null) { child.paint(canvas); child = child.nextSibling; } } } class ClipRectLayer extends ContainerLayer { ClipRectLayer({ Offset offset: Offset.zero, this.clipRect }) : super(offset: offset); // clipRect is _not_ affected by given offset Rect clipRect; void paint(sky.Canvas canvas) { canvas.save(); canvas.clipRect(clipRect); canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.restore(); } } final Paint _disableAntialias = new Paint()..isAntiAlias = false; class ClipRRectLayer extends ContainerLayer { ClipRRectLayer({ Offset offset: Offset.zero, this.bounds, this.clipRRect }) : super(offset: offset); // bounds and clipRRect are _not_ affected by given offset Rect bounds; sky.RRect clipRRect; void paint(sky.Canvas canvas) { canvas.saveLayer(bounds, _disableAntialias); canvas.clipRRect(clipRRect); canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.restore(); } } class ClipPathLayer extends ContainerLayer { ClipPathLayer({ Offset offset: Offset.zero, this.bounds, this.clipPath }) : super(offset: offset); // bounds and clipPath are _not_ affected by given offset Rect bounds; Path clipPath; void paint(sky.Canvas canvas) { canvas.saveLayer(bounds, _disableAntialias); canvas.clipPath(clipPath); canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.restore(); } } class TransformLayer extends ContainerLayer { TransformLayer({ Offset offset: Offset.zero, this.transform }) : super(offset: offset); Matrix4 transform; void paint(sky.Canvas canvas) { canvas.save(); canvas.translate(offset.dx, offset.dy); canvas.concat(transform.storage); paintChildren(canvas); canvas.restore(); } } class OpacityLayer extends ContainerLayer { OpacityLayer({ Offset offset: Offset.zero, this.bounds, this.alpha }) : super(offset: offset); // bounds is _not_ affected by given offset Rect bounds; int alpha; static Paint paintForAlpha(int alpha) { return new Paint() ..color = new Color.fromARGB(alpha, 0, 0, 0) ..setTransferMode(sky.TransferMode.srcOver) ..isAntiAlias = false; } void paint(sky.Canvas canvas) { canvas.saveLayer(bounds, paintForAlpha(alpha)); canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.restore(); } } class ColorFilterLayer extends ContainerLayer { ColorFilterLayer({ Offset offset: Offset.zero, this.bounds, this.color, this.transferMode }) : super(offset: offset); // bounds is _not_ affected by given offset Rect bounds; Color color; sky.TransferMode transferMode; static paintForColorFilter(Color color, sky.TransferMode transferMode) { new Paint() ..setColorFilter(new sky.ColorFilter.mode(color, transferMode)) ..isAntiAlias = false; } void paint(sky.Canvas canvas) { canvas.saveLayer(bounds, paintForColorFilter(color, transferMode)); canvas.translate(offset.dx, offset.dy); paintChildren(canvas); canvas.restore(); } }