/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
package jxtn.jfx.makers;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import javafx.scene.Node;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DataFormat;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
/**
* 拖放功能設定
*
* @author AqD
* @param <T> 拖放項目型態
*/
public final class DragAndDropSetup<T>
{
/**
* 建立新的拖放功能設定
*/
public DragAndDropSetup()
{
}
/**
* 指定從項目取得ID的函數
*
* @param idFromItem 從項目取得ID的函數
* @return 目前的建構器(this)
*/
public DragAndDropSetup<T> idFromItem(Function<T, String> idFromItem)
{
this.idFromItem = idFromItem;
return this;
}
/**
* 指定從ID尋找項目的函數
*
* @param itemFromId 從ID尋找項目的函數
* @return 目前的建構器(this)
*/
public DragAndDropSetup<T> itemFromId(Function<String, T> itemFromId)
{
this.itemFromId = itemFromId;
return this;
}
/**
* 指定測試項目是否可拖曳的條件
*
* @param acceptDrag 測試項目是否可拖曳的條件
* @return 目前的建構器(this)
*/
public DragAndDropSetup<T> acceptDrag(Predicate<T> acceptDrag)
{
this.acceptDrag = acceptDrag;
return this;
}
/**
* 指定測試項目是否可接收被拖曳項目的條件
*
* @param acceptDrop 測試項目是否可接收被拖曳項目的條件,(項目,新位置項目)
* @return 目前的建構器(this)
*/
public DragAndDropSetup<T> acceptDrop(BiPredicate<T, T> acceptDrop)
{
this.acceptDrop = acceptDrop;
return this;
}
/**
* 指定拖放結束的移動項目到新位置的動作
*
* @param moveItem 拖放結束的移動項目到新位置的動作,(項目,新位置項目)
* @return 目前的建構器(this)
*/
public DragAndDropSetup<T> moveItem(BiConsumer<T, T> moveItem)
{
this.moveItem = moveItem;
return this;
}
/**
* 套用拖放功能到指定的UI節點
*
* @param <N> UI節點型態
* @param node 要套用拖放功能的目的UI節點
* @param isEmpty 測試節點是否不含資料的條件
* @param getItem 取得節點代表資料項目的函數
*/
public <N extends Node> void apply(N node, Predicate<N> isEmpty, Function<N, T> getItem)
{
Objects.requireNonNull(node);
Objects.requireNonNull(isEmpty);
Objects.requireNonNull(getItem);
Objects.requireNonNull(this.idFromItem);
Objects.requireNonNull(this.itemFromId);
Objects.requireNonNull(this.acceptDrag);
Objects.requireNonNull(this.acceptDrop);
Objects.requireNonNull(this.moveItem);
node.setOnDragDetected(e -> this.nodeOnDragDetected(node, isEmpty, getItem, e));
node.setOnDragDone(e -> this.nodeOnDragDone(e));
node.setOnDragOver(e -> this.nodeOnDragOver(node, isEmpty, getItem, e));
node.setOnDragDropped(e -> this.nodeOnDragDropped(node, isEmpty, getItem, e));
}
private <N extends Node> void nodeOnDragDetected(
N node, Predicate<N> isEmpty, Function<N, T> getItem, MouseEvent e)
{
if (isEmpty.test(node))
return;
T item = getItem.apply(node);
if (!this.acceptDrag.test(item))
return;
Dragboard dragBoard = node.startDragAndDrop(TransferMode.MOVE);
ClipboardContent content = new ClipboardContent();
String id = this.idFromItem.apply(item);
content.put(DataFormat.PLAIN_TEXT, Objects.requireNonNull(id));
dragBoard.setContent(content);
e.consume();
}
private void nodeOnDragDone(DragEvent e)
{
e.setDropCompleted(e.getTransferMode() == TransferMode.MOVE);
e.consume();
}
private <N extends Node> void nodeOnDragOver(
N node, Predicate<N> isEmpty, Function<N, T> getItem, DragEvent e)
{
if (isEmpty.test(node))
return;
if (!e.getDragboard().hasString())
return;
if (e.getDragboard().getString().equals(this.idFromItem.apply(getItem.apply(node))))
return;
T itemToMove = this.itemFromId.apply(e.getDragboard().getString());
T newParent = getItem.apply(node);
if (this.acceptDrop.test(itemToMove, newParent))
{
e.acceptTransferModes(TransferMode.MOVE);
}
e.consume();
}
private <N extends Node> void nodeOnDragDropped(
N node, Predicate<N> isEmpty, Function<N, T> getItem, DragEvent e)
{
if (isEmpty.test(node))
return;
if (!e.getDragboard().hasString())
return;
if (e.getDragboard().getString().equals(this.idFromItem.apply(getItem.apply(node))))
return;
T itemToMove = this.itemFromId.apply(e.getDragboard().getString());
T newParent = getItem.apply(node);
if (this.acceptDrop.test(itemToMove, newParent))
{
this.moveItem.accept(itemToMove, newParent);
}
e.consume();
}
private Function<T, String> idFromItem;
private Function<String, T> itemFromId;
private Predicate<T> acceptDrag;
private BiPredicate<T, T> acceptDrop;
private BiConsumer<T, T> moveItem;
}