Hi,
I’m having problems setting up Paul’s DragAndDropDemoState in my own application: the drag and drop only works as long as I move the Draggable inside the respective container. However, once I want to drag the Draggable out, the DragSession is terminated and the icon picked returns to its orginial place. I’d like to be able to move the icon from one container to another.
I’ve mostly copied Paul’s code (except for some simplifications) and the only real difference I see is that in onDragDetected I’m giving the dragged item a name with
drag.setName("draggedItem");
so that I can update its position in onDragOver with
guiNode.getChild("draggedItem").setLocalTranslation(new Vector3f(event.getX(), event.getY(), 0));
Any help on how to get a drag&drop done between the containers is appreciated.
Here is my code:
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.debug.WireBox;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
import com.simsilica.lemur.*;
import com.simsilica.lemur.core.GuiMaterial;
import com.simsilica.lemur.dnd.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class DragTest extends SimpleApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(DragTest.class);
private static final int GRID_SIZE = 3;
private static final float LOCAL_SCALE = 13.5f;
private static final ColorRGBA INVENTORY_BORDER_COLOR = ColorRGBA.Red;
private ContainerNode container1;
private ContainerNode container2;
@Override
public void simpleInitApp() {
flyCam.setEnabled(false);
// Initialize the globals access so that the default
// components can find what they need.
GuiGlobals.initialize(this);
// hide display stats
setDisplayStatView(false);
setDisplayFps(false);
container1 = new ContainerNode("container2");
container1.setSize(GRID_SIZE, GRID_SIZE, 0);
container1.setLocalTranslation(100f, 200f, 0.5f);
container1.setLocalScale(LOCAL_SCALE);
container1.addControl(new GridControl(GRID_SIZE));
container1.addControl(new DragAndDropControl(new GridContainerListener(container1)));
guiNode.attachChild(container1);
// Add some random items to our MVC grid 'model' control
container1.getControl(GridControl.class).setCell(0, 0, createItem());
container1.getControl(GridControl.class).setCell(2, 1, createItem());
// Setup a grid based container
container2 = new ContainerNode("container2");
container2.setSize(GRID_SIZE, GRID_SIZE, 0);
container2.setLocalTranslation(300f, 200f, 0.5f);
container2.setLocalScale(LOCAL_SCALE);
container2.addControl(new GridControl(GRID_SIZE));
container2.addControl(new DragAndDropControl(new GridContainerListener(container2)));
guiNode.attachChild(container2);
// Add some random items to our MVC grid 'model' control
container2.getControl(GridControl.class).setCell(0, 0, createItem());
container2.getControl(GridControl.class).setCell(2, 1, createItem());
}
private Spatial createItem() {
Sphere sphere = new Sphere(12, 24, 0.9f);
Geometry geom = new Geometry("item", sphere);
// Create a random color
float r = (float)(Math.random() * 0.4 + 0.2);
float g = (float)(Math.random() * 0.6 + 0.2);
float b = (float)(Math.random() * 0.6 + 0.2);
geom.setMaterial(new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
return geom;
}
/**
* Just to encapsulate the visuals needed to have both a wireframe
* view but an actual box for picking.
*/
private class ContainerNode extends Node {
private GuiMaterial material;
private WireBox wire;
private Geometry wireGeom;
private Box box;
private Geometry boxGeom;
public ContainerNode( String name ) {
super(name);
material = GuiGlobals.getInstance().createMaterial(INVENTORY_BORDER_COLOR, false);
List<javafx.util.Pair<Vector3f, Vector3f>> lines = new ArrayList<>();
// horizontal lines
for (float column = 2 - GRID_SIZE; column < GRID_SIZE; column += 2) {
lines.add(new javafx.util.Pair<>(new Vector3f(-GRID_SIZE, column, 0f), new Vector3f(GRID_SIZE, column, 0f)));
}
for (float row = 2 - GRID_SIZE; row < GRID_SIZE; row += 2) {
lines.add(new javafx.util.Pair<>(new Vector3f(row, -GRID_SIZE, 0f), new Vector3f(row, GRID_SIZE, 0f)));
}
drawLines(lines);
wire = new WireBox(1, 1, 0);
wireGeom = new Geometry(name + ".wire", wire);
wireGeom.setMaterial(material.getMaterial());
attachChild(wireGeom);
box = new Box(1, 1, 0);
boxGeom = new Geometry(name + ".box", box);
boxGeom.setMaterial(material.getMaterial()); // might as well reuse it
boxGeom.setCullHint(CullHint.Always); // invisible
attachChild(boxGeom);
}
private void drawLines(List<javafx.util.Pair<Vector3f, Vector3f>> lines ) {
for (javafx.util.Pair<Vector3f, Vector3f> line : lines) {
Line l = new Line(line.getKey(), line.getValue());
Geometry lineGeometry = new Geometry("line", l);
Material lineMaterial = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
lineGeometry.setMaterial(lineMaterial);
attachChild(lineGeometry);
}
}
private void setSize( float x, float y, float z ) {
wire.updatePositions(x, y, z);
box.updateGeometry(Vector3f.ZERO, x, y, z);
box.clearCollisionData();
wireGeom.updateModelBound();
boxGeom.updateModelBound();
}
}
private class GridControl extends AbstractControl {
private ContainerNode node;
private int gridSize;
private Spatial[][] grid;
public GridControl( int gridSize ) {
this.gridSize = gridSize;
this.grid = new Spatial[gridSize][gridSize];
}
@Override
public void setSpatial( Spatial s ) {
super.setSpatial(s);
this.node = (ContainerNode)s;
updateLayout();
}
public Spatial getCell( int x, int y ) {
return grid[x][y];
}
public void setCell( int x, int y, Spatial child ) {
if( grid[x][y] != null ) {
grid[x][y].removeFromParent();
}
grid[x][y] = child;
if( child != null ) {
node.attachChild(child);
}
updateLayout();
}
public Spatial removeCell( int x, int y ) {
Spatial result = grid[x][y];
node.detachChild(result);
grid[x][y] = null;
if( result != null ) {
updateLayout();
}
return result;
}
public void addChild( Spatial child ) {
// Find the first valid cell
for( int x = 0; x < gridSize; x++ ) {
for( int y = 0; y < gridSize; y++ ) {
// just in case the child is already in the grid
if( grid[x][y] == child ) {
return;
}
if( grid[x][y] == null ) {
setCell(x, y, child);
return;
}
}
}
}
public void removeChild( Spatial child ) {
for( int x = 0; x < gridSize; x++ ) {
for( int y = 0; y < gridSize; y++ ) {
if( child == grid[x][y] ) {
if( child.getParent() == node ) {
child.removeFromParent();
}
grid[x][y] = null;
}
}
}
updateLayout();
}
protected void updateLayout() {
node.setSize(gridSize, gridSize, 0);
for( int x = 0; x < gridSize; x++ ) {
for( int y = 0; y < gridSize; y++ ) {
Spatial child = grid[x][y];
if( child != null ) {
child.setLocalTranslation(-(gridSize - 1) + x * 2, (gridSize - 1) - y * 2, 0);
}
}
}
}
@Override
protected void controlUpdate( float tpf ) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp ) {
}
}
private class GridContainerListener implements DragAndDropListener {
private Spatial container;
public GridContainerListener( Spatial container ) {
this.container = container;
}
/**
* Returns the container 'model' (in the MVC sense) for this
* container listener.
*/
public GridControl getModel() {
return container.getControl(GridControl.class);
}
private Vector2f getCellLocation( Vector3f world ) {
Vector3f local = container.worldToLocal(world, null);
// Calculate the cell location
float x = (GRID_SIZE + local.x) / 2;
float y = (GRID_SIZE - local.y) / 2;
int xCell = (int)x;
int yCell = (int)y;
return new Vector2f(xCell, yCell);
}
public Draggable onDragDetected(DragEvent event ) {
// Find the child we collided with
GridControl control = getModel();
// See where we hit
Vector2f hit = getCellLocation(event.getCollision().getContactPoint());
// Remove the item from the grid if it exists.
Spatial item = control.removeCell((int)hit.x, (int)hit.y);
if( item != null ) {
// Save the item in the session so the other containers (and ourselves)
// know what we are dragging.
event.getSession().set(DragSession.ITEM, item);
// We'll keep track of the grid cell in case the drag is
// canceled and we have to put it back.
event.getSession().set("gridLocation", hit);
// Clone the dragged item to use in our draggable and stick the
// clone in the root at the same world location.
Spatial drag = item.clone();
drag.setLocalTranslation(new Vector3f(event.getX(), event.getY(), 0));
drag.setLocalRotation(item.getWorldRotation());
drag.setLocalScale(LOCAL_SCALE * 1.2f);
drag.setName("draggedItem");
guiNode.attachChild(drag);
return new ColoredDraggable(event.getViewPort(), drag, event.getLocation());
}
return null;
}
public void onDragEnter( DragEvent event ) {
}
public void onDragExit( DragEvent event ) {
System.out.println("onDragExit");
}
public void onDragOver( DragEvent event ) {
LOGGER.info("onDragOver");
Vector2f hit = getCellLocation(event.getCollision().getContactPoint());
Spatial item = getModel().getCell((int)hit.x, (int)hit.y);
guiNode.getChild("draggedItem").setLocalTranslation(new Vector3f(event.getX(), event.getY(), 0));
if( item == null ) {
// An empty cell is a valid target
event.getSession().setDragStatus(DragStatus.ValidTarget);
} else {
// A filled slot is not
event.getSession().setDragStatus(DragStatus.InvalidTarget);
}
}
// Target specific
public void onDrop( DragEvent event ) {
LOGGER.info("onDrop");
Spatial draggedItem = event.getSession().get(DragSession.ITEM, null);
Vector2f hit = getCellLocation(event.getCollision().getContactPoint());
// One last check to see if the drop location is available
Spatial item = getModel().getCell((int)hit.x, (int)hit.y);
if( item == null ) {
// Then we can stick the new child right in
getModel().setCell((int)hit.x, (int)hit.y, draggedItem);
} else {
// It wasn't really a valid drop
event.getSession().setDragStatus(DragStatus.InvalidTarget);
}
}
// Source specific
public void onDragDone( DragEvent event ) {
LOGGER.info("onDragDone");
DragSession session = event.getSession();
// Check to see if drop target was null as this indicates
// that the drag operation didn't finish and we need to
// put the item back.
if( session.getDropTarget() == null ) {
// Grab the payload we stored during drag start
Spatial draggedItem = session.get(DragSession.ITEM, null);
// Grab the original slot of the item. We tucked this away
// during drag start just for this case.
Vector2f slot = session.get("gridLocation", null);
if( slot != null ) {
getModel().setCell((int)slot.x, (int)slot.y, draggedItem);
} else {
System.out.println("Error, missing gridLocation for dragged item");
// This should not ever happen but if it does we'll at least
// try to deal with it
getModel().addChild(draggedItem);
}
}
}
}
private class ColoredDraggable extends DefaultDraggable {
private Material originalMaterial;
private Geometry geom;
public ColoredDraggable( ViewPort view, Spatial spatial, Vector2f start ) {
super(view, spatial, start);
this.geom = (Geometry)spatial;
this.originalMaterial = geom.getMaterial();
}
@Override
public void updateDragStatus( DragStatus status ) {
switch( status ) {
case ValidTarget:
geom.setMaterial(originalMaterial);
break;
default:
break;
}
}
}
@Override
public void simpleUpdate(float tpf) {
}
@Override
public void destroy() {
}
}