mirror of
https://github.com/fork-maintainers/iceraven-browser
synced 2024-11-03 23:15:31 +00:00
[fenix] For https://github.com/mozilla-mobile/fenix/issues/26584 - Add support to align the CFR composable inside a wider anchor
Supported anchorings will now be: - INDICATOR_CENTERED_IN_ANCHOR - previous functionality - allows to have the indicator point to exactly the middle of a smaller anchor. - BODY_TO_ANCHOR_CENTER - new default - allows to align the popup inside a wider anchor - BODY_TO_ANCHOR_START - new anchoring - allows to align the popup flushed to it's anchor's start.
This commit is contained in:
parent
35e715b06b
commit
262a2128f3
@ -9,12 +9,14 @@ import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
/**
|
||||
* Properties used to customize the behavior of a [CFRPopup].
|
||||
*
|
||||
* @property popupWidth Width of the popup. Defaults to [CFRPopup.DEFAULT_WIDTH].
|
||||
* @property popupAlignment Where in relation to it's anchor should the popup be placed.
|
||||
* @property indicatorDirection The direction the indicator arrow is pointing.
|
||||
* @property dismissOnBackPress Whether the popup can be dismissed by pressing the back button.
|
||||
* If true, pressing the back button will also call onDismiss().
|
||||
@ -29,6 +31,7 @@ import java.lang.ref.WeakReference
|
||||
*/
|
||||
data class CFRPopupProperties(
|
||||
val popupWidth: Dp = CFRPopup.DEFAULT_WIDTH.dp,
|
||||
val popupAlignment: PopupAlignment = PopupAlignment.BODY_TO_ANCHOR_CENTER,
|
||||
val indicatorDirection: CFRPopup.IndicatorDirection = CFRPopup.IndicatorDirection.UP,
|
||||
val dismissOnBackPress: Boolean = true,
|
||||
val dismissOnClickOutside: Boolean = true,
|
||||
@ -90,6 +93,29 @@ class CFRPopup(
|
||||
DOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible alignments of the popup in relation to it's anchor.
|
||||
*/
|
||||
enum class PopupAlignment {
|
||||
/**
|
||||
* The popup body will be centered in the space occupied by the anchor.
|
||||
* Recommended to be used when the anchor is wider than the popup.
|
||||
*/
|
||||
BODY_TO_ANCHOR_CENTER,
|
||||
|
||||
/**
|
||||
* The popup body will be shown aligned to exactly the anchor start.
|
||||
*/
|
||||
BODY_TO_ANCHOR_START,
|
||||
|
||||
/**
|
||||
* The popup will be aligned such that the indicator arrow will point to exactly the middle of the anchor.
|
||||
* Recommended to be used when there are multiple widgets displayed horizontally so that this will allow
|
||||
* to indicate exactly which widget the popup refers to.
|
||||
*/
|
||||
INDICATOR_CENTERED_IN_ANCHOR
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Default width for all CFRs.
|
||||
|
@ -28,14 +28,17 @@ import androidx.compose.ui.window.PopupPositionProvider
|
||||
import androidx.compose.ui.window.PopupProperties
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.marginStart
|
||||
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.IndicatorDirection.DOWN
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.IndicatorDirection.UP
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment.BODY_TO_ANCHOR_CENTER
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment.BODY_TO_ANCHOR_START
|
||||
import org.mozilla.fenix.compose.cfr.CFRPopup.PopupAlignment.INDICATOR_CENTERED_IN_ANCHOR
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.gecko.GeckoScreenOrientation
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
@ -123,17 +126,16 @@ internal class CFRPopupFullScreenLayout(
|
||||
anchor.getLocationOnScreen(this)
|
||||
}
|
||||
|
||||
val anchorXCoordMiddle = Pixels(anchorLocation.first() + anchor.width / 2)
|
||||
val indicatorArrowHeight = Pixels(
|
||||
CFRPopup.DEFAULT_INDICATOR_HEIGHT.dp.toPx()
|
||||
)
|
||||
|
||||
val popupBounds = computePopupHorizontalBounds(
|
||||
anchorMiddleXCoord = anchorXCoordMiddle,
|
||||
arrowIndicatorWidth = Pixels(CFRPopupShape.getIndicatorBaseWidthForHeight(indicatorArrowHeight.value)),
|
||||
)
|
||||
|
||||
val indicatorOffset = computeIndicatorArrowStartCoord(
|
||||
anchorMiddleXCoord = anchorXCoordMiddle,
|
||||
anchorMiddleXCoord = Pixels(anchorLocation.first() + anchor.width / 2),
|
||||
popupStartCoord = popupBounds.startCoord,
|
||||
arrowIndicatorWidth = Pixels(
|
||||
CFRPopupShape.getIndicatorBaseWidthForHeight(indicatorArrowHeight.value)
|
||||
@ -219,24 +221,35 @@ internal class CFRPopupFullScreenLayout(
|
||||
* This assumes anchoring is indicated with an arrow to the horizontal middle of the anchor with the popup's
|
||||
* body potentially extending to the `start` of the arrow indicator.
|
||||
*
|
||||
* @param anchorMiddleXCoord x-coordinate for the middle of the anchor.
|
||||
* @param arrowIndicatorWidth x-distance the arrow indicator occupies.
|
||||
*/
|
||||
@Composable
|
||||
private fun computePopupHorizontalBounds(
|
||||
anchorMiddleXCoord: Pixels,
|
||||
arrowIndicatorWidth: Pixels
|
||||
): PopupHorizontalBounds {
|
||||
val arrowIndicatorHalfWidth = arrowIndicatorWidth.value / 2
|
||||
|
||||
return if (LocalConfiguration.current.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
// Push the popup as far to the start as needed including any needed paddings.
|
||||
val startCoord = Pixels(
|
||||
(anchorMiddleXCoord.value - arrowIndicatorHalfWidth)
|
||||
.minus(properties.indicatorArrowStartOffset.toPx())
|
||||
.minus(CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
.coerceAtLeast(getLeftInsets())
|
||||
)
|
||||
val startCoord = when (properties.popupAlignment) {
|
||||
BODY_TO_ANCHOR_START -> {
|
||||
val visibleAnchorStart = anchor.x.roundToInt() + anchor.paddingStart + anchor.marginStart
|
||||
Pixels(visibleAnchorStart - CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
}
|
||||
BODY_TO_ANCHOR_CENTER -> {
|
||||
val popupWidth = (properties.popupWidth + CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp * 2).toPx()
|
||||
Pixels(((anchor.x.roundToInt() + anchor.width) - popupWidth) / 2)
|
||||
}
|
||||
INDICATOR_CENTERED_IN_ANCHOR -> {
|
||||
val anchorMiddleXCoord = Pixels(anchor.x.roundToInt() + anchor.width / 2)
|
||||
// Push the popup as far to the start as needed including any needed paddings.
|
||||
Pixels(
|
||||
(anchorMiddleXCoord.value - arrowIndicatorHalfWidth)
|
||||
.minus(properties.indicatorArrowStartOffset.toPx())
|
||||
.minus(CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
.coerceAtLeast(getLeftInsets())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PopupHorizontalBounds(
|
||||
startCoord = startCoord,
|
||||
@ -247,20 +260,30 @@ internal class CFRPopupFullScreenLayout(
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val startCoord = Pixels(
|
||||
// Push the popup as far to the start (in RTL) as possible.
|
||||
anchorMiddleXCoord.value
|
||||
.plus(arrowIndicatorHalfWidth)
|
||||
.plus(properties.indicatorArrowStartOffset.toPx())
|
||||
.plus(CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
.coerceAtMost(
|
||||
LocalDensity.current.run {
|
||||
LocalConfiguration.current.screenWidthDp.dp.toPx()
|
||||
}
|
||||
.roundToInt()
|
||||
val startCoord = when (properties.popupAlignment) {
|
||||
BODY_TO_ANCHOR_START -> {
|
||||
val visibleAnchorEnd = anchor.x.roundToInt() + anchor.width - anchor.paddingStart
|
||||
Pixels(visibleAnchorEnd + CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
}
|
||||
BODY_TO_ANCHOR_CENTER -> {
|
||||
val popupWidth = (properties.popupWidth + CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp * 2).toPx()
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp.toPx()
|
||||
Pixels(screenWidth - ((anchor.x.roundToInt() + anchor.width) - popupWidth) / 2)
|
||||
}
|
||||
INDICATOR_CENTERED_IN_ANCHOR -> {
|
||||
val anchorMiddleXCoord = Pixels(anchor.x.roundToInt() + anchor.width / 2)
|
||||
Pixels(
|
||||
// Push the popup as far to the start (in RTL) as possible.
|
||||
anchorMiddleXCoord.value
|
||||
.plus(arrowIndicatorHalfWidth)
|
||||
.plus(properties.indicatorArrowStartOffset.toPx())
|
||||
.plus(CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx())
|
||||
.coerceAtMost(LocalConfiguration.current.screenWidthDp.dp.toPx())
|
||||
.plus(getLeftInsets())
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PopupHorizontalBounds(
|
||||
startCoord = startCoord,
|
||||
endCoord = Pixels(
|
||||
@ -286,18 +309,21 @@ internal class CFRPopupFullScreenLayout(
|
||||
popupStartCoord: Pixels,
|
||||
arrowIndicatorWidth: Pixels
|
||||
): Pixels {
|
||||
val arrowIndicatorHalfWidth = arrowIndicatorWidth.value / 2
|
||||
return when (properties.popupAlignment) {
|
||||
BODY_TO_ANCHOR_START,
|
||||
BODY_TO_ANCHOR_CENTER -> Pixels(properties.indicatorArrowStartOffset.toPx())
|
||||
INDICATOR_CENTERED_IN_ANCHOR -> {
|
||||
val arrowIndicatorHalfWidth = arrowIndicatorWidth.value / 2
|
||||
if (LocalConfiguration.current.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
val visiblePopupStartCoord = popupStartCoord.value + CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx()
|
||||
|
||||
return if (LocalConfiguration.current.layoutDirection == View.LAYOUT_DIRECTION_LTR) {
|
||||
val visiblePopupStartCoord = popupStartCoord.value + CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx()
|
||||
val arrowIndicatorStartCoord = anchorMiddleXCoord.value - arrowIndicatorHalfWidth
|
||||
Pixels(anchorMiddleXCoord.value - arrowIndicatorHalfWidth - visiblePopupStartCoord)
|
||||
} else {
|
||||
val visiblePopupEndCoord = popupStartCoord.value - CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx()
|
||||
|
||||
Pixels((visiblePopupStartCoord - arrowIndicatorStartCoord).absoluteValue)
|
||||
} else {
|
||||
val indicatorStartCoord = popupStartCoord.value - CFRPopup.DEFAULT_HORIZONTAL_PADDING.dp.toPx() -
|
||||
anchorMiddleXCoord.value - arrowIndicatorHalfWidth
|
||||
|
||||
Pixels(indicatorStartCoord.absoluteValue)
|
||||
Pixels(visiblePopupEndCoord - anchorMiddleXCoord.value - arrowIndicatorHalfWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user