hide cursor and select by pointing

PennController for IBEX Forums Support hide cursor and select by pointing

Viewing 13 posts - 1 through 13 (of 13 total)
  • Author
    Posts
  • #6003
    pchen
    Participant

    Hi Jeremy,

    We’re creating a task in which participants need to point to one of the four color patches (i.e., the Reversed Stroop Task).

    1) we’d like participants to start their mouse in the center of the screen, but it should not be visible because the critical stimulus will also appear in the center of the screen.
    Is there a way to hide the cursor upon clicking a button? The cursor/mouse should reappear once being moved again.

    2) In terms of the response, we want to use pointing instead of clicking. For example, the current trial ends once participants move the mouse to the correct color patch.
    My solution would be to compare the mouse location with the location of the color patches. But I wonder whether there is an easier way to achieve this.

    Thank you for your help in advance.
    Peiyao

    #6013
    Jeremy
    Keymaster

    Hi Peiyao,

    Apologies for the late reply, I was away from the office last week.

    1) The only way to hide the cursor is to change the cursor CSS property to use a custom empty image. Keep in mind that hiding the cursor (and directly manipulating the mouse in any way more generally) is considered a security threat.

    2) There is no type of PennController element, as of now, that easily lets you detect mouse hovering, so you’ll have to use javascript

    Here is a trial whose behavior gets pretty close to what you describe:

    PennController.ResetPrefix(null) // Shorten command names (keep this line here)
    
    newTrial(
        newButton("Start")
            .print("center at 50vw", "middle at 50vh")
            .wait()
            .remove()
        ,
        newCanvas('left', 200,200).css('background','red').print("center at 25vw", "middle at 50vh"),
        newCanvas('right', 200,200).css('background','green').print("center at 75vw", "middle at 50vh")
        ,
        newFunction( ()=>{
            $("body").css({
                width: '100vw',
                height: '100vh',
                cursor: 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="), auto'
            });
            setTimeout(()=>$("body").bind('mousemove', ()=>$('body').css('cursor','unset')),1000);
        }).call()
        ,
        newVar("choice", 'none').log()
        ,
        newFunction( ()=>new Promise(r=>$(".PennController-left, .PennController-right").bind('mouseenter',
            e=>getVar("choice").set( e.target.className.replace(/^.*(left|right).*$/,"$1") )._runPromises().then(r)
        )) ).call()
    )

    You’ll notice the two Function elements: the first one replaces the cursor with a custom inline (empty) image for the whole page’s body (while making it occupy 100% of the space on the page) and also listens for any movement so it can restore the cursor. Some caveat about cursor and mouse movements: it appears the cursor property is only updated if the mouse moves, so you actually want the mouse to move immediately after the button is clicked. This will happen naturally with an external mouse, but maybe not with a trackpad. But also because of that, you want to add a delay before mouse movements will restore the cursor, which is why I added a setTimeout of 1000ms.

    The second Function element tells the script to listen to mouse movements that land on either the element named left or the element named right (the two Canvases) and set the value of the choice Var element to “left” or “right” (looking up the element’s class name and deleting everything that’s not it). Because the call command on the Function element is asynchronous, I embedded all that into a Promise that’s only resolved when the mouse lands on left or right, which has the consequence of waiting until that event before reaching the end of the script (and therefore validating the trial).

    Let me know if you need assistance adapting this to your case

    Jeremy

    #6021
    pchen
    Participant

    Hi Jeremy,

    Thank you so much for your response. The script worked well for the study.
    I do have two followup questions:

    1) what does the timeout of 1000 ms do exactly? I tried to change its duration, but nothing noticeable happened. However, there were a few times when I first moved the mouse, the trial didn’t end. The trial only ended when I moved the mouse again to a different location. Does this has anything to do with the timeout?

    2) Where should I add the feedback on a trial? When I added a tooltip after your second function, the tooltip itself appeared on the screen, but for some reason I can’t click on it and thus can’t move onto the next trial.

    Let me know if you’d like more information.

    Thanks,
    Peiyao

    #6022
    Jeremy
    Keymaster

    Hi Peiyao,

    1) As I said, when you click on a button using an external mouse, your cursor will tend to keep moving for a few tens or hundreds milliseconds even after the click. If we didn’t have any timeout, the script would detect such late moves and thus make the cursor visible again almost immediately after hiding it, which would defeat the purpose. Feel free to set it to 500ms (or even lower), I can see how 1s can be too long of a delay

    2) Not sure what is happening, it works when I add this after the last call():

    ,
    getCanvas("left").remove(),getCanvas("right").remove()
    ,
    newTooltip("tooltip", "Hello world").print().wait()

    Do you happen to have a big transparent Canvas element on which you place your color patches? If you remove the color patches after moving the mouse over them, but don’t remove the big container Canvas, maybe your tooltip is actually placed behind it and clicks don’t make it through—there are multiple solutions, one would be to use .css("pointer-events","none") on the transparent Canvas

    Jeremy

    #6026
    pchen
    Participant

    Hi Jeremy,

    Yes. I did have a big canvas element. It worked now. Thank you so much for your help!

    Peiyao

    #6029
    pchen
    Participant

    Hi Jeremy,

    I modified your code to add two more color patches on the top and the bottom. Specifically, the changes included creating four images (named left, right, top, and bottom), putting them on a canvas and adding top and bottom to your original code (see below). When I tried to log the var(“choice”), it showed up “undefined” in the result while other results were recorded correctly.

     newFunction( ()=>new Promise(r=>$(".PennController-left, .PennController-right, .PennController-top, .PennController-bottom").bind('mouseenter',
            e=>getVar("choice").set( e.target.className.replace(/^.*(left|right|top|bottom).*$/,"$1") )._runPromises().then(r)
        )) ).call()

    I also added a feedback screen, and sometimes the error feedback shows up even though I choose the correct response. This usually happens after a few trials.
    To test the script, I changed the correct answer to always be the top one (see below), but I still got error feedback sometimes when I only chose the top patch. This also usually only happens after a few trials.
    Is there something wrong with my modification? Here is the link to my experiment in case you want to take a look. Thank you!

      getVar("choice").test.is("top")
           .failure(getTooltip("wrong").print("center at 50vw" , "center at 50vh").wait(),
                   newTimer(1000).start().wait())
           .success(newTimer(500).start().wait())
        
         )
    #6030
    Jeremy
    Keymaster

    Hi Peiyao,

    The undefined problem comes from your choice Var element not being global and you attempting to access it from outside the parentheses of the corresponding newTrial command. So just make it global when you create it: newVar("choice", 'none').global().log() (don’t forget the pair of parentheses on the log command)

    The second problem is a bug in PennController due to the image files being reused across trials. I need to fix it in the next release. In the meantime, just replace ".PennController-left, .PennController-right, .PennController-top, .PennController-bottom" with ".PennController-left-container, .PennController-right-container, .PennController-top-container, .PennController-bottom-container" and your code should work again

    Let me know if problems persist

    Jeremy

    #6032
    pchen
    Participant

    Hi Jeremy,

    I added “-container” to each element but still have the same issue. The var(“choice”) can’t log the actual response after a few trials.
    The undefined problem has been resolved.

    Peiyao

    #6034
    Jeremy
    Keymaster

    Hi Peiyao,

    What if you try replacing this bit:

    e=>getVar("choice").set( e.target.className.replace(/^.*(right|top|bottom|left).*$/,"$1") )._runPromises().then(r)

    with this

    function(e) { getVar("choice").set( this.className.replace(/^.*(right|top|bottom|left).*$/,"$1") )._runPromises().then(r); }

    ?

    (make sure you don’t lose/add parentheses in the process)

    Jeremy

    #6035
    pchen
    Participant

    It works now! Thank you!

    Peiyao

    #6516
    pchen
    Participant

    Hi Jeremy,

    Due to the recent upgrade (to 1.9), one function you wrote for my project doesn’t work anymore (see below). The original goal was to make a canvas disappear when the mouse moves to any of the four objects on that canvas (this thread has more details).
    Is there a way I can access the older version of PennController? If not, could you help modify the following code? Thank you!

    newFunction( ()=>new Promise(r=>$(".PennController-left-container, .PennController-right-container, .PennController-top-container, .PennController-bottom-container").bind('mouseenter',
            function(e) { getVar("choice").set( this.className.replace(/^.*(right|top|bottom|left).*$/,"$1") )._runPromises().then(r); }
        )) ).call()
    

    Peiyao

    #6517
    Jeremy
    Keymaster

    Hey Peiyao,

    There are a few things going on here, but the simplest solution is just to revert back to 1.8. Just type 1.8 in the branch field when syncing, following the instructions here

    One problem with 1.9 is that I seem to have broken how selector.shuffle keeps track of the elements indices. Otherwise, I would have suggested this code (which should behave as expected in the next release of PennController):

    newTrial(
        newButton("Start")
            .print("center at 50vw", "middle at 50vh")
            .wait()
            .remove()
        ,
        newCanvas("container",600,600).print("center at 50vw","middle at 50vh")
        ,
        newCanvas('red', 200,200).color('red').print(0, 0, getCanvas("container")),
        newCanvas('green', 200,200).color('green').print("right at 100%",0,getCanvas("container")),
        newCanvas('pink', 200,200).color('pink').print(0,"bottom at 100%",getCanvas("container")),
        newCanvas('blue', 200,200).color('blue').print("right at 100%","bottom at 100%",getCanvas("container"))
        ,
        newFunction( ()=>{
            $("body").css({
                width: '100vw',
                height: '100vh',
                cursor: 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="), auto'
            });
            setTimeout(()=>$("body").bind('mousemove', ()=>{
                $('body').css('cursor','unset');
                $('.PennController-container .PennController-Canvas').bind('mouseenter', e=>getSelector("response")
                    .select(getCanvas(e.target.className.replace(/.*(blue|red|pink|green).*/,"$1")))._runPromises()
                );
            }), 100);
        }).call()
        ,
        newSelector("response")
            .add( getCanvas("red"),getCanvas("green"),getCanvas("pink"),getCanvas("blue") )
            .shuffle()
            .log()
            .wait()
    )

    Right now the only problem with this code is that you lose track of where each element is effectively displayed to your participant (you’ll see an order reported in the results file, but it’s just incorrect). If you don’t care about that piece of information, then definitely stay with 1.9 and use this code

    Jeremy

    #6519
    pchen
    Participant

    I went back to 1.8 and everything is working now.
    Thank you!

Viewing 13 posts - 1 through 13 (of 13 total)
  • You must be logged in to reply to this topic.