Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Supported by

Vertical Touchscreen Slider with Confirm Button

I realise there have been quite a lot of threads on the implementation of sliders & their use on touch screens. So sorry to add another! But I have read through these and had lots of trial and error - but as a beginner my lack of python understanding is hindering me from achieving my desired outcome. I am hoping someone can help...

What I am trying to achieve is a vertical slider that works on a touchscreen with an image at the top and bottom, that when participants press or slide their finger up and down the bar/line it fills (or a marker moves up and down - I'm not too fussy on the aesthetics). As with the other threads - I'd prefer it if this only occurs within the area of the slider not just anywhere on the screen. Then, when participants are happy with their response, I'd like there to be a confirm button for them to press that then logs their response. I'm pretty sure much of the code has been presented on the threads I've looked at - but for the life of me I can't piece it all together!

Thanks in advance for any guidance...

Comments

  • Hi,

    The existing solutions are indeed not entirely satisfying. So let's see how you can do this more elegantly, by adding a slider widget to the forms. The following code will inject a slider into the form widgets, meaning that from then on you can use this slider just like any of the other widgets. Ideally, you would do this in the Prepare phase of an inline_script at the very start of the experiment.

    This particular variation of the slider simply registers a value between 0 and 1, and shows this as a vertically filled bar (empty = 0, filled = 1).

    from libopensesame.widgets._widget import widget
    from libopensesame import widgets
    
    
    class slider(widget):
    
        def __init__(self, form, var=None):
    
            widget.__init__(self, form)
            self._fill = .5
            self.var = var
            self.set_var(self._fill)
    
        def render(self):
    
            x, y, w, h = self.rect      
            self.draw_frame(rect=(x,y+h*(1-self._fill),w,h*self._fill))
    
        def on_mouse_click(self, pos):
    
            x, y, w, h = self.rect
            yclick = pos[1]
            dclick = yclick-y
            drect = h       
            self._fill = min(1, max(0, 1-dclick/drect))
            self.set_var(self._fill)
    
    
    widgets.slider = slider
    

    And then you can simply use the slider as a widget in a form_base, like below. This snippet will show a slider at the top and a button at the bottom to close the form. Not the most elegant of configurations, but it shows the general idea.

    widget 0 0 2 1 slider var=response
    widget 0 1 2 1 button
    

    Hope this helps!

    Cheers,
    Sebastiaan

  • Hi Sebastiaan,

    Thank you for this. This looks like a much better solution moving forward - however when I paste the code above into the prepare section (or the run section) of an inline script at the beginning of my experiment, and then later on add a new form base with the code you suggest - I get a grey box above a button.... like it doesn't recognise that there is now a new slider widget...

  • I get a grey box above a button.... like it doesn't recognise that there is now a new slider widget...

    Try clicking ont the grey box. :wink: The slider adjusts to the form geometry, so if you adjust the geometry such that the slider isn't a box but an elongated vertical rectangle, it will look more like a slider.

  • Ah! I see.... Thank you! With a bit of tinkering to make it look pretty this will work a treat.... (I will post the code when I have done so in case of use to others!)

    One thing though... on a touchscreen this doesn't truly slide when you move your finger - only when you press in different places (presumably because its recognising clicks rather than drags/mouse-movement). The sliders on earlier discussion threads did this quite well (although they couldn't be added as a widget like this which I think is much better)...Is it possible to achieve this sliding movement - within the confines of the slider area on the form?

  • .Is it possible to achieve this sliding movement - within the confines of the slider area on the form?

    Partly, yes. OpenSesame's mouse object doesn't understand mouse-up events, so to implement this you need to access the underlying libraries directly. Below is a slight extension of the above slider that does so but only for the back-ends that rely on pygame (right now all except psycho).

    from libopensesame.widgets._widget import widget
    from libopensesame import widgets
    from openexp._mouse.legacy import legacy        
    
    
    class slider(widget):
    
        def __init__(self, form, var=None):
    
            widget.__init__(self, form)
            self._fill = .5
            self.var = var
            self.set_var(self._fill)
            self._mouse = mouse()           
            self._use_pygame = isinstance(self._mouse, legacy)
    
        def render(self):
    
            x, y, w, h = self.rect      
            self.draw_frame(rect=(x,y+h*(1-self._fill),w,h*self._fill))
    
        def on_mouse_click(self, pos):
    
            return self._pygame(pos) if self._use_pygame else self._basic(pos)
    
        def _basic(self, pos):
    
            x, y, w, h = self.rect
            yclick = pos[1]
            dclick = yclick-y
            drect = h       
            self._fill = min(1, max(0, 1-dclick/drect))
            self.set_var(self._fill)
    
        def _pygame(self, pos):
    
            import pygame
    
            self._basic(pos)
            while True:
                for event in pygame.event.get():
                    if event.type == pygame.MOUSEBUTTONUP:
                        return
                    if event.type != pygame.MOUSEMOTION:
                        continue
                    pos = self._mouse.get_pos()[0]
                    self._basic(pos)
                    self.form.render()
    
    
    widgets.slider = slider
    
  • This works beautifully! Thanks ever so much. A final side note - if I wanted a frame/box round the slider area (so min and max were clearer) - is the best thing to add into the above code instructions to draw a box, or to add an image of a box to the same space on the custom form?

  • edited July 2017

    if I wanted a frame/box round the slider area (so min and max were clearer) - is the best thing to add into the above code instructions to draw a box, or to add an image of a box to the same space on the custom form?

    There are various ways to do this, but for maximum flexibility, you could customize slider.render() even further and directly draw to the form's canvas. For example like so:

        def render(self):
    
            x, y, w, h = self.rect                      
            # Slider
            self.form.canvas.rect(x,y+h*(1-self._fill),w,h*self._fill, fill=True,
                color='gray')
            # Outline
            self.form.canvas.rect(x, y, w, h, penwidth=4, color='white')
    
  • Hi @sebastiaan, this works great now. Just a couple of questions for further tweaks:

    1. How would I amend the code to have a horizontal slider if needed?

    2. Is it possible to amend the slider from being a block that fills to something like a line with a marker?
      For example - something like the Affective Slider (Betella et al., 2016)

  • That's possible. But I think it's a useful (and doable) exercise to try this yourself! If you look at the render() function (where the drawing happens), you'll find all the ingredients you need.

    • self.rect is an x, y, w, h tuple that indicates the bounding box of the slider widget in pixels.
    • self._fill is a value between 0 and 1 that indicates how full the slider is.
    • self.form.canvas is a regular canvas object that you can draw on.

    You also need to modify the _basic() function so that it determines the fill based on the horizontal position of the click (if you want a horizontal slider).

        def _basic(self, pos):
    
            x, y, w, h = self.rect
            xclick = pos[0]
            dclick = xclick-x
            drect = w
            self._fill = min(1, max(0, dclick/drect))
            self.set_var(self._fill)
    

    Cheers!

  • edited August 2017

    Hi @sebastiaan, so after a lot of trial and error I'm really close - but having a couple of issues I can't work out how to sort. My Inline Script is as follows:

    from libopensesame.widgets._widget import widget
    from libopensesame import widgets
    from openexp._mouse.legacy import legacy        
    
    class slider(widget):
    
        def __init__(self, form, var=None):
    
            widget.__init__(self, form)
            self._fill = .5
            self.var = var
            self.set_var(self._fill)
            self._mouse = mouse()           
            self._use_pygame = isinstance(self._mouse, legacy)
    
        def render(self):
    
            x, y, w, h = self.rect      
    
            self.form.canvas.rect(x,y,w*self._fill,h,fill=True,
                color='gray')
            self.form.canvas.rect(x, y, w, h, penwidth=4, color='gray')
            self.form.canvas.circle(x*(1-self._fill),y+(h/2), h/2, penwidth=3, color='black')
        def on_mouse_click(self, pos):
    
            return self._pygame(pos) if self._use_pygame else self._basic(pos)
    
        def _basic(self, pos):
    
            x, y, w, h = self.rect
            xclick = pos[0]
            dclick = xclick-x
            drect = w       
            self._fill = min(1, max(0, dclick/drect))
            self.set_var(self._fill)
    
        def _pygame(self, pos):
    
            import pygame
    
            self._basic(pos)
            while True:
                for event in pygame.event.get():
                    if event.type == pygame.MOUSEBUTTONUP:
                        return
                    if event.type != pygame.MOUSEMOTION:
                        continue
                    pos = self._mouse.get_pos()[0]
                    self._basic(pos)
                    self.form.render()
    
    
    widgets.slider = slider
    

    and in my custom form I have:

    set timeout infinite
    set spacing 10
    set rows "3;1;1;3;1;1;1;1"
    set only_render no
    set margins "50;50;50;50"
    set cols "1;5;1"
    set _theme gray
    widget 0 0 3 1 label text="Mark on the slider below how <b>Happy</b> you are right now"
    widget 1 1 1 1 slider var=s1response
    widget 1 2 1 1 image path="AS_intensity_cue-2.png"
    widget 0 1 1 2 image path="AS_happy-2.png"
    widget 2 1 1 2 image path="AS_unhappy-2.png"
    widget 0 3 3 1 label text="Mark on the slider below how <b>Alert</b> you are right now"
    widget 1 4 1 1 slider var=s2response
    widget 1 5 1 1 image path="AS_intensity_cue-2.png"
    widget 0 4 1 2 image path="AS_sleepy-2.png"
    widget 2 4 1 2 image path="AS_wideawake-2.png"
    widget 0 7 3 1 button text="<b>Confirm</b>"`
    

    My current issues are:

    1. The circle marker doesn't move inline with the fill, it only seems to go halfway.
    2. The image "AS_intensity_cue-2.png" isn't the same size as the slider - can you resize images to fit according to the widget dimensions?
    3. I'd ideally like to replace the frame [self.form.canvas.rect(x, y, w, h, penwidth=4, color='gray')] with an image of a frame with rounded edges. I tried the code below - but I can't work out how to get it to be inline with the slider
    path = exp.pool[u'AS_track.png']
    self.form.canvas.image(path)
    

    Apologies for all the questions - really appreciate the help. Will make sure I post the completed code incase of use to others viewing the forum.

  • The circle marker doesn't move inline with the fill, it only seems to go halfway.

    Yes, your trigonometry is off :wink: x*(1-self._fill) should be x+w*self._fill:

    self.form.canvas.circle(x+w*self._fill,y+(h/2), h/2, penwidth=3, color='white')
    

    The image "AS_intensity_cue-2.png" isn't the same size as the slider - can you resize images to fit according to the widget dimensions?

    That already happens. But the width/ height ratio is not changed, because that would distort the image. So you have to make sure that this ratio is correct in the original image, and then you will see that it will adjust to the size of the widget.

    I'd ideally like to replace the frame [self.form.canvas.rect(x, y, w, h, penwidth=4, color='gray')] with an image of a frame with rounded edges. I tried the code below - but I can't work out how to get it to be inline with the slider

    I wouldn't use an image for the outline. This will make it difficult to scale the size of the widget. Instead, I would use canvas.polygon() to draw a slightly fancier (but scalable) outline in the render() function:

    Cheers!

  • Thanks @sebastiaan I will have a go with that. For a curve using canvas.polygon - does that mean I just need to put in lots and lots of points to get something resembling a circle?

  • For a curve using canvas.polygon - does that mean I just need to put in lots and lots of points to get something resembling a circle?

    Exactly!

  • @sebastiaan OK - so who thought drawing a curved line would be so hard - but its bamboozling me! Using some pythag to help me work out co-ordinates relative to the size of the widget I tried something like this:
    from cmath import sqrt r=(h/2) n0 = x,y n1 = x-1,(y+sqrt((r**2)-(1**2))) n2 = x-2,(y+sqrt((r**2)-(2**2))) n3 = x-3,(y+sqrt((r**2)-(3**2))) ... n400 = x-400,(y+sqrt((r**2)-(400**2))) .... self.form.canvas.polygon([n0, n1, n2, n3, ...n400...])

    I only got so far in writing the remaining lines but nothing is showing up so far - and I'm probably barking up the wrong tree - is this even remotely close?

  • Here's another way to draw a rounded rectangle. An easier way probably. Do you see the logic? The corners are drawn with four circles, and the body is a cross of two rectangles. The rounded rectangle is filled, but to draw only an outline, you can simply draw a slightly smaller rounded rectangle inside.

    def draw_rounded_rect(cnvs, x, y, w, h, color='gray'):
    
        r = min(w//4, h//4)
        cnvs.circle(x+r, y+r, r, color=color, fill=True)
        cnvs.circle(x+r, y+h-r, r, color=color, fill=True)
        cnvs.circle(x+w-r, y+r, r, color=color, fill=True)
        cnvs.circle(x+w-r, y+h-r, r, color=color, fill=True)
        cnvs.rect(x+r, y, w-2*r, h, color=color, fill=True)
        cnvs.rect(x, y+r, w, h-2*r, color=color, fill=True)
    
    
    c = canvas()
    draw_rounded_rect(c, -100, -100, 200, 300, color='white')
    draw_rounded_rect(c, -95, -95, 190, 290, color='black')
    c.show()
    
  • OK - that last bit went a bit beyond me - but incase this code is of use to others - this is what I used to implement the Affective Slider (Betella et al., 2016)...

    In Inline Script:

    `
    from libopensesame.widgets._widget import widget
    from libopensesame import widgets
    from openexp._mouse.legacy import legacy

    class slider(widget):

    def __init__(self, form, var=None):
    
        widget.__init__(self, form)
        self._fill = .5
        self.var = var
        self.set_var(self._fill)
        self._mouse = mouse()           
        self._use_pygame = isinstance(self._mouse, legacy)
    
    def render(self):
    
        x, y, w, h = self.rect      
    
        self.form.canvas.rect(x,y,w*self._fill,h,fill=True,
            color='white')
    
        self.form.canvas.rect(x-(h/2), y, w+(h+(h*.1)), h, penwidth=1, color='black')
    
        self.form.canvas.rect(x+w*(self._fill)-((h/2)+(h*.05)), y-(h*.05), h+(h*0.1), h+(h*0.1), penwidth=3, color='black')
    
    def on_mouse_click(self, pos):
    
        return self._pygame(pos) if self._use_pygame else self._basic(pos)
    
    def _basic(self, pos):
    
        x, y, w, h = self.rect
        xclick = pos[0]
        dclick = xclick-x
        drect = w       
        self._fill = min(1, max(0, dclick/drect))
        self.set_var(self._fill)
    
    def _pygame(self, pos):
    
        import pygame
    
        self._basic(pos)
        while True:
            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONUP:
                    return
                if event.type != pygame.MOUSEMOTION:
                    continue
                pos = self._mouse.get_pos()[0]
                self._basic(pos)
                self.form.render()
    

    widgets.slider = slider`

    In Custom Form

    set timeout infinite set spacing 10 set rows "3;1;1;3;1;1;1;1" set only_render no set margins "50;50;50;50" set cols "1;5;1" set _theme gray widget 0 0 3 1 label text="Mark on the slider below how <b>Happy</b> you are right now" widget 1 1 1 1 slider var=s1response widget 1 2 1 1 image path="AS_intensity_cue-2.png" widget 0 1 1 2 image path="AS_happy-2.png" widget 2 1 1 2 image path="AS_unhappy-2.png" widget 0 3 3 1 label text="Mark on the slider below how <b>Alert</b> you are right now" widget 1 4 1 1 slider var=s2response widget 1 5 1 1 image path="AS_intensity_cue-2.png" widget 0 4 1 2 image path="AS_sleepy-2.png" widget 2 4 1 2 image path="AS_wideawake-2.png" widget 0 7 3 1 button text="<b>Confirm</b>"

    Seems to work well - Thanks @sebastiaan for all your help.

  • Hi all

    Is this suggestion still valid?

    I copied the original code to an inline script at the start of the experiment, then I call the widget in a inline_script, but an empty screen appears.

    Any thoughts?


    Thanks,

    Fotis


  • Hi @fotisfotiadis ,

    No this code is based on outdated version of OpenSesame. I would check out this discussion. I haven't tried it, but the code shown there by @Lara94 is for recent versions of OpenSesame.

    — Sebastiaan

  • Hi @sebastiaan

    Thanks for the response, always appreciated!

    Best,

    Fotis

Sign In or Register to comment.

agen judi bola , sportbook, casino, togel, number game, singapore, tangkas, basket, slot, poker, dominoqq, agen bola. Semua permainan bisa dimainkan hanya dengan 1 ID. minimal deposit 50.000 ,- bonus cashback hingga 10% , diskon togel hingga 66% bisa bermain di android dan IOS kapanpun dan dimana pun. poker , bandarq , aduq, domino qq , dominobet. Semua permainan bisa dimainkan hanya dengan 1 ID. minimal deposit 10.000 ,- bonus turnover 0.5% dan bonus referral 20%. Bonus - bonus yang dihadirkan bisa terbilang cukup tinggi dan memuaskan, anda hanya perlu memasang pada situs yang memberikan bursa pasaran terbaik yaitu http://45.77.173.118/ Bola168. Situs penyedia segala jenis permainan poker online kini semakin banyak ditemukan di Internet, salah satunya TahunQQ merupakan situs Agen Judi Domino66 Dan BandarQ Terpercaya yang mampu memberikan banyak provit bagi bettornya. Permainan Yang Di Sediakan Dewi365 Juga sangat banyak Dan menarik dan Peluang untuk memenangkan Taruhan Judi online ini juga sangat mudah . Mainkan Segera Taruhan Sportbook anda bersama Agen Judi Bola Bersama Dewi365 Kemenangan Anda Berapa pun akan Terbayarkan. Tersedia 9 macam permainan seru yang bisa kamu mainkan hanya di dalam 1 ID saja. Permainan seru yang tersedia seperti Poker, Domino QQ Dan juga BandarQ Online. Semuanya tersedia lengkap hanya di ABGQQ. Situs ABGQQ sangat mudah dimenangkan, kamu juga akan mendapatkan mega bonus dan setiap pemain berhak mendapatkan cashback mingguan. ABGQQ juga telah diakui sebagai Bandar Domino Online yang menjamin sistem FAIR PLAY disetiap permainan yang bisa dimainkan dengan deposit minimal hanya Rp.25.000. DEWI365 adalah Bandar Judi Bola Terpercaya & resmi dan terpercaya di indonesia. Situs judi bola ini menyediakan fasilitas bagi anda untuk dapat bermain memainkan permainan judi bola. Didalam situs ini memiliki berbagai permainan taruhan bola terlengkap seperti Sbobet, yang membuat DEWI365 menjadi situs judi bola terbaik dan terpercaya di Indonesia. Tentunya sebagai situs yang bertugas sebagai Bandar Poker Online pastinya akan berusaha untuk menjaga semua informasi dan keamanan yang terdapat di POKERQQ13. Kotakqq adalah situs Judi Poker Online Terpercayayang menyediakan 9 jenis permainan sakong online, dominoqq, domino99, bandarq, bandar ceme, aduq, poker online, bandar poker, balak66, perang baccarat, dan capsa susun. Dengan minimal deposit withdraw 15.000 Anda sudah bisa memainkan semua permaina pkv games di situs kami. Jackpot besar,Win rate tinggi, Fair play, PKV Games