using System; using System.Collections.Generic; using UIWidgets.foundation; using UIWidgets.gestures; using UIWidgets.ui; using UnityEngine; using Canvas = UIWidgets.ui.Canvas; using Rect = UIWidgets.ui.Rect; namespace UIWidgets.rendering { public class ParentData { public virtual void detach() { } } public delegate void PaintingContextCallback(PaintingContext context, Offset offset); public class PaintingContext { private PaintingContext(ContainerLayer containerLayer) { this._containerLayer = containerLayer; } public readonly ContainerLayer _containerLayer; public static void repaintCompositedChild(RenderObject child) { if (child._layer == null) { child._layer = new OffsetLayer(); } else { child._layer.removeAllChildren(); } var childContext = new PaintingContext(child._layer); child._paintWithContext(childContext, Offset.zero); childContext._stopRecordingIfNeeded(); } public void paintChild(RenderObject child, Offset offset) { if (child.isRepaintBoundary) { this._stopRecordingIfNeeded(); this._compositeChild(child, offset); } else { child._paintWithContext(this, offset); } } public void _compositeChild(RenderObject child, Offset offset) { if (child._needsPaint) { PaintingContext.repaintCompositedChild(child); } child._layer.offset = offset; this._appendLayer(child._layer); } public void _appendLayer(Layer layer) { layer.remove(); this._containerLayer.append(layer); } public bool _isRecording { get { bool hasCanvas = this._canvas != null; return hasCanvas; } } public PictureLayer _currentLayer; public PictureRecorder _recorder; public Canvas _canvas; public Canvas canvas { get { if (this._canvas == null) { this._startRecording(); } return this._canvas; } } public void _startRecording() { this._currentLayer = new PictureLayer(); this._recorder = new PictureRecorder(); this._canvas = new RecorderCanvas(this._recorder); this._containerLayer.append(this._currentLayer); } public void _stopRecordingIfNeeded() { if (!this._isRecording) { return; } this._currentLayer.picture = this._recorder.endRecording(); this._currentLayer = null; this._recorder = null; this._canvas = null; } public void addLayer(Layer layer) { this._stopRecordingIfNeeded(); this._appendLayer(layer); } void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset) { this._stopRecordingIfNeeded(); this._appendLayer(childLayer); var childContext = new PaintingContext((ContainerLayer) childLayer); painter(childContext, offset); childContext._stopRecordingIfNeeded(); } public void pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter) { Rect offsetClipRect = clipRect.shift(offset); if (needsCompositing) { this.pushLayer(new ClipRectLayer(offsetClipRect), painter, offset); } else { this.canvas.save(); this.canvas.clipRect(offsetClipRect); painter(this, offset); this.canvas.restore(); } } public void pushClipRRect(bool needsCompositing, Offset offset, RRect clipRRect, PaintingContextCallback painter) { RRect offsetClipRRect = clipRRect.shift(offset); if (needsCompositing) { this.pushLayer(new ClipRRectLayer(offsetClipRRect), painter, offset); } else { this.canvas.save(); this.canvas.clipRRect(offsetClipRRect); painter(this, offset); this.canvas.restore(); } } void pushTransform(bool needsCompositing, Offset offset, Matrix4x4 transform, PaintingContextCallback painter) { var effectiveTransform = Matrix4x4.Translate(offset.toVector()) * transform * Matrix4x4.Translate(-offset.toVector()); if (needsCompositing) { this.pushLayer(new TransformLayer(effectiveTransform), painter, offset); } else { this.canvas.save(); this.canvas.concat(effectiveTransform); painter(this, offset); this.canvas.restore(); } } public void pushOpacity(Offset offset, int alpha, PaintingContextCallback painter) { this.pushLayer(new OpacityLayer(alpha), painter, offset); } } public abstract class Constraints { public abstract bool isTight { get; } public abstract bool isNormalized { get; } } public delegate void RenderObjectVisitor(RenderObject child); public delegate void LayoutCallback(T constraints) where T : Constraints; public class PipelineOwner { public PipelineOwner( VoidCallback onNeedVisualUpdate = null) { this.onNeedVisualUpdate = onNeedVisualUpdate; } public readonly VoidCallback onNeedVisualUpdate; public void requestVisualUpdate() { if (this.onNeedVisualUpdate != null) { this.onNeedVisualUpdate(); } } public AbstractNodeMixinDiagnosticableTree rootNode { get { return this._rootNode; } set { if (this._rootNode == value) { return; } if (this._rootNode != null) { this._rootNode.detach(); } this._rootNode = value; if (this._rootNode != null) { this._rootNode.attach(this); } } } public AbstractNodeMixinDiagnosticableTree _rootNode; public List _nodesNeedingLayout = new List(); public bool debugDoingLayout { get { return this._debugDoingLayout; } } bool _debugDoingLayout = false; public void flushLayout() { D.assert(() => { this._debugDoingLayout = true; return true; }); try { while (this._nodesNeedingLayout.Count > 0) { var dirtyNodes = this._nodesNeedingLayout; this._nodesNeedingLayout = new List(); dirtyNodes.Sort((a, b) => a.depth - b.depth); foreach (var node in dirtyNodes) { if (node._needsLayout && node.owner == this) { node._layoutWithoutResize(); } } } } finally { D.assert(() => { this._debugDoingLayout = false; return true; }); } } public List _nodesNeedingCompositingBitsUpdate = new List(); public void flushCompositingBits() { this._nodesNeedingCompositingBitsUpdate.Sort((a, b) => a.depth - b.depth); foreach (RenderObject node in this._nodesNeedingCompositingBitsUpdate) { if (node._needsCompositingBitsUpdate && node.owner == this) { node._updateCompositingBits(); } } this._nodesNeedingCompositingBitsUpdate.Clear(); } public List _nodesNeedingPaint = new List(); public void flushPaint() { var dirtyNodes = this._nodesNeedingPaint; this._nodesNeedingPaint = new List(); dirtyNodes.Sort((a, b) => a.depth - b.depth); foreach (var node in dirtyNodes) { if (node._needsPaint && node.owner == this) { if (node._layer.attached) { PaintingContext.repaintCompositedChild(node); } else { node._skippedPaintingOnLayer(); } } } } } public interface RenderObjectWithChildMixin { bool debugValidateChild(RenderObject child); RenderObject child { get; set; } } public interface RenderObjectWithChildMixin : RenderObjectWithChildMixin where ChildType : RenderObject { new ChildType child { get; set; } } public interface ContainerParentDataMixin where ChildType : RenderObject { ChildType previousSibling { get; set; } ChildType nextSibling { get; set; } } public interface ContainerRenderObjectMixin { int childCount { get; } bool debugValidateChild(RenderObject child); void insert(RenderObject child, RenderObject after = null); void remove(RenderObject child); void move(RenderObject child, RenderObject after = null); RenderObject firstChild { get; } RenderObject lastChild { get; } RenderObject childBefore(RenderObject child); RenderObject childAfter(RenderObject child); } public abstract class RenderObject : AbstractNodeMixinDiagnosticableTree, HitTestTarget { protected RenderObject() { this._needsCompositing = this.isRepaintBoundary || this.alwaysNeedsCompositing; } public ParentData parentData; public virtual void setupParentData(RenderObject child) { if (!(child.parentData is ParentData)) { child.parentData = new ParentData(); } } protected override void adoptChild(AbstractNodeMixinDiagnosticableTree childNode) { var child = (RenderObject) childNode; this.setupParentData(child); base.adoptChild(child); this.markNeedsLayout(); this.markNeedsCompositingBitsUpdate(); } protected override void dropChild(AbstractNodeMixinDiagnosticableTree childNode) { var child = (RenderObject) childNode; child._cleanRelayoutBoundary(); child.parentData.detach(); child.parentData = null; base.dropChild(child); this.markNeedsLayout(); this.markNeedsCompositingBitsUpdate(); } public virtual void visitChildren(RenderObjectVisitor visitor) { } public object debugCreator; public bool debugCanParentUseSize { get { return this._debugCanParentUseSize; } } bool _debugCanParentUseSize; public new PipelineOwner owner { get { return (PipelineOwner) base.owner; } } public override void attach(object ownerObject) { var owner = (PipelineOwner) ownerObject; base.attach(owner); if (this._needsLayout && this._relayoutBoundary != null) { this._needsLayout = false; this.markNeedsLayout(); } if (this._needsCompositingBitsUpdate) { this._needsCompositingBitsUpdate = false; this.markNeedsCompositingBitsUpdate(); } if (this._needsPaint && this._layer != null) { this._needsPaint = false; this.markNeedsPaint(); } } public bool debugNeedsLayout { get { bool result = false; D.assert(() => { result = this._needsLayout; return true; }); return result; } } internal bool _needsLayout = true; public RenderObject _relayoutBoundary; bool _doingThisLayoutWithCallback = false; public Constraints constraints { get { return this._constraints; } } public Constraints _constraints; public virtual void markNeedsLayout() { if (this._needsLayout) { return; } if (this._relayoutBoundary != this) { this.markParentNeedsLayout(); } else { this._needsLayout = true; if (this.owner != null) { this.owner._nodesNeedingLayout.Add(this); this.owner.requestVisualUpdate(); } } } public void markParentNeedsLayout() { this._needsLayout = true; if (!this._doingThisLayoutWithCallback) { ((RenderObject) this.parent).markNeedsLayout(); } } public void markNeedsLayoutForSizedByParentChange() { this.markNeedsLayout(); this.markParentNeedsLayout(); } public void _cleanRelayoutBoundary() { if (this._relayoutBoundary != this) { this._relayoutBoundary = null; this._needsLayout = true; this.visitChildren(child => { child._cleanRelayoutBoundary(); }); } } public void scheduleInitialLayout() { this._relayoutBoundary = this; this.owner._nodesNeedingLayout.Add(this); } public void _layoutWithoutResize() { try { this.performLayout(); } catch (Exception ex) { Debug.LogError("error in performLayout: " + ex); } this._needsLayout = false; this.markNeedsPaint(); } public void layout(Constraints constraints, bool parentUsesSize = false) { RenderObject relayoutBoundary; if (!parentUsesSize || this.sizedByParent || constraints.isTight || !(this.parent is RenderObject)) { relayoutBoundary = this; } else { relayoutBoundary = ((RenderObject) this.parent)._relayoutBoundary; } if (!this._needsLayout && object.Equals(constraints, this._constraints) && relayoutBoundary == this._relayoutBoundary) { return; } this._constraints = constraints; this._relayoutBoundary = relayoutBoundary; if (this.sizedByParent) { try { this.performResize(); } catch (Exception ex) { Debug.LogError("error in performResize: " + ex); } } try { this.performLayout(); } catch (Exception ex) { Debug.LogError("error in performLayout: " + ex); } this._needsLayout = false; this.markNeedsPaint(); } public virtual bool sizedByParent { get { return false; } } public abstract void performResize(); public abstract void performLayout(); public void invokeLayoutCallback(LayoutCallback callback) where T : Constraints { this._doingThisLayoutWithCallback = true; try { callback((T) this.constraints); } finally { this._doingThisLayoutWithCallback = false; } } public virtual bool isRepaintBoundary { get { return false; } } public virtual bool alwaysNeedsCompositing { get { return false; } } public OffsetLayer _layer; public OffsetLayer layer { get { return this._layer; } } public bool _needsCompositingBitsUpdate = false; public void markNeedsCompositingBitsUpdate() { if (this._needsCompositingBitsUpdate) { return; } this._needsCompositingBitsUpdate = true; if (this.parent is RenderObject) { var parent = (RenderObject) this.parent; if (parent._needsCompositingBitsUpdate) { return; } if (!this.isRepaintBoundary && !parent.isRepaintBoundary) { parent.markNeedsCompositingBitsUpdate(); return; } } if (this.owner != null) { this.owner._nodesNeedingCompositingBitsUpdate.Add(this); } } public bool _needsCompositing; public bool needsCompositing { get { return _needsCompositing; } } public void _updateCompositingBits() { if (!this._needsCompositingBitsUpdate) { return; } bool oldNeedsCompositing = this._needsCompositing; this._needsCompositing = false; this.visitChildren(child => { child._updateCompositingBits(); if (child.needsCompositing) { this._needsCompositing = true; } }); if (this.isRepaintBoundary || this.alwaysNeedsCompositing) { this._needsCompositing = true; } if (oldNeedsCompositing != this._needsCompositing) { this.markNeedsPaint(); } this._needsCompositingBitsUpdate = false; } public bool _needsPaint = true; public void markNeedsPaint() { if (this._needsPaint) { return; } this._needsPaint = true; if (this.isRepaintBoundary) { if (this.owner != null) { this.owner._nodesNeedingPaint.Add(this); this.owner.requestVisualUpdate(); } } else if (this.parent is RenderObject) { var parent = (RenderObject) this.parent; parent.markNeedsPaint(); } else { if (this.owner != null) { this.owner.requestVisualUpdate(); } } } public void _skippedPaintingOnLayer() { var ancestor = this.parent; while (ancestor is RenderObject) { var node = (RenderObject) ancestor; if (node.isRepaintBoundary) { if (node._layer == null) { break; } if (node._layer.attached) { break; } node._needsPaint = true; } ancestor = ancestor.parent; } } public void scheduleInitialPaint(ContainerLayer rootLayer) { this._layer = (OffsetLayer) rootLayer; this.owner._nodesNeedingPaint.Add(this); } public void replaceRootLayer(OffsetLayer rootLayer) { this._layer.detach(); this._layer = rootLayer; this.markNeedsPaint(); } public void _paintWithContext(PaintingContext context, Offset offset) { if (this._needsLayout) { return; } this._needsPaint = false; try { this.paint(context, offset); } catch (Exception ex) { Debug.LogError("error in paint: " + ex); } } public abstract Rect paintBounds { get; } public virtual void paint(PaintingContext context, Offset offset) { } public virtual void applyPaintTransform(RenderObject child, ref Matrix4x4 transform) { } public Matrix4x4 getTransformTo(RenderObject ancestor) { if (ancestor == null) { var rootNode = this.owner.rootNode; if (rootNode is RenderObject) { ancestor = (RenderObject) rootNode; } } var renderers = new List(); for (RenderObject renderer = this; renderer != ancestor; renderer = (RenderObject) renderer.parent) { renderers.Add(renderer); } var transform = Matrix4x4.identity; for (int index = renderers.Count - 1; index > 0; index -= 1) { renderers[index].applyPaintTransform(renderers[index - 1], ref transform); } return transform; } public virtual void handleEvent(PointerEvent evt, HitTestEntry entry) { } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties.add(new DiagnosticsProperty( "creator", this.debugCreator, defaultValue: Diagnostics.kNullDefaultValue, level: DiagnosticLevel.debug)); properties.add(new DiagnosticsProperty("parentData", this.parentData, tooltip: this._debugCanParentUseSize ? "can use size" : null, missingIfNull: true)); properties.add(new DiagnosticsProperty("constraints", this.constraints, missingIfNull: true)); properties.add(new DiagnosticsProperty("layer", this._layer, defaultValue: Diagnostics.kNullDefaultValue)); } } }