TFT GUI version 0.1

pull/7/head
Peter Hinch 2016-04-27 14:58:53 +01:00
rodzic d2f4e9e79b
commit ceda9894b4
7 zmienionych plików z 795 dodań i 224 usunięć

Wyświetl plik

@ -1,25 +1,42 @@
# button.py Pushbutton classes for Pybboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from delay import Delay
from ui import get_stringsize, print_centered, touchable, CIRCLE, RECTANGLE, CLIPPED_RECT
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
# If font is None button will be rendered without text
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,
class Button(Touchable):
def __init__(self, objsched, tft, objtouch, location, *, font, shape=CIRCLE, height=50, width=50, fill=True,
fgcolor=None, bgcolor=None, fontcolor=None, litcolor=None, text='', show=True, callback=lambda x, y : None,
args=[]):
super().__init__(objsched, objtouch)
self.objsched = objsched
self.tft = tft
self.location = location
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, None)
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
@ -35,19 +52,14 @@ class Button(touchable):
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)
self.set_color(self.bgcolor)
tft.fillRectangle(x, y, x + self.width, y + self.height)
tft.setColor(fgcolor)
self.restore_color()
return
if self.fgcolor is not None:
tft.setColor(self.fgcolor)
if self.bgcolor is not None:
tft.setBGColor(self.bgcolor)
self.set_color() # to foreground
if self.shape == CIRCLE: # Button coords are of top left corner of bounding box
x += self.radius
y += self.radius
@ -74,8 +86,7 @@ class Button(touchable):
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)
self.restore_color()
def shownormal(self):
self.fgcolor = self.orig_fgcolor
@ -93,7 +104,7 @@ class Button(touchable):
x0 = self.location[0]
x1 = self.location[0] + self.width
y0 = self.location[1]
y1 = self.location[1] + self.height
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:
@ -169,3 +180,52 @@ class RadioButtons(Buttons):
but.fgcolor = but.orig_fgcolor
but.show()
self.user_callback(button, args) # user gets button with args they specified
class Checkbox(Touchable):
def __init__(self, objsched, tft, objtouch, location, *, height=30, fillcolor=None,
fgcolor=None, bgcolor=None, callback=lambda x, y : None, args=[], value=False, border=None):
super().__init__(objsched, tft, objtouch, location, None, height, height, fgcolor, bgcolor, None, border)
self.callback = callback
self.callback_args = args
self.fillcolor = fillcolor
self.busy = False
self.value = value
self.show()
def show(self):
tft = self.tft
bw = self.draw_border() # and background if required. Result is width of border
x = self.location[0] + bw
y = self.location[1] + bw
height = self.height - 2 * bw
x1 = x + height
y1 = y + height
if self.fillcolor is None or not self.value:
self.set_color(self.bgcolor) # blank
tft.fillRectangle(x, y, x1, y1)
if self.fillcolor is not None and self.value:
self.set_color(self.fillcolor)
tft.fillRectangle(x, y, x1, y1)
self.set_color()
tft.drawRectangle(x, y, x1, y1)
if self.fillcolor is None and self.value:
tft.drawLine(x, y, x1, y1)
tft.drawLine(x, y1, x1, y)
def touched(self, x, y): # If touched, process it otherwise do nothing
is_touched = False
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 not self.busy: # Respond once to a press
self.value = not self.value
self.callback(self, self.callback_args) # Callback not a bound method so pass self
self.busy = True # Ensure no response to continued press
self.show()
def untouched(self): # User has released touchpad or touched elsewhere
self.busy = False

Wyświetl plik

