0
# Input and Interaction
1
2
Multi-platform input handling system supporting touch, mouse, keyboard input, and gesture recognition with comprehensive focus management and accessibility features.
3
4
## Capabilities
5
6
### Pointer Input System
7
8
Low-level pointer input handling supporting touch, mouse, and stylus input with gesture recognition capabilities.
9
10
```kotlin { .api }
11
/**
12
* Modifier that provides access to pointer input events.
13
*/
14
fun Modifier.pointerInput(
15
key1: Any?,
16
block: suspend PointerInputScope.() -> Unit
17
): Modifier
18
19
/**
20
* Modifier that provides access to pointer input events with multiple keys.
21
*/
22
fun Modifier.pointerInput(
23
key1: Any?,
24
key2: Any?,
25
block: suspend PointerInputScope.() -> Unit
26
): Modifier
27
28
/**
29
* Modifier that provides access to pointer input events with arbitrary keys.
30
*/
31
fun Modifier.pointerInput(
32
vararg keys: Any?,
33
block: suspend PointerInputScope.() -> Unit
34
): Modifier
35
36
/**
37
* Scope for handling pointer input events.
38
*/
39
interface PointerInputScope : Density {
40
/**
41
* The size of the pointer input region.
42
*/
43
val size: IntSize
44
45
/**
46
* The extended touch slop for gestures.
47
*/
48
val extendedTouchSlop: Float
49
50
/**
51
* Configuration for pointer input behavior.
52
*/
53
val viewConfiguration: ViewConfiguration
54
55
/**
56
* Wait for a pointer event to occur.
57
*/
58
suspend fun awaitPointerEventScope(
59
pass: PointerEventPass = PointerEventPass.Main,
60
block: suspend AwaitPointerEventScope.() -> Unit
61
)
62
}
63
64
/**
65
* Scope for awaiting and handling pointer events.
66
*/
67
interface AwaitPointerEventScope : Density {
68
/**
69
* The size of the pointer input region.
70
*/
71
val size: IntSize
72
73
/**
74
* The current pointer event.
75
*/
76
val currentEvent: PointerEvent
77
78
/**
79
* Extended touch slop configuration.
80
*/
81
val extendedTouchSlop: Float
82
83
/**
84
* View configuration for pointer input.
85
*/
86
val viewConfiguration: ViewConfiguration
87
88
/**
89
* Wait for the next pointer event.
90
*/
91
suspend fun awaitPointerEvent(
92
pass: PointerEventPass = PointerEventPass.Main
93
): PointerEvent
94
95
/**
96
* Attempt to drag one pointer.
97
*/
98
suspend fun drag(
99
pointerId: PointerId,
100
onDrag: (PointerInputChange) -> Unit
101
): Boolean
102
103
/**
104
* Wait for all pointers to be up.
105
*/
106
suspend fun waitForUpOrCancellation(
107
pass: PointerEventPass = PointerEventPass.Main
108
): PointerInputChange?
109
}
110
111
/**
112
* Represents a pointer event containing information about pointer changes.
113
*/
114
class PointerEvent(
115
val changes: List<PointerInputChange>,
116
val buttons: PointerButtons = PointerButtons(),
117
val keyboardModifiers: PointerKeyboardModifiers = PointerKeyboardModifiers(),
118
val type: PointerEventType = PointerEventType.Unknown
119
) {
120
/**
121
* The number of pointers in this event.
122
*/
123
val pointerCount: Int
124
125
/**
126
* Get a pointer change by its ID.
127
*/
128
fun getPointerById(pointerId: PointerId): PointerInputChange?
129
}
130
131
/**
132
* Information about a single pointer's state changes.
133
*/
134
@Immutable
135
data class PointerInputChange(
136
val id: PointerId,
137
val uptimeMillis: Long,
138
val position: Offset,
139
val pressed: Boolean,
140
val pressure: Float = 1.0f,
141
val previousUptimeMillis: Long = uptimeMillis,
142
val previousPosition: Offset = position,
143
val previousPressed: Boolean = pressed,
144
val isConsumed: Boolean = false,
145
val type: PointerType = PointerType.Unknown,
146
val scrollDelta: Offset = Offset.Zero
147
) {
148
/**
149
* Whether this pointer change represents a press event.
150
*/
151
val changedToDown: Boolean
152
153
/**
154
* Whether this pointer change represents a release event.
155
*/
156
val changedToUp: Boolean
157
158
/**
159
* Whether this pointer change represents movement.
160
*/
161
val changedToDownIgnoreConsumed: Boolean
162
163
/**
164
* The change in position.
165
*/
166
val positionChange: Offset
167
168
/**
169
* The change in position, ignoring consumption.
170
*/
171
val positionChangeIgnoreConsumed: Offset
172
173
/**
174
* Consume this pointer change.
175
*/
176
fun consume()
177
178
/**
179
* Copy this change with consumption applied.
180
*/
181
fun consumeAllChanges(): PointerInputChange
182
183
/**
184
* Copy this change with position change consumed.
185
*/
186
fun consumePositionChange(): PointerInputChange
187
}
188
189
/**
190
* Unique identifier for a pointer.
191
*/
192
@JvmInline
193
value class PointerId(val value: Long)
194
195
/**
196
* Types of pointer events.
197
*/
198
enum class PointerEventType {
199
Unknown, Press, Release, Move, Enter, Exit, Scroll
200
}
201
202
/**
203
* Types of pointers.
204
*/
205
enum class PointerType {
206
Unknown, Touch, Mouse, Stylus, Eraser
207
}
208
209
/**
210
* Pointer event pass for event handling phases.
211
*/
212
enum class PointerEventPass {
213
Initial, Main, Final
214
}
215
```
216
217
**Usage Examples:**
218
219
```kotlin
220
// Basic pointer input handling
221
@Composable
222
fun PointerInputExample() {
223
var position by remember { mutableStateOf(Offset.Zero) }
224
var isPressed by remember { mutableStateOf(false) }
225
226
Box(
227
modifier = Modifier
228
.size(200.dp)
229
.background(if (isPressed) Color.Red else Color.Blue)
230
.pointerInput(Unit) {
231
awaitPointerEventScope {
232
while (true) {
233
val event = awaitPointerEvent()
234
val change = event.changes.first()
235
236
position = change.position
237
isPressed = change.pressed
238
239
if (change.changedToDown || change.changedToUp) {
240
change.consume()
241
}
242
}
243
}
244
}
245
) {
246
Text(
247
text = "Position: (${position.x.toInt()}, ${position.y.toInt()})\nPressed: $isPressed",
248
color = Color.White,
249
modifier = Modifier.align(Alignment.Center)
250
)
251
}
252
}
253
254
// Multi-touch handling
255
@Composable
256
fun MultiTouchExample() {
257
var pointers by remember { mutableStateOf(mapOf<PointerId, Offset>()) }
258
259
Box(
260
modifier = Modifier
261
.fillMaxSize()
262
.pointerInput(Unit) {
263
awaitPointerEventScope {
264
while (true) {
265
val event = awaitPointerEvent()
266
267
pointers = event.changes.associate { change ->
268
if (change.pressed) {
269
change.id to change.position
270
} else {
271
change.id to Offset.Unspecified
272
}
273
}.filterValues { it != Offset.Unspecified }
274
275
event.changes.forEach { it.consume() }
276
}
277
}
278
}
279
) {
280
pointers.forEach { (id, position) ->
281
Box(
282
modifier = Modifier
283
.offset(position.x.dp - 25.dp, position.y.dp - 25.dp)
284
.size(50.dp)
285
.background(Color.Red, CircleShape)
286
)
287
}
288
}
289
}
290
```
291
292
### High-Level Gesture Detection
293
294
Pre-built gesture detectors for common interaction patterns like taps, drags, and swipes.
295
296
```kotlin { .api }
297
/**
298
* Detects tap gestures.
299
*/
300
suspend fun PointerInputScope.detectTapGestures(
301
onDoubleTap: ((Offset) -> Unit)? = null,
302
onLongPress: ((Offset) -> Unit)? = null,
303
onPress: (suspend PressGestureScope.(Offset) -> Unit)? = null,
304
onTap: ((Offset) -> Unit)? = null
305
)
306
307
/**
308
* Detects drag gestures.
309
*/
310
suspend fun PointerInputScope.detectDragGestures(
311
onDragStart: (Offset) -> Unit = { },
312
onDragEnd: () -> Unit = { },
313
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
314
)
315
316
/**
317
* Detects drag gestures after a long press.
318
*/
319
suspend fun PointerInputScope.detectDragGesturesAfterLongPress(
320
onDragStart: (Offset) -> Unit = { },
321
onDragEnd: () -> Unit = { },
322
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
323
)
324
325
/**
326
* Detects horizontal drag gestures.
327
*/
328
suspend fun PointerInputScope.detectHorizontalDragGestures(
329
onDragStart: (Offset) -> Unit = { },
330
onDragEnd: () -> Unit = { },
331
onHorizontalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit
332
)
333
334
/**
335
* Detects vertical drag gestures.
336
*/
337
suspend fun PointerInputScope.detectVerticalDragGestures(
338
onDragStart: (Offset) -> Unit = { },
339
onDragEnd: () -> Unit = { },
340
onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit
341
)
342
343
/**
344
* Detects rotation and zoom gestures.
345
*/
346
suspend fun PointerInputScope.detectTransformGestures(
347
panZoomLock: Boolean = false,
348
onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
349
)
350
351
/**
352
* Scope for press gestures.
353
*/
354
interface PressGestureScope : Density {
355
/**
356
* Try to await release of the press.
357
*/
358
suspend fun tryAwaitRelease(): Boolean
359
360
/**
361
* Await release of the press.
362
*/
363
suspend fun awaitRelease()
364
}
365
366
/**
367
* Configuration for drag gestures.
368
*/
369
data class DragGestureDetectorConfig(
370
val canDrag: (PointerInputChange) -> Boolean = { true }
371
)
372
```
373
374
**Usage Examples:**
375
376
```kotlin
377
// Tap gesture detection
378
@Composable
379
fun TapGestureExample() {
380
var tapCount by remember { mutableStateOf(0) }
381
var lastTapPosition by remember { mutableStateOf(Offset.Zero) }
382
383
Box(
384
modifier = Modifier
385
.size(200.dp)
386
.background(Color.Green)
387
.pointerInput(Unit) {
388
detectTapGestures(
389
onTap = { offset ->
390
tapCount++
391
lastTapPosition = offset
392
},
393
onDoubleTap = { offset ->
394
tapCount += 2
395
lastTapPosition = offset
396
},
397
onLongPress = { offset ->
398
tapCount = 0
399
lastTapPosition = offset
400
}
401
)
402
}
403
) {
404
Text(
405
text = "Taps: $tapCount\nLast: (${lastTapPosition.x.toInt()}, ${lastTapPosition.y.toInt()})",
406
color = Color.White,
407
modifier = Modifier.align(Alignment.Center)
408
)
409
}
410
}
411
412
// Drag gesture detection
413
@Composable
414
fun DragGestureExample() {
415
var offset by remember { mutableStateOf(Offset.Zero) }
416
var isDragging by remember { mutableStateOf(false) }
417
418
Box(
419
modifier = Modifier.fillMaxSize()
420
) {
421
Box(
422
modifier = Modifier
423
.offset(offset.x.dp, offset.y.dp)
424
.size(100.dp)
425
.background(if (isDragging) Color.Red else Color.Blue)
426
.pointerInput(Unit) {
427
detectDragGestures(
428
onDragStart = {
429
isDragging = true
430
},
431
onDragEnd = {
432
isDragging = false
433
},
434
onDrag = { change, dragAmount ->
435
offset += dragAmount
436
}
437
)
438
}
439
)
440
}
441
}
442
443
// Transform gestures (zoom, rotate, pan)
444
@Composable
445
fun TransformGestureExample() {
446
var scale by remember { mutableStateOf(1f) }
447
var rotation by remember { mutableStateOf(0f) }
448
var offset by remember { mutableStateOf(Offset.Zero) }
449
450
Box(
451
modifier = Modifier
452
.fillMaxSize()
453
.pointerInput(Unit) {
454
detectTransformGestures { centroid, pan, zoom, rotationChange ->
455
scale *= zoom
456
rotation += rotationChange
457
offset += pan
458
}
459
}
460
) {
461
Box(
462
modifier = Modifier
463
.size(200.dp)
464
.offset(offset.x.dp, offset.y.dp)
465
.scale(scale)
466
.rotate(rotation)
467
.background(Color.Yellow)
468
.align(Alignment.Center)
469
) {
470
Text(
471
text = "Transform me!",
472
modifier = Modifier.align(Alignment.Center)
473
)
474
}
475
}
476
}
477
```
478
479
### Clickable and Interactive Modifiers
480
481
High-level modifiers for common interactive behaviors like clicks, selections, and toggles.
482
483
```kotlin { .api }
484
/**
485
* Modifier that makes a component clickable.
486
*/
487
fun Modifier.clickable(
488
enabled: Boolean = true,
489
onClickLabel: String? = null,
490
role: Role? = null,
491
onClick: () -> Unit
492
): Modifier
493
494
/**
495
* Modifier that makes a component clickable with custom indication.
496
*/
497
fun Modifier.clickable(
498
interactionSource: MutableInteractionSource,
499
indication: Indication?,
500
enabled: Boolean = true,
501
onClickLabel: String? = null,
502
role: Role? = null,
503
onClick: () -> Unit
504
): Modifier
505
506
/**
507
* Modifier that makes a component selectable.
508
*/
509
fun Modifier.selectable(
510
selected: Boolean,
511
enabled: Boolean = true,
512
role: Role? = null,
513
onValueChange: (Boolean) -> Unit
514
): Modifier
515
516
/**
517
* Modifier that makes a component toggleable.
518
*/
519
fun Modifier.toggleable(
520
value: Boolean,
521
enabled: Boolean = true,
522
role: Role? = null,
523
onValueChange: (Boolean) -> Unit
524
): Modifier
525
526
/**
527
* Modifier that makes a component tristate toggleable.
528
*/
529
fun Modifier.triStateToggleable(
530
state: ToggleableState,
531
enabled: Boolean = true,
532
role: Role? = null,
533
onClick: () -> Unit
534
): Modifier
535
536
/**
537
* Represents the state of a toggleable component.
538
*/
539
enum class ToggleableState {
540
On, Off, Indeterminate
541
}
542
543
/**
544
* Semantic roles for accessibility.
545
*/
546
enum class Role {
547
Button, Checkbox, Switch, RadioButton, Tab, Image, DropdownList
548
}
549
```
550
551
**Usage Examples:**
552
553
```kotlin
554
// Basic clickable component
555
@Composable
556
fun ClickableExample() {
557
var clickCount by remember { mutableStateOf(0) }
558
559
Box(
560
modifier = Modifier
561
.size(150.dp)
562
.background(Color.Blue)
563
.clickable {
564
clickCount++
565
}
566
) {
567
Text(
568
text = "Clicked: $clickCount",
569
color = Color.White,
570
modifier = Modifier.align(Alignment.Center)
571
)
572
}
573
}
574
575
// Selectable component
576
@Composable
577
fun SelectableExample() {
578
var selected by remember { mutableStateOf(false) }
579
580
Row(
581
modifier = Modifier
582
.fillMaxWidth()
583
.selectable(
584
selected = selected,
585
onClick = { selected = !selected },
586
role = Role.Checkbox
587
)
588
.padding(16.dp)
589
) {
590
Box(
591
modifier = Modifier
592
.size(24.dp)
593
.background(
594
if (selected) Color.Blue else Color.Gray,
595
RoundedCornerShape(4.dp)
596
)
597
) {
598
if (selected) {
599
Text(
600
text = "✓",
601
color = Color.White,
602
modifier = Modifier.align(Alignment.Center)
603
)
604
}
605
}
606
607
Spacer(modifier = Modifier.width(16.dp))
608
609
Text(text = "Select this option")
610
}
611
}
612
613
// Toggleable component
614
@Composable
615
fun ToggleableExample() {
616
var isOn by remember { mutableStateOf(false) }
617
618
Row(
619
modifier = Modifier
620
.toggleable(
621
value = isOn,
622
onValueChange = { isOn = it },
623
role = Role.Switch
624
)
625
.padding(16.dp),
626
verticalAlignment = Alignment.CenterVertically
627
) {
628
Text(text = "Enable feature")
629
630
Spacer(modifier = Modifier.width(16.dp))
631
632
Box(
633
modifier = Modifier
634
.size(50.dp, 30.dp)
635
.background(
636
if (isOn) Color.Green else Color.Gray,
637
RoundedCornerShape(15.dp)
638
)
639
) {
640
Box(
641
modifier = Modifier
642
.size(26.dp)
643
.background(Color.White, CircleShape)
644
.align(if (isOn) Alignment.CenterEnd else Alignment.CenterStart)
645
)
646
}
647
}
648
}
649
650
// Custom interaction source and indication
651
@Composable
652
fun CustomInteractionExample() {
653
val interactionSource = remember { MutableInteractionSource() }
654
val isPressed by interactionSource.collectIsPressedAsState()
655
val isHovered by interactionSource.collectIsHoveredAsState()
656
657
Box(
658
modifier = Modifier
659
.size(150.dp)
660
.background(
661
when {
662
isPressed -> Color.Red
663
isHovered -> Color.Yellow
664
else -> Color.Blue
665
}
666
)
667
.clickable(
668
interactionSource = interactionSource,
669
indication = null // Custom indication handling
670
) {
671
// Handle click
672
}
673
) {
674
Text(
675
text = when {
676
isPressed -> "Pressed"
677
isHovered -> "Hovered"
678
else -> "Normal"
679
},
680
color = Color.White,
681
modifier = Modifier.align(Alignment.Center)
682
)
683
}
684
}
685
```
686
687
### Focus Management
688
689
Comprehensive focus handling system for keyboard navigation and accessibility.
690
691
```kotlin { .api }
692
/**
693
* Modifier that makes a component focusable.
694
*/
695
fun Modifier.focusable(
696
enabled: Boolean = true,
697
interactionSource: MutableInteractionSource? = null
698
): Modifier
699
700
/**
701
* Modifier that specifies focus behavior.
702
*/
703
fun Modifier.focusTarget(): Modifier
704
705
/**
706
* Modifier that specifies focus properties.
707
*/
708
fun Modifier.focusProperties(scope: FocusProperties.() -> Unit): Modifier
709
710
/**
711
* Modifier for observing focus changes.
712
*/
713
fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier
714
715
/**
716
* Modifier for observing focus events.
717
*/
718
fun Modifier.onFocusEvent(onFocusEvent: (FocusState) -> Unit): Modifier
719
720
/**
721
* Represents the focus state of a component.
722
*/
723
interface FocusState {
724
/**
725
* Whether this component has focus.
726
*/
727
val hasFocus: Boolean
728
729
/**
730
* Whether this component is focused or contains a focused descendant.
731
*/
732
val isFocused: Boolean
733
734
/**
735
* Whether focus is captured by this component.
736
*/
737
val isCaptured: Boolean
738
}
739
740
/**
741
* Properties for configuring focus behavior.
742
*/
743
interface FocusProperties {
744
/**
745
* Whether this component can be focused.
746
*/
747
var canFocus: Boolean
748
749
/**
750
* Custom focus enter behavior.
751
*/
752
var enter: (FocusDirection) -> FocusRequester?
753
754
/**
755
* Custom focus exit behavior.
756
*/
757
var exit: (FocusDirection) -> FocusRequester?
758
759
/**
760
* Focus order within the parent.
761
*/
762
var next: FocusRequester?
763
var previous: FocusRequester?
764
var up: FocusRequester?
765
var down: FocusRequester?
766
var left: FocusRequester?
767
var right: FocusRequester?
768
var start: FocusRequester?
769
var end: FocusRequester?
770
}
771
772
/**
773
* Used to request focus programmatically.
774
*/
775
class FocusRequester {
776
/**
777
* Request focus for this component.
778
*/
779
fun requestFocus()
780
781
/**
782
* Capture focus for this component.
783
*/
784
fun captureFocus(): Boolean
785
786
/**
787
* Free captured focus.
788
*/
789
fun freeFocus(): Boolean
790
791
companion object {
792
/**
793
* Default focus requester.
794
*/
795
val Default: FocusRequester
796
797
/**
798
* Focus requester that cancels focus.
799
*/
800
val Cancel: FocusRequester
801
}
802
}
803
804
/**
805
* Directions for focus movement.
806
*/
807
enum class FocusDirection {
808
Next, Previous, Up, Down, Left, Right, In, Out, Enter, Exit
809
}
810
811
/**
812
* CompositionLocal for accessing the focus manager.
813
*/
814
val LocalFocusManager: ProvidableCompositionLocal<FocusManager>
815
816
/**
817
* Interface for managing focus.
818
*/
819
interface FocusManager {
820
/**
821
* Clear focus from the currently focused component.
822
*/
823
fun clearFocus(force: Boolean = false)
824
825
/**
826
* Move focus in the specified direction.
827
*/
828
fun moveFocus(focusDirection: FocusDirection): Boolean
829
}
830
```
831
832
**Usage Examples:**
833
834
```kotlin
835
// Basic focus handling
836
@Composable
837
fun FocusExample() {
838
val focusRequester = remember { FocusRequester() }
839
var isFocused by remember { mutableStateOf(false) }
840
841
Column {
842
Box(
843
modifier = Modifier
844
.size(100.dp)
845
.background(if (isFocused) Color.Blue else Color.Gray)
846
.focusRequester(focusRequester)
847
.focusable()
848
.onFocusChanged { focusState ->
849
isFocused = focusState.isFocused
850
}
851
) {
852
Text(
853
text = if (isFocused) "Focused" else "Not Focused",
854
color = Color.White,
855
modifier = Modifier.align(Alignment.Center)
856
)
857
}
858
859
Button(
860
onClick = { focusRequester.requestFocus() }
861
) {
862
Text("Request Focus")
863
}
864
}
865
}
866
867
// Custom focus navigation
868
@Composable
869
fun FocusNavigationExample() {
870
val focusRequesters = remember { List(4) { FocusRequester() } }
871
val focusManager = LocalFocusManager.current
872
873
LazyVerticalGrid(
874
columns = GridCells.Fixed(2),
875
modifier = Modifier.padding(16.dp)
876
) {
877
items(4) { index ->
878
var isFocused by remember { mutableStateOf(false) }
879
880
Box(
881
modifier = Modifier
882
.size(100.dp)
883
.padding(4.dp)
884
.background(
885
if (isFocused) Color.Blue else Color.LightGray,
886
RoundedCornerShape(8.dp)
887
)
888
.focusRequester(focusRequesters[index])
889
.focusProperties {
890
// Custom focus navigation
891
next = if (index < 3) focusRequesters[index + 1] else FocusRequester.Default
892
previous = if (index > 0) focusRequesters[index - 1] else FocusRequester.Default
893
894
// Grid-like navigation
895
down = if (index < 2) focusRequesters[index + 2] else FocusRequester.Default
896
up = if (index >= 2) focusRequesters[index - 2] else FocusRequester.Default
897
}
898
.focusable()
899
.onFocusChanged { focusState ->
900
isFocused = focusState.isFocused
901
}
902
.clickable {
903
focusRequesters[index].requestFocus()
904
}
905
) {
906
Text(
907
text = "Item ${index + 1}",
908
color = if (isFocused) Color.White else Color.Black,
909
modifier = Modifier.align(Alignment.Center)
910
)
911
}
912
}
913
}
914
}
915
916
// Focus capture for modal behavior
917
@Composable
918
fun FocusCaptureExample() {
919
var showModal by remember { mutableStateOf(false) }
920
val modalFocusRequester = remember { FocusRequester() }
921
922
Box(modifier = Modifier.fillMaxSize()) {
923
Column {
924
Button(onClick = { showModal = true }) {
925
Text("Show Modal")
926
}
927
928
Text("This content becomes unfocusable when modal is shown")
929
930
Button(onClick = { /* Regular button */ }) {
931
Text("Regular Button")
932
}
933
}
934
935
if (showModal) {
936
Box(
937
modifier = Modifier
938
.fillMaxSize()
939
.background(Color.Black.copy(alpha = 0.5f))
940
.clickable { showModal = false }
941
) {
942
Box(
943
modifier = Modifier
944
.size(300.dp, 200.dp)
945
.background(Color.White, RoundedCornerShape(8.dp))
946
.align(Alignment.Center)
947
.focusRequester(modalFocusRequester)
948
.focusProperties {
949
// Capture focus to prevent navigation outside modal
950
canFocus = true
951
}
952
.focusable()
953
) {
954
Column(
955
modifier = Modifier
956
.fillMaxSize()
957
.padding(16.dp),
958
verticalArrangement = Arrangement.SpaceAround,
959
horizontalAlignment = Alignment.CenterHorizontally
960
) {
961
Text("Modal Dialog")
962
Button(onClick = { showModal = false }) {
963
Text("Close")
964
}
965
}
966
}
967
}
968
969
LaunchedEffect(showModal) {
970
modalFocusRequester.requestFocus()
971
}
972
}
973
}
974
}
975
```
976
977
### Keyboard Input
978
979
Keyboard event handling for text input, shortcuts, and navigation keys.
980
981
```kotlin { .api }
982
/**
983
* Modifier for handling key events.
984
*/
985
fun Modifier.onKeyEvent(
986
onKeyEvent: (KeyEvent) -> Boolean
987
): Modifier
988
989
/**
990
* Modifier for handling pre-view key events.
991
*/
992
fun Modifier.onPreviewKeyEvent(
993
onPreviewKeyEvent: (KeyEvent) -> Boolean
994
): Modifier
995
996
/**
997
* Represents a keyboard event.
998
*/
999
expect class KeyEvent {
1000
/**
1001
* The key that was pressed or released.
1002
*/
1003
val key: Key
1004
1005
/**
1006
* The type of key event.
1007
*/
1008
val type: KeyEventType
1009
1010
/**
1011
* Whether the Alt key is pressed.
1012
*/
1013
val isAltPressed: Boolean
1014
1015
/**
1016
* Whether the Ctrl key is pressed.
1017
*/
1018
val isCtrlPressed: Boolean
1019
1020
/**
1021
* Whether the Meta key is pressed.
1022
*/
1023
val isMetaPressed: Boolean
1024
1025
/**
1026
* Whether the Shift key is pressed.
1027
*/
1028
val isShiftPressed: Boolean
1029
}
1030
1031
/**
1032
* Types of key events.
1033
*/
1034
enum class KeyEventType {
1035
KeyDown, KeyUp, Unknown
1036
}
1037
1038
/**
1039
* Represents keyboard keys.
1040
*/
1041
expect class Key {
1042
companion object {
1043
val A: Key
1044
val B: Key
1045
// ... other alphabet keys
1046
val Zero: Key
1047
val One: Key
1048
// ... other number keys
1049
val Enter: Key
1050
val Escape: Key
1051
val Backspace: Key
1052
val Delete: Key
1053
val Tab: Key
1054
val Spacebar: Key
1055
val DirectionUp: Key
1056
val DirectionDown: Key
1057
val DirectionLeft: Key
1058
val DirectionRight: Key
1059
val PageUp: Key
1060
val PageDown: Key
1061
val Home: Key
1062
val MoveEnd: Key
1063
val F1: Key
1064
val F2: Key
1065
// ... other function keys
1066
}
1067
}
1068
```
1069
1070
**Usage Examples:**
1071
1072
```kotlin
1073
// Keyboard event handling
1074
@Composable
1075
fun KeyboardHandlingExample() {
1076
var lastKey by remember { mutableStateOf("None") }
1077
var keyCount by remember { mutableStateOf(0) }
1078
1079
Box(
1080
modifier = Modifier
1081
.size(300.dp, 200.dp)
1082
.background(Color.LightGray, RoundedCornerShape(8.dp))
1083
.focusable()
1084
.onKeyEvent { keyEvent ->
1085
if (keyEvent.type == KeyEventType.KeyDown) {
1086
lastKey = keyEvent.key.toString()
1087
keyCount++
1088
1089
// Handle specific keys
1090
when (keyEvent.key) {
1091
Key.Escape -> {
1092
lastKey = "Escape pressed"
1093
true // Consume event
1094
}
1095
Key.Enter -> {
1096
lastKey = "Enter pressed"
1097
true
1098
}
1099
else -> false // Don't consume
1100
}
1101
} else {
1102
false
1103
}
1104
}
1105
) {
1106
Column(
1107
modifier = Modifier.align(Alignment.Center),
1108
horizontalAlignment = Alignment.CenterHorizontally
1109
) {
1110
Text("Focus this box and press keys")
1111
Text("Last key: $lastKey")
1112
Text("Key count: $keyCount")
1113
}
1114
}
1115
}
1116
1117
// Keyboard shortcuts
1118
@Composable
1119
fun KeyboardShortcutsExample() {
1120
var content by remember { mutableStateOf("Type here...") }
1121
var saved by remember { mutableStateOf(false) }
1122
1123
Column(
1124
modifier = Modifier
1125
.fillMaxSize()
1126
.padding(16.dp)
1127
.onPreviewKeyEvent { keyEvent ->
1128
if (keyEvent.type == KeyEventType.KeyDown && keyEvent.isCtrlPressed) {
1129
when (keyEvent.key) {
1130
Key.S -> {
1131
// Ctrl+S to save
1132
saved = true
1133
true
1134
}
1135
Key.N -> {
1136
// Ctrl+N for new
1137
content = ""
1138
saved = false
1139
true
1140
}
1141
else -> false
1142
}
1143
} else {
1144
false
1145
}
1146
}
1147
) {
1148
if (saved) {
1149
Text(
1150
text = "Saved!",
1151
color = Color.Green,
1152
fontWeight = FontWeight.Bold
1153
)
1154
} else {
1155
Text(
1156
text = "Unsaved changes",
1157
color = Color.Orange
1158
)
1159
}
1160
1161
Spacer(modifier = Modifier.height(16.dp))
1162
1163
TextField(
1164
value = content,
1165
onValueChange = {
1166
content = it
1167
saved = false
1168
},
1169
label = { Text("Content (Ctrl+S to save, Ctrl+N for new)") },
1170
modifier = Modifier.fillMaxWidth()
1171
)
1172
1173
Spacer(modifier = Modifier.height(16.dp))
1174
1175
Text("Keyboard shortcuts:")
1176
Text("• Ctrl+S: Save")
1177
Text("• Ctrl+N: New document")
1178
}
1179
}
1180
```