Problems with Drag and Drop

Since the old thread probably won’t get any attention anymore, I will start a new one describing my current problems with Drag and Drop elements in detail.



First I have a custom Droppable which can store additional information. I use it to do actions when right-clicking on it:



[xml]

<controlDefinition name=“monsterDroppable” controller=“gui.MonsterDroppableControl”>

<panel childLayout=“center” id=“mainPanel” backgroundColor="#00000000" width=“49px” height=“49px”>

<control id="$droppableID" name=“droppable” width=“49px” height=“49px”>

<image filename=“Interface/ui/droppable.png”/>

</control>

<interact onSecondaryClick=“onRightClicked()” />

</panel>

</controlDefinition>

[/xml]



Its controller:



[java]

public class MonsterDroppableControl extends AbstractController implements MonsterDroppable {



/** The Draggable attached to this Droppable. /

private Element m_attachedDraggable;

/
* The Monster currently dropped into this control. /

private Monster m_attachedMonster;

/
* The underlying droppable */

private Droppable m_droppable;

private Nifty m_nifty;

private Screen m_screen;



@Override

public void bind(Nifty nifty, Screen screen, Element element, Properties parameter, Attributes controlDefinitionAttributes) {

super.bind(element);

element.setVisibleToMouseEvents(true);

m_nifty = nifty;

m_screen = screen;



m_droppable = element.findNiftyControl(controlDefinitionAttributes.get(“droppableID”), Droppable.class);

}



@Override

public void onStartScreen() {}



@Override

public boolean inputEvent(NiftyInputEvent inputEvent) {

return false;

}



@Override

public void addDraggable(final Monster monster) {

ControlBuilder buildDraggable = new ControlBuilder(“Draggable”) {{

childLayoutCenter();

width(“49px”);

height(“49px”);

control(new DraggableBuilder(“boxDraggable” + monster.getID()) {{

image(new ImageBuilder() {{

filename(Monster.MONSTERICON_PATH + monster.getID() + “.png”);

}});

}});

}};



if(m_attachedDraggable != null)

m_attachedDraggable.markForRemoval();



m_attachedMonster = monster;



m_attachedDraggable = buildDraggable.build(m_nifty, m_screen, m_droppable.getElement());

}



@Override

public Monster getMonster() {

return m_attachedMonster;

}



@Override

public void setMonster(Monster monster, Draggable draggable) {

if(m_attachedDraggable != null)

m_attachedDraggable.markForRemoval();



draggable.getElement().markForRemoval();



this.addDraggable(monster);

}



@Override

public void removeMonster() {

m_attachedMonster = null;

m_attachedDraggable = null;

}



/**

  • Called when this draggable is right clicked on.

    /

    public void onRightClicked() {

    if(m_attachedMonster != null)

    GameClient.getInstance().showMonsterInformation(m_attachedMonster);

    }

    }

    [/java]



    I use this custom control in another custom control. The other one consists of 25 of these in a grid pattern.



    [xml]

    <controlDefinition name=“monsterBoxPage” controller=“evocri3Dclient.gui.MonsterBoxPageControl”>

    <panel childLayout=“vertical” id=“mainPanel” backgroundColor="#00000000" width=“100%” height=“100%”>

    <panel childLayout=“horizontal” backgroundColor="#00000000" width=“100%” height=“49px”>

    <control id=“boxDroppable1” droppableID=“boxDroppable1” name=“monsterDroppable”/>

    <control id=“boxDroppable2” droppableID=“boxDroppable2” name=“monsterDroppable”/>

    <control id=“boxDroppable3” droppableID=“boxDroppable3” name=“monsterDroppable”/>

    <control id=“boxDroppable4” droppableID=“boxDroppable4” name=“monsterDroppable”/>

    <control id=“boxDroppable5” droppableID=“boxDroppable5” name=“monsterDroppable”/>

    </panel>

    <panel childLayout=“horizontal” backgroundColor="#00000000" width=“100%” height=“49px”>

    <control id=“boxDroppable6” droppableID=“boxDroppable6” name=“monsterDroppable”/>

    <control id=“boxDroppable7” droppableID=“boxDroppable7” name=“monsterDroppable”/>

    <control id=“boxDroppable8” droppableID=“boxDroppable8” name=“monsterDroppable”/>

    <control id=“boxDroppable9” droppableID=“boxDroppable9” name=“monsterDroppable”/>

    <control id=“boxDroppable10” droppableID=“boxDroppable10” name=“monsterDroppable”/>

    </panel>

    <panel childLayout=“horizontal” backgroundColor="#00000000" width=“100%” height=“49px”>

    <control id=“boxDroppable11” droppableID=“boxDroppable11” name=“monsterDroppable”/>

    <control id=“boxDroppable12” droppableID=“boxDroppable12” name=“monsterDroppable”/>

    <control id=“boxDroppable13” droppableID=“boxDroppable13” name=“monsterDroppable”/>

    <control id=“boxDroppable14” droppableID=“boxDroppable14” name=“monsterDroppable”/>

    <control id=“boxDroppable15” droppableID=“boxDroppable15” name=“monsterDroppable”/>

    </panel>

    <panel childLayout=“horizontal” backgroundColor="#00000000" width=“100%” height=“49px”>

    <control id=“boxDroppable16” droppableID=“boxDroppable16” name=“monsterDroppable”/>

    <control id=“boxDroppable17” droppableID=“boxDroppable17” name=“monsterDroppable”/>

    <control id=“boxDroppable18” droppableID=“boxDroppable18” name=“monsterDroppable”/>

    <control id=“boxDroppable19” droppableID=“boxDroppable19” name=“monsterDroppable”/>

    <control id=“boxDroppable20” droppableID=“boxDroppable20” name=“monsterDroppable”/>

    </panel>

    <panel childLayout=“horizontal” backgroundColor="#00000000" width=“100%” height=“49px”>

    <control id=“boxDroppable21” droppableID=“boxDroppable21” name=“monsterDroppable”/>

    <control id=“boxDroppable22” droppableID=“boxDroppable22” name=“monsterDroppable”/>

    <control id=“boxDroppable23” droppableID=“boxDroppable23” name=“monsterDroppable”/>

    <control id=“boxDroppable24” droppableID=“boxDroppable24” name=“monsterDroppable”/>

    <control id=“boxDroppable25” droppableID=“boxDroppable25” name=“monsterDroppable”/>

    </panel>

    </panel>

    </controlDefinition>

    [/xml]



    Its controller is:



    [java]

    public class MonsterBoxPageControl extends AbstractController implements MonsterBoxPage {



    /
    * The names of the Droppables where the monster icons are displayed in. /

    private static final String DROPPABLE_NAMES = “boxDroppable”;



    /
    * The Droppables where the monster icons are displayed in. */

