Browse Source

moved updated desktop tutorials to kmp portal

move-tutorials-to-kmp-portal
Elvira.Mustafina 2 months ago
parent
commit
6fe66ad9de
  1. 247
      tutorials/Desktop_Components/README.md
  2. BIN
      tutorials/Desktop_Components/lazy_scrollbar.gif
  3. BIN
      tutorials/Desktop_Components/scrollbars.gif
  4. BIN
      tutorials/Desktop_Components/tooltips.gif
  5. 492
      tutorials/Mouse_Events/README.md
  6. BIN
      tutorials/Mouse_Events/mouse_click.gif
  7. BIN
      tutorials/Mouse_Events/mouse_enter.gif
  8. BIN
      tutorials/Mouse_Events/mouse_event.gif
  9. BIN
      tutorials/Mouse_Events/mouse_move.gif
  10. 6
      tutorials/README.md

247
tutorials/Desktop_Components/README.md

@ -1,247 +0,0 @@
# Desktop components
## What is covered
In this tutorial, we will show you how to use desktop-specific components of Compose for Desktop such as scrollbars and tooltips.
## Scrollbars
You can apply scrollbars to scrollable components. The scrollbar and scrollable components share a common state to synchronize with each other. For example, `VerticalScrollbar` can be attached to `Modifier.verticalScroll`, and `LazyColumn` and `HorizontalScrollbar` can be attached to `Modifier.horizontalScroll` and `LazyRow`.
```kotlin
import androidx.compose.foundation.HorizontalScrollbar
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication(
title = "Scrollbars",
state = WindowState(width = 250.dp, height = 400.dp)
) {
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color(180, 180, 180))
.padding(10.dp)
) {
val stateVertical = rememberScrollState(0)
val stateHorizontal = rememberScrollState(0)
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
.padding(end = 12.dp, bottom = 12.dp)
.horizontalScroll(stateHorizontal)
) {
Column {
for (item in 0..30) {
TextBox("Item #$item")
if (item < 30) {
Spacer(modifier = Modifier.height(5.dp))
}
}
}
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd)
.fillMaxHeight(),
adapter = rememberScrollbarAdapter(stateVertical)
)
HorizontalScrollbar(
modifier = Modifier.align(Alignment.BottomStart)
.fillMaxWidth()
.padding(end = 12.dp),
adapter = rememberScrollbarAdapter(stateHorizontal)
)
}
}
@Composable
fun TextBox(text: String = "Item") {
Box(
modifier = Modifier.height(32.dp)
.width(400.dp)
.background(color = Color(200, 0, 0, 20))
.padding(start = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(text = text)
}
}
```
Scrollbars can be moved by dragging the bars and using the mouse wheel or the touchpad. Horizontal scrolling with the mouse wheel can be performed by side-clicking the wheel or by holding down `Shift`.
<img alt="Scrollbars" src="scrollbars.gif" height="412" />
## Lazy scrollable components with Scrollbar
You can use scrollbars with lazy scrollable components, for example, `LazyColumn`.
```kotlin
import androidx.compose.foundation.VerticalScrollbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Scrollbars",
state = rememberWindowState(width = 250.dp, height = 400.dp)
) {
LazyScrollable()
}
}
@Composable
fun LazyScrollable() {
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color(180, 180, 180))
.padding(10.dp)
) {
val state = rememberLazyListState()
LazyColumn(Modifier.fillMaxSize().padding(end = 12.dp), state) {
items(1000) { x ->
TextBox("Item #$x")
Spacer(modifier = Modifier.height(5.dp))
}
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
adapter = rememberScrollbarAdapter(
scrollState = state
)
)
}
}
@Composable
fun TextBox(text: String = "Item") {
Box(
modifier = Modifier.height(32.dp)
.fillMaxWidth()
.background(color = Color(0, 0, 0, 20))
.padding(start = 10.dp),
contentAlignment = Alignment.CenterStart
) {
Text(text = text)
}
}
```
<img alt="Lazy component" src="lazy_scrollbar.gif" height="412" />
## Tooltips
You can add tooltip to any components using `TooltipArea`. `TooltipArea` is similar to a `Box`, but with the ability to show a tooltip.
The main arguments of the `TooltipArea` function:
- tooltip - composable content representing tooltip
- tooltipPlacement - describes how to place tooltip. You can specify an anchor (the mouse cursor or the component), an offset and an alignment
- delay - time delay in milliseconds after which the tooltip will be shown (default is 500 ms)
```kotlin
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.TooltipPlacement
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Tooltip Example",
state = rememberWindowState(width = 300.dp, height = 300.dp)
) {
val buttons = listOf("Button A", "Button B", "Button C", "Button D", "Button E", "Button F")
Column(Modifier.fillMaxSize(), Arrangement.spacedBy(5.dp)) {
buttons.forEachIndexed { index, name ->
// wrap button in BoxWithTooltip
TooltipArea(
tooltip = {
// composable tooltip content
Surface(
modifier = Modifier.shadow(4.dp),
color = Color(255, 255, 210),
shape = RoundedCornerShape(4.dp)
) {
Text(
text = "Tooltip for ${name}",
modifier = Modifier.padding(10.dp)
)
}
},
modifier = Modifier.padding(start = 40.dp),
delayMillis = 600, // in milliseconds
tooltipPlacement = TooltipPlacement.CursorPoint(
alignment = Alignment.BottomEnd,
offset = if (index % 2 == 0) DpOffset(-16.dp, 0.dp) else DpOffset.Zero // tooltip offset
)
) {
Button(onClick = {}) { Text(text = name) }
}
}
}
}
}
```
<img alt="Tooltip" src="tooltips.gif" height="314" />

