[go: nahoru, domu]

blob: d8370dd782b9817c56fbe33076d5b15a4d2443b2 [file] [log] [blame]
package androidx.ui.rendering.box
import androidx.annotation.CallSuper
import androidx.ui.assert
import androidx.ui.engine.geometry.Offset
import androidx.ui.engine.geometry.Rect
import androidx.ui.engine.geometry.Size
import androidx.ui.engine.text.TextBaseline
import androidx.ui.foundation.assertions.FlutterError
import androidx.ui.foundation.diagnostics.DiagnosticPropertiesBuilder
import androidx.ui.foundation.diagnostics.DiagnosticsProperty
import androidx.ui.gestures.events.PointerCancelEvent
import androidx.ui.gestures.events.PointerDownEvent
import androidx.ui.gestures.events.PointerEvent
import androidx.ui.gestures.events.PointerUpEvent
import androidx.ui.gestures.hit_test.HitTestEntry
import androidx.ui.gestures.hit_test.HitTestResult
import androidx.ui.painting.Color
import androidx.ui.painting.Paint
import androidx.ui.painting.PaintingStyle
import androidx.ui.painting.matrixutils.transformPoint
import androidx.ui.rendering.debugCheckIntrinsicSizes
import androidx.ui.rendering.debugPaintBaselinesEnabled
import androidx.ui.rendering.debugPaintPointersEnabled
import androidx.ui.rendering.debugPaintSizeEnabled
import androidx.ui.rendering.obj.PaintingContext
import androidx.ui.rendering.obj.RenderObject
import androidx.ui.rendering.obj.RenderObjectWithChildMixin
import androidx.ui.runtimeType
import androidx.ui.vectormath64.Matrix4
/**
* A render object in a 2D Cartesian coordinate system.
*
* The [size] of each box is expressed as a width and a height. Each box has
* its own coordinate system in which its upper left corner is placed at (0,
* 0). The lower right corner of the box is therefore at (width, height). The
* box contains all the points including the upper left corner and extending
* to, but not including, the lower right corner.
*
* Box layout is performed by passing a [BoxConstraints] object down the tree.
* The box constraints establish a min and max value for the child's width and
* height. In determining its size, the child must respect the constraints
* given to it by its parent.
*
* This protocol is sufficient for expressing a number of common box layout
* data flows. For example, to implement a width-in-height-out data flow, call
* your child's [layout] function with a set of box constraints with a tight
* width value (and pass true for parentUsesSize). After the child determines
* its height, use the child's height to determine your size.
*
* ## Writing a RenderBox subclass
*
* One would implement a new [RenderBox] subclass to describe a new layout
* model, new paint model, new hit-testing model, or new semantics model, while
* remaining in the Cartesian space defined by the [RenderBox] protocol.
*
* To create a new protocol, consider subclassing [RenderObject] instead.
*
* ### Constructors and properties of a new RenderBox subclass
*
* The constructor will typically take a named argument for each property of
* the class. The value is then passed to a private field of the class and the
* constructor asserts its correctness (e.g. if it should not be null, it
* asserts it's not null).
*
* Properties have the form of a getter/setter/field group like the following:
*
* ```dart
* AxisDirection get axis => _axis;
* AxisDirection _axis;
* set axis(AxisDirection value) {
* assert(value != null); // same check as in the constructor
* if (value == _axis)
* return;
* _axis = value;
* markNeedsLayout();
* }
* ```
*
* The setter will typically finish with either a call to [markNeedsLayout], if
* the layout uses this property, or [markNeedsPaint], if only the painter
* function does. (No need to call both, [markNeedsLayout] implies
* [markNeedsPaint].)
*
* Consider layout and paint to be expensive; be conservative about calling
* [markNeedsLayout] or [markNeedsPaint]. They should only be called if the
* layout (or paint, respectively) has actually changed.
*
* ### Children
*
* If a render object is a leaf, that is, it cannot have any children, then
* ignore this section. (Examples of leaf render objects are [RenderImage] and
* [RenderParagraph].)
*
* For render objects with children, there are four possible scenarios:
*
* * A single [RenderBox] child. In this scenario, consider inheriting from
* [RenderProxyBox] (if the render object sizes itself to match the child) or
* [RenderShiftedBox] (if the child will be smaller than the box and the box
* will align the child inside itself).
*
* * A single child, but it isn't a [RenderBox]. Use the
* [RenderObjectWithChildMixin] mixin.
*
* * A single list of children. Use the [ContainerRenderObjectMixin] mixin.
*
* * A more complicated child model.
*
* #### Using RenderProxyBox
*
* By default, a [RenderProxyBox] render object sizes itself to fit its child, or
* to be as small as possible if there is no child; it passes all hit testing
* and painting on to the child, and intrinsic dimensions and baseline
* measurements similarly are proxied to the child.
*
* A subclass of [RenderProxyBox] just needs to override the parts of the
* [RenderBox] protocol that matter. For example, [RenderOpacity] just
* overrides the paint method (and [alwaysNeedsCompositing] to reflect what the
* paint method does, and the [visitChildrenForSemantics] method so that the
* child is hidden from accessibility tools when it's invisible), and adds an
* [RenderOpacity.opacity] field.
*
* [RenderProxyBox] assumes that the child is the size of the parent and
* positioned at 0,0. If this is not true, then use [RenderShiftedBox] instead.
*
* See
* [proxy_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/proxy_box.dart)
* for examples of inheriting from [RenderProxyBox].
*
* #### Using RenderShiftedBox
*
* By default, a [RenderShiftedBox] acts much like a [RenderProxyBox] but
* without assuming that the child is positioned at 0,0 (the actual position
* recorded in the child's [parentData] field is used), and without providing a
* default layout algorithm.
*
* See
* [shifted_box.dart](https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/shifted_box.dart)
* for examples of inheriting from [RenderShiftedBox].
*
* #### Kinds of children and child-specific data
*
* A [RenderBox] doesn't have to have [RenderBox] children. One can use another
* subclass of [RenderObject] for a [RenderBox]'s children. See the discussion
* at [RenderObject].
*
* Children can have additional data owned by the parent but stored on the
* child using the [parentData] field. The class used for that data must
* inherit from [ParentData]. The [setupParentData] method is used to
* initialize the [parentData] field of a child when the child is attached.
*
* By convention, [RenderBox] objects that have [RenderBox] children use the
* [BoxParentData] class, which has a [BoxParentData.offset] field to store the
* position of the child relative to the parent. ([RenderProxyBox] does not
* need this offset and therefore is an exception to this rule.)
*
* #### Using RenderObjectWithChildMixin
*
* If a render object has a single child but it isn't a [RenderBox], then the
* [RenderObjectWithChildMixin] class, which is a mixin that will handle the
* boilerplate of managing a child, will be useful.
*
* It's a generic class with one type argument, the type of the child. For
* example, if you are building a `RenderFoo` class which takes a single
* `RenderBar` child, you would use the mixin as follows:
*
* ```dart
* class RenderFoo extends RenderBox
* with RenderObjectWithChildMixin<RenderBar> {
* // ...
* }
* ```
*
* Since the `RenderFoo` class itself is still a [RenderBox] in this case, you
* still have to implement the [RenderBox] layout algorithm, as well as
* features like intrinsics and baselines, painting, and hit testing.
*
* #### Using ContainerRenderObjectMixin
*
* If a render box can have multiple children, then the
* [ContainerRenderObjectMixin] mixin can be used to handle the boilerplate. It
* uses a linked list to model the children in a manner that is easy to mutate
* dynamically and that can be walked efficiently. Random access is not
* efficient in this model; if you need random access to the children consider
* the next section on more complicated child models.
*
* The [ContainerRenderObjectMixin] class has two type arguments. The first is
* the type of the child objects. The second is the type for their
* [parentData]. The class used for [parentData] must itself have the
* [ContainerParentDataMixin] class mixed into it; this is where
* [ContainerRenderObjectMixin] stores the linked list. A [ParentData] class
* can extend [ContainerBoxParentData]; this is essentially
* [BoxParentData] mixed with [ContainerParentDataMixin]. For example, if a
* `RenderFoo` class wanted to have a linked list of [RenderBox] children, one
* might create a `FooParentData` class as follows:
*
* ```dart
* class FooParentData extends ContainerBoxParentData<RenderBox> {
* // (any fields you might need for these children)
* }
* ```
*
* When using [ContainerRenderObjectMixin] in a [RenderBox], consider mixing in
* [RenderBoxContainerDefaultsMixin], which provides a collection of utility
* methods that implement common parts of the [RenderBox] protocol (such as
* painting the children).
*
* The declaration of the `RenderFoo` class itself would thus look like this:
*
* ```dart
* class RenderFlex extends RenderBox with
* ContainerRenderObjectMixin<RenderBox, FooParentData>,
* RenderBoxContainerDefaultsMixin<RenderBox, FooParentData> {
* // ...
* }
* ```
*
* When walking the children (e.g. during layout), the following pattern is
* commonly used (in this case assuming that the children are all [RenderBox]
* objects and that this render object uses `FooParentData` objects for its
* children's [parentData] fields):
*
* ```dart
* RenderBox child = firstChild;
* while (child != null) {
* final FooParentData childParentData = child.parentData;
* // ...operate on child and childParentData...
* assert(child.parentData == childParentData);
* child = childParentData.nextSibling;
* }
* ```
*
* #### More complicated child models
*
* Render objects can have more complicated models, for example a map of
* children keyed on an enum, or a 2D grid of efficiently randomly-accessible
* children, or multiple lists of children, etc. If a render object has a model
* that can't be handled by the mixins above, it must implement the
* [RenderObject] child protocol, as follows:
*
* * Any time a child is removed, call [dropChild] with the child.
*
* * Any time a child is added, call [adoptChild] with the child.
*
* * Implement the [attach] method such that it calls [attach] on each child.
*
* * Implement the [detach] method such that it calls [detach] on each child.
*
* * Implement the [redepthChildren] method such that it calls [redepthChild]
* on each child.
*
* * Implement the [visitChildren] method such that it calls its argument for
* each child, typically in paint order (back-most to front-most).
*
* * Implement [debugDescribeChildren] such that it outputs a [DiagnosticsNode]
* for each child.
*
* Implementing these seven bullet points is essentially all that the two
* aforementioned mixins do.
*
* ### Layout
*
* [RenderBox] classes implement a layout algorithm. They have a set of
* constraints provided to them, and they size themselves based on those
* constraints and whatever other inputs they may have (for example, their
* children or properties).
*
* When implementing a [RenderBox] subclass, one must make a choice. Does it
* size itself exclusively based on the constraints, or does it use any other
* information in sizing itself? An example of sizing purely based on the
* constraints would be growing to fit the parent.
*
* Sizing purely based on the constraints allows the system to make some
* significant optimizations. Classes that use this approach should override
* [sizedByParent] to return true, and then override [performResize] to set the
* [size] using nothing but the constraints, e.g.:
*
* ```dart
* @override
* bool get sizedByParent => true;
*
* @override
* void performResize() {
* size = constraints.smallest;
* }
* ```
*
* Otherwise, the size is set in the [performLayout] function.
*
* The [performLayout] function is where render boxes decide, if they are not
* [sizedByParent], what [size] they should be, and also where they decide
* where their children should be.
*
* #### Layout of RenderBox children
*
* The [performLayout] function should call the [layout] function of each (box)
* child, passing it a [BoxConstraints] object describing the constraints
* within which the child can render. Passing tight constraints (see
* [BoxConstraints.isTight]) to the child will allow the rendering library to
* apply some optimizations, as it knows that if the constraints are tight, the
* child's dimensions cannot change even if the layout of the child itself
* changes.
*
* If the [performLayout] function will use the child's size to affect other
* aspects of the layout, for example if the render box sizes itself around the
* child, or positions several children based on the size of those children,
* then it must specify the `parentUsesSize` argument to the child's [layout]
* function, setting it to true.
*
* This flag turns off some optimizations; algorithms that do not rely on the
* children's sizes will be more efficient. (In particular, relying on the
* child's [size] means that if the child is marked dirty for layout, the
* parent will probably also be marked dirty for layout, unless the
* [constraints] given by the parent to the child were tight constraints.)
*
* For [RenderBox] classes that do not inherit from [RenderProxyBox], once they
* have laid out their children, should also position them, by setting the
* [BoxParentData.offset] field of each child's [parentData] object.
*
* #### Layout of non-RenderBox children
*
* The children of a [RenderBox] do not have to be [RenderBox]es themselves. If
* they use another protocol (as discussed at [RenderObject]), then instead of
* [BoxConstraints], the parent would pass in the appropriate [Constraints]
* subclass, and instead of reading the child's size, the parent would read
* whatever the output of [layout] is for that layout protocol. The
* `parentUsesSize` flag is still used to indicate whether the parent is going
* to read that output, and optimizations still kick in if the child has tight
* constraints (as defined by [Constraints.isTight]).
*
* ### Painting
*
* To describe how a render box paints, implement the [paint] method. It is
* given a [PaintingContext] object and an [Offset]. The painting context
* provides methods to affect the layer tree as well as a
* [PaintingContext.canvas] which can be used to add drawing commands. The
* canvas object should not be cached across calls to the [PaintingContext]'s
* methods; every time a method on [PaintingContext] is called, there is a
* chance that the canvas will change identity. The offset specifies the
* position of the top left corner of the box in the coordinate system of the
* [PaintingContext.canvas].
*
* To draw text on a canvas, use a [TextPainter].
*
* To draw an image to a canvas, use the [paintImage] method.
*
* A [RenderBox] that uses methods on [PaintingContext] that introduce new
* layers should override the [alwaysNeedsCompositing] getter and set it to
* true. If the object sometimes does and sometimes does not, it can have that
* getter return true in some cases and false in others. In that case, whenever
* the return value would change, call [markNeedsCompositingBitsUpdate]. (This
* is done automatically when a child is added or removed, so you don't have to
* call it explicitly if the [alwaysNeedsCompositing] getter only changes value
* based on the presence or absence of children.)
*
* Anytime anything changes on the object that would cause the [paint] method
* to paint something different (but would not cause the layout to change),
* the object should call [markNeedsPaint].
*
* #### Painting children
*
* The [paint] method's `context` argument has a [PaintingContext.paintChild]
* method, which should be called for each child that is to be painted. It
* should be given a reference to the child, and an [Offset] giving the
* position of the child relative to the parent.
*
* If the [paint] method applies a transform to the painting context before
* painting children (or generally applies an additional offset beyond the
* offset it was itself given as an argument), then the [applyPaintTransform]
* method should also be overridden. That method must adjust the matrix that it
* is given in the same manner as it transformed the painting context and
* offset before painting the given child. This is used by the [globalToLocal]
* and [localToGlobal] methods.
*
* #### Hit Tests
*
* Hit testing for render boxes is implemented by the [hitTest] method. The
* default implementation of this method defers to [hitTestSelf] and
* [hitTestChildren]. When implementing hit testing, you can either override
* these latter two methods, or ignore them and just override [hitTest].
*
* The [hitTest] method itself is given an [Offset], and must return true if the
* object or one of its children has absorbed the hit (preventing objects below
* this one from being hit), or false if the hit can continue to other objects
* below this one.
*
* For each child [RenderBox], the [hitTest] method on the child should be
* called with the same [HitTestResult] argument and with the point transformed
* into the child's coordinate space (in the same manner that the
* [applyPaintTransform] method would). The default implementation defers to
* [hitTestChildren] to call the children. [RenderBoxContainerDefaultsMixin]
* provides a [RenderBoxContainerDefaultsMixin.defaultHitTestChildren] method
* that does this assuming that the children are axis-aligned, not transformed,
* and positioned according to the [BoxParentData.offset] field of the
* [parentData]; more elaborate boxes can override [hitTestChildren]
* accordingly.
*
* If the object is hit, then it should also add itself to the [HitTestResult]
* object that is given as an argument to the [hitTest] method, using
* [HitTestResult.add]. The default implementation defers to [hitTestSelf] to
* determine if the box is hit. If the object adds itself before the children
* can add themselves, then it will be as if the object was above the children.
* If it adds itself after the children, then it will be as if it was below the
* children. Entries added to the [HitTestResult] object should use the
* [BoxHitTestEntry] class. The entries are subsequently walked by the system
* in the order they were added, and for each entry, the target's [handleEvent]
* method is called, passing in the [HitTestEntry] object.
*
* Hit testing cannot rely on painting having happened.
*
* ### Semantics
*
* For a render box to be accessible, implement the
* [describeApproximatePaintClip] and [visitChildrenForSemantics] methods, and
* the [semanticsAnnotator] getter. The default implementations are sufficient
* for objects that only affect layout, but nodes that represent interactive
* components or information (diagrams, text, images, etc) should provide more
* complete implementations. For more information, see the documentation for
* these members.
*
* ### Intrinsics and Baselines
*
* The layout, painting, hit testing, and semantics protocols are common to all
* render objects. [RenderBox] objects must implement two additional protocols:
* intrinsic sizing and baseline measurements.
*
* There are four methods to implement for intrinsic sizing, to compute the
* minimum and maximum intrinsic width and height of the box. The documentation
* for these methods discusses the protocol in detail:
* [computeMinIntrinsicWidth], [computeMaxIntrinsicWidth],
* [computeMinIntrinsicHeight], [computeMaxIntrinsicHeight].
*
* In addition, if the box has any children, it must implement
* [computeDistanceToActualBaseline]. [RenderProxyBox] provides a simple
* implementation that forwards to the child; [RenderShiftedBox] provides an
* implementation that offsets the child's baseline information by the position
* of the child relative to the parent. If you do not inherited from either of
* these classes, however, you must implement the algorithm yourself.
*/
// (Migration/Andrey): Replaced ": RenderObject" with ": RenderObjectWithChildMixin" to make
// RenderProxyBoxMixin works.
abstract class RenderBox : RenderObjectWithChildMixin<RenderBox>() {
override fun setupParentData(child: RenderObject) {
if (child.parentData !is BoxParentData)
child.parentData = BoxParentData()
}
var _cachedIntrinsicDimensions: MutableMap<_IntrinsicDimensionsCacheEntry, Float>? = null
fun _computeIntrinsicDimension(
dimension: _IntrinsicDimension,
argument: Float,
computer: (Float) -> Float
): Float {
// performResize should not depend on anything except the incoming constraints
assert(RenderObject.debugCheckingIntrinsics || !debugDoingThisResize)
var shouldCache = true
assert {
// we don't want the checked-mode intrinsic tests to affect
// who gets marked dirty, etc.
if (RenderObject.debugCheckingIntrinsics)
shouldCache = false
true
}
if (shouldCache) {
if (_cachedIntrinsicDimensions == null) {
_cachedIntrinsicDimensions = mutableMapOf()
}
return _cachedIntrinsicDimensions!!.getOrPut(
_IntrinsicDimensionsCacheEntry(dimension, argument), { computer(argument) }
)
}
return computer(argument)
}
/**
* Returns the minimum width that this box could be without failing to
* correctly paint its contents within itself, without clipping.
*
* The height argument may give a specific height to assume. The given height
* can be infinite, meaning that the intrinsic width in an unconstrained
* environment is being requested. The given height should never be negative
* or null.
*
* This function should only be called on one's children. Calling this
* function couples the child with the parent so that when the child's layout
* changes, the parent is notified (via [markNeedsLayout]).
*
* Calling this function is expensive and as it can result in O(N^2)
* behavior.
*
* Do not override this method. Instead, implement [computeMinIntrinsicWidth].
*/
@CallSuper
fun getMinIntrinsicWidth(height: Float?): Float {
assert {
if (height == null) {
throw FlutterError(
"The height argument to getMinIntrinsicWidth was null.\n" +
"The argument to getMinIntrinsicWidth must not be " +
"negative or null. If you do not have a specific height in mind, " +
"then pass Float.POSITIVE_INFINITY instead."
)
}
if (height < 0.0f) {
throw FlutterError(
"The height argument to getMinIntrinsicWidth was negative.\n" +
"The argument to getMinIntrinsicWidth must not be negative or null. " +
"If you perform computations on another height before passing it to " +
"getMinIntrinsicWidth, consider using math.max() or Float.clamp() " +
"to force the value into the valid range."
)
}
true
}
return _computeIntrinsicDimension(
_IntrinsicDimension.minWidth, height!!,
::computeMinIntrinsicWidth
)
}
/**
* Computes the value returned by [getMinIntrinsicWidth]. Do not call this
* function directly, instead, call [getMinIntrinsicWidth].
*
* Override in subclasses that implement [performLayout]. This method should
* return the minimum width that this box could be without failing to
* correctly paint its contents within itself, without clipping.
*
* If the layout algorithm is independent of the context (e.g. it always
* tries to be a particular size), or if the layout algorithm is
* width-in-height-out, or if the layout algorithm uses both the incoming
* width and height constraints (e.g. it always sizes itself to
* [BoxConstraints.biggest]), then the `height` argument should be ignored.
*
* If the layout algorithm is strictly height-in-width-out, or is
* height-in-width-out when the width is unconstrained, then the height
* argument is the height to use.
*
* The `height` argument will never be negative or null. It may be infinite.
*
* If this algorithm depends on the intrinsic dimensions of a child, the
* intrinsic dimensions of that child should be obtained using the functions
* whose names start with `get`, not `compute`.
*
* This function should never return a negative or infinite value.
*
* ## Examples
*
* ### Text
*
* Text is the canonical example of a width-in-height-out algorithm. The
* `height` argument is therefore ignored.
*
* Consider the string "Hello World" The _maximum_ intrinsic width (as
* returned from [computeMaxIntrinsicWidth]) would be the width of the string
* with no line breaks.
*
* The minimum intrinsic width would be the width of the widest word, "Hello"
* or "World". If the text is rendered in an even narrower width, however, it
* might still not overflow. For example, maybe the rendering would put a
* line-break half-way through the words, as in "Hel⁞lo⁞Wor⁞ld". However,
* this wouldn't be a _correct_ rendering, and [computeMinIntrinsicWidth] is
* supposed to render the minimum width that the box could be without failing
* to _correctly_ paint the contents within itself.
*
* The minimum intrinsic _height_ for a given width smaller than the minimum
* intrinsic width could therefore be greater than the minimum intrinsic
* height for the minimum intrinsic width.
*
* ### Viewports (e.g. scrolling lists)
*
* Some render boxes are intended to clip their children. For example, the
* render box for a scrolling list might always size itself to its parents'
* size (or rather, to the maximum incoming constraints), regardless of the
* children's sizes, and then clip the children and position them based on
* the current scroll offset.
*
* The intrinsic dimensions in these cases still depend on the children, even
* though the layout algorithm sizes the box in a way independent of the
* children. It is the size that is needed to paint the box's contents (in
* this case, the children) _without clipping_ that matters.
*
* ### When the intrinsic dimensions cannot be known
*
* There are cases where render objects do not have an efficient way to
* compute their intrinsic dimensions. For example, it may be prohibitively
* expensive to reify and measure every child of a lazy viewport (viewports
* generally only instantiate the actually visible children), or the
* dimensions may be computed by a callback about which the render object
* cannot reason.
*
* In such cases, it may be impossible (or at least impractical) to actually
* return a valid answer. In such cases, the intrinsic functions should throw
* when [RenderObject.debugCheckingIntrinsics] is false and asserts are
* enabled, and return 0.0 otherwise.
*
* See the implementations of [LayoutBuilder] or [RenderViewportBase] for
* examples (in particular,
* [RenderViewportBase.debugThrowIfNotCheckingIntrinsics]).
*
* ### Aspect-ratio-driven boxes
*
* Some boxes always return a fixed size based on the constraints. For these
* boxes, the intrinsic functions should return the appropriate size when the
* incoming `height` or `width` argument is finite, treating that as a tight
* constraint in the respective direction and treating the other direction's
* constraints as unbounded. This is because the definitions of
* [computeMinIntrinsicWidth] and [computeMinIntrinsicHeight] are in terms of
* what the dimensions _could be_, and such boxes can only be one size in
* such cases.
*
* When the incoming argument is not finite, then they should return the
* actual intrinsic dimensions based on the contents, as any other box would.
*/
protected open fun computeMinIntrinsicWidth(height: Float): Float {
return 0.0f
}
/**
* Returns the smallest width beyond which increasing the width never
* decreases the preferred height. The preferred height is the value that
* would be returned by [getMinIntrinsicHeight] for that width.
*
* The height argument may give a specific height to assume. The given height
* can be infinite, meaning that the intrinsic width in an unconstrained
* environment is being requested. The given height should never be negative
* or null.
*
* This function should only be called on one's children. Calling this
* function couples the child with the parent so that when the child's layout
* changes, the parent is notified (via [markNeedsLayout]).
*
* Calling this function is expensive and as it can result in O(N^2)
* behavior.
*
* Do not override this method. Instead, implement
* [computeMaxIntrinsicWidth].
*/
@CallSuper
fun getMaxIntrinsicWidth(height: Float?): Float {
assert {
if (height == null) {
throw FlutterError(
"The height argument to getMaxIntrinsicWidth was null.\n" +
"The argument to getMaxIntrinsicWidth must not be negative or null. " +
"If you do not have a specific height in mind, " +
"then pass Float.POSITIVE_INFINITY instead."
)
}
if (height < 0.0f) {
throw FlutterError(
"The height argument to getMaxIntrinsicWidth was negative.\n" +
"The argument to getMaxIntrinsicWidth must not be negative or null. " +
"If you perform computations on another height before passing it to " +
"getMaxIntrinsicWidth, consider using max() or coerceIn() " +
"to force the value into the valid range."
)
}
true
}
return _computeIntrinsicDimension(
_IntrinsicDimension.maxWidth, height!!,
::computeMaxIntrinsicWidth
)
}
/**
* Computes the value returned by [getMaxIntrinsicWidth]. Do not call this
* function directly, instead, call [getMaxIntrinsicWidth].
*
* Override in subclasses that implement [performLayout]. This should return
* the smallest width beyond which increasing the width never decreases the
* preferred height. The preferred height is the value that would be returned
* by [computeMinIntrinsicHeight] for that width.
*
* If the layout algorithm is strictly height-in-width-out, or is
* height-in-width-out when the width is unconstrained, then this should
* return the same value as [computeMinIntrinsicWidth] for the same height.
*
* Otherwise, the height argument should be ignored, and the returned value
* should be equal to or bigger than the value returned by
* [computeMinIntrinsicWidth].
*
* The `height` argument will never be negative or null. It may be infinite.
*
* The value returned by this method might not match the size that the object
* would actually take. For example, a [RenderBox] subclass that always
* exactly sizes itself using [BoxConstraints.biggest] might well size itself
* bigger than its max intrinsic size.
*
* If this algorithm depends on the intrinsic dimensions of a child, the
* intrinsic dimensions of that child should be obtained using the functions
* whose names start with `get`, not `compute`.
*
* This function should never return a negative or infinite value.
*
* See also examples in the definition of [computeMinIntrinsicWidth].
*/
protected open fun computeMaxIntrinsicWidth(height: Float): Float {
return 0.0f
}
/** Returns the minimum height that this box could be without failing to
* correctly paint its contents within itself, without clipping.
*
* The width argument may give a specific width to assume. The given width
* can be infinite, meaning that the intrinsic height in an unconstrained
* environment is being requested. The given width should never be negative
* or null.
*
* This function should only be called on one's children. Calling this
* function couples the child with the parent so that when the child's layout
* changes, the parent is notified (via [markNeedsLayout]).
*
* Calling this function is expensive and as it can result in O(N^2)
* behavior.
*
* Do not override this method. Instead, implement
* [computeMinIntrinsicHeight].
*/
@CallSuper
fun getMinIntrinsicHeight(width: Float?): Float {
assert {
if (width == null) {
throw FlutterError(
"The width argument to getMinIntrinsicHeight was null.\n" +
"The argument to getMinIntrinsicHeight must not be negative or null. " +
"If you do not have a specific width in mind, " +
"then pass Float.POSITIVE_INFINITY instead."
)
}
if (width < 0.0f) {
throw FlutterError(
"The width argument to getMinIntrinsicHeight was negative.\n" +
"The argument to getMinIntrinsicHeight must not be negative or null. " +
"If you perform computations on another width before passing it to " +
"getMinIntrinsicHeight, consider using math.max() or Float.clamp() " +
"to force the value into the valid range."
)
}
true
}
return _computeIntrinsicDimension(
_IntrinsicDimension.minHeight, width!!,
::computeMinIntrinsicHeight
)
}
/**
* Computes the value returned by [getMinIntrinsicHeight]. Do not call this
* function directly, instead, call [getMinIntrinsicHeight].
*
* Override in subclasses that implement [performLayout]. Should return the
* minimum height that this box could be without failing to correctly paint
* its contents within itself, without clipping.
*
* If the layout algorithm is independent of the context (e.g. it always
* tries to be a particular size), or if the layout algorithm is
* height-in-width-out, or if the layout algorithm uses both the incoming
* height and width constraints (e.g. it always sizes itself to
* [BoxConstraints.biggest]), then the `width` argument should be ignored.
*
* If the layout algorithm is strictly width-in-height-out, or is
* width-in-height-out when the height is unconstrained, then the width
* argument is the width to use.
*
* The `width` argument will never be negative or null. It may be infinite.
*
* If this algorithm depends on the intrinsic dimensions of a child, the
* intrinsic dimensions of that child should be obtained using the functions
* whose names start with `get`, not `compute`.
*
* This function should never return a negative or infinite value.
*
* See also examples in the definition of [computeMinIntrinsicWidth].
*/
protected open fun computeMinIntrinsicHeight(width: Float): Float {
return 0.0f
}
/**
* Returns the smallest height beyond which increasing the height never
* decreases the preferred width. The preferred width is the value that
* would be returned by [getMinIntrinsicWidth] for that height.
*
* The width argument may give a specific width to assume. The given width
* can be infinite, meaning that the intrinsic height in an unconstrained
* environment is being requested. The given width should never be negative
* or null.
*
* This function should only be called on one's children. Calling this
* function couples the child with the parent so that when the child's layout
* changes, the parent is notified (via [markNeedsLayout]).
*
* Calling this function is expensive and as it can result in O(N^2)
* behavior.
*
* Do not override this method. Instead, implement
* [computeMaxIntrinsicHeight].
*/
@CallSuper
fun getMaxIntrinsicHeight(width: Float?): Float {
assert {
if (width == null) {
throw FlutterError(
"The width argument to getMaxIntrinsicHeight was null.\n" +
"The argument to getMaxIntrinsicHeight must not be negative or null. " +
"If you do not have a specific width in mind, " +
"then pass Float.POSITIVE_INFINITY instead."
)
}
if (width < 0.0f) {
throw FlutterError(
"The width argument to getMaxIntrinsicHeight was negative.\n" +
"The argument to getMaxIntrinsicHeight must not be negative or null. " +
"If you perform computations on another width before passing it to " +
"getMaxIntrinsicHeight, consider using math.max() or Float.clamp() " +
"to force the value into the valid range."
)
}
true
}
return _computeIntrinsicDimension(
_IntrinsicDimension.maxHeight, width!!,
::computeMaxIntrinsicHeight
)
}
/**
* Computes the value returned by [getMaxIntrinsicHeight]. Do not call this
* function directly, instead, call [getMaxIntrinsicHeight].
*
* Override in subclasses that implement [performLayout]. Should return the
* smallest height beyond which increasing the height never decreases the
* preferred width. The preferred width is the value that would be returned
* by [computeMinIntrinsicWidth] for that height.
*
* If the layout algorithm is strictly width-in-height-out, or is
* width-in-height-out when the height is unconstrained, then this should
* return the same value as [computeMinIntrinsicHeight] for the same width.
*
* Otherwise, the width argument should be ignored, and the returned value
* should be equal to or bigger than the value returned by
* [computeMinIntrinsicHeight].
*
* The `width` argument will never be negative or null. It may be infinite.
*
* The value returned by this method might not match the size that the object
* would actually take. For example, a [RenderBox] subclass that always
* exactly sizes itself using [BoxConstraints.biggest] might well size itself
* bigger than its max intrinsic size.
*
* If this algorithm depends on the intrinsic dimensions of a child, the
* intrinsic dimensions of that child should be obtained using the functions
* whose names start with `get`, not `compute`.
*
* This function should never return a negative or infinite value.
*
* See also examples in the definition of [computeMinIntrinsicWidth].
*/
protected open fun computeMaxIntrinsicHeight(width: Float): Float {
return 0.0f
}
// Whether this render object has undergone layout and has a [size].
val hasSize get() = _size != null
/**
* The size of this render box computed during layout.
*
* This value is stale whenever this object is marked as needing layout.
* During [performLayout], do not read the size of a child unless you pass
* true for parentUsesSize when calling the child's [layout] function.
*
* The size of a box should be set only during the box's [performLayout] or
* [performResize] functions. If you wish to change the size of a box outside
* of those functions, call [markNeedsLayout] instead to schedule a layout of
* the box.
*/
override var size: Size
get() {
assert(hasSize, { "RenderBox was not laid out: ${toString()}" })
assert {
if (_size is _DebugSize) {
val _size = this._size as _DebugSize
assert(_size._owner == this)
if (RenderObject.debugActiveLayout != null) {
// We are always allowed to access our own size (for print debugging
// and asserts if nothing else). Other than us, the only object that's
// allowed to read our size is our parent, if they've said they will.
// If you hit this assert trying to access a child's size, pass
// "parentUsesSize: true" to that child's layout().
assert(debugDoingThisResize || debugDoingThisLayout ||
(RenderObject.debugActiveLayout == parent &&
_size._canBeUsedByParent))
}
assert(_size == this._size)
}
true
}
return _size!! // TODO(Migration/Andrey): Force notnull as we have assert hasSize before
}
// Setting the size, in checked mode, triggers some analysis of the render box,
// as implemented by [debugAssertDoesMeetConstraints], including calling the intrinsic
// sizing methods and checking that they meet certain invariants.
protected set(value) {
// TODO(Migration/xbhatnag): Hack because 'value' is immutable
var mutableValue = value
assert(!(debugDoingThisResize && debugDoingThisLayout))
assert(sizedByParent || !debugDoingThisResize)
assert {
if ((sizedByParent && debugDoingThisResize) ||
(!sizedByParent && debugDoingThisLayout)
)
return@assert true
assert(!debugDoingThisResize)
val contract: String
val violation: String
val hint: String
if (debugDoingThisLayout) {
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."
else
hint = ""
}
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 FlutterError(
"RenderBox size setter called incorrectly.\n" +
"$violation\n" +
"$hint\n" +
"$contract\n" +
"The RenderBox in question is:\n" +
" $this"
)
}
assert {
mutableValue = debugAdoptSize(mutableValue)
true
}
_size = mutableValue
assert { debugAssertDoesMeetConstraints(); true; }
}
var _size: Size? = null
/** Claims ownership of the given [Size].
*
* In debug mode, the [RenderBox] class verifies that [Size] objects obtained
* from other [RenderBox] objects are only used according to the semantics of
* the [RenderBox] protocol, namely that a [Size] from a [RenderBox] can only
* be used by its parent, and then only if `parentUsesSize` was set.
*
* Sometimes, a [Size] that can validly be used ends up no longer being valid
* over time. The common example is a [Size] taken from a child that is later
* removed from the parent. In such cases, this method can be called to first
* check whether the size can legitimately be used, and if so, to then create
* a new [Size] that can be used going forward, regardless of what happens to
* the original owner.
*/
fun debugAdoptSize(value: Size): Size {
var result = value
assert {
if (value is _DebugSize) {
if (value._owner != this) {
if (value._owner.parent != this) {
throw FlutterError(
"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 FlutterError(
"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 = _DebugSize(value, this, debugCanParentUseSize!!)
true
}
return result
}
override val semanticBounds: Rect get() = Offset.zero.and(size)
override fun debugResetSize() {
// updates the value of size._canBeUsedByParent if necessary
size = size
}
private var _cachedBaselines: MutableMap<TextBaseline, Float?>? = null
companion object {
var _debugDoingBaseline = false
fun _debugSetDoingBaseline(value: Boolean): Boolean {
_debugDoingBaseline = value
return true
}
}
/**
* Returns the distance from the y-coordinate of the position of the box to the y-coordinate of
* the first given baseline in the box's contents.
*
* Used by certain layout models to align adjacent boxes on a common baseline, regardless of
* padding, font size differences, etc. If there is no baseline, this function returns the
* distance from the y-coordinate of the position of the box to the y-coordinate of the bottom
* of the box(i.e., the height of the box) unless the caller passes true for `onlyReal`, in
* which case the function returns null.
*
* Only call this function after calling [layout] on this box. You are only allowed to call this
* from the parent of this box during that parent's [performLayout] or [paint] functions.
*/
fun getDistanceToBaseline(baseline: TextBaseline, onlyReal: Boolean = false): Float? {
assert(!debugNeedsLayout)
assert(!_debugDoingBaseline)
assert {
val parent = this.parent as RenderObject
if (owner!!.debugDoingLayout) {
(RenderObject.debugActiveLayout == parent) && parent.debugDoingThisLayout
}
if (owner!!.debugDoingPaint) {
((RenderObject.debugActivePaint == parent) && parent.debugDoingThisPaint) ||
((RenderObject.debugActivePaint == this) && debugDoingThisPaint)
}
assert(parent == this.parent)
false
}
assert(_debugSetDoingBaseline(true))
val result = getDistanceToActualBaseline(baseline)
assert(_debugSetDoingBaseline(false))
if (result == null && !onlyReal) return size.height
return result
}
/**
* Calls [computeDistanceToActualBaseline] and caches the result.
*
* This function must only be called from [getDistanceToBaseline] and
* [computeDistanceToActualBaseline]. Do not call this function directly from outside those two
* methods.
*/
@CallSuper
protected fun getDistanceToActualBaseline(baseline: TextBaseline): Float? {
assert(_debugDoingBaseline)
val cachedBaselinesTemp = _cachedBaselines ?: mutableMapOf()
cachedBaselinesTemp.getOrPut(baseline) { computeDistanceToActualBaseline(baseline) }
_cachedBaselines = cachedBaselinesTemp
return cachedBaselinesTemp[baseline]
}
/**
* Returns the distance from the y-coordinate of the position of the box to the y-coordinate of
* the first given baseline in the box's contents, if any, or null otherwise.
*
* Do not call this function directly. Instead, call [getDistanceToBaseline] if you need to know
* the baseline of a child from an invocation of [performLayout] or [paint] and call
* [getDistanceToActualBaseline] if you are implementing [computeDistanceToActualBaseline] and
* need to defer to a child.
*
* Subclasses should override this method to supply the distances to their baselines.
*/
protected open fun computeDistanceToActualBaseline(baseline: TextBaseline): Float? {
assert(_debugDoingBaseline)
return null
}
// The box constraints most recently received from the parent.
override val constraints: BoxConstraints?
get() = super.constraints as BoxConstraints?
override fun debugAssertDoesMeetConstraints() {
assert(constraints != null)
assert {
val runtimeType = this.runtimeType()
if (!hasSize) {
// this is called in the size= setter during layout, but in that case we have a size
assert(!debugNeedsLayout)
var contract: String
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 FlutterError(
"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"
)
}
// verify that the size is not infinite
if (!_size!!.isFinite()) {
val information = StringBuffer()
if (!constraints!!.hasBoundedWidth) {
var node: RenderBox = this
while (!node.constraints!!.hasBoundedWidth && node.parent is RenderBox)
// TODO(Migration/xbhatnag): Explicit cast to RenderBox
node = node.parent as RenderBox
information.appendln(
"The nearest ancestor providing an unbounded width constraint is:"
)
information.appendln(" ")
// TODO(Migraiton/xbhatnag): toStringShallow not implemented
// information.appendln(node.toStringShallow(joiner: '\n '));
}
if (!constraints!!.hasBoundedHeight) {
var node: RenderBox = this
while (!node.constraints!!.hasBoundedHeight && node.parent is RenderBox)
// TODO(Migration/xbhatnag): Explicit cast to RenderBox
node = node.parent as RenderBox
information.appendln(
"The nearest ancestor providing an unbounded height constraint is:"
)
information.appendln(" ")
// TODO(Migraiton/xbhatnag): toStringShallow not implemented
// information.appendln(node.toStringShallow(joiner: '\n '));
}
throw FlutterError(
"$runtimeType 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 $runtimeType were:\n" +
" $constraints\n" +
"The exact size it was given was:\n" +
" $_size\n" +
"See https://flutter.io/layout/ for more information."
)
}
// verify that the size is within the constraints
if (!constraints!!.isSatisfiedBy(_size!!)) {
throw FlutterError(
"$runtimeType 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. " +
"Contact support: https://github.com/flutter/flutter/issues/new"
)
}
if (debugCheckIntrinsicSizes) {
// verify that the intrinsics are sane
assert(!RenderObject.debugCheckingIntrinsics)
RenderObject.debugCheckingIntrinsics = true
val failures = StringBuffer()
var failureCount = 0
fun testIntrinsic(
function: (Float) -> Float,
name: String,
constraint: Float
): Float {
val result = function(constraint)
if (result < 0) {
failures.appendln(
" * $name($constraint) returned a negative value: $result"
)
failureCount += 1
}
if (!result.isFinite()) {
failures.appendln(
" * $name($constraint) returned a non-finite value: $result"
)
failureCount += 1
}
return result
}
fun testIntrinsicsForValues(
getMin: (Float) -> Float,
getMax: (Float) -> Float,
name: String,
constraint: Float
) {
val min = testIntrinsic(getMin, "getMinIntrinsic$name", constraint)
val max = testIntrinsic(getMax, "getMaxIntrinsic$name", constraint)
if (min > max) {
failures.appendln(" * getMinIntrinsic$name($constraint) returned a " +
"larger value ($min) than " +
"getMaxIntrinsic$name($constraint) ($max)"
)
failureCount += 1
}
}
testIntrinsicsForValues(
::getMinIntrinsicWidth, ::getMaxIntrinsicWidth, "Width",
Float.POSITIVE_INFINITY
)
testIntrinsicsForValues(
::getMinIntrinsicHeight, ::getMaxIntrinsicHeight, "Height",
Float.POSITIVE_INFINITY
)
if (constraints!!.hasBoundedWidth)
testIntrinsicsForValues(
::getMinIntrinsicWidth, ::getMaxIntrinsicWidth, "Width",
constraints!!.maxWidth
)
if (constraints!!.hasBoundedHeight)
testIntrinsicsForValues(
::getMinIntrinsicHeight, ::getMaxIntrinsicHeight,
"Height", constraints!!.maxHeight
)
// TODO(ianh): Test that values are internally consistent in more ways than the above.
RenderObject.debugCheckingIntrinsics = false
if (failures.isNotEmpty()) {
assert(failureCount > 0)
throw FlutterError(
"The intrinsic dimension methods of the $runtimeType class " +
"returned values that violate the intrinsic protocol contract.\n" +
"The following " +
"${if (failureCount > 1) "failures" else "failure"} was detected:\n" +
"$failures" +
"If you are not writing your own RenderBox subclass, " +
"then this is not\n" + "your fault. " +
"Contact support: https://github.com/flutter/flutter/issues/new"
)
}
}
true
}
}
override fun markNeedsLayout() {
if ((_cachedBaselines != null && _cachedBaselines!!.isNotEmpty()) ||
(_cachedIntrinsicDimensions != null && _cachedIntrinsicDimensions!!.isNotEmpty())) {
// If we have cached data, then someone must have used our data.
// Since the parent will shortly be marked dirty, we can forget that they
// used the baseline and/or intrinsic dimensions. If they use them again,
// then we'll fill the cache again, and if we get dirty again, we'll
// notify them again.
_cachedBaselines?.clear()
_cachedIntrinsicDimensions?.clear()
if (parent is RenderObject) {
markParentNeedsLayout()
return
}
}
super.markNeedsLayout()
}
override fun performResize() {
// default behavior for subclasses that have sizedByParent = true
size = constraints!!.smallest
assert(size.isFinite())
}
override fun performLayout() {
assert {
if (!sizedByParent) {
throw FlutterError(
"${this.runtimeType()} 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."
)
}
true
}
}
// /// Determines the set of render objects located at the given position.
// ///
// /// Returns true, and adds any render objects that contain the point to the
// /// given hit test result, if this render object or one of its descendants
// /// absorbs the hit (preventing objects below this one from being hit).
// /// Returns false if the hit can continue to other objects below this one.
// ///
// /// The caller is responsible for transforming [position] from global
// /// coordinates to its location relative to the origin of this [RenderBox].
// /// This [RenderBox] is responsible for checking whether the given position is
// /// within its bounds.
// ///
// /// Hit testing requires layout to be up-to-date but does not require painting
// /// to be up-to-date. That means a render object can rely upon [performLayout]
// /// having been called in [hitTest] but cannot rely upon [paint] having been
// /// called. For example, a render object might be a child of a [RenderOpacity]
// /// object, which calls [hitTest] on its children when its opacity is zero
// /// even through it does not [paint] its children.
open fun hitTest(result: HitTestResult, position: Offset): Boolean {
assert {
if (!hasSize) {
if (debugNeedsLayout) {
throw FlutterError(
"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 FlutterError(
"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."
)
}
true
}
if (_size!!.contains(position)) {
if (hitTestChildren(result, position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position))
return true
}
}
return false
}
/**
* Override this method if this render object can be hit even if its
* children were not hit.
*
* The caller is responsible for transforming [position] from global
* coordinates to its location relative to the origin of this [RenderBox].
* This [RenderBox] is responsible for checking whether the given position is
* within its bounds.
*
* Used by [hitTest]. If you override [hitTest] and do not call this
* function, then you don't need to implement this function.
*/
protected open fun hitTestSelf(position: Offset): Boolean = false
// /// Override this method to check whether any children are located at the
// /// given position.
// ///
// /// Typically children should be hit-tested in reverse paint order so that
// /// hit tests at locations where children overlap hit the child that is
// /// visually "on top" (i.e., paints later).
// ///
// /// The caller is responsible for transforming [position] from global
// /// coordinates to its location relative to the origin of this [RenderBox].
// /// This [RenderBox] is responsible for checking whether the given position is
// /// within its bounds.
// ///
// /// Used by [hitTest]. If you override [hitTest] and do not call this
// /// function, then you don't need to implement this function.
protected open fun hitTestChildren(result: HitTestResult, position: Offset): Boolean {
return false
}
/**
* Multiply the transform from the parent's coordinate system to this box's
* coordinate system into the given transform.
*
* This function is used to convert coordinate systems between boxes.
* Subclasses that apply transforms during painting should override this
* function to factor those transforms into the calculation.
*
* The [RenderBox] implementation takes care of adjusting the matrix for the
* position of the given child as determined during layout and stored on the
* child's [parentData] in the [BoxParentData.offset] field.
*/
override fun applyPaintTransform(child: RenderObject, transform: Matrix4) {
assert(child.parent == this)
assert {
if (child.parentData !is BoxParentData) {
throw FlutterError(
"${runtimeType()} does not implement applyPaintTransform.\n" +
"The following ${runtimeType()} object:\n ${toStringShallow()}\n" +
"...did not use a BoxParentData class for the parentData field of the " +
"following child:\n ${child.toStringShallow()}\n" +
"The ${runtimeType()} class inherits from RenderBox. " +
"The default applyPaintTransform implementation provided by RenderBox assumes that " +
"the children all use BoxParentData objects for their parentData field. " +
"Since ${runtimeType()} 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!!.runtimeType()})."
)
}
true
}
val childParentData = child.parentData as BoxParentData
val offset = childParentData.offset
transform.translate(offset.dx, offset.dy)
}
/**
* Convert the given point from the global coordinate system in logical pixels
* to the local coordinate system for this box.
*
* If the transform from global coordinates to local coordinates is
* degenerate, this function returns [Offset.zero].
*
* If `ancestor` is non-null, this function converts the given point from the
* coordinate system of `ancestor` (which must be an ancestor of this render
* object) instead of from the global coordinate system.
*
* This method is implemented in terms of [getTransformTo].
*/
fun globalToLocal(point: Offset, ancestor: RenderObject? = null): Offset {
val transform = getTransformTo(ancestor)
val det = transform.invert()
if (det == 0.0f)
return Offset.zero
return transform.transformPoint(point)
}
/**
* Convert the given point from the local coordinate system for this box to
* the global coordinate system in logical pixels.
*
* If `ancestor` is non-null, this function converts the given point to the
* coordinate system of `ancestor` (which must be an ancestor of this render
* object) instead of to the global coordinate system.
*
* This method is implemented in terms of [getTransformTo].
*/
fun localToGlobal(point: Offset, ancestor: RenderObject? = null): Offset {
return getTransformTo(ancestor).transformPoint(point)
}
/**
* Returns a rectangle that contains all the pixels painted by this box.
*
* The paint bounds can be larger or smaller than [size], which is the amount
* of space this box takes up during layout. For example, if this box casts a
* shadow, that shadow might extend beyond the space allocated to this box
* during layout.
*
* The paint bounds are used to size the buffers into which this box paints.
* If the box attempts to paints outside its paint bounds, there might not be
* enough memory allocated to represent the box's visual appearance, which
* can lead to undefined behavior.
*
* The returned paint bounds are in the local coordinate system of this box.
*/
override val paintBounds: Rect
get() = Offset.zero.and(size)
// TODO(Migration/xbhatnag): RenderObject does not implement HitTestTarget
// /// Override this method to handle pointer events that hit this render object.
// ///
// /// For [RenderBox] objects, the `entry` argument is a [BoxHitTestEntry]. From this
// /// object you can determine the [PointerDownEvent]'s position in local coordinates.
// /// (This is useful because [PointerEvent.position] is in global coordinates.)
// ///
// /// If you override this, consider calling [debugHandleEvent] as follows, so
// /// that you can support [debugPaintPointersEnabled]:
// ///
// /// ```dart
// /// @override
// /// void handleEvent(PointerEvent event, HitTestEntry entry) {
// /// assert(debugHandleEvent(event, entry));
// /// // ... handle the event ...
// /// }
// /// ```
// // TODO(ianh): Fix the type of the argument here once https://github.com/dart-lang/sdk/issues/25232 is fixed
// @override
// void handleEvent(PointerEvent event, covariant HitTestEntry entry) {
// super.handleEvent(event, entry);
// }
private var debugActivePointers = 0
/**
* Implements the [debugPaintPointersEnabled] debugging feature.
*
* [RenderBox] subclasses that implement [handleEvent] should call
* [debugHandleEvent] from their [handleEvent] method, as follows:
*
* ```dart
* @override
* void handleEvent(PointerEvent event, HitTestEntry entry) {
* assert(debugHandleEvent(event, entry));
* // ... handle the event ...
* }
* ```
*
* If you call this for a [PointerDownEvent], make sure you also call it for
* the corresponding [PointerUpEvent] or [PointerCancelEvent].
*/
fun debugHandleEvent(event: PointerEvent, entry: HitTestEntry): Boolean {
assert {
if (debugPaintPointersEnabled) {
if (event is PointerDownEvent) {
debugActivePointers += 1
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
debugActivePointers -= 1
}
markNeedsPaint()
}
true
}
return true
}
override fun debugPaint(context: PaintingContext, offset: Offset) {
assert {
if (debugPaintSizeEnabled)
debugPaintSize(context, offset)
if (debugPaintBaselinesEnabled)
debugPaintBaselines(context, offset)
if (debugPaintPointersEnabled)
debugPaintPointers(context, offset)
true
}
}
/**
* In debug mode, paints a border around this render box.
*
* Called for every [RenderBox] when [debugPaintSizeEnabled] is true.
*/
protected open fun debugPaintSize(context: PaintingContext, offset: Offset) {
assert {
val paint = Paint().apply {
style = PaintingStyle.stroke
strokeWidth = 1.0f
color = Color(0xFF00FFFF.toInt())
}
context.canvas.drawRect((offset.and(size)).deflate(0.5f), paint)
true
}
}
/**
* In debug mode, paints a line for each baseline.
*
* Called for every [RenderBox] when [debugPaintBaselinesEnabled] is true.
*/
protected fun debugPaintBaselines(context: PaintingContext, offset: Offset) {
// TODO(Migration/andrey): Needs TextBaseline
TODO()
// assert {
// val paint = Paint().apply {
// style = PaintingStyle.stroke
// strokeWidth = 0.25
// }
// var path : Path? = null
// // ideographic baseline
// val baselineI : Double? = getDistanceToBaseline(TextBaseline.ideographic, onlyReal: true);
// if (baselineI != null) {
// paint.color = 0xFFFFD000.toInt()
// path = Path()
// path.moveTo(offset.dx, offset.dy + baselineI)
// path.lineTo(offset.dx + size!!.width, offset.dy + baselineI)
// context.canvas.drawPath(path, paint);
// }
// // alphabetic baseline
// val baselineA : Double? = getDistanceToBaseline(TextBaseline.alphabetic, onlyReal: true);
// if (baselineA != null) {
// paint.color = 0xFF00FF00.toInt()
// path = Path();
// path.moveTo(offset.dx, offset.dy + baselineA);
// path.lineTo(offset.dx + size!!.width, offset.dy + baselineA);
// context.canvas.drawPath(path, paint);
// }
// true
// }
}
/**
* In debug mode, paints a rectangle if this render box has counted more
* pointer downs than pointer up events.
*
* Called for every [RenderBox] when [debugPaintPointersEnabled] is true.
*
* By default, events are not counted. For details on how to ensure that
* events are counted for your class, see [debugHandleEvent].
*/
protected fun debugPaintPointers(context: PaintingContext, offset: Offset) {
assert {
if (debugActivePointers > 0) {
val paint = Paint().apply {
// new Color(0x00BBBB | ((0x04000000 * depth) & 0xFF000000));
color = Color(0x00BBBB or ((0x04000000 * depth) and 0xFF000000.toInt()))
}
context.canvas.drawRect(offset.and(size), paint)
}
true
}
}
override fun debugFillProperties(properties: DiagnosticPropertiesBuilder) {
super.debugFillProperties(properties)
properties.add(DiagnosticsProperty.create("size", _size, missingIfNull = true))
}
}