    private MonsterDroppable[] m_droppables;



    private boolean m_firstSubscriberCall = true;



    @Override

    public void bind(Nifty nifty, Screen screen, Element element, Properties parameter, Attributes controlDefinitionAttributes) {

    super.bind(element);

    m_droppables = new MonsterDroppable[25];



    for(int i = 0; i < 25; i++)

    m_droppables = element.findNiftyControl(DROPPABLE_NAMES + (i + 1), MonsterDroppable.class);

    }



    @Override

    public void onStartScreen() {}



    @Override

    public boolean inputEvent(NiftyInputEvent inputEvent) {

    return false;

    }



    @Override

    public void addMonster(final int position, final Monster monster) throws ArrayIndexOutOfBoundsException {

    MonsterDroppable droppableToFill = m_droppables[position];

    droppableToFill.addDraggable(monster);

    }



    @NiftyEventSubscriber(pattern=“boxDroppable[0-9]+”)

    public void handleDragOperation(final String id, final DroppableDroppedEvent event) {

    //This method gets called twice for each successful drag and drop operation. But I only need it once.

    if(m_firstSubscriberCall) {

    String sourceNumberString = event.getSource().getElement().getId().substring(12);

    String targetNumberString = event.getTarget().getElement().getId().substring(12);



    int sourceNumber = Integer.parseInt(sourceNumberString);

    int targetNumber = Integer.parseInt(targetNumberString);



    MonsterDroppable sourceDroppable = m_droppables[sourceNumber - 1];

    MonsterDroppable targetDroppable = m_droppables[targetNumber - 1];



    Monster sourceMonster = sourceDroppable.getMonster();

    Monster targetMonster = targetDroppable.getMonster();



    targetDroppable.setMonster(sourceMonster, event.getDraggable());



    if(targetMonster != null) {

    sourceDroppable.removeMonster();

    sourceDroppable.addDraggable(targetMonster);

    } else {

    sourceDroppable.removeMonster();

    }

    }



    m_firstSubscriberCall = !m_firstSubscriberCall;

    }

    }

    [/java]



    This control is being used in a simple window:



    [xml]

    <control title=“Monster Box” name=“window” id=“monsterBoxWindow” align=“left” valign=“top” hideOnClose=“true” revert=“false” height=“281px” width=“261px”>

    <control name=“monsterBoxPage” id=“monsterBoxPage1”/>

    </control>

    [/xml]



    I populate the box with 4 test monsters. A Monster is just a model class holding some information, like the icon name for the Draggable to display.

    One Droppable should only hold a maximum of one monster. So when I drag a monster from one to the other and the other has one monster/icon it it already, they should switch. This is basically what I am trying to do with my code.



    However there are a few problems: When a new Draggable is created and added to the Droppable, it is not initially rendered in the Droppable.

    The Draggable switching code doesn’t work at all. It sometimes makes a Draggable disappear, it sometimes duplicates it and they are generally being placed in wrong positions.



    Any idea what is wrong?