@ -1,106 +1,137 @@
# buttontest.py Test/demo of pushbutton classes for Pybboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import gc
from font14 import font14
from tft import TFT, LANDSCAPE
from usched import Sched
from touch import TOUCH
from button import Button, Buttonset, RadioButtons
from ui import CIRCLE, RECTANGLE, CLIPPED_RECT
#gc.collect()
from button import Button, Buttonset, RadioButtons, Checkbox
from ui import CIRCLE, RECTANGLE, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
from displays import Label
def callback(button, args):
arg = args[0]
print('Returned: ', arg)
tft = button.tft
tft.setTextPos(0, 240)
tft.setTextStyle(None, None, 0, font14)
tft.printString('Button argument zero: {} '.format(arg))
label = args[1]
label.show(arg)
if arg == 'Q':
button.objsched.stop()
#gc.collect()
def cbcb(checkbox, args):
label = args[0]
if checkbox.value:
label.show('True')
else:
label.show('False')
# These tables contain args that differ between members of a set of related buttons
table = [
{'fgcolor' : (0, 128, 0), 'litcolor' : (0, 255, 0), 'text' : 'Yes', 'args' : ('A'), 'fontcolor' : (0, 0, 0)},
{'fgcolor' : (255, 0, 0), 'text' : 'No', 'args' : ('B')},
{'fgcolor' : (0, 0, 255), 'text' : '???', 'args' : ('C'), 'fill': False},
{'fgcolor' : (128, 128, 128), 'text' : 'Quit', 'args' : ('Q'), 'shape' : CLIPPED_RECT},
{'fgcolor' : GREEN, 'text' : 'Yes', 'args' : ['A'], 'fontcolor' : (0, 0, 0)},
{'fgcolor' : RED, 'text' : 'No', 'args' : ['B']},
{'fgcolor' : BLUE, 'text' : '???', 'args' : ['C'], 'fill': False},
{'fgcolor' : GREY, 'text' : 'Quit', 'args' : ['Q'], 'shape' : CLIPPED_RECT},
]
# similar buttons: only tabulate data that varies
table2 = [
{'text' : 'P', 'args' : ('p')},
{'text' : 'Q', 'args' : ('q')},
{'text' : 'R', 'args' : ('r')},
{'text' : 'S', 'args' : ('s')},
{'text' : 'P', 'args' : ['p']},
{'text' : 'Q', 'args' : ['q']},
{'text' : 'R', 'args' : ['r']},
{'text' : 'S', 'args' : ['s']},
]
# A Buttonset with two entries
# If buttons to be used in a buttonset, Use list rather than tuple for args because buttonset appends.
table3 = [
{'fgcolor' : (0, 255, 0), 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']},
{'fgcolor' : (255, 0, 0), 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']},
{'fgcolor' : GREEN, 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']},
{'fgcolor' : RED, 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']},
]
table4 = [
{'text' : '1', 'args' : ('1')},
{'text' : '2', 'args' : ('2')},
{'text' : '3', 'args' : ('3')},
{'text' : '4', 'args' : ('4')},
{'text' : '1', 'args' : ['1']},
{'text' : '2', 'args' : ['2']},
{'text' : '3', 'args' : ['3']},
{'text' : '4', 'args' : ['4']},
]
#gc.collect()
# THREADS
def stop(fTim, objsched): # Stop the scheduler after fTim seconds
yield fTim
objsched.stop()
labels = { 'width' : 70,
'fontcolor' : WHITE,
'border' : 2,
'fgcolor' : RED,
'bgcolor' : (0, 40, 0),
'font' : font14,
}
# USER TEST FUNCTION
def test(duration = 0):
if duration:
print("Test TFT panel for {:3d} seconds".format(duration))
else:
print('Testing TFT...')
def test():
print('Testing TFT...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched)
mytft.backlight(100) # light on
lstlbl = []
for n in range(3):
lstlbl.append(Label(mytft, (350, 50 * n), **labels))
# Button assortment
x = 50
x = 0
for t in table:
t['args'].append(lstlbl[2])
Button(objsched, mytft, mytouch, (x, 0), font = font14, callback = callback, **t)
x += 70
# Highlighting buttons
x = 50
x = 0
for t in table2:
Button(objsched, mytft, mytouch, (x, 60), font = font14, fgcolor = (128, 128, 128),
fontcolor = (0, 0, 0), litcolor = (255, 255, 255), callback = callback, **t)
t['args'].append(lstlbl[2])
Button(objsched, mytft, mytouch, (x, 60), fgcolor = GREY,
fontcolor = BLACK, litcolor = WHITE, font = font14, callback = callback, **t)
x += 70
# On/Off toggle
x = 50
x = 0
bs = Buttonset(callback)
for t in table3: # Buttons overlay each other at same location
bs.add_button(objsched, mytft, mytouch, (x, 120), font = font14, fontcolor = (0, 0, 0), **t)
t['args'].append(lstlbl[2])
bs.add_button(objsched, mytft, mytouch, (x, 120), font = font14, fontcolor = BLACK, **t)
bs.run()
# Radio buttons
x = 50
rb = RadioButtons(callback, (0, 0, 255)) # color of selected button
x = 0
rb = RadioButtons(callback, BLUE) # color of selected button
for t in table4:
rb.add_button(objsched, mytft, mytouch, (x, 180), font = font14, fontcolor = (255, 255, 255),
t['args'].append(lstlbl[2])
rb.add_button(objsched, mytft, mytouch, (x, 180), font = font14, fontcolor = WHITE,
fgcolor = (0, 0, 90), height = 30, **t)
x += 40
rb.run()
# Start scheduler
if duration:
objsched.add_thread(stop(duration, objsched)) # Commit suicide after specified no. of seconds
# Checkbox
Checkbox(objsched, mytft, mytouch, (300, 0), callback = cbcb, args = [lstlbl[0]])
Checkbox(objsched, mytft, mytouch, (300, 50), fillcolor = RED, callback = cbcb, args = [lstlbl[1]])
objsched.run() # Run it!
test() # Forever: we have a Quit button!
test()

184
tft_gui/displays.py 100644
Wyświetl plik

@ -0,0 +1,184 @@
# displays.py Non touch sensitive display elements for Pyboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from ui import print_centered, NoTouch, BLACK, RED
import math
import TFT_io
class Label(NoTouch):
def __init__(self, tft, location, *, font, border=None, width, fgcolor=None, bgcolor=None, fontcolor=None, text=''):
super().__init__(tft, location, font, None, width, fgcolor, bgcolor, fontcolor, border)
self.height = self.font.bits_vert
self.height += 2 * self.border # Height determined by font and border
self.draw_border() # Must explicitly draw because ctor did not have height
self.show(text)
def show(self, text):
tft = self.tft
bw = self.border
if text:
x = self.location[0]
y = self.location[1]
self.set_color(self.bgcolor)
tft.fillRectangle(x + bw, y + bw, x + self.width - bw, y + self.height - bw)
tft.setTextStyle(self.fontcolor, None, 2, self.font)
tft.setTextPos(x + bw, y + bw, clip = self.width - 2 * bw, scroll = False)
tft.printString(text)
self.restore_color() # restore fg color
# class displays angles
class Dial(NoTouch):
def __init__(self, tft, location, *, height=100, fgcolor=None, bgcolor=None, border=None, pointers=(0.9,), ticks=4):
NoTouch.__init__(self, tft, location, None, height, height, fgcolor, bgcolor, None, border) # __super__ provoked Python bug
border = self.border # border width
radius = height / 2 - border
self.radius = radius
self.xorigin = location[0] + border + radius
self.yorigin = location[1] + border + radius
self.pointers = tuple(z * self.radius for z in pointers) # Pointer lengths
self.angles = [None for _ in pointers]
self.set_color() # set fg color
ticklen = 0.1 * radius
for tick in range(ticks):
theta = 2 * tick * math.pi / ticks
x_start = int(self.xorigin + radius * math.sin(theta))
y_start = int(self.yorigin - radius * math.cos(theta))
x_end = int(self.xorigin + (radius - ticklen) * math.sin(theta))
y_end = int(self.yorigin - (radius - ticklen) * math.cos(theta))
self.tft.drawLine(x_start, y_start, x_end, y_end)
tft.drawCircle(self.xorigin, self.yorigin, radius)
self.restore_color()
def show(self, angle, pointer=0):
tft = self.tft
if self.angles[pointer] is not None:
self.set_color(self.bgcolor)
self.drawpointer(self.angles[pointer], pointer) # erase old
self.set_color()
self.drawpointer(angle, pointer) # draw new
self.angles[pointer] = angle # update old
self.restore_color()
def drawpointer(self, radians, pointer):
length = self.pointers[pointer]
x_end = int(self.xorigin + length * math.sin(radians))
y_end = int(self.yorigin - length * math.cos(radians))
self.tft.drawLine(int(self.xorigin), int(self.yorigin), x_end, y_end)
class LED(NoTouch):
def __init__(self, tft, location, *, border=None, height=30, fgcolor=None, bgcolor=None, color=RED):
super().__init__(tft, location, None, height, height, fgcolor, bgcolor, None, border)
self.radius = (self.height - 2 * self.border) / 2
self.x = location[0] + self.radius + self.border
self.y = location[1] + self.radius + self.border
self.color = color
self.off()
def _show(self, color): # Light the LED
self.set_color(color)
self.tft.fillCircle(int(self.x), int(self.y), int(self.radius))
self.set_color()
self.tft.drawCircle(int(self.x), int(self.y), int(self.radius))
self.restore_color()
def on(self, color=None): # Light in current color
if color is not None:
self.color = color
self._show(self.color)
def off(self):
self._show(BLACK)
class Meter(NoTouch):
def __init__(self, tft, location, *, font=None, height=200, width=30,
fgcolor=None, bgcolor=None, pointercolor=None, fontcolor=None,
divisions=10, legends=None, value=0):
border = 5 if font is None else 1 + font.bits_vert / 2
NoTouch.__init__(self, tft, location, font, height, width, fgcolor, bgcolor, fontcolor, border) # __super__ provoked Python bug
border = self.border # border width
self.ptrbytes = 3 * (self.width + 1) # 3 bytes per pixel
self.ptrbuf = bytearray(self.ptrbytes) #???
self.x0 = self.location[0]
self.x1 = self.location[0] + self.width
self.y0 = self.location[1] + border + 2
self.y1 = self.location[1] + self.height - border
self.divisions = divisions
self.legends = legends
self.pointercolor = pointercolor if pointercolor is not None else fgcolor
self._value = value
self._old_value = -1 # invalidate
self.ptr_y = -1 # Invalidate old position
self.show()
def show(self):
tft = self.tft
bw = self.draw_border() # and background if required. Result is width of border
width = self.width
dx = 5
self.set_color()
x0 = self.x0
x1 = self.x1
y0 = self.y0
y1 = self.y1
height = y1 - y0
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
ypos = int(y0 + dy * tick)
tft.drawHLine(x0, ypos, dx)
tft.drawHLine(x1 - dx, ypos, dx)
if self.legends is not None and self.font is not None: # Legends
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if len(self.legends) <= 1:
dy = 0
else:
dy = height / (len(self.legends) -1)
yl = self.y1 # Start at bottom
for legend in self.legends:
print_centered(tft, int(self.x0 + self.width /2), int(yl), legend, self.fontcolor, self.font)
yl -= dy
y0 = self.ptr_y
y1 = y0
if self.ptr_y >= 0: # Restore background
tft.setXY(x0, y0, x1, y1)
TFT_io.tft_write_data_AS(self.ptrbuf, self.ptrbytes)
ptrpos = int(self.y1 - self._value * height)
y0 = ptrpos
y1 = ptrpos
tft.setXY(x0, y0, x1, y1) # Read background
TFT_io.tft_read_cmd_data_AS(0x2e, self.ptrbuf, self.ptrbytes)
self.ptr_y = y0
self.set_color(self.pointercolor)
tft.drawHLine(x0, y0, width) # Draw pointer
self.restore_color()
def value(self, val=None):
if val is None:
return self._value
self._value = min(max(val, 0.0), 1.0)
if self._value != self._old_value:
self._old_value = self._value
self.show()

124
tft_gui/hst.py 100644
Wyświetl plik

@ -0,0 +1,124 @@
# hst.py Demo/test for Horizontal Slider class for Pyboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from font10 import font10
from tft import TFT, LANDSCAPE
from usched import Sched
from touch import TOUCH
from slider import HorizSlider
from button import Button
from displays import Dial, Label, LED, Meter
from ui import CLIPPED_RECT, GREEN, RED, YELLOW, WHITE, BLUE
import pyb
# CALLBACKS
# cb_end occurs when user stops touching the control
def callback(slider, args):
print('{} returned {}'.format(args[0], slider.value()))
def to_string(val):
return '{:3.1f} ohms'.format(val * 10)
def master_moved(slider, args):
val = slider.value()
slave1 = args[0]
slave1.value(val)
slave2 = args[1]
slave2.value(val)
label = args[2]
label.show(to_string(val))
led = args[3]
if val > 0.8:
led.on()
else:
led.off()
# Either slave has had its slider moved (by user or by having value altered)
def slave_moved(slider, args):
val = slider.value()
if val > 0.8:
slider.fgcolor = RED
else:
slider.fgcolor = GREEN
label = args[0]
label.show(to_string(val))
def doquit(button, args):
button.objsched.stop()
# USER TEST FUNCTION
# Common args for the labels
labels = { 'width' : 70,
'fontcolor' : WHITE,
'border' : 2,
'fgcolor' : RED,
'bgcolor' : (0, 40, 0),
}
# '0', '1','2','3','4','5','6','7','8','9','10'
# Common arguments for all three sliders
table = {'fontcolor' : WHITE,
'legends' : ('0', '5', '10'),
'cb_end' : callback,
}
# 'border' : 2,
def testmeter(meter):
oldvalue = 0
yield
while True:
val = pyb.rng()/2**30
steps = 20
delta = (val - oldvalue) / steps
for _ in range(steps):
oldvalue += delta
meter.value(oldvalue)
yield 0.05
def test(duration = 0):
print('Test TFT panel...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched, confidence = 50) #, calibration = (-3886,-0.1287,-3812,-0.132,-3797,-0.07685,-3798,-0.07681))
mytft.backlight(100) # light on
led = LED(mytft, (420, 0), border = 2)
meter1 = Meter(mytft, (320, 0), font=font10, legends=('0','5','10'), pointercolor = YELLOW, fgcolor = GREEN)
meter2 = Meter(mytft, (360, 0), font=font10, legends=('0','5','10'), pointercolor = YELLOW)
Button(objsched, mytft, mytouch, (420, 240), font = font10, callback = doquit, fgcolor = RED,
height = 30, text = 'Quit', shape = CLIPPED_RECT)
x = 230
lstlbl = []
for n in range(3):
lstlbl.append(Label(mytft, (x, 40 + 60 * n), font = font10, **labels))
x = 0
slave1 = HorizSlider(objsched, mytft, mytouch, (x, 100), font10,
fgcolor = GREEN, cbe_args = ('Slave1',), cb_move = slave_moved, cbm_args = (lstlbl[1],), **table)
slave2 = HorizSlider(objsched, mytft, mytouch, (x, 160), font10,
fgcolor = GREEN, cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = (lstlbl[2],), **table)
master = HorizSlider(objsched, mytft, mytouch, (x, 40), font10,
fgcolor = YELLOW, cbe_args = ('Master',), cb_move = master_moved, slidecolor=RED, cbm_args = (slave1, slave2, lstlbl[0], led), value=0.5, **table)
objsched.add_thread(testmeter(meter1))
objsched.add_thread(testmeter(meter2))
objsched.run() # Run it!
test()

Wyświetl plik

@ -1,146 +1,231 @@
# slider.py
# slider.py Vertical and horizontal slider control classes for Pyboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# A slider's text items lie outside its bounding box (area sensitive to touch)
from ui import touchable
from TFT_io import TFT_io
class Slider(touchable):
from ui import Touchable, get_stringsize
import TFT_io
class Slider(Touchable):
def __init__(self, objsched, tft, objtouch, location, font, *, height=200, width=30, divisions=10, legends=None,
fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, cb_end=lambda x, y : None,
cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], to_string=lambda x : str(x), value=0.0):
super().__init__(objsched, objtouch)
self.objsched = objsched
self.tft = tft
self.location = location
self.height = height
self.width = width
fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None,
cb_end=lambda x, y : None, cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], value=0.0):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
self.divisions = divisions
self.legends = legends
self.fgcolor = fgcolor
self.bgcolor = bgcolor
self.fontcolor = fontcolor if fontcolor is not None else tft.getColor()
self.font = font
self.slidecolor = slidecolor
self.cb_end = cb_end
self.cbe_args = cbe_args
self.cb_move = cb_move
self.cbm_args = cbm_args
self.to_string = to_string # Applied to display at bottom: user converts 0-1.0 to string with any scaling applied
self.was_touched = False
self.old_text_end = False
self.slidewidth = int(width / 1.3)
self.slidewidth += self.slidewidth % 2 # Ensure divisible by 2
slidewidth = int(width / 1.3) & 0xfe # Ensure divisible by 2
self.slideheight = 6 # must be divisible by 2
# We draw an odd number of pixels:
self.slidebytes = (self.slideheight + 1) * (self.slidewidth + 1) * 3
self.slidebytes = (self.slideheight + 1) * (slidewidth + 1) * 3
self.slidebuf = bytearray(self.slidebytes)
self.slide_x = -1
self._old_value = -1 # Invalidate
b = self.border
self.pot_dimension = self.height - 2 * (b + self.slideheight // 2)
width = self.width - 2 * b
xcentre = self.location[0] + b + width // 2
self.slide_x0 = xcentre - slidewidth // 2
self.slide_x1 = xcentre + slidewidth // 2 # slide X coordinates
self.slide_y = -1 # Invalidate old position
self.border_y = min(self.slideheight // 2, 10) # Allow space above and below slot
self._value = min(max(value, 0.0), 1.0) # User supplies 0-1.0
self.show()
objsched.add_thread(self.mainthread())
self.value(value)
def show(self):
tft = self.tft
fgcolor = tft.getColor() # save old colors
bgcolor = tft.getBGColor()
mybgcolor = bgcolor
x = self.location[0]
y = self.location[1] + self.border_y
if self.bgcolor is not None:
tft.setColor(self.bgcolor)
else:
tft.setColor(bgcolor)
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if self.fgcolor is not None:
tft.setColor(self.fgcolor)
else:
tft.setColor(fgcolor)
if self.bgcolor is not None:
mybgcolor = self.bgcolor
tft.setBGColor(mybgcolor)
height = self.height
width = self.width
dx = width // 3
xcentre = x + width // 2
bw = self.draw_border() # and background if required. Result is width of border
x = self.location[0] + bw
y = self.location[1] + bw + self.slideheight // 2 # Allow space above and below slot
width = self.width - 2 * bw
self.set_color()
height = self.pot_dimension # Height of slot
dx = width / 3
tft.drawRectangle(x + dx, y, x + 2 * dx, y + height)
if self.divisions > 0:
dy = height // self.divisions # Tick marks
ytick = y
fhdelta = self.font.bits_vert // 2
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
tft.drawHLine(x, ytick, dx)
tft.drawHLine(x + 2 * dx, ytick, dx)
ytick += dy
ypos = int(y + dy * tick)
tft.drawHLine(x + 1, ypos, dx)
tft.drawHLine(x + 1 + 2 * dx, ypos, dx)
if self.legends is not None: # Legends
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if len(self.legends) <= 1:
dy = 0
else:
dy = height // (len(self.legends) -1)
dy = height / (len(self.legends) -1)
yl = y + height # Start at bottom
fhdelta = self.font.bits_vert / 2
for legend in self.legends:
tft.setTextPos(x + width, yl - fhdelta)
tft.setTextPos(x + self.width, int(yl - fhdelta))
tft.printString(legend)
yl -= dy
sw = self.slidewidth # Handle slider
sh = self.slideheight
sliderpos = int(y + height - self._value * height)
if self.slidecolor is not None:
tft.setColor(self.slidecolor)
if self.slide_x >= 0: # Restore background
tft.setXY(self.slide_x, self.slide_y, self.slide_x + sw, self.slide_y + sh)
sh = self.slideheight # Handle slider
x0 = self.slide_x0
y0 = self.slide_y
x1 = self.slide_x1
y1 = y0 + sh
if self.slide_y >= 0: # Restore background
tft.setXY(x0, y0, x1, y1)
TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes)
x0 = xcentre - sw // 2
sliderpos = int(y + height - self._value * height)
y0 = sliderpos - sh // 2
x1 = xcentre + sw // 2
y1 = sliderpos + sh // 2
tft.setXY(x0, y0, x1, y1) # Read background
TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes)
self.slide_x = x0
self.slide_y = y0
if self.slidecolor is not None:
self.set_color(self.slidecolor)
tft.fillRectangle(x0, y0, x1, y1) # Draw slider
textx = x
texty = y + height + 2 * self.border_y
tft.setTextPos(textx, texty)
if self.old_text_end:
tft.setColor(mybgcolor)
tft.fillRectangle(textx, texty, self.old_text_end, texty + self.font.bits_vert)
tft.printString(self.to_string(self._value))
self.old_text_end = tft.getTextPos()[0]
tft.setColor(fgcolor) # restore them
tft.setBGColor(bgcolor)
self.restore_color()
def value(self, val=None):
if val is None:
return self._value
self._value = min(max(val, 0.0), 1.0)
self.show()
if self._value != self._old_value:
self._old_value = self._value
self.cb_move(self, self.cbm_args) # Callback not a bound method so pass self
self.show()
def touched(self, x, y): # If touched in bounding box, process it otherwise do nothing
x0 = self.location[0]
x1 = self.location[0] + self.width
y0 = self.location[1]
y1 = self.location[1] + self.height
y1 = self.location[1] + self.height
if x0 <= x <= x1 and y0 <= y <= y1:
self.was_touched = True
self._value = (y1 - y) / self.height
self.value((y1 - y) / self.pot_dimension)
def untouched(self): # User has released touchpad or touched elsewhere
if self.was_touched:
self.cb_end(self, self.cbe_args) # Callback not a bound method so pass self
self.was_touched = False
def mainthread(self):
old_value = self._value
while True:
yield
val = self._value
if val != old_value:
old_value = val
self.cb_move(self, self.cbm_args) # Callback not a bound method so pass self
self.show()
class HorizSlider(Touchable):
def __init__(self, objsched, tft, objtouch, location, font, *, height=30, width=200, divisions=10, legends=None,
fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None,
cb_end=lambda x, y : None, cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], value=0.0):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
self.divisions = divisions
self.legends = legends
self.slidecolor = slidecolor
self.cb_end = cb_end
self.cbe_args = cbe_args
self.cb_move = cb_move
self.cbm_args = cbm_args
self.was_touched = False
slideheight = int(height / 1.3) & 0xfe # Ensure divisible by 2
self.slidewidth = 6 # must be divisible by 2
# We draw an odd number of pixels:
self.slidebytes = (slideheight + 1) * (self.slidewidth + 1) * 3
self.slidebuf = bytearray(self.slidebytes)
self._old_value = -1 # Invalidate
b = self.border
self.pot_dimension = self.width - 2 * (b + self.slidewidth // 2)
height = self.height - 2 * b
ycentre = self.location[1] + b + height // 2
self.slide_y0 = ycentre - slideheight // 2
self.slide_y1 = ycentre + slideheight // 2 # slide Y coordinates
self.slide_x = -1 # Invalidate old position
self.value(value)
def show(self):
tft = self.tft
bw = self.draw_border() # and background if required. Result is width of border
x = self.location[0] + bw + self.slidewidth // 2 # Allow space left and right slot for slider at extremes
y = self.location[1] + bw
height = self.height - 2 * bw
self.set_color()
width = self.pot_dimension # Length of slot
dy = height / 3
ycentre = y + height // 2
tft.drawRectangle(x, y + dy, x + width, y + 2 * dy)
if self.divisions > 0:
dx = width / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
xpos = int(x + dx * tick)
tft.drawVLine(xpos, y + 1, dy) # TODO Why is +1 fiddle required here?
tft.drawVLine(xpos, y + 1 + 2 * dy, dy) # and here
if self.legends is not None: # Legends
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if len(self.legends) <= 1:
dx = 0
else:
dx = width / (len(self.legends) -1)
xl = x
for legend in self.legends:
offset = get_stringsize(legend, self.font)[0] / 2
tft.setTextPos(int(xl - offset), y - self.font.bits_vert) # Arbitrary left shift should be char width /2
tft.printString(legend)
xl += dx
sw = self.slidewidth # Handle slider
x0 = self.slide_x
y0 = self.slide_y0
x1 = x0 + sw
y1 = self.slide_y1
if self.slide_x >= 0: # Restore background
tft.setXY(x0, y0, x1, y1)
TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes)
sliderpos = int(x + self._value * width)
x0 = sliderpos - sw // 2
x1 = sliderpos + sw // 2
tft.setXY(x0, y0, x1, y1) # Read background
TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes)
self.slide_x = x0
if self.slidecolor is not None:
self.set_color(self.slidecolor)
tft.fillRectangle(x0, y0, x1, y1) # Draw slider
self.restore_color()
def value(self, val=None):
if val is None:
return self._value
self._value = min(max(val, 0.0), 1.0)
if self._value != self._old_value:
self._old_value = self._value
self.cb_move(self, self.cbm_args) # Callback not a bound method so pass self
self.show()
def touched(self, x, y): # If touched in bounding box, process it otherwise do nothing
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:
self.was_touched = True
self.value((x - x0) / self.pot_dimension)
def untouched(self): # User has released touchpad or touched elsewhere
if self.was_touched:
self.cb_end(self, self.cbe_args) # Callback not a bound method so pass self
self.was_touched = False

Wyświetl plik

@ -1,52 +1,36 @@
# slidetest.py Demo/test program for vertical slider class for Pyboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import gc
from font10 import font10
from tft import TFT, LANDSCAPE
from usched import Sched
from touch import TOUCH
from slider import Slider
from button import Button
from ui import CLIPPED_RECT
import math
#gc.collect()
class Dial(object):
def __init__(self, objsched, tft, x, y, r, l):
self.objsched = objsched
self.tft = tft
self.xorigin = x
self.yorigin = y
self.radius = r
self.pointerlen = l
self.angle = None
self.delta = 1
tft.drawCircle(x, y, r)
objsched.add_thread(self.mainthread())
def update(self, angle):
tft = self.tft
fgcolor = tft.getColor() # save old colors
bgcolor = tft.getBGColor()
if self.angle is not None:
tft.setColor(bgcolor)
self.drawpointer(self.angle) # erase old
tft.setColor(fgcolor)
self.drawpointer(angle) # draw new
self.angle = angle # update old
tft.setColor(fgcolor) # restore them
tft.setBGColor(bgcolor)
def drawpointer(self, radians):
x_end = int(self.xorigin + self.pointerlen * math.sin(radians))
y_end = int(self.yorigin - self.pointerlen * math.cos(radians))
self.tft.drawLine(self.xorigin, self.yorigin, x_end, y_end)
def mainthread(self):
while True:
yield 0.1
angle = self.angle if self.angle is not None else 0
angle += math.pi * 2 * self.delta / 10
self.update(angle)
from displays import Dial, Label
from ui import CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
from math import pi
# CALLBACKS
# cb_end occurs when user stops touching the control
@ -54,7 +38,7 @@ def callback(slider, args):
print('{} returned {}'.format(args[0], slider.value()))
def to_string(val):
return '{:4.1f}ohms'.format(val * 10)
return '{:3.1f} ohms'.format(val * 10)
def master_moved(slider, args):
val = slider.value()
@ -62,41 +46,70 @@ def master_moved(slider, args):
slave1.value(val)
slave2 = args[1]
slave2.value(val)
label = args[2]
label.show(to_string(val))
# Either slave has had its slider moved (by user or by having value altered)
def slave_moved(slider, args):
val = slider.value()
dial = args[0]
dial.delta = slider.value()
dial.delta = val
label = args[1]
label.show(to_string(val))
def doquit(button, args):
button.objsched.stop()
# USER TEST FUNCTION
# THREADS
def mainthread(slider, dial):
angle = 0
yield
while True:
yield 0.1
delta = slider.value()
angle += pi * 2 * delta / 10
dial.show(angle)
dial.show(angle /10, 1)
# DATA
# Common args for the labels
labels = { 'width' : 70,
'fontcolor' : WHITE,
'border' : 2,
'fgcolor' : RED,
'bgcolor' : (0, 40, 0),
}
# '0', '1','2','3','4','5','6','7','8','9','10'
# Common arguments for all three sliders
table = {'fontcolor' : (255, 255, 255),
table = {'fontcolor' : WHITE,
'legends' : ('0', '5', '10'),
'to_string' : to_string,
'cb_end' : callback,
'value' : 0.5}
}
# 'border' : 2,
def test(duration = 0):
print('Test TFT panel...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched)
mytouch = TOUCH("XPT2046", objsched, confidence=50)
mytft.backlight(100) # light on
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = (255, 0, 0),
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = RED,
height = 30, text = 'Quit', shape = CLIPPED_RECT)
dial1 = Dial(objsched, mytft, 350, 60, 50, 48)
dial2 = Dial(objsched, mytft, 350, 170, 50, 48)
dial1 = Dial(mytft, (350, 10), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7))
dial2 = Dial(mytft, (350, 120), fgcolor = YELLOW, bgcolor = GREY, border = 2, pointers = (0.9, 0.7))
lstlbl = []
for n in range(3):
lstlbl.append(Label(mytft, (80 * n, 240), font = font10, **labels))
y = 5
slave1 = Slider(objsched, mytft, mytouch, (80, y), font10,
fgcolor = (0, 255, 0), cbe_args = ('Slave1',), cb_move = slave_moved, cbm_args = (dial1,), **table)
fgcolor = (0, 255, 0), cbe_args = ('Slave1',), cb_move = slave_moved, cbm_args = (dial1, lstlbl[1]), **table)
slave2 = Slider(objsched, mytft, mytouch, (160, y), font10,
fgcolor = (0, 255, 0), cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = (dial2,), **table)
Slider(objsched, mytft, mytouch, (0, y), font10,
fgcolor = (255, 255, 0), cbe_args = ('Master',), cb_move = master_moved, cbm_args = (slave1, slave2), **table)
fgcolor = (0, 255, 0), cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = (dial2, lstlbl[2]), **table)
master = Slider(objsched, mytft, mytouch, (0, y), font10,
fgcolor = (255, 255, 0), cbe_args = ('Master',), cb_move = master_moved, cbm_args = (slave1, slave2, lstlbl[0]), value=0.5, **table)
objsched.add_thread(mainthread(slave1, dial1))
objsched.add_thread(mainthread(slave2, dial2))
objsched.run() # Run it!
test()

