PennController for IBEX › Forums › Support › hide cursor and select by pointing
- This topic has 12 replies, 2 voices, and was last updated 4 years, 2 months ago by
pchen.
-
AuthorPosts
-
August 14, 2020 at 5:07 pm #6003
pchen
ParticipantHi 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.
PeiyaoAugust 24, 2020 at 2:39 pm #6013Jeremy
KeymasterHi 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
August 28, 2020 at 9:51 pm #6021pchen
ParticipantHi 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,
PeiyaoAugust 31, 2020 at 10:47 am #6022Jeremy
KeymasterHi 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
August 31, 2020 at 4:59 pm #6026pchen
ParticipantHi Jeremy,
Yes. I did have a big canvas element. It worked now. Thank you so much for your help!
Peiyao
September 1, 2020 at 3:23 pm #6029pchen
ParticipantHi 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()) )
September 1, 2020 at 4:44 pm #6030Jeremy
KeymasterHi 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 againLet me know if problems persist
Jeremy
September 2, 2020 at 9:13 am #6032pchen
ParticipantHi 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
September 2, 2020 at 10:42 am #6034Jeremy
KeymasterHi 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
September 2, 2020 at 11:08 am #6035pchen
ParticipantIt works now! Thank you!
Peiyao
January 15, 2021 at 4:39 pm #6516pchen
ParticipantHi 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
January 15, 2021 at 5:59 pm #6517Jeremy
KeymasterHey 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
January 15, 2021 at 6:25 pm #6519pchen
ParticipantI went back to 1.8 and everything is working now.
Thank you! -
AuthorPosts
- You must be logged in to reply to this topic.