using System; using System.Collections.Generic; using System.Linq; using System.Text; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using Unity.UIWidgets.ui; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; using Rect = Unity.UIWidgets.ui.Rect; namespace Unity.UIWidgets.rendering { class _DebugSize : Size { internal _DebugSize(Size source, RenderBox _owner, bool _canBeUsedByParent) : base(source.width, source.height) { this._owner = _owner; this._canBeUsedByParent = _canBeUsedByParent; } internal readonly RenderBox _owner; internal readonly bool _canBeUsedByParent; } public class BoxConstraints : Constraints, IEquatable { public BoxConstraints( float minWidth = 0.0f, float maxWidth = float.PositiveInfinity, float minHeight = 0.0f, float maxHeight = float.PositiveInfinity) { this.minWidth = minWidth; this.maxWidth = maxWidth; this.minHeight = minHeight; this.maxHeight = maxHeight; } public readonly float minWidth; public readonly float maxWidth; public readonly float minHeight; public readonly float maxHeight; public static BoxConstraints tight(Size size) { return new BoxConstraints( size.width, size.width, size.height, size.height ); } public static BoxConstraints tightFor( float? width = null, float? height = null ) { return new BoxConstraints( width ?? 0.0f, width ?? float.PositiveInfinity, height ?? 0.0f, height ?? float.PositiveInfinity ); } public static BoxConstraints tightForFinite( float width = float.PositiveInfinity, float height = float.PositiveInfinity ) { return new BoxConstraints( !float.IsPositiveInfinity(width) ? width : 0.0f, !float.IsPositiveInfinity(width) ? width : float.PositiveInfinity, !float.IsPositiveInfinity(height) ? height : 0.0f, !float.IsPositiveInfinity(height) ? height : float.PositiveInfinity ); } public static BoxConstraints loose(Size size) { return new BoxConstraints( minWidth: 0, maxWidth: size.width, minHeight: 0, maxHeight: size.height ); } public static BoxConstraints expand( float? width = null, float? height = null ) { return new BoxConstraints( width ?? float.PositiveInfinity, width ?? float.PositiveInfinity, height ?? float.PositiveInfinity, height ?? float.PositiveInfinity ); } public BoxConstraints copyWith( float? minWidth = null, float? maxWidth = null, float? minHeight = null, float? maxHeight = null ) { return new BoxConstraints( minWidth ?? this.minWidth, maxWidth ?? this.maxWidth, minHeight ?? this.minHeight, maxHeight ?? this.maxHeight ); } public BoxConstraints deflate(EdgeInsets edges) { D.assert(edges != null); D.assert(debugAssertIsValid()); float horizontal = edges.horizontal; float vertical = edges.vertical; float deflatedMinWidth = Mathf.Max(0.0f, minWidth - horizontal); float deflatedMinHeight = Mathf.Max(0.0f, minHeight - vertical); return new BoxConstraints( minWidth: deflatedMinWidth, maxWidth: Mathf.Max(deflatedMinWidth, maxWidth - horizontal), minHeight: deflatedMinHeight, maxHeight: Mathf.Max(deflatedMinHeight, maxHeight - vertical) ); } public BoxConstraints loosen() { D.assert(debugAssertIsValid()); return new BoxConstraints( minWidth: 0.0f, maxWidth: maxWidth, minHeight: 0.0f, maxHeight: maxHeight ); } public BoxConstraints enforce(BoxConstraints constraints) { return new BoxConstraints( minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth), maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth), minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight), maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight) ); } public BoxConstraints tighten( float? width = null, float? height = null ) { return new BoxConstraints( minWidth: width == null ? minWidth : width.Value.clamp(minWidth, maxWidth), maxWidth: width == null ? maxWidth : width.Value.clamp(minWidth, maxWidth), minHeight: height == null ? minHeight : height.Value.clamp(minHeight, maxHeight), maxHeight: height == null ? maxHeight : height.Value.clamp(minHeight, maxHeight) ); } public BoxConstraints flipped { get { return new BoxConstraints( minWidth: minHeight, maxWidth: maxHeight, minHeight: minWidth, maxHeight: maxWidth ); } } public BoxConstraints widthConstraints() { return new BoxConstraints(minWidth: minWidth, maxWidth: maxWidth); } public BoxConstraints heightConstraints() { return new BoxConstraints(minHeight: minHeight, maxHeight: maxHeight); } public float constrainWidth(float width = float.PositiveInfinity) { D.assert(debugAssertIsValid()); return width.clamp(minWidth, maxWidth); } public float constrainHeight(float height = float.PositiveInfinity) { D.assert(debugAssertIsValid()); return height.clamp(minHeight, maxHeight); } Size _debugPropagateDebugSize(Size size, Size result) { D.assert(() => { if (size is _DebugSize) { result = new _DebugSize(result, ((_DebugSize) size)._owner, ((_DebugSize) size)._canBeUsedByParent); } return true; }); return result; } public Size constrain(Size size) { var result = new Size(constrainWidth(size.width), constrainHeight(size.height)); D.assert(() => { result = _debugPropagateDebugSize(size, result); return true; }); return result; } public Size constrainDimensions(float width, float height) { return new Size(constrainWidth(width), constrainHeight(height)); } public Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) { if (isTight) { Size result1 = smallest; D.assert(() => { result1 = _debugPropagateDebugSize(size, result1); return true; }); return result1; } float width = size.width; float height = size.height; D.assert(width > 0.0); D.assert(height > 0.0); float aspectRatio = width / height; if (width > maxWidth) { width = maxWidth; height = width / aspectRatio; } if (height > maxHeight) { height = maxHeight; width = height * aspectRatio; } if (width < minWidth) { width = minWidth; height = width / aspectRatio; } if (height < minHeight) { height = minHeight; width = height * aspectRatio; } var result = new Size(constrainWidth(width), constrainHeight(height)); D.assert(() => { result = _debugPropagateDebugSize(size, result); return true; }); return result; } public Size biggest { get { return new Size(constrainWidth(), constrainHeight()); } } public Size smallest { get { return new Size(constrainWidth(0.0f), constrainHeight(0.0f)); } } public bool hasTightWidth { get { return minWidth >= maxWidth; } } public bool hasTightHeight { get { return minHeight >= maxHeight; } } public override bool isTight { get { return hasTightWidth && hasTightHeight; } } public bool hasBoundedWidth { get { return maxWidth < float.PositiveInfinity; } } public bool hasBoundedHeight { get { return maxHeight < float.PositiveInfinity; } } public bool hasInfiniteWidth { get { return minWidth >= float.PositiveInfinity; } } public bool hasInfiniteHeight { get { return minHeight >= float.PositiveInfinity; } } public bool isSatisfiedBy(Size size) { D.assert(debugAssertIsValid()); return minWidth <= size.width && size.width <= maxWidth && minHeight <= size.height && size.height <= maxHeight; } public static BoxConstraints operator *(BoxConstraints it, float factor) { return new BoxConstraints( minWidth: it.minWidth * factor, maxWidth: it.maxWidth * factor, minHeight: it.minHeight * factor, maxHeight: it.maxHeight * factor ); } public static BoxConstraints operator /(BoxConstraints it, float factor) { return new BoxConstraints( minWidth: it.minWidth / factor, maxWidth: it.maxWidth / factor, minHeight: it.minHeight / factor, maxHeight: it.maxHeight / factor ); } public static BoxConstraints operator %(BoxConstraints it, float value) { return new BoxConstraints( minWidth: it.minWidth % value, maxWidth: it.maxWidth % value, minHeight: it.minHeight % value, maxHeight: it.maxHeight % value ); } public static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, float t) { if (a == null && b == null) { return null; } if (a == null) { return b * t; } if (b == null) { return a * (1.0f - t); } D.assert(a.debugAssertIsValid()); D.assert(b.debugAssertIsValid()); D.assert( (a.minWidth.isFinite() && b.minWidth.isFinite()) || (a.minWidth == float.PositiveInfinity && b.minWidth == float.PositiveInfinity), () => "Cannot interpolate between finite constraints and unbounded constraints."); D.assert( (a.maxWidth.isFinite() && b.maxWidth.isFinite()) || (a.maxWidth == float.PositiveInfinity && b.maxWidth == float.PositiveInfinity), () => "Cannot interpolate between finite constraints and unbounded constraints."); D.assert( (a.minHeight.isFinite() && b.minHeight.isFinite()) || (a.minHeight == float.PositiveInfinity && b.minHeight == float.PositiveInfinity), () => "Cannot interpolate between finite constraints and unbounded constraints."); D.assert( (a.maxHeight.isFinite() && b.maxHeight.isFinite()) || (a.maxHeight == float.PositiveInfinity && b.maxHeight == float.PositiveInfinity), () => "Cannot interpolate between finite constraints and unbounded constraints."); return new BoxConstraints( minWidth: a.minWidth.isFinite() ? MathUtils.lerpFloat(a.minWidth, b.minWidth, t) : float.PositiveInfinity, maxWidth: a.maxWidth.isFinite() ? MathUtils.lerpFloat(a.maxWidth, b.maxWidth, t) : float.PositiveInfinity, minHeight: a.minHeight.isFinite() ? MathUtils.lerpFloat(a.minHeight, b.minHeight, t) : float.PositiveInfinity, maxHeight: a.maxHeight.isFinite() ? MathUtils.lerpFloat(a.maxHeight, b.maxHeight, t) : float.PositiveInfinity ); } public override bool isNormalized { get { return minWidth >= 0.0 && minWidth <= maxWidth && minHeight >= 0.0 && minHeight <= maxHeight; } } public override bool debugAssertIsValid( bool isAppliedConstraint = false, InformationCollector informationCollector = null ) { D.assert(() => { var throwError = new Action(message => { var information = new StringBuilder(); if (informationCollector != null) { informationCollector(information); } throw new UIWidgetsError($"{message}\n{information}The offending constraints were:\n {this}"); }); if (minWidth.isNaN() || maxWidth.isNaN() || minHeight.isNaN() || maxHeight.isNaN()) { var affectedFieldsList = new List(); if (minWidth.isNaN()) { affectedFieldsList.Add("minWidth"); } if (maxWidth.isNaN()) { affectedFieldsList.Add("maxWidth"); } if (minHeight.isNaN()) { affectedFieldsList.Add("minHeight"); } if (maxHeight.isNaN()) { affectedFieldsList.Add("maxHeight"); } D.assert(affectedFieldsList.isNotEmpty()); if (affectedFieldsList.Count > 1) { var last = affectedFieldsList.Last(); affectedFieldsList.RemoveAt(affectedFieldsList.Count - 1); affectedFieldsList.Add("and " + last); } string whichFields; if (affectedFieldsList.Count > 2) { whichFields = string.Join(", ", affectedFieldsList.ToArray()); } else if (affectedFieldsList.Count == 2) { whichFields = string.Join(" ", affectedFieldsList.ToArray()); } else { whichFields = affectedFieldsList.Single(); } throwError("BoxConstraints has NaN values in " + whichFields + "."); } if (minWidth < 0.0 && minHeight < 0.0) { throwError("BoxConstraints has both a negative minimum width and a negative minimum height."); } if (minWidth < 0.0) { throwError("BoxConstraints has a negative minimum width."); } if (minHeight < 0.0) { throwError("BoxConstraints has a negative minimum height."); } if (maxWidth < minWidth && maxHeight < minHeight) { throwError("BoxConstraints has both width and height constraints non-normalized."); } if (maxWidth < minWidth) { throwError("BoxConstraints has non-normalized width constraints."); } if (maxHeight < minHeight) { throwError("BoxConstraints has non-normalized height constraints."); } if (isAppliedConstraint) { if (minWidth.isInfinite() && minHeight.isInfinite()) { throwError("BoxConstraints forces an infinite width and infinite height."); } if (minWidth.isInfinite()) { throwError("BoxConstraints forces an infinite width."); } if (minHeight.isInfinite()) { throwError("BoxConstraints forces an infinite height."); } } D.assert(isNormalized); return true; }); return isNormalized; } public BoxConstraints normalize() { if (isNormalized) { return this; } var minWidth = this.minWidth >= 0.0 ? this.minWidth : 0.0f; var minHeight = this.minHeight >= 0.0 ? this.minHeight : 0.0f; return new BoxConstraints( minWidth, minWidth > maxWidth ? minWidth : maxWidth, minHeight, minHeight > maxHeight ? minHeight : maxHeight ); } public bool Equals(BoxConstraints other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return minWidth.Equals(other.minWidth) && maxWidth.Equals(other.maxWidth) && minHeight.Equals(other.minHeight) && maxHeight.Equals(other.maxHeight); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((BoxConstraints) obj); } public override int GetHashCode() { unchecked { var hashCode = minWidth.GetHashCode(); hashCode = (hashCode * 397) ^ maxWidth.GetHashCode(); hashCode = (hashCode * 397) ^ minHeight.GetHashCode(); hashCode = (hashCode * 397) ^ maxHeight.GetHashCode(); return hashCode; } } public static bool operator ==(BoxConstraints left, BoxConstraints right) { return Equals(left, right); } public static bool operator !=(BoxConstraints left, BoxConstraints right) { return !Equals(left, right); } public override string ToString() { string annotation = isNormalized ? "" : "; NOT NORMALIZED"; if (minWidth == float.PositiveInfinity && minHeight == float.PositiveInfinity) { return "BoxConstraints(biggest" + annotation + ")"; } if (minWidth == 0 && maxWidth == float.PositiveInfinity && minHeight == 0 && maxHeight == float.PositiveInfinity) { return "BoxConstraints(unconstrained" + annotation + ")"; } var describe = new Func((min, max, dim) => { if (min == max) { return dim + "=" + min.ToString("F1"); } return min.ToString("F1") + "<=" + dim + "<=" + max.ToString("F1"); }); string width = describe(minWidth, maxWidth, "w"); string height = describe(minHeight, maxHeight, "h"); return "BoxConstraints(" + width + ", " + height + annotation + ")"; } } public class BoxHitTestEntry : HitTestEntry { public BoxHitTestEntry(RenderBox target, Offset localPosition) : base(target) { D.assert(localPosition != null); this.localPosition = localPosition; } public new RenderBox target { get { return (RenderBox) base.target; } } public readonly Offset localPosition; public override string ToString() { return $"{foundation_.describeIdentity(target)}@{localPosition}"; } } public delegate bool BoxHitTest(BoxHitTestResult result, Offset position); public class BoxHitTestResult : HitTestResult { public BoxHitTestResult() : base() { } public BoxHitTestResult(HitTestResult result) : base(result) { } public bool addWithPaintTransform( Matrix4 transform, Offset position, BoxHitTest hitTest ) { D.assert(hitTest != null); if (transform != null) { transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform)); if (transform == null) { // Objects are not visible on screen and cannot be hit-tested. return false; } } return addWithRawTransform( transform: transform, position: position, hitTest: hitTest ); } public bool addWithPaintOffset( Offset offset, Offset position, BoxHitTest hitTest ) { D.assert(hitTest != null); return addWithRawTransform( transform: offset != null ? new Matrix4().translationValues(-offset.dx, -offset.dy, 0) : null, position: position, hitTest: hitTest ); } public bool addWithRawTransform( Matrix4 transform, Offset position, BoxHitTest hitTest ) { D.assert(hitTest != null); Offset transformedPosition = position == null || transform == null ? position : MatrixUtils.transformPoint(transform, position); if (transform != null) { pushTransform(transform); } bool isHit = hitTest(this, transformedPosition); if (transform != null) { popTransform(); } return isHit; } } public class BoxParentData : ParentData { public Offset offset { get { return _offset; } set { _offset = value; } } Offset _offset = Offset.zero; public override string ToString() { return "offset=" + offset; } } enum _IntrinsicDimension { minWidth, maxWidth, minHeight, maxHeight } class _IntrinsicDimensionsCacheEntry : IEquatable<_IntrinsicDimensionsCacheEntry> { internal _IntrinsicDimensionsCacheEntry(_IntrinsicDimension dimension, float argument) { this.dimension = dimension; this.argument = argument; } public readonly _IntrinsicDimension dimension; public readonly float argument; public bool Equals(_IntrinsicDimensionsCacheEntry other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return dimension == other.dimension && argument.Equals(other.argument); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((_IntrinsicDimensionsCacheEntry) obj); } public override int GetHashCode() { unchecked { return ((int) dimension * 397) ^ argument.GetHashCode(); } } public static bool operator ==(_IntrinsicDimensionsCacheEntry a, _IntrinsicDimensionsCacheEntry b) { return Equals(a, b); } public static bool operator !=(_IntrinsicDimensionsCacheEntry a, _IntrinsicDimensionsCacheEntry b) { return !Equals(a, b); } } public abstract class RenderBox : RenderObject { public override void setupParentData(RenderObject child) { if (!(child.parentData is BoxParentData)) { child.parentData = new BoxParentData(); } } Dictionary<_IntrinsicDimensionsCacheEntry, float> _cachedIntrinsicDimensions; float _computeIntrinsicDimension(_IntrinsicDimension dimension, float argument, Func computer) { D.assert(debugCheckingIntrinsics || !debugDoingThisResize); bool shouldCache = true; D.assert(() => { if (debugCheckingIntrinsics) { shouldCache = false; } return true; }); if (shouldCache) { _cachedIntrinsicDimensions = _cachedIntrinsicDimensions ?? new Dictionary<_IntrinsicDimensionsCacheEntry, float>(); return _cachedIntrinsicDimensions.putIfAbsent( new _IntrinsicDimensionsCacheEntry(dimension, argument), () => computer(argument)); } return computer(argument); } public float getMinIntrinsicWidth(float height) { D.assert(() => { if (height < 0.0) { throw new UIWidgetsError( "The height argument to getMinIntrinsicWidth was negative.\n" + "The argument to getMinIntrinsicWidth must not be negative. " + "If you perform computations on another height before passing it to " + "getMinIntrinsicWidth, consider using Mathf.Max() or float.clamp() " + "to force the value into the valid range." ); } return true; }); return _computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, computeMinIntrinsicWidth); } protected virtual float computeMinIntrinsicWidth(float height) { return 0.0f; } public float getMaxIntrinsicWidth(float height) { D.assert(() => { if (height < 0.0) { throw new UIWidgetsError( "The height argument to getMaxIntrinsicWidth was negative.\n" + "The argument to getMaxIntrinsicWidth must not be negative. " + "If you perform computations on another height before passing it to " + "getMaxIntrinsicWidth, consider using Mathf.Max() or float.clamp() " + "to force the value into the valid range." ); } return true; }); return _computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, computeMaxIntrinsicWidth); } protected virtual float computeMaxIntrinsicWidth(float height) { return 0.0f; } public float getMinIntrinsicHeight(float width) { D.assert(() => { if (width < 0.0) { throw new UIWidgetsError( "The width argument to getMinIntrinsicHeight was negative.\n" + "The argument to getMinIntrinsicHeight must not be negative. " + "If you perform computations on another width before passing it to " + "getMinIntrinsicHeight, consider using Mathf.Max() or float.clamp() " + "to force the value into the valid range." ); } return true; }); return _computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, computeMinIntrinsicHeight); } protected virtual float computeMinIntrinsicHeight(float width) { return 0.0f; } public float getMaxIntrinsicHeight(float width) { D.assert(() => { if (width < 0.0) { throw new UIWidgetsError( "The width argument to getMaxIntrinsicHeight was negative.\n" + "The argument to getMaxIntrinsicHeight must not be negative. " + "If you perform computations on another width before passing it to " + "getMaxIntrinsicHeight, consider using Mathf.Max() or float.clamp() " + "to force the value into the valid range." ); } return true; }); return _computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, computeMaxIntrinsicHeight); } protected internal virtual float computeMaxIntrinsicHeight(float width) { return 0.0f; } public bool hasSize { get { return _size != null; } } public Size size { get { D.assert(hasSize, () => "RenderBox was not laid out: " + this); D.assert(() => { if (this._size is _DebugSize) { _DebugSize _size = (_DebugSize) this._size; D.assert(_size._owner == this); if (debugActiveLayout != null) { D.assert(debugDoingThisResize || debugDoingThisLayout || (debugActiveLayout == parent && _size._canBeUsedByParent)); } D.assert(_size == this._size); } return true; }); return this._size; } set { D.assert(!(debugDoingThisResize && debugDoingThisLayout)); D.assert(sizedByParent || !debugDoingThisResize); D.assert(() => { if ((sizedByParent && debugDoingThisResize) || (!sizedByParent && debugDoingThisLayout)) { return true; } D.assert(!debugDoingThisResize); string contract = "", violation = "", hint = ""; if (debugDoingThisLayout) { D.assert(sizedByParent); violation = "It appears that the size setter was called from performLayout()."; hint = ""; } else { violation = "The size setter was called from outside layout (neither performResize() nor performLayout() were being run for this object)."; if (owner != null && owner.debugDoingLayout) { hint = "Only the object itself can set its size. It is a contract violation for other objects to set it."; } } if (sizedByParent) { contract = "Because this RenderBox has sizedByParent set to true, it must set its size in performResize()."; } else { contract = "Because this RenderBox has sizedByParent set to false, it must set its size in performLayout()."; } throw new UIWidgetsError( "RenderBox size setter called incorrectly.\n" + violation + "\n" + hint + "\n" + contract + "\n" + "The RenderBox in question is:\n" + " " + this ); }); D.assert(() => { value = debugAdoptSize(value); return true; }); _size = value; D.assert(() => { debugAssertDoesMeetConstraints(); return true; }); } } Size _size; public Size debugAdoptSize(Size valueRaw) { Size result = valueRaw; D.assert(() => { if (valueRaw is _DebugSize) { var value = (_DebugSize) valueRaw; if (value._owner != this) { if (value._owner.parent != this) { throw new UIWidgetsError( "The size property was assigned a size inappropriately.\n" + "The following render object:\n" + " " + this + "\n" + "...was assigned a size obtained from:\n" + " " + value._owner + "\n" + "However, this second render object is not, or is no longer, a " + "child of the first, and it is therefore a violation of the " + "RenderBox layout protocol to use that size in the layout of the " + "first render object.\n" + "If the size was obtained at a time where it was valid to read " + "the size (because the second render object above was a child " + "of the first at the time), then it should be adopted using " + "debugAdoptSize at that time.\n" + "If the size comes from a grandchild or a render object from an " + "entirely different part of the render tree, then there is no " + "way to be notified when the size changes and therefore attempts " + "to read that size are almost certainly a source of bugs. A different " + "approach should be used." ); } if (!value._canBeUsedByParent) { throw new UIWidgetsError( "A child\"s size was used without setting parentUsesSize.\n" + "The following render object:\n" + " " + this + "\n" + "...was assigned a size obtained from its child:\n" + " " + value._owner + "\n" + "However, when the child was laid out, the parentUsesSize argument " + "was not set or set to false. Subsequently this transpired to be " + "inaccurate: the size was nonetheless used by the parent.\n" + "It is important to tell the framework if the size will be used or not " + "as several important performance optimizations can be made if the " + "size will not be used by the parent." ); } } } result = new _DebugSize(valueRaw, this, debugCanParentUseSize); return true; }); return result; } public override Rect semanticBounds { get { return Offset.zero & size; } } protected override void debugResetSize() { size = size; } Dictionary _cachedBaselines; static bool _debugDoingBaseline = false; static bool _debugSetDoingBaseline(bool value) { _debugDoingBaseline = value; return true; } public float? getDistanceToBaseline(TextBaseline baseline, bool onlyReal = false) { D.assert(!_debugDoingBaseline, () => "Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method."); D.assert(!debugNeedsLayout); D.assert(() => { RenderObject parent = (RenderObject) this.parent; if (owner.debugDoingLayout) { return (debugActiveLayout == parent) && parent.debugDoingThisLayout; } if (owner.debugDoingPaint) { return ((debugActivePaint == parent) && parent.debugDoingThisPaint) || ((debugActivePaint == this) && debugDoingThisPaint); } D.assert(parent == this.parent); return false; }); D.assert(_debugSetDoingBaseline(true)); float? result = getDistanceToActualBaseline(baseline); D.assert(_debugSetDoingBaseline(false)); if (result == null && !onlyReal) { return size.height; } return result; } public virtual float? getDistanceToActualBaseline(TextBaseline baseline) { D.assert(_debugDoingBaseline, () => "Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method."); _cachedBaselines = _cachedBaselines ?? new Dictionary(); return _cachedBaselines.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline)); } protected virtual float? computeDistanceToActualBaseline(TextBaseline baseline) { D.assert(_debugDoingBaseline, () => "Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method."); return null; } public new BoxConstraints constraints { get { return (BoxConstraints) base.constraints; } } protected override void debugAssertDoesMeetConstraints() { D.assert(constraints != null); D.assert(() => { if (!hasSize) { D.assert(!debugNeedsLayout); string contract = ""; if (sizedByParent) { contract = "Because this RenderBox has sizedByParent set to true, it must set its size in performResize().\n"; } else { contract = "Because this RenderBox has sizedByParent set to false, it must set its size in performLayout().\n"; } throw new UIWidgetsError( "RenderBox did not set its size during layout.\n" + contract + "It appears that this did not happen; layout completed, but the size property is still null.\n" + "The RenderBox in question is:\n" + " " + this); } if (!_size.isFinite) { var information = new StringBuilder(); if (!constraints.hasBoundedWidth) { RenderBox node = this; while (!node.constraints.hasBoundedWidth && node.parent is RenderBox) { node = (RenderBox) node.parent; } information.AppendLine("The nearest ancestor providing an unbounded width constraint is:"); information.Append(" "); information.AppendLine(node.toStringShallow(joiner: "\n ")); } if (!constraints.hasBoundedHeight) { RenderBox node = this; while (!node.constraints.hasBoundedHeight && node.parent is RenderBox) { node = (RenderBox) node.parent; } information.AppendLine("The nearest ancestor providing an unbounded height constraint is:"); information.Append(" "); information.AppendLine(node.toStringShallow(joiner: "\n ")); } throw new UIWidgetsError( GetType() + " object was given an infinite size during layout.\n" + "This probably means that it is a render object that tries to be " + "as big as possible, but it was put inside another render object " + "that allows its children to pick their own size.\n" + information + "The constraints that applied to the " + GetType() + " were:\n" + " " + constraints + "\n" + "The exact size it was given was:\n" + " " + _size ); } if (!constraints.isSatisfiedBy(_size)) { throw new UIWidgetsError( GetType() + " does not meet its constraints.\n" + "Constraints: " + constraints + "\n" + "Size: " + _size + "\n" + "If you are not writing your own RenderBox subclass, then this is not " + "your fault." ); } if (D.debugCheckIntrinsicSizes) { D.assert(!debugCheckingIntrinsics); debugCheckingIntrinsics = true; var failures = new StringBuilder(); int failureCount = 0; var testIntrinsic = new Func, string, float, float>( (function, name, constraint) => { float result = function(constraint); if (result < 0) { failures.AppendLine(" * " + name + "(" + constraint + ") returned a negative value: " + result); failureCount += 1; } if (result.isInfinite()) { failures.AppendLine(" * " + name + "(" + constraint + ") returned a non-finite value: " + result); failureCount += 1; } return result; }); var testIntrinsicsForValues = new Action, Func, string, float>( (getMin, getMax, name, constraint) => { float min = testIntrinsic(getMin, "getMinIntrinsic" + name, constraint); float max = testIntrinsic(getMax, "getMaxIntrinsic" + name, constraint); if (min > max) { failures.AppendLine( " * getMinIntrinsic" + name + "(" + constraint + ") returned a larger value (" + min + ") than getMaxIntrinsic" + name + "(" + constraint + ") (" + max + ")"); failureCount += 1; } }); testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, "Width", float.PositiveInfinity); testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, "Height", float.PositiveInfinity); if (constraints.hasBoundedWidth) { testIntrinsicsForValues(getMinIntrinsicWidth, getMaxIntrinsicWidth, "Width", constraints.maxHeight); } if (constraints.hasBoundedHeight) { testIntrinsicsForValues(getMinIntrinsicHeight, getMaxIntrinsicHeight, "Height", constraints.maxWidth); } debugCheckingIntrinsics = false; if (failures.Length > 0) { D.assert(failureCount > 0); throw new UIWidgetsError( "The intrinsic dimension methods of the " + GetType() + " class returned values that violate the intrinsic protocol contract.\n" + "The following failures was detected:\n" + failures + "If you are not writing your own RenderBox subclass, then this is not\n" + "your fault." ); } } return true; }); } public override void markNeedsLayout() { if (_cachedBaselines != null && _cachedBaselines.isNotEmpty() || _cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions.isNotEmpty()) { if (_cachedBaselines != null) { _cachedBaselines.Clear(); } if (_cachedIntrinsicDimensions != null) { _cachedIntrinsicDimensions.Clear(); } if (parent is RenderObject) { markParentNeedsLayout(); return; } } base.markNeedsLayout(); } protected override void performResize() { size = constraints.smallest; D.assert(size.isFinite); } protected override void performLayout() { D.assert(() => { if (!sizedByParent) { throw new UIWidgetsError( GetType() + " did not implement performLayout().\n" + "RenderBox subclasses need to either override performLayout() to " + "set a size and lay out any children, or, set sizedByParent to true " + "so that performResize() sizes the render object." ); } return true; }); } public virtual bool hitTest(BoxHitTestResult result, Offset position = null) { D.assert(position != null); D.assert(() => { if (!hasSize) { if (debugNeedsLayout) { throw new UIWidgetsError( "Cannot hit test a render box that has never been laid out.\n" + "The hitTest() method was called on this RenderBox:\n" + " " + this + "\n" + "Unfortunately, this object\"s geometry is not known at this time, " + "probably because it has never been laid out. " + "This means it cannot be accurately hit-tested. If you are trying " + "to perform a hit test during the layout phase itself, make sure " + "you only hit test nodes that have completed layout (e.g. the node\"s " + "children, after their layout() method has been called)." ); } throw new UIWidgetsError( "Cannot hit test a render box with no size.\n" + "The hitTest() method was called on this RenderBox:\n" + " " + this + "\n" + "Although this node is not marked as needing layout, " + "its size is not set. A RenderBox object must have an " + "explicit size before it can be hit-tested. Make sure " + "that the RenderBox in question sets its size during layout." ); } return true; }); if (_size.contains(position)) { if (hitTestChildren(result, position: position) || hitTestSelf(position)) { result.add(new BoxHitTestEntry(this, position)); return true; } } return false; } protected virtual bool hitTestSelf(Offset position) { return false; } protected virtual bool hitTestChildren(BoxHitTestResult result, Offset position = null) { return false; } public override void applyPaintTransform(RenderObject child, Matrix4 transform) { D.assert(child != null); D.assert(child.parent == this); D.assert(() => { if (!(child.parentData is BoxParentData)) { throw new UIWidgetsError( GetType() + " does not implement applyPaintTransform.\n" + "The following " + GetType() + " object:\n" + " " + toStringShallow() + "\n" + "...did not use a BoxParentData class for the parentData field of the following child:\n" + " " + child.toStringShallow() + "\n" + "The " + GetType() + " class inherits from RenderBox. " + "The default applyPaintTransform implementation provided by RenderBox assumes that the " + "children all use BoxParentData objects for their parentData field. " + "Since " + GetType() + " does not in fact use that ParentData class for its children, it must " + "provide an implementation of applyPaintTransform that supports the specific ParentData " + "subclass used by its children (which apparently is " + child.parentData.GetType() + ")." ); } return true; }); var childParentData = (BoxParentData) child.parentData; var offset = childParentData.offset; transform.translate(offset.dx, offset.dy); } public Offset globalToLocal(Offset point, RenderObject ancestor = null) { var transform = getTransformTo(ancestor); float det = transform.invert(); if (det == 0) { return Offset.zero; } Vector3 n = new Vector3(0, 0, 1); Vector3 i = transform.perspectiveTransform(new Vector3(0, 0, 0)); Vector3 d = transform.perspectiveTransform(new Vector3(0, 0, 1)) - i; Vector3 s = transform.perspectiveTransform(new Vector3(point.dx, point.dy, 0)); Vector3 p = s - d * (n.dot(s) / n.dot(d)); return new Offset(p.x, p.y); } public Offset localToGlobal(Offset point, RenderObject ancestor = null) { return MatrixUtils.transformPoint(getTransformTo(ancestor), point); } public override Rect paintBounds { get { return Offset.zero & size; } } int _debugActivePointers = 0; protected bool debugHandleEvent(PointerEvent evt, HitTestEntry entry) { D.assert(() => { if (D.debugPaintPointersEnabled) { if (evt is PointerDownEvent) { _debugActivePointers += 1; } else if (evt is PointerUpEvent || evt is PointerCancelEvent) { _debugActivePointers -= 1; } markNeedsPaint(); } return true; }); return true; } public override void debugPaint(PaintingContext context, Offset offset) { D.assert(() => { if (D.debugPaintSizeEnabled) { debugPaintSize(context, offset); } if (D.debugPaintBaselinesEnabled) { debugPaintBaselines(context, offset); } if (D.debugPaintPointersEnabled) { debugPaintPointers(context, offset); } return true; }); } protected virtual void debugPaintSize(PaintingContext context, Offset offset) { D.assert(() => { var paint = new Paint { color = new Color(0xFF00FFFF), strokeWidth = 1, style = PaintingStyle.stroke, }; context.canvas.drawRect((offset & size).deflate(0.5f), paint); return true; }); } protected virtual void debugPaintBaselines(PaintingContext context, Offset offset) { D.assert(() => { Paint paint = new Paint { style = PaintingStyle.stroke, strokeWidth = 0.25f }; Path path; // ideographic baseline float? baselineI = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true); if (baselineI != null) { paint.color = new Color(0xFFFFD000); path = new Path(); path.moveTo(offset.dx, offset.dy + baselineI.Value); path.lineTo(offset.dx + size.width, offset.dy + baselineI.Value); context.canvas.drawPath(path, paint); } // alphabetic baseline float? baselineA = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true); if (baselineA != null) { paint.color = new Color(0xFF00FF00); path = new Path(); path.moveTo(offset.dx, offset.dy + baselineA.Value); path.lineTo(offset.dx + size.width, offset.dy + baselineA.Value); context.canvas.drawPath(path, paint); } return true; }); } protected virtual void debugPaintPointers(PaintingContext context, Offset offset) { D.assert(() => { if (_debugActivePointers > 0) { var paint = new Paint { color = new Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000)), }; context.canvas.drawRect(offset & size, paint); } return true; }); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("size", _size, missingIfNull: true)); } } public abstract class ContainerBoxParentData : ContainerParentDataMixinBoxParentData where ChildType : RenderBox { } public abstract class RenderBoxContainerDefaultsMixin : ContainerRenderObjectMixinRenderBox where ChildType : RenderBox where ParentDataType : ContainerParentDataMixinBoxParentData { public float? defaultComputeDistanceToFirstActualBaseline(TextBaseline baseline) { D.assert(!debugNeedsLayout); var child = firstChild; while (child != null) { var childParentData = (ParentDataType) child.parentData; float? result = child.getDistanceToActualBaseline(baseline); if (result != null) { return result.Value + childParentData.offset.dy; } child = childParentData.nextSibling; } return null; } public float? defaultComputeDistanceToHighestActualBaseline(TextBaseline baseline) { D.assert(!debugNeedsLayout); float? result = null; var child = firstChild; while (child != null) { var childParentData = (ParentDataType) child.parentData; float? candidate = child.getDistanceToActualBaseline(baseline); if (candidate != null) { candidate += childParentData.offset.dy; if (result != null) { result = Mathf.Min(result.Value, candidate.Value); } else { result = candidate; } } child = childParentData.nextSibling; } return result; } public bool defaultHitTestChildren(BoxHitTestResult result, Offset position = null) { ChildType child = lastChild; while (child != null) { ParentDataType childParentData = child.parentData as ParentDataType; bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult resultIn, Offset transformed) => { D.assert(transformed == position - childParentData.offset); return child.hitTest(resultIn, position: transformed); } ); if (isHit) { return true; } child = childParentData.previousSibling; } return false; } public void defaultPaint(PaintingContext context, Offset offset) { var child = firstChild; while (child != null) { var childParentData = (ParentDataType) child.parentData; context.paintChild(child, childParentData.offset + offset); child = childParentData.nextSibling; } } public List getChildrenAsList() { var result = new List(); var child = firstChild; while (child != null) { var childParentData = (ParentDataType) child.parentData; result.Add(child); child = childParentData.nextSibling; } return result; } } }