Forum Archive

I just don't get it

polymerchm

Perhaps this is a 1.6 issue, but I think I'm just missing something. I want to create a spinner widget that allows me to enter a number and increment/decrement it via two buttons (up and down). I have commented out much to isolate what I am doing wrong. On pythonista 1.6, this give me a ivory filled square with a bottom and right black shadow. No upper or left "border". And no uparrow. There is no pyui file, as I want to build this on the fly.

# spinner class

import ui

class Spinner(ui.View):
    ''' creates a view with a data entry field and up/down arrows which allow for increment/decrement
    valid types are int, list, float.  A list will be fixed possibilities and limits are ignored
    '''

    def __init__(self, parent,
                             initialValue= 10,
                             increment=1,
                             frame=(20,20, 200,200),
                             limits=(0,100),
                             dataType='int',
                             action=None
                 ):
        self.parent = parent
        self.initialValue = initialValue
        self._value = initialValue
        self.increment = increment
        self.action = action
        self.dataType = dataType
        self.limits = limits
        self.frame = frame
        self.list = []



    def buildView(self):
        # build the view here

        self.v = ui.View(frame=self.frame)
        self.v.background_color = "white"
        self.border_color = 'black'
        self.border_width = 3
        self.parent.add_subview(self.v)


#       self.vInput = ui.Label( )
#       self.vInput.text = "{}".format(self.initialValue)
#       self.vInput.bounds = (0,0,40,40)
#       self.vInput.text_color = 'black'
#       self.vInput.border_color = 'black'
#       self.vInput.alignment = ui.ALIGN_CENTER
#       self.vInput.border_width = 1
#       self.v.add_subview(self.vInput)
#       self.vInput.bring_to_front()
#       self.vInput.hidden = False

        self.upArrow= ui.Button()
        self.upArrow.bounds = (0,60,50,50)
        self.upArrow.bg_color = 'ivory'
        self.upArrow.border_color = 'black'
        self.upArrow.border_width = 1
        self.upArrow.name = 'upBtn'
        self.upArrow.action = self.onArrow
        self.upArrow.background_image = ui.Image.named('ionicons-arrow-up-b-24')
        self.upArrow.enabled = True
        self.v.add_subview(self.upArrow)
        self.parent.add_subview(self.v)
#       
#       self.dnArrow = ui.Button()
#       self.v.add_subview(self.dnArrow)
#       self.dnArrow.name = 'dnBtn'
#       self.dnArrow.action = self.onArrow


    @property
    def value(self):
        return self._value

    @value.setter
    def value(self,input):
        self._value = input

    def onArrow(self,sender):
        pass

    def reset(self):
        self.value = initialValue       

if __name__ == '__main__':
    view = ui.View(background_color = 'white')

    spinner = Spinner(view,frame=(200,200,100,100),initialValue = 500,
                      limits=(0,1000),increment=10)
    spinner.buildView()
    view.present('full_screen')

ccc

The basic problem is that a Spinner is a subclass of ui.View but you were also trying to add a ui.View (self.v) to it which confused the logic.

# spinner class

import ui

def make_button(name, bg_image_name, loc_xy=(10, 10)):
    button = ui.Button(name=name)
    button.frame = (loc_xy[0], loc_xy[1], 50, 50)
    button.bg_color = 'ivory'
    button.border_color = 'black'
    button.border_width = 1
    button.background_image = ui.Image.named(bg_image_name)
    button.enabled = True
    return button

def make_label(text, loc_xy = (10, 10)):
    label = ui.Label( )
    label.text = str(text)
    label.bounds = (loc_xy[0], loc_xy[1], 40, 40)
    label.text_color = 'black'
    label.border_color = 'black'
    label.alignment = ui.ALIGN_CENTER
    label.border_width = 1
    label.bring_to_front()
    return label

