mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-26 16:15:51 +01:00
Material3's DropdownMenu clamps every popup to 48dp below the window's top edge (MenuVerticalMargin in its position provider); the margin is applied after the offset parameter and the provider is not replaceable, so the in-window menu bar's menus all opened with a visible gap hanging below the bar. Replace them with a plain focusable Popup in MenuDefaults styling and an own position provider that puts the menu directly under its anchor, flipping above only when there is no room below. The anchor box takes the full bar height so the menu starts at the bar's bottom edge, not at the centred title text. Claude-Session: https://claude.ai/code/session_01BegdsGSNNxKR6VPNPNvfoc Co-authored-by: Claude <noreply@anthropic.com>
66 lines
2.3 KiB
Kotlin
66 lines
2.3 KiB
Kotlin
package kst4contest.view.compose
|
|
|
|
import androidx.compose.ui.unit.IntOffset
|
|
import androidx.compose.ui.unit.IntRect
|
|
import androidx.compose.ui.unit.IntSize
|
|
import androidx.compose.ui.unit.LayoutDirection
|
|
import org.junit.jupiter.api.Assertions.assertEquals
|
|
import org.junit.jupiter.api.Test
|
|
|
|
/**
|
|
* The menu row's own popup placement, as pure arithmetic.
|
|
*
|
|
* Material3's DropdownMenuPositionProvider refuses any y closer than 48 dp to the window's
|
|
* top edge, which pushed every menu of the bar down to that margin. This provider must not:
|
|
* a menu-bar menu starts flush under its anchor, whatever the distance to the window edge.
|
|
*/
|
|
class MenuRowPopupPositionProviderTest {
|
|
|
|
private val window = IntSize(800, 600)
|
|
private val menu = IntSize(200, 150)
|
|
|
|
private fun positionOf(
|
|
anchor: IntRect,
|
|
window: IntSize = this.window,
|
|
direction: LayoutDirection = LayoutDirection.Ltr,
|
|
menu: IntSize = this.menu,
|
|
) = MenuRowPopupPositionProvider.calculatePosition(
|
|
anchorBounds = anchor,
|
|
windowSize = window,
|
|
layoutDirection = direction,
|
|
popupContentSize = menu,
|
|
)
|
|
|
|
/** The regression itself: an anchor at the very top gets its menu directly below it. */
|
|
@Test
|
|
fun opensFlushUnderAnAnchorAtTheTopOfTheWindow() {
|
|
val position = positionOf(IntRect(0, 0, 40, 30))
|
|
assertEquals(IntOffset(0, 30), position)
|
|
}
|
|
|
|
@Test
|
|
fun staysInsideTheWindowWhenTheAnchorIsNearTheRightEdge() {
|
|
val position = positionOf(IntRect(760, 0, 800, 30))
|
|
assertEquals(IntOffset(600, 30), position)
|
|
}
|
|
|
|
@Test
|
|
fun opensAboveTheAnchorWhenThereIsNoRoomBelow() {
|
|
val position = positionOf(IntRect(0, 560, 40, 590))
|
|
assertEquals(IntOffset(0, 410), position)
|
|
}
|
|
|
|
/** No room on either side: pinned to the window's bottom edge rather than clipped. */
|
|
@Test
|
|
fun staysInsideAWindowTooSmallForTheMenu() {
|
|
val position = positionOf(IntRect(0, 40, 40, 70), window = IntSize(800, 100), menu = IntSize(200, 90))
|
|
assertEquals(IntOffset(0, 10), position)
|
|
}
|
|
|
|
@Test
|
|
fun alignsTheRightEdgesInRightToLeftLayouts() {
|
|
val position = positionOf(IntRect(600, 0, 640, 30), direction = LayoutDirection.Rtl)
|
|
assertEquals(IntOffset(440, 30), position)
|
|
}
|
|
}
|