Drag and drop just one of multiple stimuli at a time

Hello,

I am brand new to python and PsychoPy, so I apologize in advance for my current limited knowledge.

I am trying to code so that a person can drag squares and drop them in a central location to create a picture. If I drag the first square there are no issues. If I drag the second square, when it moves over the first, the second square is dropped and the mouse picks up the first square. The same happens when I move square 3 over squares 1 and 2.

I’ve tried:
if (mouse_resp.isPressedIn(square, buttons=[0])):
square.setPos(mouse_resp.getPos()) # set the image where the cursor is
squareTx, squareTy = mouse_resp.getPos()
# counterMoves += 1

        if (mouse_resp.isPressedIn(square1, buttons=[0])):
            square1.setPos(mouse_resp.getPos()) #  set the image where the cursor is
            bsquare1Tx, bsquare1Ty = mouse_resp.getPos()
        
        if (mouse_resp.isPressedIn(square2, buttons=[0])):
            square2.setPos(mouse_resp.getPos())
            square2Tx, square2Ty = mouse_resp.getPos()

I’ve also tried the above with if, elif and else. I’ve also tried:

        if (mouse_resp.isPressedIn(square, buttons=[0]) 
            and not mouse_resp.isPressedIn(square1, buttons=[0]) 
            and not mouse_resp.isPressedIn(square2, buttons=[0])):
            square.setPos(mouse_resp.getPos()) #  set the image where the cursor is
            squareTx, squareTy = mouse_resp.getPos()
            #    counterMoves += 1
        
        elif (mouse_resp.isPressedIn(square1, buttons=[0]) 
            and not mouse_resp.isPressedIn(square, buttons=[0]) 
            and not mouse_resp.isPressedIn(square2, buttons=[0])):
            square1.setPos(mouse_resp.getPos()) #  set the image where the cursor is
            square1Tx, square1Ty = mouse_resp.getPos()
        
        else (mouse_resp.isPressedIn(square2, buttons=[0]) 
            and not mouse_resp.isPressedIn(square, buttons=[0]) 
            and not mouse_resp.isPressedIn(square1, buttons=[0])):
            square2.setPos(mouse_resp.getPos())
            square2Tx, square2Ty = mouse_resp.getPos()

What do I need to add so that it doesn’t switch to a “higher” square while a lower one is being dragged over a “higher” one?

Thank you very much for your help with this.
Kym

Hi Kym,

Cycle through the stimuli on each screen refresh, and only respond to the first one that contains the mouse pointer:

# cycle though all stimuli in order:
for stimulus in [square, square1, square2]: # add your other stimulus names
    if mouse.isPressedIn(stimulus):
        stimulus.setPos(mouse.getPos())
        # etc
        break # this exits the loop
        # so only the first stimulus at the current mouse position will 
        # respond, so the order of the list is important.
         

No guarantees this will work: try it and see, and modify as required.

Hi Michael,

First, thank you so very much for responding, and for responding so quickly!

Unfortunately, you are talking over my head. My knowledge is extremely basic.

I think you are saying what I write depends on whether or not the square will be touched again. All the squares can be touched and moved many times. Basically, they will be dragged and dropped until the full picture is correct, which could mean moving things around several times (there are actually 9 squares in total, I just only gave the first 3 as an example).

So, I wrote exactly this in the screen to test the first three:

for i, mouse_resp.In([square, square1, square2]):
if mouse.isPressedIn (square):
if selected.sum() == 0
selected[i] = True
else:
selected[i] = False
if selected[i] == True:
stimulus.setPos(mouse.getPos())

if mouse.isPressedIn (square1):
    if selected.sum() == 0
        selected[i] = True
    else:
        selected[i] = False
    if selected[i] == True:
        stimulus.setPos(mouse.getPos())

if mouse.isPressedIn (square2):
    if selected.sum() == 0
        selected[i] = True
    else:
        selected[i] = False
    if selected[i] == True:
        stimulus.setPos(mouse.getPos())

I’m getting a syntax error with the first line (a carat pointing at the colon), but I don’t know how to fix that (tried several things). I pasted in the rest in case I’m making another assumption error later on.

Thank you again!

Hi, sorry for delay, am on holiday. The syntax error is likely because you have a space between the function name .isPressedIn() and the following brackets.

I’ve edited the code I provided above to simplify it, it should now do what you need.

You shouldn’t write individual code for each stimulus, as:

  • this is repetitive and becomes hard to manage with nine different cases (eg each time you need to make a change, you have to make that exact change nine times, which can easily lead to errors).
  • at the moment, the same code runs for each stimulus, so multiple stimuli can respond to the same mouse press.

