micropython-samples/tft_gui/button.py

172 wiersze
6.8 KiB
Python

from delay import Delay
from ui import get_stringsize, print_centered, touchable, CIRCLE, RECTANGLE, CLIPPED_RECT
# Button coordinates relate to bounding box (BB). x, y are of BB top left corner.
# likewise width and height refer to BB, regardless of button shape
class Button(touchable):
def __init__(self, objsched, tft, objtouch, location, *, shape=CIRCLE, height=50, width=50, fill=True,
fgcolor=None, bgcolor=None, fontcolor=None, litcolor=None, text='', font=None, show=True, callback=lambda x : None,
args=[]):
super().__init__(objsched, objtouch)
self.objsched = objsched
self.tft = tft
self.location = location
self.shape = shape
self.height = height
self.width = width
self.radius = height // 2
self.fill = fill
self.fgcolor = fgcolor
self.bgcolor = bgcolor
self.font = font if font is not None else tft.text_font
self.fontcolor = fontcolor if fontcolor is not None else tft.getColor()
self.litcolor = litcolor
self.text = text
self.callback = callback
self.callback_args = args
self.orig_fgcolor = fgcolor
if self.litcolor is not None:
self.delay = Delay(objsched, self.shownormal)
self.visible = True # ditto
self.litcolor = litcolor if self.fgcolor is not None else None
self.busy = False
if show:
self.show()
def show(self):
tft = self.tft
fgcolor = tft.getColor() # save old colors
bgcolor = tft.getBGColor()
x = self.location[0]
y = self.location[1]
if not self.visible: # erase the button
tft.setColor(bgcolor)
tft.fillRectangle(x, y, x + self.width, y + self.height)
tft.setColor(fgcolor)
return
if self.fgcolor is not None:
tft.setColor(self.fgcolor)
if self.bgcolor is not None:
tft.setBGColor(self.bgcolor)
if self.shape == CIRCLE: # Button coords are of top left corner of bounding box
x += self.radius
y += self.radius
if self.fill:
tft.fillCircle(x, y, self.radius)
else:
tft.drawCircle(x, y, self.radius)
if self.font is not None and len(self.text):
print_centered(tft, x, y, self.text, self.fontcolor, self.font)
else:
x1 = x + self.width
y1 = y + self.height
if self.shape == RECTANGLE: # rectangle
if self.fill:
tft.fillRectangle(x, y, x1, y1)
else:
tft.drawRectangle(x, y, x1, y1)
if self.font is not None and len(self.text):
print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font)
elif self.shape == CLIPPED_RECT: # clipped rectangle
if self.fill:
tft.fillClippedRectangle(x, y, x1, y1)
else:
tft.drawClippedRectangle(x, y, x1, y1)
if self.font is not None and len(self.text):
print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font)
tft.setColor(fgcolor) # restore them
tft.setBGColor(bgcolor)
def shownormal(self):
self.fgcolor = self.orig_fgcolor
self.show()
def touched(self, x, y): # If touched, process it otherwise do nothing
is_touched = False
if self.shape == CIRCLE:
r = self.radius
dx = r - (x - self.location[0])
dy = r - (y - self.location[1])
if (dx * dx + dy * dy) < (r * r): # Pythagoras is alive!
is_touched = True
elif self.shape in (RECTANGLE, CLIPPED_RECT): # rectangle
x0 = self.location[0]
x1 = self.location[0] + self.width
y0 = self.location[1]
y1 = self.location[1] + self.height
if x0 <= x <= x1 and y0 <= y <= y1:
is_touched = True
if is_touched and self.litcolor is not None:
self.fgcolor = self.litcolor
self.show()
self.delay.trigger(1)
if is_touched and not self.busy: # Respond once to a press
self.callback(self, self.callback_args) # Callback not a bound method so pass self
self.busy = True # Ensure no response to continued press
def untouched(self): # User has released touchpad or touched elsewhere
self.busy = False
class Buttons(object):
def __init__(self, user_callback):
self.user_callback = user_callback
self.lstbuttons = []
def add_button(self, *args, **kwargs):
kwargs['show'] = False
self.lstbuttons.append(Button(*args, **kwargs))
# Group of buttons, typically at same location, where pressing one shows
# the next e.g. start/stop toggle or sequential select from short list
class Buttonset(Buttons):
def __init__(self, user_callback):
super().__init__(user_callback)
def run(self):
for idx, button in enumerate(self.lstbuttons):
if idx:
button.visible = False # Only button zero visible and sensitive
button.enabled = False
button.callback_args.append(idx)
button.callback = self.callback
self.lstbuttons[0].show()
def callback(self, button, args):
button_no = args[-1]
old = self.lstbuttons[button_no]
new = self.lstbuttons[(button_no + 1) % len(self.lstbuttons)]
old.enabled = False
old.visible = False
old.show()
new.enabled = True
new.visible = True
new.busy = True # Don't respond to continued press
new.show()
self.user_callback(new, args[:-1]) # user gets button with args they specified
# Group of buttons at different locations, where pressing one shows
# only current button highlighted and oes callback from current one
class RadioButtons(Buttons):
def __init__(self, user_callback, highlight, selected=0):
super().__init__(user_callback)
self.highlight = highlight
self.selected = selected
def run(self):
for idx, button in enumerate(self.lstbuttons):
if idx == self.selected: # Initial selection
button.fgcolor = self.highlight
else:
button.fgcolor = button.orig_fgcolor
button.show()
button.callback = self.callback
def callback(self, button, args):
for but in self.lstbuttons:
if but is button:
but.fgcolor = self.highlight
else:
but.fgcolor = but.orig_fgcolor
but.show()
self.user_callback(button, args) # user gets button with args they specified