class Spinner(ui.View):
    ''' creates a view with a data entry field and up/down arrows which allow for increment/decrement
    valid types are int, list, float.  A list will be fixed possibilities and limits are ignored
    '''

    def __init__(self, initialValue= 10, increment=1, limits=(0,100), dataType='int', action=None):
        self._value = self.initialValue = initialValue
        self.increment = increment
        self.limits = limits
        self.dataType = dataType
        self.action = action
        self.list = []
        self.add_ui()

    def add_ui(self):
        # add user interface elements
        self.background_color = "white"
        self.border_color = 'blue'
        self.border_width = 3

        self.label = make_label(self._value, (10, 10))
        self.add_subview(self.label)

        self.upArrow = make_button('upBtn', 'ionicons-arrow-up-b-24', (80, 25))
        self.upArrow.action = self.onArrow
        self.add_subview(self.upArrow)

        self.downArrow = make_button('downBtn', 'ionicons-arrow-down-b-24', (140, 25))
        self.downArrow.action = self.onArrow
        self.add_subview(self.downArrow)

    @property
    def value(self):
        return self._value

    @value.setter
    def value(self,input):
        self._value = input

    def onArrow(self,sender):
        # you might want to consider tap-and-hold functionality
        increment = self.increment * (-1 if 'down' in sender.name.lower() else 1)
        if self.limits[0] <= self._value + increment <= self.limits[1]:
            self._value += increment
            self.label.text = str(self._value)

    def reset(self):
        self._value = self.initialValue       

if __name__ == '__main__':
    view = ui.View(background_color = 'white')
    spinner = Spinner(initialValue = 500, limits = (0,1000), increment = 10)
    view.present('full_screen')
    spinner.frame = view.bounds
    view.add_subview(spinner)
JonB

I'm pretty sure a view can only be added as a subview to one view, one time.
The code above, you added v to the parent twice! In that process, the button is getting its frame confused.

My suggestion: since Spinner is a ui.View, the behavior should act like a View. That means, to get it to snow, you would use view.add_subview(spinner) in main. The spinner should not add anything into the parent view.
v should be a subview of self, not of the parent view.

See Checkout ui.CheckBox in the uicomponents for an example... In your case you'll add two buttons and a textfield to self. Or, for another example, see dropdown, which does have a textfield and a single button, so the setup will look similar to what you want.

In the dropdown case, I specifically DID need to add something to the root view, though you can find that dynamically --- but the only time you need to do something like that is if you need to display something outside your custom view's frame... In this case I wanted to prevent interaction with any other ui components, so I create a view that covers up the root view until a choice is made.

JonB

Here is my quick solution: includes a textfield for direct data entry . I didn't include limits, but I see ccc's approach does.

import ui
class Spinner(ui.View):
    def __init__(self,frame=(0,0,300,32),name='spinner', value=0):
        '''Simple spinner, supports direct entry.
        '''
        self.frame=frame
        self.textfield=ui.TextField(frame=frame,name='textfield')
        self.textfield.autocapitalization_type=ui.AUTOCAPITALIZE_NONE 
        self.textfield.autocorrection_type=False 

        self.up=ui.Button(name='button',bg_color=None)
        self.down=ui.Button(name='button',bg_color=None)
        self.add_subview(self.textfield)
        self.add_subview(self.up)
        self.add_subview(self.down)
        h=frame[3]
        self.up.frame=  (self.width-64, h-32, 32,32)
        self.down.frame=(self.width-32, h-32, 32,32)

        self.up.image=ui.Image.named('ionicons-arrow-up-b-32')
        self.down.image=ui.Image.named('ionicons-arrow-down-b-32')
        self.up.action=self.inc
        self.down.action=self.dec

        self.up.flex='l'
        self.down.flex='l'
        self.textfield.flex='w'  
        self.textfield.text=str(value)
    def inc(self,sender):
        self.textfield.text = str(self.value+1)
    def dec(self,sender):
        self.textfield.text = str(self.value-1)

    @property
    def value(self):
        return int(self.textfield.text)

    @value.setter
    def text(self,value):
        self.textfield.text=value

    #lets you set the spinner.action    
    @property
    def action(self):
        return self.textfield.action

    @action.setter
    def action(self,value):
        if callable(value):
            self.textfield.action=value
        else: 
            self.textfield.action = lambda : None



if __name__=='__main__':
    v=ui.View()
    v.present()

    s=Spinner()
    v.add_subview(s)