Skip to content

Commit 938b86f

Browse files
committed
Fixed context menu position when scaling is different than 100%
Fixes #290
1 parent de02829 commit 938b86f

File tree

1 file changed

+39
-30
lines changed

1 file changed

+39
-30
lines changed

src/main/kotlin/com/jetpackduba/gitnuro/ui/context_menu/ContextMenu.kt

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.jetpackduba.gitnuro.ui.context_menu
22

33
import androidx.compose.foundation.*
4+
import androidx.compose.foundation.gestures.awaitEachGesture
45
import androidx.compose.foundation.layout.*
56
import androidx.compose.foundation.text.TextContextMenu
67
import androidx.compose.foundation.text.selection.DisableSelection
@@ -23,15 +24,13 @@ import androidx.compose.ui.graphics.painter.Painter
2324
import androidx.compose.ui.input.InputMode
2425
import androidx.compose.ui.input.InputModeManager
2526
import androidx.compose.ui.input.key.*
26-
import androidx.compose.ui.input.pointer.PointerIcon
27-
import androidx.compose.ui.input.pointer.isSecondary
28-
import androidx.compose.ui.input.pointer.pointerHoverIcon
29-
import androidx.compose.ui.input.pointer.pointerInput
27+
import androidx.compose.ui.input.pointer.*
3028
import androidx.compose.ui.layout.onGloballyPositioned
3129
import androidx.compose.ui.platform.LocalFocusManager
3230
import androidx.compose.ui.platform.LocalInputModeManager
3331
import androidx.compose.ui.platform.LocalLocalization
3432
import androidx.compose.ui.unit.*
33+
import androidx.compose.ui.util.fastAll
3534
import androidx.compose.ui.window.Popup
3635
import androidx.compose.ui.window.PopupPositionProvider
3736
import androidx.compose.ui.window.PopupProperties
@@ -76,44 +75,43 @@ fun DropDownMenu(showIcons: Boolean = true, items: () -> List<ContextMenuElement
7675
@OptIn(ExperimentalComposeUiApi::class)
7776
@Composable
7877
private fun Modifier.contextMenu(enabled: Boolean, items: () -> List<ContextMenuElement>): Modifier {
79-
val (mouseEvent, setMouseEvent) = remember { mutableStateOf<MouseEvent?>(null) }
80-
81-
val modifier = this.pointerInput(enabled) {
82-
awaitPointerEventScope {
83-
while (true) {
84-
val lastPointerEvent = awaitFirstDownEvent()
85-
val lastMouseEvent = lastPointerEvent.awtEventOrNull
86-
87-
if (lastMouseEvent != null && enabled) {
88-
if (lastPointerEvent.button.isSecondary) {
89-
lastPointerEvent.changes.forEach {
90-
it.consume()
91-
}
78+
val (mouseOffset, setMouseOffset) = remember { mutableStateOf<Offset?>(null) }
79+
val (elementPosition, setElementPosition) = remember { mutableStateOf<Offset?>(null) }
80+
81+
val modifier = this
82+
.onGloballyPositioned { layoutCoordinates ->
83+
val offsetToRoot = layoutCoordinates.localToRoot(Offset.Zero)
84+
setElementPosition(offsetToRoot)
85+
}
86+
.pointerInput(items, enabled) {
87+
awaitEachGesture {
88+
val event = awaitEventFirstDown()
9289

93-
val currentCheck = System.currentTimeMillis()
94-
if (lastCheck != 0L && currentCheck - lastCheck < MIN_TIME_BETWEEN_POPUPS_IN_MS) {
95-
println("Popup ignored!")
96-
} else {
97-
lastCheck = currentCheck
90+
if (enabled && event.buttons.isSecondaryPressed) {
91+
println("Secondary is pressed")
92+
val currentCheck = System.currentTimeMillis()
9893

99-
setMouseEvent(lastMouseEvent)
100-
}
94+
if (lastCheck != 0L && currentCheck - lastCheck < MIN_TIME_BETWEEN_POPUPS_IN_MS) {
95+
println("Popup ignored!")
96+
} else {
97+
lastCheck = currentCheck
98+
event.changes.forEach { it.consume() }
99+
setMouseOffset(event.changes[0].position)
101100
}
102101
}
103102
}
104103
}
105-
}
106104

107-
if (mouseEvent != null) {
105+
if (mouseOffset != null && elementPosition != null) {
108106
val contextMenuElements = items()
109107
if (contextMenuElements.isNotEmpty()) {
110108
DisableSelection {
111109
showPopup(
112110
showIcons = true,
113-
x = mouseEvent.x,
114-
y = mouseEvent.y,
111+
x = (elementPosition.x + mouseOffset.x).toInt(),
112+
y = (elementPosition.y + mouseOffset.y).toInt(),
115113
contextMenuElements = contextMenuElements,
116-
onDismissRequest = { setMouseEvent(null) },
114+
onDismissRequest = { setMouseOffset(null) },
117115
)
118116
}
119117
}
@@ -122,6 +120,17 @@ private fun Modifier.contextMenu(enabled: Boolean, items: () -> List<ContextMenu
122120
return modifier
123121
}
124122

123+
private suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
124+
var event: PointerEvent
125+
do {
126+
event = awaitPointerEvent()
127+
} while (
128+
!event.changes.fastAll { it.changedToDown() }
129+
)
130+
return event
131+
}
132+
133+
125134
@Composable
126135
private fun Modifier.dropdownMenu(showIcons: Boolean, items: () -> List<ContextMenuElement>): Modifier {
127136
val (isClicked, setIsClicked) = remember { mutableStateOf(false) }
@@ -268,7 +277,7 @@ fun TextEntry(
268277
contextTextEntry.onClick()
269278
}
270279
.pointerHoverIcon(PointerIcon.Default)
271-
.padding(horizontal = if(showIcons) 8.dp else 12.dp, vertical = 8.dp),
280+
.padding(horizontal = if (showIcons) 8.dp else 12.dp, vertical = 8.dp),
272281
verticalAlignment = Alignment.CenterVertically,
273282
) {
274283
if (showIcons) {

0 commit comments

Comments
 (0)