blob: b78b5d7ad569d028cf5ae5ef37a43df0d3eb2630 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
/// Defines canvas interface common across canvases that the [SceneBuilder]
/// renders to.
///
/// This can be used either as an interface or super-class.
abstract class EngineCanvas {
/// The element that is attached to the DOM.
html.Element get rootElement;
void dispose() {
clear();
}
void clear();
void save();
void restore();
void translate(double dx, double dy);
void scale(double sx, double sy);
void rotate(double radians);
void skew(double sx, double sy);
void transform(Float32List matrix4);
void clipRect(ui.Rect rect);
void clipRRect(ui.RRect rrect);
void clipPath(ui.Path path);
void drawColor(ui.Color color, ui.BlendMode blendMode);
void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint);
void drawPaint(SurfacePaintData paint);
void drawRect(ui.Rect rect, SurfacePaintData paint);
void drawRRect(ui.RRect rrect, SurfacePaintData paint);
void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint);
void drawOval(ui.Rect rect, SurfacePaintData paint);
void drawCircle(ui.Offset c, double radius, SurfacePaintData paint);
void drawPath(ui.Path path, SurfacePaintData paint);
void drawShadow(
ui.Path path, ui.Color color, double elevation, bool transparentOccluder);
void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint);
void drawImageRect(
ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint);
void drawParagraph(EngineParagraph paragraph, ui.Offset offset);
void drawVertices(
ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint);
void drawPoints(ui.PointMode pointMode, Float32List points, SurfacePaintData paint);
/// Extension of Canvas API to mark the end of a stream of painting commands
/// to enable re-use/dispose optimizations.
void endOfPaint();
}
/// Adds an [offset] transformation to a [transform] matrix and returns the
/// combined result.
///
/// If the given offset is zero, returns [transform] matrix as is. Otherwise,
/// returns a new [Matrix4] object representing the combined transformation.
Matrix4 transformWithOffset(Matrix4 transform, ui.Offset offset) {
if (offset == ui.Offset.zero) {
return transform;
}
// Clone to avoid mutating transform.
final Matrix4 effectiveTransform = transform.clone();
effectiveTransform.translate(offset.dx, offset.dy, 0.0);
return effectiveTransform;
}
class _SaveStackEntry {
_SaveStackEntry({
@required this.transform,
@required this.clipStack,
});
final Matrix4 transform;
final List<_SaveClipEntry> clipStack;
}
/// Tagged union of clipping parameters used for canvas.
class _SaveClipEntry {
final ui.Rect rect;
final ui.RRect rrect;
final ui.Path path;
final Matrix4 currentTransform;
_SaveClipEntry.rect(this.rect, this.currentTransform)
: rrect = null,
path = null;
_SaveClipEntry.rrect(this.rrect, this.currentTransform)
: rect = null,
path = null;
_SaveClipEntry.path(this.path, this.currentTransform)
: rect = null,
rrect = null;
}
/// Provides save stack tracking functionality to implementations of
/// [EngineCanvas].
mixin SaveStackTracking on EngineCanvas {
static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0);
final List<_SaveStackEntry> _saveStack = <_SaveStackEntry>[];
/// The stack that maintains clipping operations used when text is painted
/// onto bitmap canvas but is composited as separate element.
List<_SaveClipEntry> _clipStack;
/// Returns whether there are active clipping regions on the canvas.
bool get isClipped => _clipStack != null;
/// Empties the save stack and the element stack, and resets the transform
/// and clip parameters.
///
/// Classes that override this method must call `super.clear()`.
@override
void clear() {
_saveStack.clear();
_clipStack = null;
_currentTransform = Matrix4.identity();
}
/// The current transformation matrix.
Matrix4 get currentTransform => _currentTransform;
Matrix4 _currentTransform = Matrix4.identity();
/// Saves current clip and transform on the save stack.
///
/// Classes that override this method must call `super.save()`.
@override
void save() {
_saveStack.add(_SaveStackEntry(
transform: _currentTransform.clone(),
clipStack:
_clipStack == null ? null : List<_SaveClipEntry>.from(_clipStack),
));
}
/// Restores current clip and transform from the save stack.
///
/// Classes that override this method must call `super.restore()`.
@override
void restore() {
if (_saveStack.isEmpty) {
return;
}
final _SaveStackEntry entry = _saveStack.removeLast();
_currentTransform = entry.transform;
_clipStack = entry.clipStack;
}
/// Multiplies the [currentTransform] matrix by a translation.
///
/// Classes that override this method must call `super.translate()`.
@override
void translate(double dx, double dy) {
_currentTransform.translate(dx, dy);
}
/// Scales the [currentTransform] matrix.
///
/// Classes that override this method must call `super.scale()`.
@override
void scale(double sx, double sy) {
_currentTransform.scale(sx, sy);
}
/// Rotates the [currentTransform] matrix.
///
/// Classes that override this method must call `super.rotate()`.
@override
void rotate(double radians) {
_currentTransform.rotate(_unitZ, radians);
}
/// Skews the [currentTransform] matrix.
///
/// Classes that override this method must call `super.skew()`.
@override
void skew(double sx, double sy) {
final Matrix4 skewMatrix = Matrix4.identity();
final Float32List storage = skewMatrix.storage;
storage[1] = sy;
storage[4] = sx;
_currentTransform.multiply(skewMatrix);
}
/// Multiplies the [currentTransform] matrix by another matrix.
///
/// Classes that override this method must call `super.transform()`.
@override
void transform(Float32List matrix4) {
_currentTransform.multiply(Matrix4.fromFloat32List(matrix4));
}
/// Adds a rectangle to clipping stack.
///
/// Classes that override this method must call `super.clipRect()`.
@override
void clipRect(ui.Rect rect) {
_clipStack ??= <_SaveClipEntry>[];
_clipStack.add(_SaveClipEntry.rect(rect, _currentTransform.clone()));
}
/// Adds a round rectangle to clipping stack.
///
/// Classes that override this method must call `super.clipRRect()`.
@override
void clipRRect(ui.RRect rrect) {
_clipStack ??= <_SaveClipEntry>[];
_clipStack.add(_SaveClipEntry.rrect(rrect, _currentTransform.clone()));
}
/// Adds a path to clipping stack.
///
/// Classes that override this method must call `super.clipPath()`.
@override
void clipPath(ui.Path path) {
_clipStack ??= <_SaveClipEntry>[];
_clipStack.add(_SaveClipEntry.path(path, _currentTransform.clone()));
}
}
html.Element _drawParagraphElement(
EngineParagraph paragraph,
ui.Offset offset, {
Matrix4 transform,
}) {
assert(paragraph._isLaidOut);
final html.Element paragraphElement = paragraph._paragraphElement.clone(true);
final html.CssStyleDeclaration paragraphStyle = paragraphElement.style;
paragraphStyle
..position = 'absolute'
..whiteSpace = 'pre-wrap'
..overflowWrap = 'break-word'
..overflow = 'hidden'
..height = '${paragraph.height}px'
..width = '${paragraph.width}px';
if (transform != null) {
setElementTransform(
paragraphElement,
transformWithOffset(transform, offset).storage,
);
}
final ParagraphGeometricStyle style = paragraph._geometricStyle;
// TODO(flutter_web): https://github.com/flutter/flutter/issues/33223
if (style.ellipsis != null &&
(style.maxLines == null || style.maxLines == 1)) {
paragraphStyle
..whiteSpace = 'pre'
..textOverflow = 'ellipsis';
}
return paragraphElement;
}