mirror of
https://github.com/amir1376/ab-download-manager.git
synced 2025-02-20 11:43:24 +08:00
Merge pull request #117 from amir1376/update-drag-and-drop-logic
update drag and drop logic
This commit is contained in:
commit
ac3e9841aa
@ -65,7 +65,6 @@ dependencies {
|
||||
implementation(project(":integration:server"))
|
||||
implementation(project(":desktop:shared"))
|
||||
implementation(project(":desktop:tray"))
|
||||
implementation(project(":desktop:external-draggable"))
|
||||
implementation(project(":desktop:custom-window-frame"))
|
||||
implementation(project(":shared:app-utils"))
|
||||
implementation(project(":shared:utils"))
|
||||
|
@ -674,16 +674,14 @@ class HomeComponent(
|
||||
currentActiveDrops.update { parsedLinks }
|
||||
}
|
||||
|
||||
fun onExternalFilesDraggedIn(getFilePaths: () -> List<String>) {
|
||||
val filePaths = getFilePaths().map {
|
||||
URI.create(it)
|
||||
}
|
||||
.mapNotNull {
|
||||
runCatching { File(it.path) }.getOrNull()
|
||||
}
|
||||
fun onExternalFilesDraggedIn(getFilePaths: () -> List<File>) {
|
||||
val filePaths = kotlin.runCatching { getFilePaths() }
|
||||
.getOrNull()?.filter { it.length() <= 1024 * 1024 } ?: return
|
||||
onExternalTextDraggedIn {
|
||||
filePaths.first()
|
||||
.readText()
|
||||
filePaths
|
||||
.firstOrNull()
|
||||
?.readText()
|
||||
.orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import com.abdownloadmanager.desktop.ui.widget.Text
|
||||
import androidx.compose.runtime.*
|
||||
import com.abdownloadmanager.desktop.utils.externaldraggable.onExternalDrag
|
||||
import androidx.compose.ui.*
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -32,8 +31,12 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import com.abdownloadmanager.desktop.ui.widget.ActionButton
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.draganddrop.dragAndDropTarget
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.ui.draganddrop.DragAndDropEvent
|
||||
import androidx.compose.ui.draganddrop.DragAndDropTarget
|
||||
import androidx.compose.ui.draganddrop.awtTransferable
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@ -41,10 +44,11 @@ import androidx.compose.ui.platform.LocalWindowInfo
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.abdownloadmanager.desktop.ui.customwindow.*
|
||||
import com.abdownloadmanager.desktop.ui.widget.menu.ShowOptionsInDropDown
|
||||
import com.abdownloadmanager.desktop.utils.externaldraggable.DragData
|
||||
import com.abdownloadmanager.utils.category.Category
|
||||
import com.abdownloadmanager.utils.category.rememberIconPainter
|
||||
import ir.amirab.util.compose.action.MenuItem
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.io.File
|
||||
|
||||
|
||||
@Composable
|
||||
@ -162,27 +166,40 @@ fun HomePage(component: HomeComponent) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.onExternalDrag(
|
||||
onDragStart = {
|
||||
isDragging = true
|
||||
it.availableDragData.get<DragData.Text>()?.also {
|
||||
component.onExternalTextDraggedIn { it.readText() }
|
||||
return@onExternalDrag
|
||||
}
|
||||
it.availableDragData.get<DragData.FilesList>()?.also {
|
||||
//Caution FileList::readFiles sometimes throws exception
|
||||
component.onExternalFilesDraggedIn { it.readFiles() }
|
||||
return@onExternalDrag
|
||||
}
|
||||
.dragAndDropTarget(
|
||||
shouldStartDragAndDrop = {
|
||||
it.awtTransferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor) ||
|
||||
it.awtTransferable.isDataFlavorSupported(DataFlavor.stringFlavor)
|
||||
},
|
||||
onDragExit = {
|
||||
isDragging = false
|
||||
component.onDragExit()
|
||||
target = remember {
|
||||
object : DragAndDropTarget {
|
||||
override fun onStarted(event: DragAndDropEvent) {
|
||||
isDragging = true
|
||||
if (event.awtTransferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
component.onExternalTextDraggedIn { (event.awtTransferable.getTransferData(DataFlavor.stringFlavor) as String) }
|
||||
return
|
||||
}
|
||||
if (event.awtTransferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
component.onExternalFilesDraggedIn {
|
||||
(event.awtTransferable.getTransferData(DataFlavor.javaFileListFlavor) as List<File>)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnded(event: DragAndDropEvent) {
|
||||
isDragging = false
|
||||
component.onDragExit()
|
||||
}
|
||||
|
||||
override fun onDrop(event: DragAndDropEvent): Boolean {
|
||||
isDragging = false
|
||||
component.onDropped()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
isDragging = false
|
||||
component.onDropped()
|
||||
}
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
Modifier.alpha(
|
||||
|
@ -1,7 +0,0 @@
|
||||
plugins{
|
||||
id(MyPlugins.kotlin)
|
||||
id(MyPlugins.composeDesktop)
|
||||
}
|
||||
dependencies{
|
||||
implementation(project(":desktop:shared"))
|
||||
}
|
@ -1,462 +0,0 @@
|
||||
package com.abdownloadmanager.desktop.utils.externaldraggable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.composed
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.unit.Density
|
||||
import ir.amirab.util.desktop.LocalWindow
|
||||
import java.awt.Component
|
||||
import java.awt.GraphicsConfiguration
|
||||
import java.awt.Point
|
||||
import java.awt.Window
|
||||
import java.awt.dnd.DnDConstants
|
||||
import java.awt.dnd.DropTarget
|
||||
import java.awt.dnd.DropTargetDragEvent
|
||||
import java.awt.dnd.DropTargetDropEvent
|
||||
import java.awt.dnd.DropTargetEvent
|
||||
import java.awt.dnd.DropTargetListener
|
||||
|
||||
/**
|
||||
* Represent data that is being dragged (or dropped) to a component from outside an application.
|
||||
*/
|
||||
interface DragData {
|
||||
/**
|
||||
* Represents list of files drag and dropped to a component.
|
||||
*/
|
||||
interface FilesList : DragData {
|
||||
/**
|
||||
* Returns list of file paths drag and droppped to an application in a URI format.
|
||||
*/
|
||||
fun readFiles(): List<String>
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an image drag and dropped to a component.
|
||||
*/
|
||||
interface Image : DragData {
|
||||
/**
|
||||
* Returns an image drag and dropped to an application as a [Painter] type.
|
||||
*/
|
||||
fun readImage(): Painter
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent text drag and dropped to a component.
|
||||
*/
|
||||
interface Text : DragData {
|
||||
/**
|
||||
* Provides the best MIME type that describes text returned in [readText]
|
||||
*/
|
||||
val bestMimeType: String
|
||||
|
||||
/**
|
||||
* Returns a text dropped to an application.
|
||||
*/
|
||||
fun readText(): String
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent the current state of drag and drop to a component from outside an application.
|
||||
* This state is passed to external drag callbacks.
|
||||
*
|
||||
* @see onExternalDrag
|
||||
*/
|
||||
@Immutable
|
||||
class ExternalDragValue(
|
||||
/**
|
||||
* Position of the pointer relative to the component
|
||||
*/
|
||||
val dragPosition: Offset,
|
||||
/**
|
||||
* Data that it being dragged (or dropped) in a component bounds
|
||||
*/
|
||||
val availableDragData: AvailableDragData
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds detector of external drag and drop (e.g. files DnD from Finder to an application)
|
||||
*
|
||||
* @param onDragStart will be called when the pointer with external content entered the component.
|
||||
* @param onDrag will be called for pointer movements inside the component.
|
||||
* @param onDragExit is called if the pointer exited the component bounds.
|
||||
* @param onDrop is called when the pointer is released.
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.onExternalDrag(
|
||||
enabled: Boolean = true,
|
||||
onDragStart: (ExternalDragValue) -> Unit = {},
|
||||
onDrag: (ExternalDragValue) -> Unit = {},
|
||||
onDragExit: () -> Unit = {},
|
||||
onDrop: (ExternalDragValue) -> Unit = {},
|
||||
): Modifier = composed {
|
||||
if (!enabled) {
|
||||
return@composed Modifier
|
||||
}
|
||||
val window = LocalWindow.current ?: return@composed Modifier
|
||||
|
||||
val componentDragHandler = rememberUpdatedState(
|
||||
AwtWindowDropTarget.ComponentDragHandler(onDragStart, onDrag, onDragExit, onDrop)
|
||||
)
|
||||
|
||||
var componentDragHandleId by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
DisposableEffect(window) {
|
||||
when (val currentDropTarget = window.dropTarget) {
|
||||
is AwtWindowDropTarget -> {
|
||||
// if our drop target is already assigned simply add new drag handler for the current component
|
||||
componentDragHandleId =
|
||||
currentDropTarget.installComponentDragHandler(componentDragHandler)
|
||||
}
|
||||
|
||||
null -> {
|
||||
// drop target is not installed for the window, so assign it and add new drag handler for the current component
|
||||
val newDropTarget = AwtWindowDropTarget(window)
|
||||
componentDragHandleId =
|
||||
newDropTarget.installComponentDragHandler(componentDragHandler)
|
||||
window.dropTarget = newDropTarget
|
||||
}
|
||||
|
||||
else -> {
|
||||
error("Window already has unknown external dnd handler, cannot attach onExternalDrag")
|
||||
}
|
||||
}
|
||||
|
||||
onDispose {
|
||||
// stop drag events handling for this component when window is changed
|
||||
// or the component leaves the composition
|
||||
val dropTarget = window.dropTarget as? AwtWindowDropTarget ?: return@onDispose
|
||||
val handleIdToRemove = componentDragHandleId ?: return@onDispose
|
||||
dropTarget.stopDragHandling(handleIdToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
Modifier
|
||||
.onGloballyPositioned { position ->
|
||||
// provide new component bounds to Swing to properly detect drag events
|
||||
val dropTarget = window.dropTarget as? AwtWindowDropTarget
|
||||
?: return@onGloballyPositioned
|
||||
val handleIdToUpdate = componentDragHandleId ?: return@onGloballyPositioned
|
||||
val componentBounds = position.boundsInWindow()
|
||||
dropTarget.updateComponentBounds(handleIdToUpdate, componentBounds)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a way to subscribe on external drag for given [window] using [installComponentDragHandler]
|
||||
*
|
||||
* [Window] allows having only one [DropTarget], so this is the main [DropTarget] that handles all the drag subscriptions
|
||||
*/
|
||||
internal class AwtWindowDropTarget(
|
||||
private val window: Window
|
||||
) : DropTarget(window, DnDConstants.ACTION_MOVE, null, true) {
|
||||
private var idsCounter = 0
|
||||
|
||||
// all components that are subscribed to external drag and drop for the window
|
||||
// handler's callbacks can be changed on recompositions, so State is kept here
|
||||
private val handlers = mutableMapOf<Int, State<ComponentDragHandler>>()
|
||||
|
||||
// bounds of all components that are subscribed to external drag and drop for the window
|
||||
private val componentBoundsHolder = mutableMapOf<Int, Rect>()
|
||||
|
||||
// state of ongoing external drag and drop in the [window], contains pointer coordinates and data that is dragged
|
||||
private var currentDragValue: AwtWindowDragTargetListener.WindowDragValue? = null
|
||||
|
||||
val dragTargetListener = AwtWindowDragTargetListener(
|
||||
window,
|
||||
// notify components on window border that drag is started.
|
||||
onDragEnterWindow = { newDragValue ->
|
||||
currentDragValue = newDragValue
|
||||
forEachPositionedComponent { handler, componentBounds ->
|
||||
handleDragEvent(
|
||||
handler,
|
||||
oldComponentBounds = componentBounds, currentComponentBounds = componentBounds,
|
||||
oldDragValue = null, currentDragValue = newDragValue,
|
||||
)
|
||||
}
|
||||
},
|
||||
// drag moved inside window, we should calculate whether drag entered/exited components or just moved inside them
|
||||
onDragInsideWindow = { newDragValue ->
|
||||
val oldDragValue = currentDragValue
|
||||
currentDragValue = newDragValue
|
||||
forEachPositionedComponent { handler, componentBounds ->
|
||||
handleDragEvent(
|
||||
handler,
|
||||
oldComponentBounds = componentBounds, currentComponentBounds = componentBounds,
|
||||
oldDragValue, newDragValue
|
||||
)
|
||||
}
|
||||
},
|
||||
// notify components on window border drag exited window
|
||||
onDragExit = {
|
||||
val oldDragValue = currentDragValue
|
||||
currentDragValue = null
|
||||
forEachPositionedComponent { handler, componentBounds ->
|
||||
handleDragEvent(
|
||||
handler,
|
||||
oldComponentBounds = componentBounds, currentComponentBounds = componentBounds,
|
||||
oldDragValue = oldDragValue, currentDragValue = null
|
||||
)
|
||||
}
|
||||
},
|
||||
// notify all components under the pointer that drop happened
|
||||
onDrop = { newDragValue ->
|
||||
var anyDrops = false
|
||||
currentDragValue = null
|
||||
forEachPositionedComponent { handler, componentBounds ->
|
||||
val isInside = isExternalDragInsideComponent(
|
||||
componentBounds,
|
||||
newDragValue.dragPositionInWindow
|
||||
)
|
||||
if (isInside) {
|
||||
val offset = calculateOffset(componentBounds, newDragValue.dragPositionInWindow)
|
||||
handler.onDrop(ExternalDragValue(offset, newDragValue.dragData))
|
||||
anyDrops = true
|
||||
}
|
||||
}
|
||||
// tell swing whether some components accepted the drop
|
||||
return@AwtWindowDragTargetListener anyDrops
|
||||
}
|
||||
)
|
||||
|
||||
init {
|
||||
addDropTargetListener(dragTargetListener)
|
||||
}
|
||||
|
||||
override fun setActive(isActive: Boolean) {
|
||||
super.setActive(isActive)
|
||||
if (!isActive) {
|
||||
currentDragValue = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds handler that will be notified on drag events for [window].
|
||||
* If component bounds are provided using [updateComponentBounds],
|
||||
* given lambdas will be called on drag events.
|
||||
*
|
||||
* [handlerState]'s callbacks can be changed on recompositions.
|
||||
* New callbacks won't be called with old events, they will be called on new AWT events only.
|
||||
*
|
||||
* @return handler id that can be used later to remove subscription using [stopDragHandling]
|
||||
* or to update component bounds using [updateComponentBounds]
|
||||
*/
|
||||
fun installComponentDragHandler(handlerState: State<ComponentDragHandler>): Int {
|
||||
isActive = true
|
||||
val handleId = idsCounter++
|
||||
handlers[handleId] = handlerState
|
||||
return handleId
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes handler with [handleId].
|
||||
* Calls [ComponentDragHandler.onDragCancel] if drag is going and handler's component is under pointer
|
||||
*
|
||||
* Disable drag handling for [window] if there are no more handlers.
|
||||
*
|
||||
* @param handleId id provided by [installComponentDragHandler] function
|
||||
*/
|
||||
fun stopDragHandling(handleId: Int) {
|
||||
val handler = handlers.remove(handleId)
|
||||
val componentBounds = componentBoundsHolder.remove(handleId)
|
||||
if (handler != null && componentBounds != null &&
|
||||
isExternalDragInsideComponent(componentBounds, currentDragValue?.dragPositionInWindow)
|
||||
) {
|
||||
handler.value.onDragCancel()
|
||||
}
|
||||
|
||||
if (handlers.isEmpty()) {
|
||||
isActive = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates component bounds within the [window], so drag events will be properly handled.
|
||||
* If drag is going and component is under the pointer, onDragStart and onDrag will be called.
|
||||
* If drag is going and component moved/became smaller, so that pointer now is not the component, onDragCancel is called.
|
||||
*
|
||||
* All further drag events will use [newComponentBounds] to notify handler with [handleId].
|
||||
*
|
||||
* @param newComponentBounds new bounds of the component inside [window] used to properly detect when drag entered/exited component
|
||||
*/
|
||||
fun updateComponentBounds(handleId: Int, newComponentBounds: Rect) {
|
||||
val handler = handlers[handleId] ?: return
|
||||
val oldComponentBounds = componentBoundsHolder.put(handleId, newComponentBounds)
|
||||
handleDragEvent(
|
||||
handler.value, oldComponentBounds, newComponentBounds,
|
||||
oldDragValue = currentDragValue,
|
||||
currentDragValue = currentDragValue
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun forEachPositionedComponent(action: (handler: ComponentDragHandler, bounds: Rect) -> Unit) {
|
||||
for ((handleId, handler) in handlers) {
|
||||
val bounds = componentBoundsHolder[handleId] ?: continue
|
||||
action(handler.value, bounds)
|
||||
}
|
||||
}
|
||||
|
||||
data class ComponentDragHandler(
|
||||
val onDragStart: (ExternalDragValue) -> Unit,
|
||||
val onDrag: (ExternalDragValue) -> Unit,
|
||||
val onDragCancel: () -> Unit,
|
||||
val onDrop: (ExternalDragValue) -> Unit
|
||||
)
|
||||
|
||||
companion object {
|
||||
private fun isExternalDragInsideComponent(
|
||||
componentBounds: Rect?,
|
||||
windowDragCoordinates: Offset?
|
||||
): Boolean {
|
||||
if (componentBounds == null || windowDragCoordinates == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return componentBounds.contains(windowDragCoordinates)
|
||||
}
|
||||
|
||||
private fun calculateOffset(
|
||||
componentBounds: Rect,
|
||||
windowDragCoordinates: Offset
|
||||
): Offset {
|
||||
return windowDragCoordinates - componentBounds.topLeft
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies [handler] about drag events.
|
||||
*
|
||||
* Note: this function is pure, so it doesn't update any states
|
||||
*/
|
||||
private fun handleDragEvent(
|
||||
handler: ComponentDragHandler,
|
||||
oldComponentBounds: Rect?,
|
||||
currentComponentBounds: Rect?,
|
||||
oldDragValue: AwtWindowDragTargetListener.WindowDragValue?,
|
||||
currentDragValue: AwtWindowDragTargetListener.WindowDragValue?,
|
||||
) {
|
||||
val wasDragInside = isExternalDragInsideComponent(
|
||||
oldComponentBounds,
|
||||
oldDragValue?.dragPositionInWindow
|
||||
)
|
||||
val newIsDragInside = isExternalDragInsideComponent(
|
||||
currentComponentBounds,
|
||||
currentDragValue?.dragPositionInWindow
|
||||
)
|
||||
if (!wasDragInside && newIsDragInside) {
|
||||
val dragOffset = calculateOffset(
|
||||
currentComponentBounds!!,
|
||||
currentDragValue!!.dragPositionInWindow
|
||||
)
|
||||
handler.onDragStart(ExternalDragValue(dragOffset, currentDragValue.dragData))
|
||||
return
|
||||
}
|
||||
|
||||
if (wasDragInside && !newIsDragInside) {
|
||||
handler.onDragCancel()
|
||||
return
|
||||
}
|
||||
|
||||
if (newIsDragInside) {
|
||||
val dragOffset = calculateOffset(
|
||||
currentComponentBounds!!,
|
||||
currentDragValue!!.dragPositionInWindow
|
||||
)
|
||||
handler.onDrag(ExternalDragValue(dragOffset, currentDragValue.dragData))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val GraphicsConfiguration.density: Density
|
||||
get() = Density(
|
||||
defaultTransform.scaleX.toFloat(),
|
||||
fontScale = 1f
|
||||
)
|
||||
private val Component.density: Density get() = graphicsConfiguration.density
|
||||
|
||||
internal class AwtWindowDragTargetListener(
|
||||
private val window: Window,
|
||||
val onDragEnterWindow: (WindowDragValue) -> Unit,
|
||||
val onDragInsideWindow: (WindowDragValue) -> Unit,
|
||||
val onDragExit: () -> Unit,
|
||||
val onDrop: (WindowDragValue) -> Boolean,
|
||||
) : DropTargetListener {
|
||||
private val density = window.density.density
|
||||
|
||||
override fun dragEnter(dtde: DropTargetDragEvent) {
|
||||
onDragEnterWindow(
|
||||
WindowDragValue(
|
||||
dtde.location.windowOffset(),
|
||||
AvailableDragData(dtde.transferable.dragData())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun dragOver(dtde: DropTargetDragEvent) {
|
||||
onDragInsideWindow(
|
||||
WindowDragValue(
|
||||
dtde.location.windowOffset(),
|
||||
AvailableDragData(dtde.transferable.dragData())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// takes title bar and other insets into account
|
||||
private fun Point.windowOffset(): Offset {
|
||||
val offsetX = (x - window.insets.left) * density
|
||||
val offsetY = (y - window.insets.top) * density
|
||||
|
||||
return Offset(offsetX, offsetY)
|
||||
}
|
||||
|
||||
override fun dropActionChanged(dtde: DropTargetDragEvent) {
|
||||
// Should we notify about it?
|
||||
}
|
||||
|
||||
override fun dragExit(dte: DropTargetEvent) {
|
||||
onDragExit()
|
||||
}
|
||||
|
||||
override fun drop(dtde: DropTargetDropEvent) {
|
||||
dtde.acceptDrop(dtde.dropAction)
|
||||
|
||||
val transferable = dtde.transferable
|
||||
try {
|
||||
onDrop(WindowDragValue(dtde.location.windowOffset(), transferable.dragData()))
|
||||
dtde.dropComplete(true)
|
||||
} catch (e: Exception) {
|
||||
onDragExit()
|
||||
dtde.dropComplete(false)
|
||||
}
|
||||
}
|
||||
|
||||
data class WindowDragValue(
|
||||
val dragPositionInWindow: Offset,
|
||||
val dragData: AvailableDragData
|
||||
)
|
||||
}
|
||||
|
||||
data class AvailableDragData(val list: List<DragData>) : List<DragData> by list {
|
||||
inline fun <reified T : DragData> get(): T? {
|
||||
for (i in this) {
|
||||
if (i is T) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.abdownloadmanager.desktop.utils.externaldraggable
|
||||
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.toPainter
|
||||
import java.awt.Image
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.datatransfer.DataFlavor.selectBestTextFlavor
|
||||
import java.awt.datatransfer.Transferable
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
|
||||
internal fun Transferable.dragData(): AvailableDragData {
|
||||
val o = mutableListOf<DragData>()
|
||||
if (isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
|
||||
o.add(DragDataFilesListImpl(this))
|
||||
}
|
||||
if (isDataFlavorSupported(DataFlavor.imageFlavor)) {
|
||||
o.add(DragDataImageImpl(this))
|
||||
}
|
||||
selectBestTextFlavor(transferDataFlavors)?.let {
|
||||
o.add(DragDataTextImpl(it, this))
|
||||
}
|
||||
return AvailableDragData(o)
|
||||
}
|
||||
|
||||
|
||||
private class DragDataFilesListImpl(
|
||||
private val transferable: Transferable
|
||||
) : DragData.FilesList {
|
||||
override fun readFiles(): List<String> {
|
||||
val files = transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*>
|
||||
return files.filterIsInstance<File>().map { it.toURI().toString() }
|
||||
}
|
||||
}
|
||||
|
||||
private class DragDataImageImpl(
|
||||
private val transferable: Transferable
|
||||
) : DragData.Image {
|
||||
override fun readImage(): Painter {
|
||||
return (transferable.getTransferData(DataFlavor.imageFlavor) as Image).painter()
|
||||
}
|
||||
|
||||
private fun Image.painter(): Painter {
|
||||
if (this is BufferedImage) {
|
||||
return this.toPainter()
|
||||
}
|
||||
val bufferedImage =
|
||||
BufferedImage(getWidth(null), getHeight(null), BufferedImage.TYPE_INT_ARGB)
|
||||
|
||||
val g2 = bufferedImage.createGraphics()
|
||||
try {
|
||||
g2.drawImage(this, 0, 0, null)
|
||||
} finally {
|
||||
g2.dispose()
|
||||
}
|
||||
|
||||
return bufferedImage.toPainter()
|
||||
}
|
||||
}
|
||||
|
||||
private class DragDataTextImpl(
|
||||
private val bestTextFlavor: DataFlavor,
|
||||
private val transferable: Transferable
|
||||
) : DragData.Text {
|
||||
override val bestMimeType: String = bestTextFlavor.mimeType
|
||||
|
||||
override fun readText(): String {
|
||||
val reader = bestTextFlavor.getReaderForText(transferable)
|
||||
return reader.readText()
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ include("desktop:app")
|
||||
include("desktop:custom-window-frame")
|
||||
include("desktop:shared")
|
||||
include("desktop:tray")
|
||||
include("desktop:external-draggable")
|
||||
include("downloader:core")
|
||||
include("downloader:monitor")
|
||||
include("integration:server")
|
||||
|
Loading…
x
Reference in New Issue
Block a user