[go: nahoru, domu]

blob: 94de70461f374299e692832060f10db65b8b258d [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")
package androidx.ui.core
import androidx.ui.geometry.Rect
import androidx.ui.graphics.Canvas
import androidx.ui.graphics.Color
import androidx.ui.graphics.Paint
import androidx.ui.graphics.PaintingStyle
import androidx.ui.unit.Density
import androidx.ui.unit.IntPx
import androidx.ui.unit.IntPxPosition
import androidx.ui.unit.IntPxSize
import androidx.ui.unit.PxPosition
import androidx.ui.unit.round
import androidx.ui.unit.toPx
import androidx.ui.unit.toPxPosition
import androidx.ui.unit.toPxSize
private val Unmeasured = IntPxSize(IntPx.Zero, IntPx.Zero)
/**
* Measurable and Placeable type that has a position.
*/
internal sealed class LayoutNodeWrapper(
internal val layoutNode: LayoutNode
) : Placeable(), Measurable, LayoutCoordinates {
protected open val wrapped: LayoutNodeWrapper? = null
internal var wrappedBy: LayoutNodeWrapper? = null
var position = IntPxPosition.Origin
private var dirtySize: Boolean = false
fun hasDirtySize(): Boolean = dirtySize || (wrapped?.hasDirtySize() ?: false)
override var size: IntPxSize = Unmeasured
protected set(value) {
if (field != value) dirtySize = true
field = value
}
override val parentCoordinates: LayoutCoordinates?
get() {
check(isAttached) { ExpectAttachedLayoutCoordinates }
return layoutNode.layoutNodeWrapper.wrappedBy
}
/**
* Assigns a layout size to this [LayoutNodeWrapper] given the assigned innermost size
* from the call to [MeasureScope.layout].
* @return The size after adjusting for the modifier.
*/
abstract fun layoutSize(innermostSize: IntPxSize): IntPxSize
/**
* Places the modified child.
*/
abstract fun place(position: IntPxPosition)
/**
* Places the modified child.
*/
final override fun performPlace(position: IntPxPosition) {
place(position)
dirtySize = false
}
/**
* Draws the content of the LayoutNode
*/
abstract fun draw(canvas: Canvas, density: Density)
override fun childToLocal(child: LayoutCoordinates, childLocal: PxPosition): PxPosition {
check(isAttached) { ExpectAttachedLayoutCoordinates }
check(child.isAttached) { "Child $child is not attached!" }
var wrapper = child as LayoutNodeWrapper
var position = childLocal
while (wrapper !== this) {
position = wrapper.toParentPosition(position)
val parent = wrapper.wrappedBy
check(parent != null) {
"childToLocal: child parameter is not a child of the LayoutCoordinates"
}
wrapper = parent
}
return position
}
override fun globalToLocal(global: PxPosition): PxPosition {
check(isAttached) { ExpectAttachedLayoutCoordinates }
val wrapper = wrappedBy ?: return fromParentPosition(
global - layoutNode.requireOwner().calculatePosition().toPxPosition()
)
return fromParentPosition(wrapper.globalToLocal(global))
}
override fun localToGlobal(local: PxPosition): PxPosition {
return localToRoot(local) + layoutNode.requireOwner().calculatePosition()
}
override fun localToRoot(local: PxPosition): PxPosition {
check(isAttached) { ExpectAttachedLayoutCoordinates }
var wrapper: LayoutNodeWrapper? = this
var position = local
while (wrapper != null) {
position = wrapper.toParentPosition(position)
wrapper = wrapper.wrappedBy
}
return position
}
protected inline fun withPositionTranslation(canvas: Canvas, block: (Canvas) -> Unit) {
val x = position.x.value.toFloat()
val y = position.y.value.toFloat()
canvas.translate(x, y)
block(canvas)
canvas.translate(-x, -y)
}
/**
* Converts [position] in the local coordinate system to a [PxPosition] in the
* [parentCoordinates] coordinate system.
*/
open fun toParentPosition(position: PxPosition): PxPosition = position + this.position
/**
* Converts [position] in the [parentCoordinates] coordinate system to a [PxPosition] in the
* local coordinate system.
*/
open fun fromParentPosition(position: PxPosition): PxPosition = position - this.position
protected fun drawBorder(canvas: Canvas, paint: Paint) {
val rect = Rect(
left = 0.5f,
top = 0.5f,
right = size.width.value.toFloat() - 0.5f,
bottom = size.height.value.toFloat() - 0.5f
)
canvas.drawRect(rect, paint)
}
/**
* Detaches the LayoutNodeWrapper and its wrapped LayoutNodeWrapper from an active LayoutNode.
* This will be called whenever the modifier chain is replaced and the LayoutNodeWrappers
* are recreated.
*/
abstract fun detach()
internal companion object {
const val ExpectAttachedLayoutCoordinates = "LayoutCoordinate operations are only valid " +
"when isAttached is true"
}
}
/**
* [LayoutNodeWrapper] with default implementations for methods.
*/
internal sealed class DelegatingLayoutNodeWrapper(
override val wrapped: LayoutNodeWrapper
) : LayoutNodeWrapper(wrapped.layoutNode) {
override val providedAlignmentLines: Set<AlignmentLine>
get() = wrapped.providedAlignmentLines
private var _isAttached = true
override val isAttached: Boolean
get() = _isAttached && layoutNode.isAttached()
init {
wrapped.wrappedBy = this
}
override fun layoutSize(innermostSize: IntPxSize): IntPxSize {
size = wrapped.layoutSize(innermostSize)
return size
}
override fun draw(canvas: Canvas, density: Density) {
withPositionTranslation(canvas) {
wrapped.draw(canvas, density)
}
}
override fun get(line: AlignmentLine): IntPx? {
val value = wrapped[line] ?: return null
val px = value.toPx()
val pos = wrapped.toParentPosition(PxPosition(px, px))
return if (line is HorizontalAlignmentLine) pos.y.round() else pos.y.round()
}
override fun place(position: IntPxPosition) {
this.position = position
wrapped.place(IntPxPosition.Origin)
}
override fun measure(constraints: Constraints): Placeable {
wrapped.measure(constraints)
return this
}
override fun minIntrinsicWidth(height: IntPx) = wrapped.minIntrinsicWidth(height)
override fun maxIntrinsicWidth(height: IntPx) = wrapped.maxIntrinsicWidth(height)
override fun minIntrinsicHeight(width: IntPx) = wrapped.minIntrinsicHeight(width)
override fun maxIntrinsicHeight(width: IntPx) = wrapped.maxIntrinsicHeight(width)
override val parentData: Any? get() = wrapped.parentData
override fun detach() {
_isAttached = false
wrapped.detach()
}
}
internal class InnerPlaceable(
layoutNode: LayoutNode
) : LayoutNodeWrapper(layoutNode), Density by layoutNode.measureScope {
override val providedAlignmentLines: Set<AlignmentLine>
get() = layoutNode.providedAlignmentLines.keys
override val isAttached: Boolean
get() = layoutNode.isAttached()
override fun measure(constraints: Constraints): Placeable {
val layoutResult = layoutNode.measureBlocks.measure(
layoutNode.measureScope,
layoutNode.layoutChildren,
constraints
)
layoutNode.handleLayoutResult(layoutResult)
return this
}
override val parentData: Any?
get() = layoutNode.parentDataNode?.value
override fun minIntrinsicWidth(height: IntPx): IntPx =
layoutNode.measureBlocks.minIntrinsicWidth(
layoutNode.measureScope,
layoutNode.layoutChildren,
height
)
override fun minIntrinsicHeight(width: IntPx): IntPx =
layoutNode.measureBlocks.minIntrinsicHeight(
layoutNode.measureScope,
layoutNode.layoutChildren,
width
)
override fun maxIntrinsicWidth(height: IntPx): IntPx =
layoutNode.measureBlocks.maxIntrinsicWidth(
layoutNode.measureScope,
layoutNode.layoutChildren,
height
)
override fun maxIntrinsicHeight(width: IntPx): IntPx =
layoutNode.measureBlocks.maxIntrinsicHeight(
layoutNode.measureScope,
layoutNode.layoutChildren,
width
)
override fun place(position: IntPxPosition) {
layoutNode.isPlaced = true
val wasMoved = position != this.position
this.position = position
if (wasMoved) {
layoutNode.owner?.onPositionChange(layoutNode)
}
layoutNode.layout()
}
override fun layoutSize(innermostSize: IntPxSize): IntPxSize {
size = innermostSize
return innermostSize
}
override operator fun get(line: AlignmentLine): IntPx? {
return layoutNode.calculateAlignmentLines()[line]
}
override fun draw(canvas: Canvas, density: Density) {
withPositionTranslation(canvas) {
val owner = layoutNode.requireOwner()
val sizePx = size.toPxSize()
layoutNode.children.forEach { child -> owner.callDraw(canvas, child, sizePx) }
if (owner.showLayoutBounds) {
drawBorder(canvas, innerBoundsPaint)
}
}
}
override fun detach() {
// Do nothing. InnerPlaceable only is detached when the LayoutNode is detached.
}
internal companion object {
val innerBoundsPaint = Paint().also { paint ->
paint.color = Color.Red
paint.strokeWidth = 1f
paint.style = PaintingStyle.stroke
}
}
}
internal class ModifiedParentDataNode(
wrapped: LayoutNodeWrapper,
val parentDataModifier: ParentDataModifier
) : DelegatingLayoutNodeWrapper(wrapped) {
override val parentData: Any?
get() = with(parentDataModifier) {
/**
* ParentData provided through the parentData node will override the data provided
* through a modifier
*/
layoutNode.parentDataNode?.value
?: layoutNode.measureScope.modifyParentData(wrapped.parentData)
}
}
internal class ModifiedLayoutNode(
wrapped: LayoutNodeWrapper,
val layoutModifier: LayoutModifier
) : DelegatingLayoutNodeWrapper(wrapped) {
/**
* The [Placeable] returned by measuring [wrapped] in [measure].
* Used to avoid creating more wrapper objects than necessary since [ModifiedLayoutNode]
* also
*/
private var measuredPlaceable: Placeable? = null
/**
* The [Constraints] used in the current measurement of this modified node wrapper.
* See [withMeasuredConstraints]
*/
private var measuredConstraints: Constraints? = null
/**
* Sets [measuredConstraints] for the duration of [block].
*/
private inline fun <R> withMeasuredConstraints(
constraints: Constraints,
block: () -> R
): R = try {
measuredConstraints = constraints
block()
} finally {
measuredConstraints = null
}
override fun measure(constraints: Constraints): Placeable = with(layoutModifier) {
updateLayoutDirection()
val measureResult = withMeasuredConstraints(constraints) {
wrapped.measure(layoutNode.measureScope.modifyConstraints(constraints))
}
measuredPlaceable = measureResult
this@ModifiedLayoutNode
}
override fun minIntrinsicWidth(height: IntPx): IntPx = with(layoutModifier) {
updateLayoutDirection()
layoutNode.measureScope.minIntrinsicWidthOf(wrapped, height)
}
override fun maxIntrinsicWidth(height: IntPx): IntPx = with(layoutModifier) {
updateLayoutDirection()
layoutNode.measureScope.maxIntrinsicWidthOf(wrapped, height)
}
override fun minIntrinsicHeight(width: IntPx): IntPx = with(layoutModifier) {
updateLayoutDirection()
layoutNode.measureScope.minIntrinsicHeightOf(wrapped, width)
}
override fun maxIntrinsicHeight(width: IntPx): IntPx = with(layoutModifier) {
updateLayoutDirection()
layoutNode.measureScope.maxIntrinsicHeightOf(wrapped, width)
}
override fun place(position: IntPxPosition) {
this.position = position
val placeable = measuredPlaceable ?: error("Placeable not measured")
val relativePosition = with(layoutModifier) {
layoutNode.measureScope.modifyPosition(placeable.size, size)
}
placeable.place(relativePosition)
}
override operator fun get(line: AlignmentLine): IntPx? = with(layoutModifier) {
return layoutNode.measureScope.modifyAlignmentLine(line, super.get(line))
}
override fun layoutSize(innermostSize: IntPxSize): IntPxSize = with(layoutModifier) {
val constraints = measuredConstraints ?: error("must be called during measurement")
layoutNode.measureScope.modifySize(constraints, wrapped.layoutSize(innermostSize)).also {
size = it
}
}
override fun draw(canvas: Canvas, density: Density) {
withPositionTranslation(canvas) {
wrapped.draw(canvas, density)
if (layoutNode.requireOwner().showLayoutBounds) {
drawBorder(canvas, modifierBoundsPaint)
}
}
}
internal companion object {
val modifierBoundsPaint = Paint().also { paint ->
paint.color = Color.Blue
paint.strokeWidth = 1f
paint.style = PaintingStyle.stroke
}
}
private fun updateLayoutDirection() = with(layoutModifier) {
layoutNode.measureScope.layoutDirection = layoutNode.measureScope.modifyLayoutDirection()
}
}
internal class ModifiedDrawNode(
wrapped: LayoutNodeWrapper,
val drawModifier: DrawModifier
) : DelegatingLayoutNodeWrapper(wrapped), () -> Unit {
private var density: Density? = null
private var canvas: Canvas? = null
override fun draw(canvas: Canvas, density: Density) {
withPositionTranslation(canvas) {
this.density = density
this.canvas = canvas
val pxSize = size.toPxSize()
drawModifier.draw(density, this, canvas, pxSize)
this.density = null
this.canvas = null
}
}
// This is the implementation of drawContent()
override fun invoke() {
wrapped.draw(canvas!!, density!!)
}
}