/*
* 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.
*/
package androidx.compose.ui.window
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
/**
* Calculates the position of a [Popup] on screen.
*/
@Immutable
interface PopupPositionProvider {
/**
* Calculates the position of a [Popup] on screen.
*
* The window size is useful in cases where the popup is meant to be positioned next to its
* anchor instead of inside of it. The size can be used to calculate available space
* around the parent to find a spot with enough clearance (e.g. when implementing a dropdown).
* Note that positioning the popup outside of the window bounds might prevent it from being
* visible.
*
* @param anchorBounds The window relative bounds of the layout which this popup is anchored to.
* @param windowSize The size of the window containing the anchor layout.
* @param layoutDirection The layout direction of the anchor layout.
* @param popupContentSize The size of the popup's content.
*
* @return The window relative position where the popup should be positioned.
*/
fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset
}
internal class AlignmentOffsetPositionProvider(
val alignment: Alignment,
val offset: IntOffset
) : PopupPositionProvider {
override fun calculatePosition(
anchorBounds: IntRect,
windowSize: IntSize,
layoutDirection: LayoutDirection,
popupContentSize: IntSize
): IntOffset {
// TODO: Decide which is the best way to round to result without reimplementing Alignment.align
var popupPosition = IntOffset(0, 0)
// Get the aligned point inside the parent
val parentAlignmentPoint = alignment.align(
IntSize.Zero,
IntSize(anchorBounds.width, anchorBounds.height),
layoutDirection
)
// Get the aligned point inside the child
val relativePopupPos = alignment.align(
IntSize.Zero,
IntSize(popupContentSize.width, popupContentSize.height),
layoutDirection
)
// Add the position of the parent
popupPosition += IntOffset(anchorBounds.left, anchorBounds.top)
// Add the distance between the parent's top left corner and the alignment point
popupPosition += parentAlignmentPoint
// Subtract the distance between the children's top left corner and the alignment point
popupPosition -= IntOffset(relativePopupPos.x, relativePopupPos.y)
// Add the user offset
val resolvedOffset = IntOffset(
offset.x * (if (layoutDirection == LayoutDirection.Ltr) 1 else -1),
offset.y
)
popupPosition += resolvedOffset
return popupPosition
}
}