Drag-dropping NodePaths

Hmmm, actually I just realised that in the GNOME desktop anyway, when you’re dragging an object, it’s whether the mouse pointer is over a drop location that counts, not whether the object being dragged intersects a drop location. Implementing it that way makes the code simpler, and gets rid of the highlight bug when you try to successively drop multiple objects on top of each other.

import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.task import Task
import sys

cm = CardMaker('cm')
left,right,bottom,top = 0,2,0,-2
width = right - left
height = top - bottom
cm.setFrame(left,right,bottom,top)

# Colours.
normal = (1,1,1,1)
highlight = (.7,.7,.7,1)

node = aspect2d.attachNewNode('')
node.setPos(-1.2,0,0.9)

cards = []
for i in range(3):        
    for j in range(3):
        card = node.attachNewNode(cm.generate())
        card.setScale(.2)
        card.setPos(i/2.0,0,-j/2.0)
        card.setColor(*normal)
        card.setCollideMask(BitMask32.bit(3))
        cards.append(card)

class DragNDrop:

    def __init__(self):
        base.accept("escape",sys.exit)
        base.accept('mouse1',self.drag)
        base.accept('mouse1-up',self.drop)

        cn = CollisionNode('')
        cn.addSolid(CollisionRay(0,-100,0, 0,1,0))
        cn.setFromCollideMask(BitMask32.bit(3))
        cn.setIntoCollideMask(BitMask32.allOff())
        self.cnp = aspect2d.attachNewNode(cn)
        self.ctrav=CollisionTraverser()
        self.queue = CollisionHandlerQueue()
        self.ctrav.addCollider(self.cnp,self.queue)
        # self.ctrav.showCollisions(aspect2d)
        
        taskMgr.add(self.rolloverTask,'rolloverTask')
        self.rollover = None
        
        self.draggee = None
        self.difference = None

    def rolloverTask(self,t):
        """Move the mouse CollisionRay to the position of the mouse
        pointer, check for collisions, and update self.rollover.
        
        """            
        if not base.mouseWatcherNode.hasMouse():
            return Task.cont        
        if self.rollover is not None:
            self.rollover.setColor(*normal)
        mpos = base.mouseWatcherNode.getMouse()
        if self.draggee is not None:
            self.draggee.setPos(render2d,mpos[0]+self.difference[0],0,mpos[1]+self.difference[1])                        
        self.cnp.setPos(render2d,mpos[0],0,mpos[1])        
        self.ctrav.traverse(aspect2d)                        
        self.queue.sortEntries()
        if self.queue.getNumEntries():	
            self.rollover = self.queue.getEntry(self.queue.getNumEntries()-1).getIntoNodePath()
            self.rollover.setColor(*highlight)
        else:
            self.rollover = None
            
        return Task.cont

    def drag(self):
        if self.rollover is not None:
            self.draggee = self.rollover
            self.draggee.setCollideMask(BitMask32.allOff())
            mpos = base.mouseWatcherNode.getMouse()
            self.difference = (self.draggee.getX(render2d)-mpos[0],self.draggee.getZ(render2d)-mpos[1])
        
    def drop(self):
        if self.draggee is not None:
            if self.rollover is not None:
                self.draggee.setPos(self.rollover.getPos())
            self.draggee.setCollideMask(BitMask32.bit(3))
            self.draggee = None
            self.difference = None

d = DragNDrop()
run()