BIN
tutorials/Desktop_Components/lazy_scrollbar.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

BIN
tutorials/Desktop_Components/scrollbars.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

BIN
tutorials/Desktop_Components/tooltips.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

492
tutorials/Mouse_Events/README.md

@ -1,492 +0,0 @@
# Mouse events
## What is covered
In this tutorial we will see how to install mouse event listeners on components
in Compose for Desktop.
## Mouse event listeners
### Click listeners
Click listeners are available in both Compose on Android and Compose for Desktop, so code like this will work on both platforms:
```kotlin
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication {
var count by remember { mutableStateOf(0) }
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxWidth()) {
var text by remember { mutableStateOf("Click magenta box!") }
Column {
@OptIn(ExperimentalFoundationApi::class)
Box(
modifier = Modifier
.background(Color.Magenta)
.fillMaxWidth(0.7f)
.fillMaxHeight(0.2f)
.combinedClickable(
onClick = {
text = "Click! ${count++}"
},
onDoubleClick = {
text = "Double click! ${count++}"
},
onLongClick = {
text = "Long click! ${count++}"
}
)
)
Text(text = text, fontSize = 40.sp)
}
}
}
```
<img alt="Application running" src="mouse_click.gif" height="500" />
`combinedClickable` supports only the Primary button (Left Mouse Button) and touch events. If there is a need to handle other buttons differently, please have a look at `Modifier.onClick` below (note: `Modifier.onClick` is currently avilable only for Desktop-JVM platform).
### Mouse move listeners
Let's create a window and install a pointer move listener on it that changes the background color according to the mouse pointer position:
```kotlin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
var color by remember { mutableStateOf(Color(0, 0, 0)) }
Box(
modifier = Modifier
.wrapContentSize(Alignment.Center)
.fillMaxSize()
.background(color = color)
.onPointerEvent(PointerEventType.Move) {
val position = it.changes.first().position
color = Color(position.x.toInt() % 256, position.y.toInt() % 256, 0)
}
)
}
```
*Note that onPointerEvent is experimental and can be changed in the future. For more stable API look at [Modifier.pointerInput](#listenining-raw-events-in-commonmain-via-modifierpointerinput)*.
<img alt="Application running" src="mouse_move.gif" height="519" />
### Mouse enter listeners
Compose for Desktop also supports pointer enter and exit handlers, like this:
```kotlin
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
Column(
Modifier.background(Color.White),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
repeat(10) { index ->
var active by remember { mutableStateOf(false) }
Text(
modifier = Modifier
.fillMaxWidth()
.background(color = if (active) Color.Green else Color.White)
.onPointerEvent(PointerEventType.Enter) { active = true }
.onPointerEvent(PointerEventType.Exit) { active = false },
fontSize = 30.sp,
fontStyle = if (active) FontStyle.Italic else FontStyle.Normal,
text = "Item $index"
)
}
}
}
```
*Note that onPointerEvent is experimental and can be changed in the future. For more stable API look at [Modifier.pointerInput](#listenining-raw-events-in-commonmain-via-modifierpointerinput)*.
<img alt="Application running" src="mouse_enter.gif" height="500" />
### Mouse scroll listeners
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
var number by remember { mutableStateOf(0f) }
Box(
Modifier
.fillMaxSize()
.onPointerEvent(PointerEventType.Scroll) {
number += it.changes.first().scrollDelta.y
},
contentAlignment = Alignment.Center
) {
Text("Scroll to change the number: $number", fontSize = 30.sp)
}
}
```
*Note that onPointerEvent is experimental and can be changed in the future. For more stable API look at [Modifier.pointerInput](#listenining-raw-events-in-commonmain-via-modifierpointerinput)*.
### Swing interoperability
Compose for Desktop uses Swing underneath and allows to access raw AWT events:
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.awt.awtEventOrNull
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalComposeUiApi::class)
fun main() = singleWindowApplication {
var text by remember { mutableStateOf("") }
Box(
Modifier
.fillMaxSize()
.onPointerEvent(PointerEventType.Press) {
text = it.awtEventOrNull?.locationOnScreen?.toString().orEmpty()
},
contentAlignment = Alignment.Center
) {
Text(text)
}
}
```
*Note that onPointerEvent is experimental and can be changed in the future. For more stable API look at [Modifier.pointerInput](#listenining-raw-events-in-commonmain-via-modifierpointerinput)*.
### Listenining raw events in commonMain via Modifier.pointerInput
In the snippets above we use `Modifier.onPointerEvent`, which is a helper function to subscribe to some type of pointer events. It is a shorter variant of `Modifier.pointerInput`. For now it is experimental, and desktop-only (you can't use it in commonMain code). If you need to subscribe to events in commonMain or you need stable API, you can use `Modifier.pointerInput`:
```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.window.singleWindowApplication
fun main() = singleWindowApplication {
val list = remember { mutableStateListOf<String>() }
Column(
Modifier
.fillMaxSize()
.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
val position = event.changes.first().position
// on every relayout Compose will send synthetic Move event,
// so we skip it to avoid event spam
if (event.type != PointerEventType.Move) {
list.add(0, "${event.type} $position")
}
}
}
},
) {
for (item in list.take(20)) {
Text(item)
}
}
}
```
### New experimental onClick handlers (only for Desktop-JVM platform)
`Modifier.onClick` provides independent callbacks for clicks, double clicks, long clicks. It handles clicks originated only from pointer events and accessibility `click` event is not handled out of a box.
Each `onClick` can be configured to target specific pointer events (using `matcher: PointerMatcher` and `keyboardModifiers: PointerKeyboardModifiers.() -> Boolean`). `matcher` can be specified to choose what mouse button should trigger a click. `keyboardModifiers` allows for filtering pointer events which have specified keyboardModifiers pressed.
Multiple `onClick` modifiers can be chained to handle different clicks with different conditions (matcher and keyboard modifiers).
Unlike `clickable`, `onClick` doesn't have a default `Modifier.indication`, `Modifier.semantics`, and it doesn't trigger a click event when `Enter` pressed. These modifiers need to be added separately if necessary.
The most generic (with the least number of conditions) `onClick` handlers should be declared before other to ensure correct events propagation.
```kotlin
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.background
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.onClick
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.isAltPressed
import androidx.compose.ui.input.pointer.isShiftPressed
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalFoundationApi::class, ExperimentalAnimationApi::class)
fun main() = singleWindowApplication {
Column {
var topBoxText by remember { mutableStateOf("Click me\nusing LMB or LMB + Shift") }
var topBoxCount by remember { mutableStateOf(0) }
// No indication on interaction
Box(modifier = Modifier.size(200.dp, 100.dp).background(Color.Blue)
// the most generic click handler (without extra conditions) should be the first one
.onClick {
// it will receive all LMB clicks except when Shift is pressed
println("Click with primary button")
topBoxText = "LMB ${topBoxCount++}"
}.onClick(
keyboardModifiers = { isShiftPressed } // accept clicks only when Shift pressed
) {
// it will receive all LMB clicks when Shift is pressed
println("Click with primary button and shift pressed")
topBoxCount++
topBoxText = "LMB + Shift ${topBoxCount++}"
}
) {
AnimatedContent(
targetState = topBoxText,
modifier = Modifier.align(Alignment.Center)
) {
Text(text = it, textAlign = TextAlign.Center)
}
}
var bottomBoxText by remember { mutableStateOf("Click me\nusing LMB or\nRMB + Alt") }
var bottomBoxCount by remember { mutableStateOf(0) }
val interactionSource = remember { MutableInteractionSource() }
// With indication on interaction
Box(modifier = Modifier.size(200.dp, 100.dp).background(Color.Yellow)
.onClick(
enabled = true,
interactionSource = interactionSource,
matcher = PointerMatcher.mouse(PointerButton.Secondary), // Right Mouse Button
keyboardModifiers = { isAltPressed }, // accept clicks only when Alt pressed
onLongClick = { // optional
bottomBoxText = "RMB Long Click + Alt ${bottomBoxCount++}"
println("Long Click with secondary button and Alt pressed")
},
onDoubleClick = { // optional
bottomBoxText = "RMB Double Click + Alt ${bottomBoxCount++}"
println("Double Click with secondary button and Alt pressed")
},
onClick = {
bottomBoxText = "RMB Click + Alt ${bottomBoxCount++}"
println("Click with secondary button and Alt pressed")
}
)
.onClick(interactionSource = interactionSource) { // use default parameters
bottomBoxText = "LMB Click ${bottomBoxCount++}"
println("Click with primary button (mouse left button)")
}
.indication(interactionSource, LocalIndication.current)
) {
AnimatedContent(
targetState = bottomBoxText,
modifier = Modifier.align(Alignment.Center)
) {
Text(text = it, textAlign = TextAlign.Center)
}
}
}
}
```
### New experimental onDrag modifier (only for Desktop-JVM platform)
`Modifier.onDrag` allows for configuration of the pointer that should trigger the drag (see `matcher: PointerMatcher`).
Many `onDrag` modifiers can be chained together.
The example below also shows how to access the state of keyboard modifiers (via `LocalWindowInfo.current.keyboardModifier`) for cases when keyboard modifiers can alter the behaviour of the drag (for example: move an item if we perform a simple drag; or copy/paste an item if dragged with Ctrl pressed)
```kotlin
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.onDrag
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.isCtrlPressed
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalFoundationApi::class)
fun main() = singleWindowApplication {
val windowInfo = LocalWindowInfo.current
Column {
var topBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) }
Box(modifier = Modifier.offset {
IntOffset(topBoxOffset.x.toInt(), topBoxOffset.y.toInt())
}.size(100.dp)
.background(Color.Green)
.onDrag { // all default: enabled = true, matcher = PointerMatcher.Primary (left mouse button)
topBoxOffset += it
}
) {
Text(text = "Drag with LMB", modifier = Modifier.align(Alignment.Center))
}
var bottomBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) }
Box(modifier = Modifier.offset {
IntOffset(bottomBoxOffset.x.toInt(), bottomBoxOffset.y.toInt())
}.size(100.dp)
.background(Color.LightGray)
.onDrag(
enabled = true,
matcher = PointerMatcher.mouse(PointerButton.Secondary), // right mouse button
onDragStart = {
println("Gray Box: drag start")
},
onDragEnd = {
println("Gray Box: drag end")
}
) {
val keyboardModifiers = windowInfo.keyboardModifiers
bottomBoxOffset += if (keyboardModifiers.isCtrlPressed) it * 2f else it
}
) {
Text(text = "Drag with RMB,\ntry with CTRL", modifier = Modifier.align(Alignment.Center))
}
}
}
```
There is also a non-modifier way to handle drags using `suspend fun PointerInputScope.detectDragGestures`:
```kotlin
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.singleWindowApplication
@OptIn(ExperimentalFoundationApi::class)
fun main() = singleWindowApplication {
var topBoxOffset by remember { mutableStateOf(Offset(0f, 0f)) }
Box(modifier = Modifier.offset {
IntOffset(topBoxOffset.x.toInt(), topBoxOffset.y.toInt())
}.size(100.dp)
.background(Color.Green)
.pointerInput(Unit) {
detectDragGestures(
matcher = PointerMatcher.Primary
) {
topBoxOffset += it
}
}
) {
Text(text = "Drag with LMB", modifier = Modifier.align(Alignment.Center))
}
}
```

BIN
tutorials/Mouse_Events/mouse_click.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

BIN
tutorials/Mouse_Events/mouse_enter.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

BIN
tutorials/Mouse_Events/mouse_event.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

BIN
tutorials/Mouse_Events/mouse_move.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

6
tutorials/README.md

@ -5,9 +5,9 @@
## Desktop
* [Image and icon manipulations](Image_And_Icons_Manipulations)
* [Mouse events and hover](Mouse_Events)
* [Scrolling and scrollbars](Desktop_Components#scrollbars)
* [Tooltips](Desktop_Components#tooltips)
* [Mouse events and hover](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-desktop-mouse-events.html)
* [Scrolling and scrollbars](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-desktop-scrollbars.html)
* [Tooltips](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-desktop-tooltips.html)
* [Context Menu](Context_Menu/README.md)
* [Top level windows management](Window_API_new)
* [Menu, tray, notifications](Tray_Notifications_MenuBar_new)

Loading…
Cancel
Save