Forum Archive

Changing button shape using up.Path

RoninSage

I want to have a button that has a different shape than a rectangle. I think ui.Path is the way to go but I am having trouble implementing it. I have code for a simple button press. Could I get some help changing it so the button changes to say a hexagon?


import ui
# Define variables
btn_counter=-1 # button pressed count
btn_color=['red','magenta','blue','green','yellow','cyan'] # button colours
btn_txt=['click again','click one more time','have another go','go again please', 'one last press','finally'] # button text when pressed
# Define functions
# What button1 does when tapped
def button1_tapped(sender):
  global btn_counter # count number of times the button is pressed
  btn_counter+=1
  if btn_counter<=5: # cycle through button colours
    sender.title = btn_txt[btn_counter] # change button text when pressed
    sender.size_to_fit() # adjust button size to fit new text exactly
    button1.width=1.5*button1.width # change button width
    button1.height=2*button1.height # change button height
    button1.bg_color = btn_color[btn_counter] # change button colour when tapped
  else:
    view.remove_subview(button1) # remove button
    some_box.title = 'thank you for your time' # change box text
    some_box.size_to_fit() # change box size to fit new text exactly
    some_box.frame=(view.width*0.38,view.height*0.45,1.1*some_box.width,2*some_box.height) # change box location and size

# Create display, further details see theBackground.py
view=ui.View()
view.present('fullscreen', hide_title_bar=True)
view.background_color = (214/255,12/255,140/255)

# Create button
button1 = ui.Button(title='Click me') # initial button text
button1.center = (view.width*0.38, view.height*0.45) # Button initial position
button1.border_width = 5 # give button a border
#button1.size_to_fit() # fit button around text exactly
button1.width = view.width*0.3 # button width
button1.height = view.height*0.2 # button height
button1.bg_color = (.3,.31,.32) # button colour (.3,.31,.32,0) fof transparent
button1.font = ('Chalkduster', 30) # button font type and size
button1.tint_color=(1,1,1) # button font colour
button1.action = button1_tapped # needed for when button pressed
view.add_subview(button1) # display button

# Include non-interactive box
some_box = ui.Label(
  text='This is not a button', # box text
  background_color=(95/255,96/255,98/255), # box colour
  text_color='white', # box text colour
  alignment=ui.ALIGN_CENTER, # text alignment in box
  number_of_lines=0, # number of lines used in box
  frame=(10, 10, view.width-20, 100)) # box position (10 in, 10 down, view.width-20 wide, 100 high)
some_box.font=('HoeflerText-BlackItalic', 40) # box font type and size
view.add_subview(some_box) # show box

brumm

Try changing the draw method in this example.

enceladus

May be this
https://github.com/encela95dus/ios_pythonista_examples/blob/master/imagecontext4.py

enceladus

import ui, math
# Define variables
btn_counter=-1 # button pressed count
btn_color=['red','magenta','blue','green','yellow','cyan'] # button colours
btn_txt=['click again','click one more time','have another go','go again please', 'one last press','finally'] # button text when pressed

def make_polygon(num_sides, x=0, y=0, radius=100, phase=0, line_width=5): 
    path = ui.Path()
    path.move_to(x,y)
    path.line_width = line_width
    for i in range(num_sides):
        t = 2*math.pi*i/num_sides
        x1, y1 = radius+radius*math.cos(t+phase), radius+radius*math.sin(t+phase)
        if i:
            path.line_to(x+x1, y+y1)
        else:
            path.move_to(x+x1,y+y1)
    path.close() 
    return path 

def create_image(w, h, bg, fg):
    img = None
    with ui.ImageContext(w, h) as ctx:  
        ui.set_color(bg)
        rect = ui.Path.rect(0,0,w,h)
        rect.fill()        
        ui.set_color(fg)
        path = make_polygon(6, 0, 0, h/2, math.pi/2) 
        path.fill() 
        img = ctx.get_image()
    return img   

# Define functions
# What button1 does when tapped
def button1_tapped(sender):
  global btn_counter # count number of times the button is pressed
  btn_counter+=1
  if btn_counter<=5: # cycle through button colours
    sender.title = btn_txt[btn_counter] # change button text when pressed
    sender.size_to_fit() # adjust button size to fit new text exactly
    button1.width=button1.width # change button width
    button1.height=button1.width # change button height
    main_bg = (214/255,12/255,140/255)
    button1.background_image = create_image(button1.width, button1.height,
                                        main_bg, btn_color[btn_counter])
    #button1.bg_color = btn_color[btn_counter] # change button colour when tapped
  else:
    view.remove_subview(button1) # remove button
    some_box.title = 'thank you for your time' # change box text
    some_box.size_to_fit() # change box size to fit new text exactly
    some_box.frame=(view.width*0.38,view.height*0.45,1.1*some_box.width,2*some_box.height) # change box location and size

# Create display, further details see theBackground.py
view=ui.View()
view.present('fullscreen')
view.background_color = (214/255,12/255,140/255)

# Create button
button1 = ui.Button(title='Click me') # initial button text
button1.center = (view.width*0.38, view.height*0.45) # Button initial position
#button1.border_width = 5 # give button a border
#button1.size_to_fit() # fit button around text exactly
button1.width = view.width*0.3 # button width
button1.height = view.height*0.2 # button height
button1.bg_color = (.3,.31,.32) # button colour (.3,.31,.32,0) fof transparent
button1.font = ('Chalkduster', 30) # button font type and size
button1.tint_color=(1,1,1) # button font colour
button1.action = button1_tapped # needed for when button pressed
view.add_subview(button1) # display button

