|
| 1 | +# CHANGELOG |
| 2 | + |
| 3 | +## v1.0.0 |
| 4 | + |
| 5 | +This release marks a complete internal and external refactor of the key handling mechanism. The new architecture is more expressive, thread-safe, and designed specifically for Compose Multiplatform. |
| 6 | + |
| 7 | +### 💥 Breaking Changes |
| 8 | + |
| 9 | +- **Refactored Key Mapping API:** All single action and continuous action properties (`keys`, `singleActionKeys`, `combinations`, etc.) have been removed. Key mappings are now grouped by a single **`Trigger`** type. |
| 10 | +- **New DSL Initialization:** Key mapping is now done via a Type-Safe Builder (DSL) structure using functions like `onPress { key(...) }` and `onRelease { combo(...) } }`. The old `addKey`/`addCombination` methods are deprecated/removed. |
| 11 | +- **Continuous Handling Method Changed:** The old `loopBasedHandling: Boolean` flag and `handleInLoop()` method have been removed. Continuous actions are now handled using two distinct, modern triggers: |
| 12 | + * **`ON_REPEAT`**: Uses the system's native key repeat events (handled in `listen`). |
| 13 | + * **`ON_HOLD`**: Uses a dedicated, cancellable coroutine loop (handled via `KeyHandlerHost`). |
| 14 | +- **Required Composable Wrapper:** To use `ON_HOLD` actions safely, all usage must now be wrapped in the **`KeyHandlerHost`** composable, which manages focus and the background coroutine lifecycle. |
| 15 | + |
| 16 | +### ✨ New Features |
| 17 | + |
| 18 | +- **Type-Safe DSL for Key Mapping:** Introduced a clean, keyword-like API for defining key and combination actions: `onPress`, `onRelease`, `onHold`, and `onRepeat`. |
| 19 | +- **Composable Lifecycle Management:** Introduced **`KeyHandlerHost`** to manage focus and safely launch the continuous `ON_HOLD` coroutine loop. This ensures thread safety and prevents resource leaks when the component leaves the composition. |
| 20 | +- **Hold Trigger Frequency:** Added `holdTriggerFrequency: Int` to control the execution rate ($\text{Hz}$) of `ON_HOLD` actions. |
| 21 | +- **Flexible Combo Definition:** The `combo` function in the DSL can accept up to 4 keys for a combination (e.g., `combo(Key.Ctrl, Key.Shift, Key.A, Key.B) { ... }`). |
| 22 | +- **Improved `toString()` Output:** The `KeyHandler.toString()` method now displays actions clearly grouped by their `Trigger` type. |
| 23 | + |
| 24 | +### 🐛 Bug Fixes and Improvements |
| 25 | +* **Multiplatform Safety:** The continuous loop for held keys is now a suspending function (`startHoldLoop`) and is tied to the Compose Coroutine Scope, making the library reliably safe for Kotlin Multiplatform (KMP) usage. |
| 26 | + |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## v0.7.0 (start of changelog) |
| 31 | + |
| 32 | +# Usage |
| 33 | + |
| 34 | +*MUST USE WITH COMPOSE MULTIPLATFORM* |
| 35 | +### build.gradle.kts |
| 36 | + |
| 37 | +```kotlin |
| 38 | +kotlin { |
| 39 | + //... |
| 40 | + sourceSets { |
| 41 | + //... |
| 42 | + commonMain.dependencies { |
| 43 | + //... |
| 44 | + //Format: groupId:artifactId:version |
| 45 | + //groupId = io.github.loop312 |
| 46 | + //artifactId = compose-keyhandler |
| 47 | + //version = 0.7.0 (choose latest version instead) |
| 48 | + implementation("io.github.loop312:compose-keyhandler:0.7.0") |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +```kotlin |
| 55 | +implementation("io.github.loop312:compose-keyhandler:0.7.0") |
| 56 | +``` |
| 57 | + |
| 58 | +### Common Main |
| 59 | +```kotlin |
| 60 | +/*initializer: |
| 61 | +KeyHandler ( |
| 62 | + loopBasedHandling -> this is for truly continuous execution rather than relying on systems input delay, |
| 63 | + requires use of KeyHandler.handleInLoop(), Default: false |
| 64 | + |
| 65 | + consume -> this is for whether to consume the key event or not, Default: true |
| 66 | +) { |
| 67 | + initialize keys here |
| 68 | +} |
| 69 | +*/ |
| 70 | +//----------------------------------------------------------------------------------- |
| 71 | +//import io.github.loop312.compose_keyhandler.KeyHandler |
| 72 | +//import androidx.compose.ui.Modifier |
| 73 | +//import androidx.compose.ui.input.key.Key |
| 74 | +//import androidx.compose.ui.input.key.onKeyEvent |
| 75 | +//... |
| 76 | + |
| 77 | +@Composable |
| 78 | +fun main() { |
| 79 | + //if initializing outside a composable, don't use remember {} |
| 80 | + val keyHandler = remember { |
| 81 | + KeyHandler(loopBasedHandling = false, consume = true) { |
| 82 | + //continuous execution |
| 83 | + addKey(key = Key.A, description = "Prints A is being pressed") { |
| 84 | + println("A is being pressed") |
| 85 | + //or any other action you want to do |
| 86 | + } |
| 87 | + |
| 88 | + //println(getDescription(Key.A)) |
| 89 | + |
| 90 | + addMultipleKeys(keySet = setOf(Key.B, Key.C), description = "Prints B or C is being pressed") { |
| 91 | + println("B or C is being pressed") |
| 92 | + //or any other action you want to do |
| 93 | + } |
| 94 | + |
| 95 | + //one-time execution |
| 96 | + addSingleActionKey(Key.D, "Prints D was pressed") { |
| 97 | + println("D was pressed") |
| 98 | + //or any other action you want to do |
| 99 | + } |
| 100 | + |
| 101 | + addMultipleSingleActionKeys(setOf(Key.E, Key.F), "Prints E or F was pressed") { |
| 102 | + println("E or F was pressed") |
| 103 | + //or any other action you want to do |
| 104 | + } |
| 105 | + |
| 106 | + //on release execution |
| 107 | + addReleaseKey(Key.G, "Prints G was released") { |
| 108 | + println("G was released") |
| 109 | + //or any other action you want to do |
| 110 | + } |
| 111 | + |
| 112 | + addMultipleReleaseKeys(setOf(Key.H, Key.I), "Prints H or I was released") { |
| 113 | + println("H or I was released") |
| 114 | + //or any other action you want to do |
| 115 | + } |
| 116 | + |
| 117 | + //continuous combination execution |
| 118 | + addCombination(setOf(Key.J, Key.K), "Prints J and K are being pressed") { |
| 119 | + println("J and K are being pressed") |
| 120 | + //or any other action you want to do |
| 121 | + } |
| 122 | + |
| 123 | + //println(getDescription(setOf(Key.J, Key.K))) |
| 124 | + |
| 125 | + //one-time combination execution |
| 126 | + addSingleActionCombination(setOf(Key.L, Key.M), "Prints L and M were pressed") { |
| 127 | + println("L and M were pressed") |
| 128 | + //or any other action you want to do |
| 129 | + } |
| 130 | + } |
| 131 | + } |
| 132 | + //or Modifier.onPreviewKeyEvent(keyHandler.listen) |
| 133 | + Box(Modifier.onKeyEvent(keyHandler.listen)) { |
| 134 | + //... |
| 135 | + } |
| 136 | + |
| 137 | + //optional, for continuous execution (set loopBasedHandling = true) |
| 138 | + LaunchedEffect(Unit) { |
| 139 | + while (true) { |
| 140 | + //if you want to automatically handle it with every frame (can use withFrameMillis too) |
| 141 | + //or just use a delay |
| 142 | + withFrameNanos { |
| 143 | + keyHandler.handleInLoop() |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +### NOTE: Make sure object with modifier `onKeyEvent` or `onPreviewKeyEvent` is in focus |
0 commit comments