using System; using System.Collections.Generic; using System.Linq; using System.Text; using Unity.UIWidgets.async2; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using UnityEngine; using Canvas = Unity.UIWidgets.ui.Canvas; using Color = Unity.UIWidgets.ui.Color; using Object = System.Object; using Rect = Unity.UIWidgets.ui.Rect; using Shader = Unity.UIWidgets.ui.Shader; namespace Unity.UIWidgets.rendering { public class AnnotationEntry { public AnnotationEntry( T annotation = default(T), Offset localPosition = null ) { D.assert(localPosition != null); this.annotation = annotation; this.localPosition = localPosition; } public readonly T annotation; public readonly Offset localPosition; public override string ToString() { return $"{GetType()}"+ $"(annotation: {annotation}, localPostion: {localPosition})"; } } public class AnnotationResult { public readonly List> _entries = new List>(); public void add(AnnotationEntry entry) { _entries.Add(entry); } public IEnumerable> entries { get { return _entries; } } public IEnumerable annotations { get{ List results = new List(); foreach (AnnotationEntry entry in _entries) results.Add(entry.annotation); return results; } } } public abstract class Layer : AbstractNodeMixinDiagnosticableTree { public new ContainerLayer parent { get { return (ContainerLayer) base.parent; } } public bool _needsAddToScene = true; protected void markNeedsAddToScene() { D.assert( !alwaysNeedsAddToScene,()=> GetType() +" with alwaysNeedsAddToScene set called markNeedsAddToScene.\n" + "The layer's alwaysNeedsAddToScene is set to true, and therefore it should not call markNeedsAddToScene." ); if (_needsAddToScene) { return; } _needsAddToScene = true; } void debugMarkClean() { D.assert(()=> { _needsAddToScene = false; return true; }); } protected virtual bool alwaysNeedsAddToScene { get { return false; } } bool debugSubtreeNeedsAddToScene { get { bool result = false; D.assert(()=>{ result = _needsAddToScene; return true; }); return result; } } protected EngineLayer engineLayer { get { return _engineLayer; } set { _engineLayer = value; if (!alwaysNeedsAddToScene) { if (parent != null && !parent.alwaysNeedsAddToScene) { parent.markNeedsAddToScene(); } } } } protected EngineLayer _engineLayer; internal virtual void updateSubtreeNeedsAddToScene() { _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene; } public Layer nextSibling { get { return _nextSibling; } } internal Layer _nextSibling; public Layer previousSibling { get { return _previousSibling; } } internal Layer _previousSibling; protected override void dropChild(AbstractNodeMixinDiagnosticableTree child) { if (!alwaysNeedsAddToScene) { markNeedsAddToScene(); } base.dropChild(child); } protected override void adoptChild(AbstractNodeMixinDiagnosticableTree child) { if (!alwaysNeedsAddToScene) { markNeedsAddToScene(); } base.adoptChild(child); } public virtual void remove() { parent?._removeChild(this); } public virtual bool findAnnotations( AnnotationResult result, Offset localPosition, bool onlyFirst ) { return false; } S find(Offset localPosition) { AnnotationResult result = new AnnotationResult(); findAnnotations(result, localPosition, onlyFirst: true); return !result.entries.Any() ? default : result.entries.First().annotation; } AnnotationResult findAllAnnotations(Offset localPosition) { AnnotationResult result = new AnnotationResult(); findAnnotations(result, localPosition, onlyFirst: false); return result; } public abstract void addToScene(SceneBuilder builder, Offset layerOffset = null); internal void _addToSceneWithRetainedRendering(SceneBuilder builder) { if (!_needsAddToScene && _engineLayer != null) { builder.addRetained(_engineLayer); return; } addToScene(builder); _needsAddToScene = false; } public object debugCreator; public override string toStringShort() { return base.toStringShort() + (owner == null ? " DETACHED" : ""); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("owner", owner, level: parent != null ? DiagnosticLevel.hidden : DiagnosticLevel.info, defaultValue: foundation_.kNullDefaultValue)); properties.add(new DiagnosticsProperty("creator", debugCreator, defaultValue: foundation_.kNullDefaultValue, level: DiagnosticLevel.debug)); properties.add(new DiagnosticsProperty("engine layer", foundation_.describeIdentity(_engineLayer))); } public void replaceWith(Layer newLayer) { D.assert(parent != null); D.assert(attached == parent.attached); D.assert(newLayer.parent == null); D.assert(newLayer._nextSibling == null); D.assert(newLayer._previousSibling == null); D.assert(!newLayer.attached); newLayer._nextSibling = nextSibling; if (_nextSibling != null) { _nextSibling._previousSibling = newLayer; } newLayer._previousSibling = previousSibling; if (_previousSibling != null) { _previousSibling._nextSibling = newLayer; } D.assert(() => { Layer node = this; while (node.parent != null) { node = node.parent; } D.assert(node != newLayer); return true; }); parent.adoptChild(newLayer); D.assert(newLayer.attached == parent.attached); if (parent.firstChild == this) { parent._firstChild = newLayer; } if (parent.lastChild == this) { parent._lastChild = newLayer; } _nextSibling = null; _previousSibling = null; parent.dropChild(this); D.assert(!attached); } //internal abstract S find(Offset regionOffset) where S : class; } public class PictureLayer : Layer { public PictureLayer(Rect canvasBounds) { this.canvasBounds = canvasBounds; } public readonly Rect canvasBounds; Picture _picture; public Picture picture { get { return _picture; } set { markNeedsAddToScene(); _picture = value; } } bool _isComplexHint = false; public bool isComplexHint { get { return _isComplexHint; } set { if (value != _isComplexHint) { _isComplexHint = value; markNeedsAddToScene(); } } } bool _willChangeHint = false; public bool willChangeHint { get { return _willChangeHint; } set { if (value != _willChangeHint) { _willChangeHint = value; markNeedsAddToScene(); } } } /*internal override S find(Offset regionOffset) { return null; }*/ public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("paint bounds", canvasBounds)); properties.add(new DiagnosticsProperty("picture", foundation_.describeIdentity(_picture))); properties.add(new DiagnosticsProperty( "raster cache hints", $"isComplex = {isComplexHint} willChange = {willChangeHint}") ); } public override bool findAnnotations(AnnotationResult result, Offset localPosition,bool onlyFirst ) { return false; } } public class TextureLayer : Layer { public TextureLayer( Rect rect, int textureId, bool freeze = false ) { D.assert(rect != null); D.assert(textureId != null); this.rect = rect; this.textureId = textureId; this.freeze = freeze; } public readonly Rect rect; public readonly int textureId; public readonly bool freeze; public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { return false; } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset); builder.addTexture( textureId, offset: shiftedRect.topLeft, width: shiftedRect.width, height: shiftedRect.height, freeze: freeze ); } } public class ContainerLayer : Layer { public Layer firstChild { get { return _firstChild; } } internal Layer _firstChild; public Layer lastChild { get { return _lastChild; } } internal Layer _lastChild; internal bool hasChildren { get { return _firstChild != null; } } public ui.Scene buildScene(ui.SceneBuilder builder) { List temporaryLayers = new List(); D.assert(()=> { if (RenderingDebugUtils.debugCheckElevationsEnabled) { temporaryLayers = _debugCheckElevations(); } return true; }); updateSubtreeNeedsAddToScene(); addToScene(builder); _needsAddToScene = false; ui.Scene scene = builder.build(); D.assert(()=>{ if (temporaryLayers != null) { foreach ( PictureLayer temporaryLayer in temporaryLayers) { temporaryLayer.remove(); } } return true; }); return scene; } bool _debugUltimatePreviousSiblingOf(Layer child, Layer equals = null) { D.assert(child.attached == attached); while (child.previousSibling != null) { D.assert(child.previousSibling != child); child = child.previousSibling; D.assert(child.attached == attached); } return child == equals; } bool _debugUltimateNextSiblingOf(Layer child, Layer equals = null) { D.assert(child.attached == attached); while (child._nextSibling != null) { D.assert(child._nextSibling != child); child = child._nextSibling; D.assert(child.attached == attached); } return child == equals; } PictureLayer _highlightConflictingLayer(PhysicalModelLayer child) { PictureRecorder recorder = new PictureRecorder(); var canvas = new Canvas(recorder); canvas.drawPath(child.clipPath, new Paint() { color = new Color(0xFFAA0000), style = PaintingStyle.stroke, strokeWidth = child.elevation.Value + 10.0f, }); PictureLayer pictureLayer = new PictureLayer(child.clipPath.getBounds()); pictureLayer.picture = recorder.endRecording(); pictureLayer.debugCreator = child; child.append(pictureLayer); return pictureLayer; } List _processConflictingPhysicalLayers(PhysicalModelLayer predecessor, PhysicalModelLayer child) { IEnumerable infoCollector() { yield return child.toDiagnosticsNode(name: "Attempted to composite layer", style: DiagnosticsTreeStyle.errorProperty); yield return predecessor.toDiagnosticsNode(name: "after layer", style: DiagnosticsTreeStyle.errorProperty); yield return new ErrorDescription("which occupies the same area at a higher elevation."); } UIWidgetsError.reportError(new UIWidgetsErrorDetails( exception: new UIWidgetsError("Painting order is out of order with respect to elevation.\n" + "See https://api.flutter.dev/flutter/rendering/debugCheckElevationsEnabled.html " + "for more details."), library: "rendering library", context: new ErrorDescription("during compositing"), informationCollector: infoCollector )); return new List { _highlightConflictingLayer(predecessor), _highlightConflictingLayer(child) }; } protected List _debugCheckElevations() { List physicalModelLayers = depthFirstIterateChildren().OfType().ToList(); List addedLayers = new List(); for (int i = 0; i < physicalModelLayers.Count; i++) { PhysicalModelLayer physicalModelLayer = physicalModelLayers[i]; D.assert(physicalModelLayer.lastChild?.debugCreator != physicalModelLayer, () => "debugCheckElevations has either already visited this layer or failed to remove the" + " added picture from it."); float accumulatedElevation = physicalModelLayer.elevation.Value; Layer ancestor = physicalModelLayer.parent; while (ancestor != null) { if (ancestor is PhysicalModelLayer modelLayer) { accumulatedElevation += modelLayer.elevation.Value; } ancestor = ancestor.parent; } for (int j = 0; j <= i; j++) { PhysicalModelLayer predecessor = physicalModelLayers[j]; float predecessorAccumulatedElevation = predecessor.elevation.Value; ancestor = predecessor.parent; while (ancestor != null) { if (ancestor == predecessor) { continue; } if (ancestor is PhysicalModelLayer modelLayer) { predecessorAccumulatedElevation += modelLayer.elevation.Value; } ancestor = ancestor.parent; } if (predecessorAccumulatedElevation <= accumulatedElevation) { continue; } Path intersection = Path.combine( PathOperation.intersect, predecessor._debugTransformedClipPath, physicalModelLayer._debugTransformedClipPath); if (intersection != null && intersection.computeMetrics().Any((metric) => metric.length > 0)) { addedLayers.AddRange(_processConflictingPhysicalLayers(predecessor, physicalModelLayer)); } } } return addedLayers; } internal override void updateSubtreeNeedsAddToScene() { base.updateSubtreeNeedsAddToScene(); Layer child = firstChild; while (child != null) { child.updateSubtreeNeedsAddToScene(); _needsAddToScene = _needsAddToScene || child._needsAddToScene; child = child.nextSibling; } } /*internal override S find(Offset regionOffset) { Layer current = lastChild; while (current != null) { S value = current.find(regionOffset); if (value != null) { return value; } current = current.previousSibling; } return null; }*/ public override bool findAnnotations( AnnotationResult result, Offset localPosition, bool onlyFirst ) { for (Layer child = lastChild; child != null; child = child.previousSibling) { bool isAbsorbed = child.findAnnotations(result, localPosition, onlyFirst: onlyFirst); if (isAbsorbed) return true; if (onlyFirst && result.entries.Any()) return isAbsorbed; } return false; } public AnnotationResult findAllAnnotations(Offset localPosition) { AnnotationResult result = new AnnotationResult(); findAnnotations(result, localPosition, onlyFirst: false); return result; } public override void attach(object owner) { base.attach(owner); var child = firstChild; while (child != null) { child.attach(owner); child = child.nextSibling; } } public override void detach() { base.detach(); var child = firstChild; while (child != null) { child.detach(); child = child.nextSibling; } } public void append(Layer child) { D.assert(child != this); D.assert(child != firstChild); D.assert(child != lastChild); D.assert(child.parent == null); D.assert(!child.attached); D.assert(child.nextSibling == null); D.assert(child.previousSibling == null); D.assert(() => { Layer node = this; while (node.parent != null) { node = node.parent; } D.assert(node != child); return true; }); adoptChild(child); child._previousSibling = lastChild; if (lastChild != null) { lastChild._nextSibling = child; } _lastChild = child; if (_firstChild == null) { _firstChild = child; } D.assert(child.attached == attached); } internal void _removeChild(Layer child) { D.assert(child.parent == this); D.assert(child.attached == attached); D.assert(_debugUltimatePreviousSiblingOf(child, equals: firstChild)); D.assert(_debugUltimateNextSiblingOf(child, equals: lastChild)); if (child._previousSibling == null) { D.assert(firstChild == child); _firstChild = child.nextSibling; } else { child._previousSibling._nextSibling = child.nextSibling; } if (child._nextSibling == null) { D.assert(lastChild == child); _lastChild = child.previousSibling; } else { child._nextSibling._previousSibling = child.previousSibling; } D.assert((firstChild == null) == (lastChild == null)); D.assert(firstChild == null || firstChild.attached == attached); D.assert(lastChild == null || lastChild.attached == attached); D.assert(firstChild == null || _debugUltimateNextSiblingOf(firstChild, equals: lastChild)); D.assert(lastChild == null || _debugUltimatePreviousSiblingOf(lastChild, equals: firstChild)); child._nextSibling = null; child._previousSibling = null; dropChild(child); D.assert(!child.attached); } public void removeAllChildren() { Layer child = firstChild; while (child != null) { Layer next = child.nextSibling; child._previousSibling = null; child._nextSibling = null; D.assert(child.attached == attached); dropChild(child); child = next; } _firstChild = null; _lastChild = null; } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; addChildrenToScene(builder, layerOffset); } public void addChildrenToScene(SceneBuilder builder, Offset childOffset = null) { Layer child = firstChild; while (child != null) { if (childOffset == null || childOffset == Offset.zero) { child._addToSceneWithRetainedRendering(builder); } else { child.addToScene(builder, childOffset); } child = child.nextSibling; } } public virtual void applyTransform(Layer child, Matrix4 transform) { D.assert(child != null); D.assert(transform != null); } public List depthFirstIterateChildren() { if (firstChild == null) { return new List(); } List children = new List(); Layer child = firstChild; while (child != null) { children.Add(child); if (child is ContainerLayer containerLayer) { children.AddRange(containerLayer.depthFirstIterateChildren()); } child = child.nextSibling; } return children; } public override List debugDescribeChildren() { var children = new List(); if (firstChild == null) { return children; } Layer child = firstChild; int count = 1; while (true) { children.Add(child.toDiagnosticsNode(name: "child " + count)); if (child == lastChild) { break; } count += 1; child = child.nextSibling; } return children; } } public class ShaderMaskLayer : ContainerLayer { public ShaderMaskLayer( Shader shader = null, Rect maskRect = null, BlendMode blendMode = BlendMode.clear ) { _shader = shader; _maskRect = maskRect; _blendMode = blendMode; } public Shader shader { get { return _shader; } set{ if (value != _shader) { _shader = value; markNeedsAddToScene(); } } } Shader _shader; public Rect maskRect { get { return _maskRect; } set { if (value != _maskRect) { _maskRect = value; markNeedsAddToScene(); } } } Rect _maskRect; public BlendMode blendMode { get { return _blendMode; } set { if (value != _blendMode) { _blendMode = value; markNeedsAddToScene(); } } } BlendMode _blendMode; public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(shader != null); D.assert(maskRect != null); D.assert(blendMode != null); D.assert(layerOffset != null); Rect shiftedMaskRect = layerOffset == Offset.zero ? maskRect : maskRect.shift(layerOffset); engineLayer = builder.pushShaderMask( shader, shiftedMaskRect, blendMode, oldLayer: _engineLayer as ui.ShaderMaskEngineLayer ); addChildrenToScene(builder, layerOffset); builder.pop(); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("shader", shader)); properties.add(new DiagnosticsProperty("maskRect", maskRect)); properties.add(new DiagnosticsProperty("blendMode", blendMode)); } } public class OffsetLayer : ContainerLayer { public OffsetLayer(Offset offset = null) { _offset = offset ?? Offset.zero; } Offset _offset; public Offset offset { get { return _offset; } set { value = value ?? Offset.zero; if (value != _offset) { _offset = value; markNeedsAddToScene(); } } } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst) { return base.findAnnotations(result, localPosition - offset, onlyFirst: onlyFirst); } public override void applyTransform(Layer child, Matrix4 transform) { D.assert(child != null); D.assert(transform != null); transform.multiply(Matrix4.translationValues(offset.dx, offset.dy, 0.0f)); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; engineLayer = builder.pushOffset( (float) (layerOffset.dx + offset.dx), (float) (layerOffset.dy + offset.dy), oldLayer: engineLayer as OffsetEngineLayer); addChildrenToScene(builder); builder.pop(); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("offset", offset)); } /*public Scene buildScene(SceneBuilder builder) { List temporaryLayers = null; D.assert(() => { if (RenderingDebugUtils.debugCheckElevationsEnabled) { temporaryLayers = _debugCheckElevations(); } return true; }); updateSubtreeNeedsAddToScene(); addToScene(builder); Scene scene = builder.build(); D.assert(() => { if (temporaryLayers != null) { foreach (PictureLayer temporaryLayer in temporaryLayers) { temporaryLayer.remove(); } } return true; }); return scene; }*/ public Future toImage(Rect bounds, float pixelRatio = 1.0f)// async { D.assert(bounds != null); ui.SceneBuilder builder = new SceneBuilder(); Matrix4 transform = Matrix4.translationValues( (-bounds.left - offset.dx) * pixelRatio, (-bounds.top - offset.dy) * pixelRatio, 0.0f ); transform.scale(pixelRatio, pixelRatio); builder.pushTransform(transform.storage); ui.Scene scene = buildScene(builder); try { // Size is rounded up to the next pixel to make sure we don't clip off // anything. return scene.toImage( (pixelRatio * bounds.width).ceil(), (pixelRatio * bounds.height).ceil() ); } finally { scene.DisposePtr(scene._ptr);// ??? } } } public class ClipRectLayer : ContainerLayer { public ClipRectLayer( Rect clipRect = null, Clip clipBehavior = Clip.hardEdge ) { D.assert(clipBehavior != Clip.none); _clipRect = clipRect; _clipBehavior = clipBehavior; } Rect _clipRect; public Rect clipRect { get { return _clipRect; } set { if (value != _clipRect) { _clipRect = value; markNeedsAddToScene(); } } } Clip _clipBehavior; public Clip clipBehavior { get { return _clipBehavior; } set { D.assert(value != null); D.assert(value != Clip.none); if (value != _clipBehavior) { _clipBehavior = value; markNeedsAddToScene(); } } } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (!clipRect.contains(localPosition)) return false; return base.findAnnotations(result, localPosition, onlyFirst: onlyFirst); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(clipRect != null); bool enabled = true; D.assert(() => { enabled = !D.debugDisableClipLayers; return true; }); if (enabled) { var shiftedClipRect = layerOffset == Offset.zero ? clipRect : clipRect.shift(layerOffset); engineLayer = builder.pushClipRect( rect: shiftedClipRect, clipBehavior: clipBehavior, oldLayer: engineLayer as ClipRectEngineLayer); } else { engineLayer = null; } addChildrenToScene(builder, layerOffset); if (enabled) { builder.pop(); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("clipRect", clipRect)); properties.add(new DiagnosticsProperty("clipBehavior", clipBehavior)); } } public class ClipRRectLayer : ContainerLayer { public ClipRRectLayer( RRect clipRRect = null, Clip clipBehavior = Clip.antiAlias ) { D.assert(clipBehavior != Clip.none); _clipRRect = clipRRect; _clipBehavior = clipBehavior; } RRect _clipRRect; public RRect clipRRect { get { return _clipRRect; } set { if (value != _clipRRect) { _clipRRect = value; markNeedsAddToScene(); } } } Clip _clipBehavior; public Clip clipBehavior { get { return _clipBehavior; } set { D.assert(value != null); D.assert(value != Clip.none); if (value != _clipBehavior) { _clipBehavior = value; markNeedsAddToScene(); } } } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (!clipRRect.contains(localPosition)) return false; return base.findAnnotations(result, localPosition, onlyFirst: onlyFirst); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(clipRRect != null); bool enabled = true; D.assert(() => { enabled = !D.debugDisableClipLayers; return true; }); if (enabled) { var shiftedClipRRect = layerOffset == Offset.zero ? clipRRect : clipRRect.shift(layerOffset); engineLayer = builder.pushClipRRect( shiftedClipRRect, clipBehavior: clipBehavior, oldLayer: engineLayer as ClipRRectEngineLayer ); } else { engineLayer = null; } addChildrenToScene(builder, layerOffset); if (enabled) { builder.pop(); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("clipRRect", clipRRect)); properties.add(new DiagnosticsProperty("clipBehavior", clipBehavior)); } } public class ClipPathLayer : ContainerLayer { public ClipPathLayer( Path clipPath = null, Clip clipBehavior = Clip.antiAlias ) { D.assert(clipPath != null); D.assert(clipBehavior != Clip.none); _clipPath = clipPath; _clipBehavior = clipBehavior; } Path _clipPath; public Path clipPath { get { return _clipPath; } set { if (value != _clipPath) { _clipPath = value; markNeedsAddToScene(); } } } Clip _clipBehavior; public Clip clipBehavior { get { return _clipBehavior; } set { D.assert(value != null); D.assert(value != Clip.none); if (value != _clipBehavior) { _clipBehavior = value; markNeedsAddToScene(); } } } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (!clipPath.contains(localPosition)) return false; return base.findAnnotations(result, localPosition, onlyFirst: onlyFirst); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(clipPath != null); bool enabled = true; D.assert(() => { enabled = !D.debugDisableClipLayers; return true; }); if (enabled) { var shiftedPath = layerOffset == Offset.zero ? clipPath : clipPath.shift(layerOffset); engineLayer = builder.pushClipPath( shiftedPath, clipBehavior: clipBehavior, oldLayer: engineLayer as ClipPathEngineLayer); } else { engineLayer = null; } addChildrenToScene(builder, layerOffset); if (enabled) { builder.pop(); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("clipBehavior", clipBehavior)); } } public class ColorFilterLayer : ContainerLayer { public ColorFilterLayer(ColorFilter colorFilter = null) { _colorFilter = colorFilter; } public ColorFilter colorFilter { get { return _colorFilter; } set { D.assert(value != null); if (value != _colorFilter) { _colorFilter = value; markNeedsAddToScene(); } } } ColorFilter _colorFilter; public override void addToScene(ui.SceneBuilder builder, Offset layerOffset = null ) { D.assert(colorFilter != null); engineLayer = builder.pushColorFilter( colorFilter, oldLayer: _engineLayer as ui.ColorFilterEngineLayer ); addChildrenToScene(builder, layerOffset); builder.pop(); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("colorFilter", colorFilter)); } } public class ImageFilterLayer : ContainerLayer { public ImageFilterLayer( ui.ImageFilter imageFilter ) { _imageFilter = imageFilter; } public ui.ImageFilter imageFilter { get { return _imageFilter; } set { D.assert(value != null); if (value != _imageFilter) { _imageFilter = value; markNeedsAddToScene(); } } } ui.ImageFilter _imageFilter; public override void addToScene(ui.SceneBuilder builder, Offset layerOffset = null) { D.assert(imageFilter != null); engineLayer = builder.pushImageFilter( imageFilter, oldLayer: _engineLayer as ui.ImageFilterEngineLayer ); addChildrenToScene(builder, layerOffset); builder.pop(); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("imageFilter", imageFilter)); } } public class TransformLayer : OffsetLayer { public TransformLayer(Matrix4 transform = null, Offset offset = null) : base(offset) { offset = offset ?? Offset.zero; _transform = transform ?? Matrix4.identity(); } public Matrix4 transform { get { return _transform; } set { D.assert(value != null); bool componentIsFinite = true; foreach (var component in value.storage) { if (!component.isFinite()) { componentIsFinite = false; break; } } D.assert(componentIsFinite); if (value == _transform) return; _transform = value; _inverseDirty = true; markNeedsAddToScene(); } } Matrix4 _transform; Matrix4 _lastEffectiveTransform; Matrix4 _invertedTransform; bool _inverseDirty = true; public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(transform != null); _lastEffectiveTransform = _transform; var totalOffset = offset + layerOffset; if (totalOffset != Offset.zero) { _lastEffectiveTransform = Matrix4.translationValues(totalOffset.dx, totalOffset.dy, 0); _lastEffectiveTransform.multiply(transform); } engineLayer = builder.pushTransform( _lastEffectiveTransform.storage, oldLayer: engineLayer as TransformEngineLayer); addChildrenToScene(builder); builder.pop(); } Offset _transformOffset(Offset localPosition) { if (_inverseDirty) { _invertedTransform = Matrix4.tryInvert( PointerEvent.removePerspectiveTransform(transform) ); _inverseDirty = false; } if (_invertedTransform == null) return null; return MatrixUtils.transformPoint(_invertedTransform, localPosition); } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { Offset transformedOffset = _transformOffset(localPosition); if (transformedOffset == null) return false; return base.findAnnotations(result, transformedOffset, onlyFirst: onlyFirst); } public override void applyTransform(Layer child, Matrix4 transform) { D.assert(child != null); D.assert(transform != null); D.assert(_lastEffectiveTransform != null || this.transform != null); if (_lastEffectiveTransform == null) { transform.multiply(this.transform); } else { transform.multiply(_lastEffectiveTransform); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("transform", transform)); } } public class OpacityLayer : ContainerLayer { public OpacityLayer(int alpha = 255, Offset offset = null) { _alpha = alpha; _offset = offset ?? Offset.zero; } int _alpha; public int alpha { get { return _alpha; } set { D.assert(value != null); if (value != _alpha) { _alpha = value; markNeedsAddToScene(); } } } Offset _offset; public Offset offset { get { return _offset; } set { value = value ?? Offset.zero; if (value != _offset) { _offset = value; markNeedsAddToScene(); } } } public override void applyTransform(Layer child, Matrix4 transform) { D.assert(child != null); D.assert(transform != null); transform.translate(offset.dx, offset.dy); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(alpha != null); bool enabled = firstChild != null; D.assert(() => { enabled = enabled && !D.debugDisableOpacityLayers; return true; }); if (enabled) { engineLayer = builder.pushOpacity( alpha, offset: offset + layerOffset, oldLayer: engineLayer as OpacityEngineLayer); } else { engineLayer = null; } addChildrenToScene(builder, layerOffset); if (enabled) { builder.pop(); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new IntProperty("alpha", alpha)); properties.add(new DiagnosticsProperty("offset", offset)); } } public class BackdropFilterLayer : ContainerLayer { public BackdropFilterLayer(ImageFilter filter = null) { D.assert(filter != null); _filter = filter; } ImageFilter _filter; public ImageFilter filter { get { return _filter; } set { if (value != _filter) { _filter = value; markNeedsAddToScene(); } } } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(filter != null); engineLayer = builder.pushBackdropFilter( filter: filter, oldLayer: engineLayer as BackdropFilterEngineLayer); addChildrenToScene(builder, layerOffset); builder.pop(); } } public class LayerLink { public LeaderLayer leader { get { return _leader; } } internal LeaderLayer _leader; public override string ToString() { return $"{foundation_.describeIdentity(this)}({(_leader != null ? "" : "")})"; } } public class LeaderLayer : ContainerLayer { public LeaderLayer(LayerLink link, Offset offset = null) { D.assert(link != null); offset = offset ?? Offset.zero; _link = link; this.offset = offset; } public LayerLink link { get { return _link; } set { D.assert(value != null); _link = value; } } LayerLink _link; public Offset offset; protected override bool alwaysNeedsAddToScene { get { return true; } } public override void attach(object owner) { base.attach(owner); D.assert(link.leader == null); _lastOffset = null; link._leader = this; } public override void detach() { D.assert(link.leader == this); link._leader = null; _lastOffset = null; base.detach(); } internal Offset _lastOffset; public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { return base.findAnnotations(result, localPosition - offset, onlyFirst: onlyFirst); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(offset != null); _lastOffset = offset + layerOffset; if (_lastOffset != Offset.zero) { engineLayer = builder.pushTransform( Matrix4.translationValues(_lastOffset.dx, _lastOffset.dy,0) ._m4storage, oldLayer: engineLayer as TransformEngineLayer); } addChildrenToScene(builder, Offset.zero); if (_lastOffset != Offset.zero) { builder.pop(); } } public override void applyTransform(Layer child, Matrix4 transform) { D.assert(_lastOffset != null); if (_lastOffset != Offset.zero) { transform.translate(_lastOffset.dx, _lastOffset.dy); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("offset", offset)); properties.add(new DiagnosticsProperty("link", link)); } } public class FollowerLayer : ContainerLayer { public FollowerLayer( LayerLink link = null, bool showWhenUnlinked = true, Offset unlinkedOffset = null, Offset linkedOffset = null ) { D.assert(link != null); _link = link; this.showWhenUnlinked = showWhenUnlinked; this.unlinkedOffset = unlinkedOffset ?? Offset.zero; this.linkedOffset = linkedOffset ?? Offset.zero; } public LayerLink link { get { return _link; } set { D.assert(value != null); _link = value; } } LayerLink _link; public bool showWhenUnlinked; public Offset unlinkedOffset; public Offset linkedOffset; Offset _lastOffset; Matrix4 _lastTransform; Matrix4 _invertedTransform = Matrix4.identity(); bool _inverseDirty = true; Offset _transformOffset(Offset localPosition) { if (_inverseDirty) { _invertedTransform = Matrix4.tryInvert(getLastTransform()); _inverseDirty = false; } if (_invertedTransform == null) return null; Vector4 vector = new Vector4(localPosition.dx, localPosition.dy, 0.0f, 1.0f); Vector4 result = _invertedTransform.transform(vector); return new Offset(result[0] - linkedOffset.dx, result[1] - linkedOffset.dy); } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (link.leader == null) { if (showWhenUnlinked) { return base.findAnnotations(result, localPosition - unlinkedOffset, onlyFirst: onlyFirst); } return false; } Offset transformedOffset = _transformOffset(localPosition); if (transformedOffset == null) { return false; } return base.findAnnotations(result, transformedOffset, onlyFirst: onlyFirst); } public Matrix4 getLastTransform() { if (_lastTransform == null) { return null; } Matrix4 result = Matrix4.translationValues(-_lastOffset.dx, -_lastOffset.dy,0 ); result.multiply(_lastTransform); return result; } Matrix4 _collectTransformForLayerChain(List layers) { Matrix4 result = Matrix4.identity(); for (int index = layers.Count - 1; index > 0; index -= 1) { layers[index].applyTransform(layers[index - 1], result); } return result; } void _establishTransform() { D.assert(link != null); _lastTransform = null; if (link._leader == null) { return; } D.assert(link.leader.owner == owner, () => "Linked LeaderLayer anchor is not in the same layer tree as the FollowerLayer."); D.assert(link.leader._lastOffset != null, () => "LeaderLayer anchor must come before FollowerLayer in paint order, but the reverse was true."); HashSet ancestors = new HashSet(); Layer ancestor = parent; while (ancestor != null) { ancestors.Add(ancestor); ancestor = ancestor.parent; } ContainerLayer layer = link.leader; List forwardLayers = new List {null, layer}; do { layer = layer.parent; forwardLayers.Add(layer); } while (!ancestors.Contains(layer)); ancestor = layer; layer = this; List inverseLayers = new List {layer}; do { layer = layer.parent; inverseLayers.Add(layer); } while (layer != ancestor); Matrix4 forwardTransform = _collectTransformForLayerChain(forwardLayers); Matrix4 inverseTransform = _collectTransformForLayerChain(inverseLayers); if (inverseTransform.invert() == 0) { return; } inverseTransform.multiply(forwardTransform); inverseTransform.translate(linkedOffset.dx, linkedOffset.dy); _lastTransform = inverseTransform; _inverseDirty = true; } protected override bool alwaysNeedsAddToScene { get { return true; } } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(link != null); D.assert(link != null); if (link.leader == null && !showWhenUnlinked) { _lastTransform = null; _lastOffset = null; _inverseDirty = true; engineLayer = null; return; } _establishTransform(); if (_lastTransform != null) { engineLayer = builder.pushTransform( _lastTransform._m4storage, oldLayer: engineLayer as TransformEngineLayer); addChildrenToScene(builder); builder.pop(); _lastOffset = unlinkedOffset + layerOffset; } else { _lastOffset = null; var matrix = Matrix4.translationValues(unlinkedOffset.dx, unlinkedOffset.dy, 0); engineLayer = builder.pushTransform( matrix._m4storage, oldLayer: engineLayer as TransformEngineLayer); addChildrenToScene(builder); builder.pop(); } _inverseDirty = true; } public override void applyTransform(Layer child, Matrix4 transform) { D.assert(child != null); D.assert(transform != null); if (_lastTransform != null) { transform.multiply(_lastTransform); } else { transform.multiply(Matrix4.translationValues(unlinkedOffset.dx, unlinkedOffset.dy, 0)); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("link", link)); properties.add(new TransformProperty("transform", getLastTransform(), defaultValue: foundation_.kNullDefaultValue)); } } /*public class PlatformViewLayer : Layer { public PlatformViewLayer( Rect rect = null, int viewId = default, MouseTrackerAnnotation hoverAnnotation = null ) { D.assert(rect != null); } public readonly Rect rect; public readonly int viewId; public readonly MouseTrackerAnnotation hoverAnnotation; public override void addToScene(ui.SceneBuilder builder, Offset layerOffset = null) { Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset); builder.addPlatformView( viewId, offset: shiftedRect.topLeft, width: shiftedRect.width, height: shiftedRect.height ); } public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (hoverAnnotation == null || !rect.contains(localPosition)) { return false; } if (typeof(S) == typeof(MouseTrackerAnnotation)) { Object untypedValue = hoverAnnotation; S typedValue = (S)untypedValue; result.add(new AnnotationEntry( annotation: typedValue, localPosition: localPosition )); return true; } return false; } }*/ public class PerformanceOverlayLayer : Layer { public PerformanceOverlayLayer( Rect overlayRect = null, int optionsMask = default, int rasterizerThreshold = default, bool checkerboardRasterCacheImages = default, bool checkerboardOffscreenLayers = default ) { _overlayRect = overlayRect; this.optionsMask = optionsMask ; this.rasterizerThreshold = rasterizerThreshold ; this.checkerboardOffscreenLayers = checkerboardRasterCacheImages; this.checkerboardRasterCacheImages = checkerboardOffscreenLayers; } public Rect overlayRect { get { return _overlayRect; } set { if (value != _overlayRect) { _overlayRect = value; markNeedsAddToScene(); } } } Rect _overlayRect; public readonly int optionsMask; public readonly int rasterizerThreshold; public readonly bool checkerboardRasterCacheImages; public readonly bool checkerboardOffscreenLayers; public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { return false; } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { D.assert(optionsMask != null); layerOffset = layerOffset ?? Offset.zero; var shiftedOverlayRect = layerOffset == Offset.zero ? overlayRect : overlayRect.shift(layerOffset); builder.addPerformanceOverlay(optionsMask, shiftedOverlayRect); //TODO: add implementations //builder.setRasterizerTracingThreshold(rasterizerThreshold); //builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages); //builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers); } } public class AnnotatedRegionLayer : ContainerLayer where T : class { public AnnotatedRegionLayer( T value = null, Size size = null, Offset offset = null, bool opaque = false ) { offset = offset ?? Offset.zero; D.assert(value != null); this.value = value; this.size = size; this.offset = offset; this.opaque = opaque; } public readonly T value; public readonly Size size; public readonly Offset offset; public readonly bool opaque; public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { bool isAbsorbed = base.findAnnotations(result, localPosition, onlyFirst: onlyFirst); if (result.entries.Any() && onlyFirst) return isAbsorbed; if (size != null && !(offset & size).contains(localPosition)) { return isAbsorbed; } if (typeof(T) == typeof(S)) { isAbsorbed = isAbsorbed || opaque; object untypedValue = value; S typedValue = (S)untypedValue; result.add(new AnnotationEntry( annotation: typedValue, localPosition: localPosition - offset )); } return isAbsorbed; } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("value", value)); properties.add(new DiagnosticsProperty("size", size, defaultValue: null)); properties.add(new DiagnosticsProperty("offset", offset, defaultValue: null)); properties.add(new DiagnosticsProperty("opaque", opaque, defaultValue: false)); } } public class PhysicalModelLayer : ContainerLayer { public PhysicalModelLayer( Path clipPath = null, Clip clipBehavior = Clip.none, float? elevation = null, Color color = null, Color shadowColor = null) { _clipPath = clipPath; _clipBehavior = clipBehavior; _elevation = elevation; _color = color; this.shadowColor = shadowColor; } public Path clipPath { get { return _clipPath; } set { if (value != _clipPath) { _clipPath = value; markNeedsAddToScene(); } } } Path _clipPath; internal Path _debugTransformedClipPath { get { ContainerLayer ancestor = parent; Matrix4 matrix = Matrix4.identity(); while (ancestor != null && ancestor.parent != null) { ancestor.applyTransform(this, matrix); ancestor = ancestor.parent; } return clipPath.transform(matrix._m4storage); } } public Clip clipBehavior { get { return _clipBehavior; } set { if (value != _clipBehavior) { _clipBehavior = value; markNeedsAddToScene(); } } } Clip _clipBehavior; public float? elevation { get { return _elevation; } set { if (value != _elevation) { _elevation = value; markNeedsAddToScene(); } } } float? _elevation; public Color color { get { return _color; } set { if (value != _color) { _color = value; markNeedsAddToScene(); } } } Color _color; public Color shadowColor { get { return _shadowColor; } set { if (value != _shadowColor) { _shadowColor = value; markNeedsAddToScene(); } } } Color _shadowColor; public override bool findAnnotations(AnnotationResult result, Offset localPosition, bool onlyFirst ) { if (!clipPath.contains(localPosition)) return false; return base.findAnnotations(result, localPosition, onlyFirst: onlyFirst); } public override void addToScene(SceneBuilder builder, Offset layerOffset = null) { layerOffset = layerOffset ?? Offset.zero; D.assert(clipPath != null); D.assert(color != null); D.assert(elevation != null); D.assert(shadowColor != null); bool enabled = true; D.assert(() => { enabled = !D.debugDisablePhysicalShapeLayers; return true; }); if (enabled) { engineLayer = builder.pushPhysicalShape( path: layerOffset == Offset.zero ? clipPath : clipPath.shift(layerOffset), elevation: elevation.Value, color: color, shadowColor: shadowColor, clipBehavior: clipBehavior, oldLayer: engineLayer as PhysicalShapeEngineLayer); } else { engineLayer = null; } addChildrenToScene(builder, layerOffset); if (enabled) { builder.pop(); } } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new FloatProperty("elevation", elevation)); properties.add(new ColorProperty("color", color)); } } }