# Include non-interactive box
some_box = ui.Label(
  text='This is not a button', # box text
  background_color=(95/255,96/255,98/255), # box colour
  text_color='white', # box text colour
  alignment=ui.ALIGN_CENTER, # text alignment in box
  number_of_lines=0, # number of lines used in box
  frame=(10, 10, view.width-20, 100)) # box position (10 in, 10 down, view.width-20 wide, 100 high)
some_box.font=('HoeflerText-BlackItalic', 40) # box font type and size
view.add_subview(some_box) # show box
RoninSage

@brumm thanks for the help but the code In the link did not work when I tried to run it.

@enceladus this is a start but the button still exists as a rectangle around the hexagon picture. Any tips to make sure that only tapping the shape will activate the button?

enceladus

Need to write "inside" function.

# coding: utf-8

import ui, math

class MyButtonClass(ui.View):
    def __init__(self):
        self.color = 'red'
        #touch event are limited to this area (left=100,top=100,right=200,bottom=200)
        self.x = 100
        self.y = 100
        self.height = 100
        self.width = 100
        self.main_bg = 'black'
        self.fg = 'red'

    def make_polygon(self, num_sides, x=0, y=0, radius=100, phase=0, line_width=5): 
        path = ui.Path()
        path.move_to(x,y)
        path.line_width = line_width
        for i in range(num_sides):
            t = 2*math.pi*i/num_sides
            x1, y1 = radius+radius*math.cos(t+phase), radius+radius*math.sin(t+phase)
            if i:
                path.line_to(x+x1, y+y1)
            else:
                path.move_to(x+x1,y+y1)
        path.close() 
        return path 


    def draw(self):
        w, h = self.width, self.height
        ui.set_color(self.main_bg)
        rect = ui.Path.rect(0,0,w,h)
        rect.fill()        
        ui.set_color(self.fg)
        path = self.make_polygon(6, 0, 0, h/2, math.pi/2) 
        path.fill() 

    def inside(self, touch):
        return True

    def touch_ended(self, touch):
        if not self.inside(touch):
            return
        if self.fg == 'red':
            self.fg = 'blue'
        else:
            self.fg = 'red'
        self.set_needs_display()

class SpecialButton(object):
    def __init__(self):
        self.view = ui.View()
        self.view.present('fullscreen')
        self.btn = MyButtonClass()
        self.view.add_subview(self.btn)

SpecialButton()

cvp

Perhaps, inside(touch) could check the color of touch.location?

enceladus

Here is an implementation. Not tested well. (Used Brumm code)

# coding: utf-8
from matplotlib import path

import ui, math

class MyButtonClass(ui.View):
    def __init__(self):
        self.color = 'red'
        #touch event are limited to this area (left=100,top=100,right=200,bottom=200)
        self.x = 100
        self.y = 100
        self.height = 100
        self.width = 100
        self.main_bg = 'black'
        self.fg = 'red'
        self.points = []

    def make_polygon(self, num_sides, x=0, y=0, radius=100, phase=0, line_width=5): 
        path1 = ui.Path()
        path1.move_to(x,y)
        path1.line_width = line_width
        points = []
        for i in range(num_sides):
            t = 2*math.pi*i/num_sides
            x1, y1 = radius+radius*math.cos(t+phase), radius+radius*math.sin(t+phase)
            points.append((x+x1, y+y1))
            if i:
                path1.line_to(x+x1, y+y1)
            else:
                path1.move_to(x+x1,y+y1)
        path1.close() 
        return (path1, points)


    def draw(self):
        w, h = self.width, self.height
        ui.set_color(self.main_bg)
        rect = ui.Path.rect(0,0,w,h)
        rect.fill()        
        ui.set_color(self.fg)
        path1, self.points = self.make_polygon(6, 0, 0, h/2, math.pi/2) 
        path1.fill() 

    def inside(self, touch):
        p = path.Path(self.points)
        return p.contains_points([touch.location])[0]

    def touch_ended(self, touch):
        if not self.inside(touch):
            return
        if self.fg == 'red':
            self.fg = 'blue'
        else:
            self.fg = 'red'
        self.set_needs_display()

class SpecialButton(object):
    def __init__(self):
        self.view = ui.View()
        self.view.present('fullscreen')
        self.btn = MyButtonClass()
        self.view.add_subview(self.btn)

SpecialButton()


RoninSage

@enceladus That last code worked, just a quick question, if I wanted a second button, is there an easy way to do that?

brumm
class MyButtonClass(ui.View):
    def __init__(self, x, y, height, width):
        self.x = x
        self.y = y
        self.height = height
        self.width = width

class SpecialButton(object):
    def __init__(self):
        self.view = ui.View()
        self.view.present('fullscreen')
        self.btn = MyButtonClass(100, 100, 100, 100)
        self.btn2 = MyButtonClass(200, 100, 100, 100)
        self.view.add_subview(self.btn)
        self.view.add_subview(self.btn2)

python classes

RoninSage

@brumm thanks, just enough info for me to figure it out.