Skip to content

Commit 59266ef

Browse files
authored
Merge pull request #5 from Loop312/DSL
DSL Rewrite
2 parents 9f06f64 + 8079d34 commit 59266ef

11 files changed

Lines changed: 917 additions & 631 deletions

File tree

CHANGELOG.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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

Comments
 (0)