When I started doing my inventory grid I ran into similar problems. What I ended up doing was implement DroppableDropFilter and override the accept(…) method. It looks like this:



[java]

/**

  • Tells nifty never to move inventory items on its own. We do it manually
  • by adding/removing/relocating the image icons ourselves.

    *
  • @param dropSource The source droppable.
  • @param draggedItem The dragged item draggable.
  • @param droppedAt The destination droppable.
  • @return Always false.

    */

    @Override

    public boolean accept(Droppable dropSource, Draggable draggedItem, Droppable droppedAt) {

    if (invAction.isValid(Integer.valueOf(lastTwo(draggedItem.getId()))) == InventoryActionMessage.Invalid) {

    // This is an invalid action. Simply return and do nothing. The icon

    // will be moved back where it was.

    // System.out.println("Checked for source of " + dropSource.getId() + " and draggable " + draggedItem.getId() + " at destination of " + droppedAt.getId());

    return false;

    }

    if (isFree(droppedAt.getElement().getId())) {

    // Make an image at destination.

    if (draggedItem.getElement().findElementByName(“i” + lastTwo(draggedItem.getId())) != null) {

    addImage(lastTwo(droppedAt.getId()), invAction.getFilename(lastTwo(dropSource.getId())));

    // Broadcast item move.

    invAction.moveToSlot(this, lastTwo(dropSource.getElement().getId()), lastTwo(droppedAt.getElement().getId()));

    // Delete image at source.

    deleteImageTag(dropSource);

    return false;

    } else {

    return false;

    }

    } else {

    if (canAddToStack(dropSource.getElement().getId(), droppedAt.getElement().getId())) {

    // There’s enough to stack. If there’s remainder

    // it’ll be dropped into the first open slot.

    deleteImageTag(dropSource);

    // Broadcast stacking.

    invAction.moveToSlot(this, lastTwo(dropSource.getElement().getId()), lastTwo(droppedAt.getElement().getId()));

    return false;

    } else {

    // This is not stackable. Simply return and do nothing. The icon

    // will be moved back where it was.

    return false;

    }

    }

    }

    [/java]



    Here, I get invAction (an interface) and depending on the action (move/add/add to stack, etc) I do something or another. The code is fairly well documented.



    In short, I always tell Nifty to refuse the drag 'n drop, but if the action is valid, I add the dragged image to the new position (or stack the item) and finally delete the image in the old draggable manually.



    I’m sure there are other ways to do this, but that’s the one I found that worked well.



    Hopefully it might help you.
1 Like

Is there a bug tracker for Nifty where I can report bugs?

Yes on the sourceforge page.

The github issue tracker is the prefered bug tracker for Nifty. The sf.net bug tracker has been updated recently by the sf.net people but is still not as nifty as the github one :slight_smile:

1 Like

Right, github that was :slight_smile:

Hello all,

Sorry to come back to this topic but I have the same issue.
After reading the code in details, I think it is because in “DraggableControl” class in line 89, the controller check if the mouse is over the element.
If it is not the case, it moves the element in top of instead of moving in the right position.

if (handle.isMouseInsideElement(mouseX, mouseY)) {
	      moveDraggableToPopup();
	      dragged = true;
	      notifyObserversDragStarted();
} else {
	      moveDraggableOnTop();
 }

Checkout the code link in NiftySource forge

If someone has a new solution … please share.

Many thanks

@Rand

I tested by calling the drag control class to set the start to drag function but nothing too
I passed to the function the X and Y position about the droppable slot :

screen.findNiftyControl(el.getId(), DraggableControl.class).dragStart( slot.getX(), slot.getY());

I get something if I add the parameter " childLayout=“vertical” " to the control element in XML file but the picture sitll not align into the slot