In the code above, we use a loop to avoid the code maintenance problem. I’ve also added a break statement so that the loop exits after the first stimulus is clicked in. This prevents you selecting multiple stimuli, which I think is your problem. This would work in the same way as a typical drawing program, where you click on an object to select or move it. Only the “top most” object is selected: any that lie underneath it are not affected. This behaviour is controlled by the order of the stimuli in the list that the loop cycles through. So if, for example, the third stimulus in the list registers a click, none of the later stimuli will respond, even if they are overlapping.

You should be able to easily extend the code above to suit your needs by just including all nine stimulus names in the list in this expression:

for stimulus in [square, square1, square2]:

Hi Michael,

No apologies necessary. I appreciate any help. I will give this a try. I need to look up numpy and some of the other things you suggest, so it might be a bit.

Thank you again,
Kym

Hi, actually you can ignore that for the timebeing. The code above has been edited for simplicity, and no longer uses a numpy array, just a standard Python list.

Hi Michael,

I’m sorry for the delayed response. I was working with someone who knows more about psychopy than me to see if he could help me understand how your code was different than what I’d used (besides being much much prettier).

We tried what you said above exactly as you have it and with adjustments. It still does the exact same thing the code I was using does - drops a later square when it passes over an earlier square.

I think the problem is that I need the order not to matter. If I pick up square3 and pass over square1, I need square3 to stay with me. But in both your version and mine, if I pick up a later square and pass over an earlier square (without clicking), it drops the later square and picks up the earlier square.

The user is building a picture with these different squares. I need the squares to be able to pass over each other no matter what the order, while moving the picture into place. People will readjust squares - is the problem. They will move one they have in place to a different spot. In my trials, that is happening frequently.

Is there a way to fix this?

Thank you so much again, for your help.

Or maybe I typed the code wrong. This is what I typed:

for stimulus in [square_9, square1_9, square2_9, square3_9, square4_9, square5_9, square6_9, square7_9, square8_9]: # add your other stimulus names
if mouse.isPressedIn(stimulus):
stimulus.setPos(mouse.getPos())
break

Ahh, OK, that description is much more clear to me now. I thought the problem was just in clicking to select a square: I didn’t realise that the issue was actually a dynamic one where the selected square could alter during a drag.

Basically the solution to this sort of problem is to keep track of what square is initially selected and then just keep manipulating that one, not allowing another to become selected until the mouse button has been raised. Let me think about how to do that here.

Am typing this on an iPad without a :snake: interpreter so this may be full of errors:

Begin routine tab:

stimuli = [square, square1, square2, …] # up to 9
current_square = -1

Each frame tab:

# create an array to record what stimulus numbers are 
# currently clicked in, defaulting to -1:
selected = np.zeros(9, dtype = int) - 1

# cycle though all stimuli in order:
for i, stimulus in enumerate(stimuli):
    if mouse.isPressedIn(stimulus):
        selected[i] = i # keep track of all at 🐭 pos
        if i == current_square: # simple:
            stimulus.setPos(mouse.getPos())
            break # exit the loop, so only one moves

# but need to check if none are selected, or, if there
# is a new selection, move it:
if selected.sum() == -9: # nothing selected
    current_square = -1
elif current_square == -1 or selected[current_square] == -1: # a new selection
    current_square = selected[selected > -1].min() # choose the lowest non-selected value
    stimuli[current_square].setPos(mouse.getPos())

Thank you! I think we are almost there. But, I got an error:

stimuli[current_square].setPos(mouse.getPos())
list indices must be integers, not numpy.float64

I assume what is in the brackets needs to be integers. Should it say i?

So, I tried i and got this:
elif current_square == -1 or 0.selected[current_square] == 0
IndexError: only integers, slices (":"), ellipsis ("…"), numpy.newaxis (“None”) and integer or boolean arrays are valid indices

That’s the problem with writing code without being able to test it at the time… the selected array I created contained floating point numbers, rather than integers (computers can’t represent floating point numbers precisely, so the floating point number 1.00000000 can’t be used to index an array but the integer 1 can. I’ve edited the code above to fix that (by specifying dtype = int), and another problem I found lower down with assigning an updated value to current_square.

Try it and see if it works for you now.

Michael,

That is absolutely brilliant! Thank you so very much. And, thank you for your patience with a complete novice.

Now I just have to work through what you wrote so I completely understand it. :grin:

Thank you again,
Kym

1 Like