Skip to content

Commit

Permalink
Merge pull request #5 from arkivanov/key-modifiers
Browse files Browse the repository at this point in the history
Support shift and ctrl keys
  • Loading branch information
arkivanov authored Feb 5, 2024
2 parents e2a548a + fcddd15 commit dbc526d
Showing 1 changed file with 83 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.arkivanov.minesweeper.game
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
Expand All @@ -15,6 +16,8 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
Expand All @@ -25,14 +28,23 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.pointer.changedToDown
import androidx.compose.ui.input.pointer.isPrimaryPressed
import androidx.compose.ui.input.pointer.isSecondaryPressed
import androidx.compose.ui.input.pointer.isTertiaryPressed
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.value.MutableValue
Expand All @@ -51,13 +63,12 @@ internal fun GameContent(component: GameComponent, modifier: Modifier = Modifier

CompositionLocalProvider(LocalGameIcons provides gameIcons()) {
Box(modifier = modifier, contentAlignment = Alignment.Center) {
Column {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RestartButton(
isWin = gameStatus == GameStatus.WIN,
isFailed = gameStatus == GameStatus.FAILED,
isTrying = pressMode != PressMode.NONE,
onClick = component::onRestartClicked,
modifier = Modifier.align(Alignment.CenterHorizontally),
)

Spacer(modifier = Modifier.height(16.dp))
Expand All @@ -83,6 +94,10 @@ internal fun GameContent(component: GameComponent, modifier: Modifier = Modifier
}
}
}

Spacer(modifier = Modifier.height(16.dp))

ControlsInfo()
}
}
}
Expand Down Expand Up @@ -135,6 +150,26 @@ private fun RestartButton(
)
}

@Composable
private fun ControlsInfo(modifier: Modifier = Modifier) {
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Left click: ", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.body2)
Text(text = "dig a cell", style = MaterialTheme.typography.body2)
}

Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Right click (or Ctrl + Left click): ", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.body2)
Text(text = "flag a cell", style = MaterialTheme.typography.body2)
}

Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Middle click (or Left + Right click, or Shift + Left click): ", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.body2)
Text(text = "dig all adjacent cells", style = MaterialTheme.typography.body2)
}
}
}

@Composable
private fun Cell.painter(): Painter =
when (status) {
Expand All @@ -161,36 +196,56 @@ private fun Modifier.touchHandler(
onTertiaryTouched: (cellX: Int, cellY: Int) -> Unit,
onReleased: (cellX: Int, cellY: Int) -> Unit,
): Modifier =
pointerInput(gridWidth, gridHeight) {
val cellWidth = size.width.toFloat() / gridWidth.toFloat()
val cellHeight = size.height.toFloat() / gridHeight.toFloat()

awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
val change = event.changes.last()
val offset = change.position
val cellX = (offset.x / cellWidth).toInt().coerceIn(0 until gridWidth)
val cellY = (offset.y / cellHeight).toInt().coerceIn(0 until gridHeight)
val isPrimaryPressed = event.buttons.isPrimaryPressed
val isSecondaryPressed = event.buttons.isSecondaryPressed
val isTertiaryPressed = event.buttons.isTertiaryPressed

if (change.pressed) {
if (isPrimaryPressed && !isSecondaryPressed && !isTertiaryPressed) {
onPrimaryTouched(cellX, cellY)
} else if (!isPrimaryPressed && isSecondaryPressed && !isTertiaryPressed && change.changedToDown()) {
onSecondaryPressed(cellX, cellY)
} else if (!isPrimaryPressed && !isSecondaryPressed && isTertiaryPressed) {
onTertiaryTouched(cellX, cellY)
} else if (isPrimaryPressed && isSecondaryPressed && !isTertiaryPressed) {
onTertiaryTouched(cellX, cellY)
composed {
var isShiftPressed by remember { mutableStateOf(false) }
var isCtrlPressed by remember { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }

pointerInput(gridWidth, gridHeight) {
val cellWidth = size.width.toFloat() / gridWidth.toFloat()
val cellHeight = size.height.toFloat() / gridHeight.toFloat()

awaitEachGesture {
while (true) {
val event = awaitPointerEvent()
val change = event.changes.last()
val offset = change.position
val cellX = (offset.x / cellWidth).toInt().coerceIn(0 until gridWidth)
val cellY = (offset.y / cellHeight).toInt().coerceIn(0 until gridHeight)
val isDown = change.changedToDown()
val isPrimaryPressed = event.buttons.isPrimaryPressed
val isSecondaryPressed = event.buttons.isSecondaryPressed
val isTertiaryPressed = event.buttons.isTertiaryPressed
val isPrimary = isPrimaryPressed && !isSecondaryPressed && !isTertiaryPressed
val isSecondary = !isPrimaryPressed && isSecondaryPressed && !isTertiaryPressed
val isTertiary = !isPrimaryPressed && !isSecondaryPressed && isTertiaryPressed
val isPrimaryAndSecondary = isPrimaryPressed && isSecondaryPressed && !isTertiaryPressed

focusRequester.requestFocus()

if (change.pressed) {
when {
isPrimary && isDown && isCtrlPressed -> onSecondaryPressed(cellX, cellY)
isPrimary && isShiftPressed -> onTertiaryTouched(cellX, cellY)
isPrimary -> onPrimaryTouched(cellX, cellY)
isSecondary && isDown -> onSecondaryPressed(cellX, cellY)
isTertiary || isPrimaryAndSecondary -> onTertiaryTouched(cellX, cellY)
}
} else {
onReleased(cellX, cellY)
}
} else {
onReleased(cellX, cellY)
}
}
}
.focusRequester(focusRequester)
.focusable()
.onKeyEvent {
when (it.key) {
Key.ShiftLeft -> isShiftPressed = it.type == KeyEventType.KeyDown
Key.CtrlLeft -> isCtrlPressed = it.type == KeyEventType.KeyDown
}
false
}
}

@Preview
Expand Down

0 comments on commit dbc526d

Please sign in to comment.