Wyświetl plik

@ -1,8 +1,37 @@
# ui.py Base classes and utilities for TFT GUI
# ui.py Constants, base classes and utilities for Pybboard TFT GUI
# The MIT License (MIT)
#
# Copyright (c) 2016 Peter Hinch
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
CIRCLE = 1
RECTANGLE = 2
CLIPPED_RECT = 3
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
GREY = (100, 100, 100)
def get_stringsize(s, font):
hor = 0
@ -17,8 +46,51 @@ def print_centered(tft, x, y, s, color, font):
tft.setTextPos(x - length // 2, y - height // 2)
tft.printString(s)
# Base class for all displayable objects
class NoTouch(object):
old_color = None
def __init__(self, tft, location, font, height, width, fgcolor, bgcolor, fontcolor, border):
self.tft = tft
self.location = location
self.font = font
self.height = height
self.width = width
self.fill = bgcolor is not None
self.fgcolor = fgcolor if fgcolor is not None else tft.getColor()
self.bgcolor = bgcolor if bgcolor is not None else tft.getBGColor()
self.fontcolor = fontcolor if fontcolor is not None else tft.getColor()
self.hasborder = border is not None
self.border = 0 if border is None else border
if NoTouch.old_color is None:
NoTouch.old_color = tft.getColor()
if height is not None and width is not None: # beware special cases where height and width not yet known
self.draw_border()
def draw_border(self): # Draw background and bounding box if required
tft = self.tft
fgcolor = tft.getColor()
x = self.location[0]
y = self.location[1]
if self.fill:
tft.setColor(self.bgcolor)
tft.fillRectangle(x, y, x + self.width, y + self.height)
bw = 0 # border width
if self.hasborder: # Draw a bounding box
bw = self.border
tft.setColor(self.fgcolor)
tft.drawRectangle(x, y, x + self.width, y + self.height)
tft.setColor(fgcolor)
return bw # Actual width (may be 0)
def set_color(self, color=None):
new = self.fgcolor if color is None else color
self.tft.setColor(new)
def restore_color(self): # Restore to system default
self.tft.setColor(NoTouch.old_color)
# Base class for touch-enabled classes.
class touchable(object):
class Touchable(NoTouch):
touchlist = []
objtouch = None
@ -36,9 +108,11 @@ class touchable(object):
for obj in cls.touchlist:
obj.untouched()
def __init__(self, objsched, objtouch):
touchable.touchlist.append(self)
def __init__(self, objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border):
super().__init__(tft, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
Touchable.touchlist.append(self)
self.enabled = True # Available to user/subclass
if touchable.objtouch is None: # Initialising class and thread
touchable.objtouch = objtouch
self.objsched = objsched
if Touchable.objtouch is None: # Initialising class and thread
Touchable.objtouch = objtouch
objsched.add_thread(self.touchtest()) # One thread only