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(this.debugAssertIsValid()); float horizontal = edges.horizontal; float vertical = edges.vertical; float deflatedMinWidth = Mathf.Max(0.0f, this.minWidth - horizontal); float deflatedMinHeight = Mathf.Max(0.0f, this.minHeight - vertical); return new BoxConstraints( minWidth: deflatedMinWidth, maxWidth: Mathf.Max(deflatedMinWidth, this.maxWidth - horizontal), minHeight: deflatedMinHeight, maxHeight: Mathf.Max(deflatedMinHeight, this.maxHeight - vertical) ); } public BoxConstraints loosen() { D.assert(this.debugAssertIsValid()); return new BoxConstraints( minWidth: 0.0f, maxWidth: this.maxWidth, minHeight: 0.0f, maxHeight: this.maxHeight ); } public BoxConstraints enforce(BoxConstraints constraints) { return new BoxConstraints( minWidth: this.minWidth.clamp(constraints.minWidth, constraints.maxWidth), maxWidth: this.maxWidth.clamp(constraints.minWidth, constraints.maxWidth), minHeight: this.minHeight.clamp(constraints.minHeight, constraints.maxHeight), maxHeight: this.maxHeight.clamp(constraints.minHeight, constraints.maxHeight) ); } public BoxConstraints tighten( float? width = null, float? height = null ) { return new BoxConstraints( minWidth: width == null ? this.minWidth : width.Value.clamp(this.minWidth, this.maxWidth), maxWidth: width == null ? this.maxWidth : width.Value.clamp(this.minWidth, this.maxWidth), minHeight: height == null ? this.minHeight : height.Value.clamp(this.minHeight, this.maxHeight), maxHeight: height == null ? this.maxHeight : height.Value.clamp(this.minHeight, this.maxHeight) ); } public BoxConstraints flipped { get { return new BoxConstraints( minWidth: this.minHeight, maxWidth: this.maxHeight, minHeight: this.minWidth, maxHeight: this.maxWidth ); } } public BoxConstraints widthConstraints() { return new BoxConstraints(minWidth: this.minWidth, maxWidth: this.maxWidth); } public BoxConstraints heightConstraints() { return new BoxConstraints(minHeight: this.minHeight, maxHeight: this.maxHeight); } public float constrainWidth(float width = float.PositiveInfinity) { D.assert(this.debugAssertIsValid()); return width.clamp(this.minWidth, this.maxWidth); } public float constrainHeight(float height = float.PositiveInfinity) { D.assert(this.debugAssertIsValid()); return height.clamp(this.minHeight, this.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(this.constrainWidth(size.width), this.constrainHeight(size.height)); D.assert(() => { result = this._debugPropagateDebugSize(size, result); return true; }); return result; } public Size constrainDimensions(float width, float height) { return new Size(this.constrainWidth(width), this.constrainHeight(height)); } public Size constrainSizeAndAttemptToPreserveAspectRatio(Size size) { if (this.isTight) { Size result1 = this.smallest; D.assert(() => { result1 = this._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 > this.maxWidth) { width = this.maxWidth; height = width / aspectRatio; } if (height > this.maxHeight) { height = this.maxHeight; width = height * aspectRatio; } if (width < this.minWidth) { width = this.minWidth; height = width / aspectRatio; } if (height < this.minHeight) { height = this.minHeight; width = height * aspectRatio; } var result = new Size(this.constrainWidth(width), this.constrainHeight(height)); D.assert(() => { result = this._debugPropagateDebugSize(size, result); return true; }); return result; } public Size biggest { get { return new Size(this.constrainWidth(), this.constrainHeight()); } } public Size smallest { get { return new Size(this.constrainWidth(0.0f), this.constrainHeight(0.0f)); } } public bool hasTightWidth { get { return this.minWidth >= this.maxWidth; } } public bool hasTightHeight { get { return this.minHeight >= this.maxHeight; } } public override bool isTight { get { return this.hasTightWidth && this.hasTightHeight; } } public bool hasBoundedWidth { get { return this.maxWidth < float.PositiveInfinity; } } public bool hasBoundedHeight { get { return this.maxHeight < float.PositiveInfinity; } } public bool hasInfiniteWidth { get { return this.minWidth >= float.PositiveInfinity; } } public bool hasInfiniteHeight { get { return this.minHeight >= float.PositiveInfinity; } } public bool isSatisfiedBy(Size size) { D.assert(this.debugAssertIsValid()); return this.minWidth <= size.width && size.width <= this.maxWidth && this.minHeight <= size.height && size.height <= this.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 this.minWidth >= 0.0 && this.minWidth <= this.maxWidth && this.minHeight >= 0.0 && this.minHeight <= this.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 (this.minWidth.isNaN() || this.maxWidth.isNaN() || this.minHeight.isNaN() || this.maxHeight.isNaN()) { var affectedFieldsList = new List(); if (this.minWidth.isNaN()) { affectedFieldsList.Add("minWidth"); } if (this.maxWidth.isNaN()) { affectedFieldsList.Add("maxWidth"); } if (this.minHeight.isNaN()) { affectedFieldsList.Add("minHeight"); } if (this.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 (this.minWidth < 0.0 && this.minHeight < 0.0) { throwError("BoxConstraints has both a negative minimum width and a negative minimum height."); } if (this.minWidth < 0.0) { throwError("BoxConstraints has a negative minimum width."); } if (this.minHeight < 0.0) { throwError("BoxConstraints has a negative minimum height."); } if (this.maxWidth < this.minWidth && this.maxHeight < this.minHeight) { throwError("BoxConstraints has both width and height constraints non-normalized."); } if (this.maxWidth < this.minWidth) { throwError("BoxConstraints has non-normalized width constraints."); } if (this.maxHeight < this.minHeight) { throwError("BoxConstraints has non-normalized height constraints."); } if (isAppliedConstraint) { if (this.minWidth.isInfinite() && this.minHeight.isInfinite()) { throwError("BoxConstraints forces an infinite width and infinite height."); } if (this.minWidth.isInfinite()) { throwError("BoxConstraints forces an infinite width."); } if (this.minHeight.isInfinite()) { throwError("BoxConstraints forces an infinite height."); } } D.assert(this.isNormalized); return true; }); return this.isNormalized; } public BoxConstraints normalize() { if (this.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 > this.maxWidth ? minWidth : this.maxWidth, minHeight, minHeight > this.maxHeight ? minHeight : this.maxHeight ); } public bool Equals(BoxConstraints other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.minWidth.Equals(other.minWidth) && this.maxWidth.Equals(other.maxWidth) && this.minHeight.Equals(other.minHeight) && this.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() != this.GetType()) { return false; } return this.Equals((BoxConstraints) obj); } public override int GetHashCode() { unchecked { var hashCode = this.minWidth.GetHashCode(); hashCode = (hashCode * 397) ^ this.maxWidth.GetHashCode(); hashCode = (hashCode * 397) ^ this.minHeight.GetHashCode(); hashCode = (hashCode * 397) ^ this.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 = this.isNormalized ? "" : "; NOT NORMALIZED"; if (this.minWidth == float.PositiveInfinity && this.minHeight == float.PositiveInfinity) { return "BoxConstraints(biggest" + annotation + ")"; } if (this.minWidth == 0 && this.maxWidth == float.PositiveInfinity && this.minHeight == 0 && this.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(this.minWidth, this.maxWidth, "w"); string height = describe(this.minHeight, this.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 $"{Diagnostics.describeIdentity(this.target)}@{this.localPosition}"; } } public class BoxParentData : ParentData { public Offset offset = Offset.zero; public override string ToString() { return "offset=" + this.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 this.dimension == other.dimension && this.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() != this.GetType()) { return false; } return this.Equals((_IntrinsicDimensionsCacheEntry) obj); } public override int GetHashCode() { unchecked { return ((int) this.dimension * 397) ^ this.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 || !this.debugDoingThisResize); bool shouldCache = true; D.assert(() => { if (debugCheckingIntrinsics) { shouldCache = false; } return true; }); if (shouldCache) { this._cachedIntrinsicDimensions = this._cachedIntrinsicDimensions ?? new Dictionary<_IntrinsicDimensionsCacheEntry, float>(); return this._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 this._computeIntrinsicDimension(_IntrinsicDimension.minWidth, height, this.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 this._computeIntrinsicDimension(_IntrinsicDimension.maxWidth, height, this.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 this._computeIntrinsicDimension(_IntrinsicDimension.minHeight, width, this.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 this._computeIntrinsicDimension(_IntrinsicDimension.maxHeight, width, this.computeMaxIntrinsicHeight); } protected virtual float computeMaxIntrinsicHeight(float width) { return 0.0f; } public bool hasSize { get { return this._size != null; } } public Size size { get { D.assert(this.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(this.debugDoingThisResize || this.debugDoingThisLayout || (debugActiveLayout == this.parent && _size._canBeUsedByParent)); } D.assert(_size == this._size); } return true; }); return this._size; } set { D.assert(!(this.debugDoingThisResize && this.debugDoingThisLayout)); D.assert(this.sizedByParent || !this.debugDoingThisResize); D.assert(() => { if ((this.sizedByParent && this.debugDoingThisResize) || (!this.sizedByParent && this.debugDoingThisLayout)) { return true; } D.assert(!this.debugDoingThisResize); string contract = "", violation = "", hint = ""; if (this.debugDoingThisLayout) { D.assert(this.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 (this.owner != null && this.owner.debugDoingLayout) { hint = "Only the object itself can set its size. It is a contract violation for other objects to set it."; } } if (this.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 = this.debugAdoptSize(value); return true; }); this._size = value; D.assert(() => { this.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, this.debugCanParentUseSize); return true; }); return result; } public override Rect semanticBounds { get { return Offset.zero & this.size; } } protected override void debugResetSize() { this.size = this.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(!this.debugNeedsLayout); D.assert(() => { RenderObject parent = (RenderObject) this.parent; if (this.owner.debugDoingLayout) { return (debugActiveLayout == parent) && parent.debugDoingThisLayout; } if (this.owner.debugDoingPaint) { return ((debugActivePaint == parent) && parent.debugDoingThisPaint) || ((debugActivePaint == this) && this.debugDoingThisPaint); } D.assert(parent == this.parent); return false; }); D.assert(_debugSetDoingBaseline(true)); float? result = this.getDistanceToActualBaseline(baseline); D.assert(_debugSetDoingBaseline(false)); if (result == null && !onlyReal) { return this.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."); this._cachedBaselines = this._cachedBaselines ?? new Dictionary(); return this._cachedBaselines.putIfAbsent(baseline, () => this.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(this.constraints != null); D.assert(() => { if (!this.hasSize) { D.assert(!this.debugNeedsLayout); string contract = ""; if (this.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 (!this._size.isFinite) { var information = new StringBuilder(); if (!this.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 (!this.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( this.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 " + this.GetType() + " were:\n" + " " + this.constraints + "\n" + "The exact size it was given was:\n" + " " + this._size ); } if (!this.constraints.isSatisfiedBy(this._size)) { throw new UIWidgetsError( this.GetType() + " does not meet its constraints.\n" + "Constraints: " + this.constraints + "\n" + "Size: " + this._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(this.getMinIntrinsicWidth, this.getMaxIntrinsicWidth, "Width", float.PositiveInfinity); testIntrinsicsForValues(this.getMinIntrinsicHeight, this.getMaxIntrinsicHeight, "Height", float.PositiveInfinity); if (this.constraints.hasBoundedWidth) { testIntrinsicsForValues(this.getMinIntrinsicWidth, this.getMaxIntrinsicWidth, "Width", this.constraints.maxWidth); } if (this.constraints.hasBoundedHeight) { testIntrinsicsForValues(this.getMinIntrinsicHeight, this.getMaxIntrinsicHeight, "Height", this.constraints.maxHeight); } debugCheckingIntrinsics = false; if (failures.Length > 0) { D.assert(failureCount > 0); throw new UIWidgetsError( "The intrinsic dimension methods of the " + this.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 (this._cachedBaselines != null && this._cachedBaselines.isNotEmpty() || this._cachedIntrinsicDimensions != null && this._cachedIntrinsicDimensions.isNotEmpty()) { if (this._cachedBaselines != null) { this._cachedBaselines.Clear(); } if (this._cachedIntrinsicDimensions != null) { this._cachedIntrinsicDimensions.Clear(); } if (this.parent is RenderObject) { this.markParentNeedsLayout(); return; } } base.markNeedsLayout(); } protected override void performResize() { this.size = this.constraints.smallest; D.assert(this.size.isFinite); } protected override void performLayout() { D.assert(() => { if (!this.sizedByParent) { throw new UIWidgetsError( this.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(HitTestResult result, Offset position) { D.assert(() => { if (!this.hasSize) { if (this.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 (this._size.contains(position)) { if (this.hitTestChildren(result, position: position) || this.hitTestSelf(position)) { result.add(new BoxHitTestEntry(this, position)); return true; } } return false; } protected virtual bool hitTestSelf(Offset position) { return false; } protected virtual bool hitTestChildren(HitTestResult result, Offset position = null) { return false; } public override void applyPaintTransform(RenderObject child, Matrix3 transform) { D.assert(child != null); D.assert(child.parent == this); D.assert(() => { if (!(child.parentData is BoxParentData)) { throw new UIWidgetsError( this.GetType() + " does not implement applyPaintTransform.\n" + "The following " + this.GetType() + " object:\n" + " " + this.toStringShallow() + "\n" + "...did not use a BoxParentData class for the parentData field of the following child:\n" + " " + child.toStringShallow() + "\n" + "The " + this.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 " + this.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.preTranslate(offset.dx, offset.dy); } public Offset globalToLocal(Offset point, RenderObject ancestor = null) { var transform = this.getTransformTo(ancestor); var inverse = Matrix3.I(); var invertible = transform.invert(inverse); return invertible ? inverse.mapPoint(point) : Offset.zero; } public Offset localToGlobal(Offset point, RenderObject ancestor = null) { return this.getTransformTo(ancestor).mapPoint(point); } public override Rect paintBounds { get { return Offset.zero & this.size; } } int _debugActivePointers = 0; protected bool debugHandleEvent(PointerEvent evt, HitTestEntry entry) { D.assert(() => { if (D.debugPaintPointersEnabled) { if (evt is PointerDownEvent) { this._debugActivePointers += 1; } else if (evt is PointerUpEvent || evt is PointerCancelEvent) { this._debugActivePointers -= 1; } this.markNeedsPaint(); } return true; }); return true; } public override void debugPaint(PaintingContext context, Offset offset) { D.assert(() => { if (D.debugPaintSizeEnabled) { this.debugPaintSize(context, offset); } if (D.debugPaintBaselinesEnabled) { this.debugPaintBaselines(context, offset); } if (D.debugPaintPointersEnabled) { this.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 & this.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 = this.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 + this.size.width, offset.dy + baselineI.Value); context.canvas.drawPath(path, paint); } // alphabetic baseline float? baselineA = this.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 + this.size.width, offset.dy + baselineA.Value); context.canvas.drawPath(path, paint); } return true; }); } protected virtual void debugPaintPointers(PaintingContext context, Offset offset) { D.assert(() => { if (this._debugActivePointers > 0) { var paint = new Paint { color = new Color(0x00BBBB | ((0x04000000 * this.depth) & 0xFF000000)), }; context.canvas.drawRect(offset & this.size, paint); } return true; }); } public override void debugFillProperties(DiagnosticPropertiesBuilder properties) { base.debugFillProperties(properties); properties.add(new DiagnosticsProperty("size", this._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(!this.debugNeedsLayout); var child = this.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(!this.debugNeedsLayout); float? result = null; var child = this.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(HitTestResult result, Offset position = null) { ChildType child = this.lastChild; while (child != null) { ParentDataType childParentData = (ParentDataType) child.parentData; if (child.hitTest(result, position: position - childParentData.offset)) { return true; } child = childParentData.previousSibling; } return false; } public void defaultPaint(PaintingContext context, Offset offset) { var child = this.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 = this.firstChild; while (child != null) { var childParentData = (ParentDataType) child.parentData; result.Add(child); child = childParentData.nextSibling; } return result; } } }