psychopy.org | Reference | Downloads | Github

Click and Drag lag

I am using code components in builder to allow participants to click and drag various stimuli to locations on a grid. I need it so that only one stimulus can be dragged at a time, and the participant can drop it within an 8x8 grid and the stimulus will snap to the spot in the grid. I have successfully coded this, but there is a bug. If the stimulus is dragged too fast, the mouse drops it. I was hoping someone could give me a suggestion on how to get rid of this bug by making my code more efficient or changing the way it works. Here’s what I have in the begin routine:

stimuli = [stim_1, stim_2, stim_3, stim_4, stim_5, stim_6, stim_7, stim_8]
current_stimulus = -1
clicked_stimulus = None
current_dest = None

and in every frame:

mouse_down = False
selected = np.zeros(8, dtype = int) -1
for i, stimulus in enumerate(stimuli):
    if mouse.isPressedIn(stimulus):
        selected[i] = i
        clicked_stimulus = stimulus
        if i == current_stimulus:
            stimulus.setPos(mouse.getPos())
            mouse_down = True
            break
if not mouse_down and clicked_stimulus is not None:
    for dest in [a1_2, a2_2, a3_2, a4_2, a5_2, a6_2, a7_2, a8_2, b1_2, b2_2, b3_2, b4_2, b5_2, b6_2, b7_2, b8_2, c1_2, c2_2, c3_2, c4_2, c5_2, c6_2, c7_2, c8_2, d1_2, d2_2, d3_2, d4_2, d5_2, d6_2, d7_2, d8_2, e1_2, e2_2, e3_2, e4_2, e5_2, e6_2, e7_2, e8_2, f1_2, f2_2, f3_2, f4_2, f5_2, f6_2, f7_2, f8_2, g1_2, g2_2, g3_2, g4_2, g5_2, g6_2, g7_2, g8_2, h1_2, h2_2, h3_2, h4_2, h5_2, h6_2, h7_2, h8_2]:
        if dest.contains(mouse.getPos()):
            current_dest = dest
            clicked_stimulus.pos = current_dest.pos
    clicked_stimulus = None
if selected.sum() == -8:
    current_stimulus = -1
elif current_stimulus == -1 or selected[current_stimulus].all() == -1:
    current_stimulus = selected[selected > -1].min()
    stimuli[current_stimulus].setPos(mouse.getPos())

The “snap to location” part of the code is fine, it only checks to see if the position of the stimulus overlaps with a location within the grid once the mouse press is released, so the problem lies somewhere in the code for while the mouse is pressed in. Thank you in advance to anyone who can help with this!

Folder with all necessary files to run simple version of task

Hi Ben,

I think it would be best if you post a minimal working example that people could play with (i.e. a .psyexp file cut down so it focusses on just this portion).

Michael,

I added a link to my post to the .psyexp file and my Stimuli folder that it pulls from. This should allow you to play around with part of my task and see the problem I’m running into. Thanks for your quick response!

OK, this isn’t a bug per se but the inherent fact that one can move the mouse faster than the screen can update. i.e. what is happening here is that you have relatively small stimuli and a screen that I guess is updating at the typical rate of 60 Hz. So you can detect a click in a stimulus and then move the stimulus to the current mouse position. But on the next screen refresh, the new mouse position can easily have moved beyond the boundaries of the stimulus, so the .isPressedIn() check fails. Crude solutions to (partially) address this are simply to use larger stimuli (not practical here) or to use a screen that updates faster (e.g. on a 60 Hz screen, the mouse position has 16.7 ms of motion between checks, whereas on a 144 Hz screen, just 6.9 ms, so it is less likely to move outside the bounds of the stimulus).

However, a more robust solution would be to amend your algorithm. I’d suggest doing an initial check of whether the mouse is pressed in any stimulus, and if so, flag that a drag has started. Thereafter, keep updating the selected stimulus position to match the mouse position, contingent only on the mouse button still being pressed (i.e. no longer also check if it is within a stimulus). That way, the mouse can “lead” the stimulus, not having to stay within its bounds, and the stimulus then catches up with it on every screen refresh. If you find that the mouse button is no longer pressed, then flag that the drag is no longer in operation and hence the stimulus position no longer gets actively updated.

e.g. the following code seems to work rock-solidly for me, in that the stimulus stays glued to the mouse, although the stimulus can move quite markedly between screen refreshes when the mouse is moving rapidly, so although it is keeping up with the mouse exactly, it can look like it is jumping across the screen rather than moving smoothly:

Begin routine code:

drag_in_process = False

Each frame code:

if not drag_in_process: # check if one should be
    for stimulus in stimuli:
        if mouse.isPressedIn(stimulus):
            clicked_stimulus = stimulus
            drag_in_process = True
            break

if True in mouse.getPressed():
    if drag_in_process:
        # I guess this is also where you might put code to
        # 'snap' the stimulus to the grid if required.
        clicked_stimulus.pos = mouse.getPos()
else:
    drag_in_process = False

This code might seem to have a strange flow, but I found that a more “elegant” logic could lead to a one-frame lag in response at times.

2 Likes

Michael,

Thank you so much for this feedback. I’ve left for the day today, but I’ll try to implement this tomorrow morning. You’ve been a huge help, not just in this thread, but I also used some of your comments on other threads to help me code the click and drag in the first place. I’ll let you know if this works! Thanks again (:

Michael,

I was able to successfully implement your solution to my problem. Thank you again!

1 Like