Closed znoraa closed 1 year ago
It's possible to add drag & drop to any Composable via onExternalDrag
(#222).
Here is one example on how you can use it:
@file:OptIn(ExperimentalComposeUiApi::class)
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import java.net.URI
import java.nio.file.LinkOption
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.toPath
private object Colors {
val default = Color.Gray
val active = Color(29, 117, 223, 255)
val fileItemBg = Color(233, 30, 99, 255)
val fileItemFg = Color.White
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Preview
fun App() {
MaterialTheme {
Box(modifier = Modifier.fillMaxSize().background(Color.White)) {
Column {
val dragAndDropModifier = Modifier.padding(horizontal = 4.dp, vertical = 4.dp)
var droppedFiles by remember { mutableStateOf<List<Path>>(emptyList()) }
DragAndDropFileBox(dragAndDropModifier.size(height = 200.dp, width = 400.dp)) { dragData ->
if (dragData is DragData.FilesList) {
val newFiles = dragData.readFiles().mapNotNull {
URI(it).toPath().takeIf { it.exists(LinkOption.NOFOLLOW_LINKS) }
}
droppedFiles = (droppedFiles + newFiles).distinct()
}
}
FileListView(modifier = dragAndDropModifier, files = droppedFiles)
}
}
}
}
@Composable
private fun FileListView(modifier: Modifier = Modifier, files: List<Path>) {
LazyColumn(modifier) {
items(files) {
Box(
Modifier.padding(bottom = 5.dp)
.background(
Colors.fileItemBg, shape = RoundedCornerShape(100.dp)
)
) {
Text(
text = it.fileName.toString(),
color = Colors.fileItemFg,
modifier = Modifier.padding(5.dp),
fontSize = 14.sp
)
}
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun DragAndDropFileBox(modifier: Modifier = Modifier, onDrop: (DragData) -> Unit) {
var isDragging by remember { mutableStateOf(false) }
val dragNDropColor = if (isDragging) Colors.active else Colors.default
Box(
modifier = modifier
.dashedBorder(color = dragNDropColor, strokeWidth = 2.dp, cornerRadiusDp = 8.dp)
.onExternalDrag(
onDragStart = { isDragging = true },
onDragExit = { isDragging = false },
onDrop = { value ->
isDragging = false
onDrop(value.dragData)
})
) {
Column(modifier = Modifier.align(Alignment.Center)) {
DragAndDropDescription(
modifier = Modifier.align(Alignment.CenterHorizontally),
color = dragNDropColor
)
}
}
}
@Composable
fun DragAndDropDescription(modifier: Modifier, color: Color) {
val modifier = modifier.padding(vertical = 2.dp)
Text(
"Drag & drop files here",
fontSize = 14.sp,
modifier = modifier,
color = color
)
}
fun Modifier.dashedBorder(strokeWidth: Dp, color: Color, cornerRadiusDp: Dp) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
val cornerRadiusPx = density.run { cornerRadiusDp.toPx() }
then(
Modifier.drawWithCache {
onDrawBehind {
val stroke = Stroke(
width = strokeWidthPx,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
drawRoundRect(
color = color,
style = stroke,
cornerRadius = CornerRadius(cornerRadiusPx)
)
}
}
)
}
)
fun main() = application {
val windowState = rememberWindowState(width = 600.dp, height = 600.dp)
Window(onCloseRequest = ::exitApplication, state = windowState) {
App()
}
}
@znoraa the questions regarding file dialogs have been asked previously. See #176, #197 and #1003 for possible solutions using AWT/Swing. For now, reimlementing file dialogs in Compose or providing bindings for system file dialogs is not on the roadmap.
It's unfortunate, that we don't have a tutorial for working with files. I've created #3309 for that
@znoraa the questions regarding file dialogs have been asked previously. See #176, #197 and #1003 for possible solutions using AWT/Swing. For now, reimlementing file dialogs in Compose or providing bindings for system file dialogs is not on the roadmap.
It's unfortunate, that we don't have a tutorial for working with files. I've created #3309 for that
@AlexeyTsvetkov Sounds good! Thank you for the example code as well.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Current it lacks components for file operations e.g. a file chooser to select local file/directory, support to drag/drop local file to the window of compose app, which is quite useful in desktop apps. Now have to use other libraries to achieve it (e.g. awt/swing). It'd be great to have native support from Compose