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 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. # 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 # likewise width and height refer to BB, regardless of button shape
# If font is None button will be rendered without text
class Button(touchable): class Button(Touchable):
def __init__(self, objsched, tft, objtouch, location, *, shape=CIRCLE, height=50, width=50, fill=True, 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='', font=None, show=True, callback=lambda x : None, fgcolor=None, bgcolor=None, fontcolor=None, litcolor=None, text='', show=True, callback=lambda x, y : None,
args=[]): args=[]):
super().__init__(objsched, objtouch) super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, None)
self.objsched = objsched
self.tft = tft
self.location = location
self.shape = shape self.shape = shape
self.height = height
self.width = width
self.radius = height // 2 self.radius = height // 2
self.fill = fill 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.litcolor = litcolor
self.text = text self.text = text
self.callback = callback self.callback = callback
@ -35,19 +52,14 @@ class Button(touchable):
def show(self): def show(self):
tft = self.tft tft = self.tft
fgcolor = tft.getColor() # save old colors
bgcolor = tft.getBGColor()
x = self.location[0] x = self.location[0]
y = self.location[1] y = self.location[1]
if not self.visible: # erase the button 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.fillRectangle(x, y, x + self.width, y + self.height)
tft.setColor(fgcolor) self.restore_color()
return return
if self.fgcolor is not None: self.set_color() # to foreground
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 if self.shape == CIRCLE: # Button coords are of top left corner of bounding box
x += self.radius x += self.radius
y += self.radius y += self.radius
@ -74,8 +86,7 @@ class Button(touchable):
tft.drawClippedRectangle(x, y, x1, y1) tft.drawClippedRectangle(x, y, x1, y1)
if self.font is not None and len(self.text): 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) print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font)
tft.setColor(fgcolor) # restore them self.restore_color()
tft.setBGColor(bgcolor)
def shownormal(self): def shownormal(self):
self.fgcolor = self.orig_fgcolor self.fgcolor = self.orig_fgcolor
@ -169,3 +180,52 @@ class RadioButtons(Buttons):
but.fgcolor = but.orig_fgcolor but.fgcolor = but.orig_fgcolor
but.show() but.show()
self.user_callback(button, args) # user gets button with args they specified 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 font14 import font14
from tft import TFT, LANDSCAPE from tft import TFT, LANDSCAPE
from usched import Sched from usched import Sched
from touch import TOUCH from touch import TOUCH
from button import Button, Buttonset, RadioButtons from button import Button, Buttonset, RadioButtons, Checkbox
from ui import CIRCLE, RECTANGLE, CLIPPED_RECT from ui import CIRCLE, RECTANGLE, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
#gc.collect() from displays import Label
def callback(button, args): def callback(button, args):
arg = args[0] arg = args[0]
print('Returned: ', arg) label = args[1]
tft = button.tft label.show(arg)
tft.setTextPos(0, 240)
tft.setTextStyle(None, None, 0, font14)
tft.printString('Button argument zero: {} '.format(arg))
if arg == 'Q': if arg == 'Q':
button.objsched.stop() 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 # These tables contain args that differ between members of a set of related buttons
table = [ table = [
{'fgcolor' : (0, 128, 0), 'litcolor' : (0, 255, 0), 'text' : 'Yes', 'args' : ('A'), 'fontcolor' : (0, 0, 0)}, {'fgcolor' : GREEN, 'text' : 'Yes', 'args' : ['A'], 'fontcolor' : (0, 0, 0)},
{'fgcolor' : (255, 0, 0), 'text' : 'No', 'args' : ('B')}, {'fgcolor' : RED, 'text' : 'No', 'args' : ['B']},
{'fgcolor' : (0, 0, 255), 'text' : '???', 'args' : ('C'), 'fill': False}, {'fgcolor' : BLUE, 'text' : '???', 'args' : ['C'], 'fill': False},
{'fgcolor' : (128, 128, 128), 'text' : 'Quit', 'args' : ('Q'), 'shape' : CLIPPED_RECT}, {'fgcolor' : GREY, 'text' : 'Quit', 'args' : ['Q'], 'shape' : CLIPPED_RECT},
] ]
# similar buttons: only tabulate data that varies # similar buttons: only tabulate data that varies
table2 = [ table2 = [
{'text' : 'P', 'args' : ('p')}, {'text' : 'P', 'args' : ['p']},
{'text' : 'Q', 'args' : ('q')}, {'text' : 'Q', 'args' : ['q']},
{'text' : 'R', 'args' : ('r')}, {'text' : 'R', 'args' : ['r']},
{'text' : 'S', 'args' : ('s')}, {'text' : 'S', 'args' : ['s']},
] ]
# A Buttonset with two entries # A Buttonset with two entries
# If buttons to be used in a buttonset, Use list rather than tuple for args because buttonset appends. # If buttons to be used in a buttonset, Use list rather than tuple for args because buttonset appends.
table3 = [ table3 = [
{'fgcolor' : (0, 255, 0), 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']}, {'fgcolor' : GREEN, 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']},
{'fgcolor' : (255, 0, 0), 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']}, {'fgcolor' : RED, 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']},
] ]
table4 = [ table4 = [
{'text' : '1', 'args' : ('1')}, {'text' : '1', 'args' : ['1']},
{'text' : '2', 'args' : ('2')}, {'text' : '2', 'args' : ['2']},
{'text' : '3', 'args' : ('3')}, {'text' : '3', 'args' : ['3']},
{'text' : '4', 'args' : ('4')}, {'text' : '4', 'args' : ['4']},
] ]
#gc.collect() labels = { 'width' : 70,
# THREADS 'fontcolor' : WHITE,
'border' : 2,
def stop(fTim, objsched): # Stop the scheduler after fTim seconds 'fgcolor' : RED,
yield fTim 'bgcolor' : (0, 40, 0),
objsched.stop() 'font' : font14,
}
# USER TEST FUNCTION # USER TEST FUNCTION
def test(duration = 0): def test():
if duration:
print("Test TFT panel for {:3d} seconds".format(duration))
else:
print('Testing TFT...') print('Testing TFT...')
objsched = Sched() # Instantiate the scheduler objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE) mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched) mytouch = TOUCH("XPT2046", objsched)
mytft.backlight(100) # light on mytft.backlight(100) # light on
lstlbl = []
for n in range(3):
lstlbl.append(Label(mytft, (350, 50 * n), **labels))
# Button assortment # Button assortment
x = 50 x = 0
for t in table: for t in table:
t['args'].append(lstlbl[2])
Button(objsched, mytft, mytouch, (x, 0), font = font14, callback = callback, **t) Button(objsched, mytft, mytouch, (x, 0), font = font14, callback = callback, **t)
x += 70 x += 70
# Highlighting buttons # Highlighting buttons
x = 50 x = 0
for t in table2: for t in table2:
Button(objsched, mytft, mytouch, (x, 60), font = font14, fgcolor = (128, 128, 128), t['args'].append(lstlbl[2])
fontcolor = (0, 0, 0), litcolor = (255, 255, 255), callback = callback, **t) Button(objsched, mytft, mytouch, (x, 60), fgcolor = GREY,
fontcolor = BLACK, litcolor = WHITE, font = font14, callback = callback, **t)
x += 70 x += 70
# On/Off toggle # On/Off toggle
x = 50 x = 0
bs = Buttonset(callback) bs = Buttonset(callback)
for t in table3: # Buttons overlay each other at same location 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() bs.run()
# Radio buttons # Radio buttons
x = 50 x = 0
rb = RadioButtons(callback, (0, 0, 255)) # color of selected button rb = RadioButtons(callback, BLUE) # color of selected button
for t in table4: 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) fgcolor = (0, 0, 90), height = 30, **t)
x += 40 x += 40
rb.run() rb.run()
# Start scheduler # Checkbox
if duration: Checkbox(objsched, mytft, mytouch, (300, 0), callback = cbcb, args = [lstlbl[0]])
objsched.add_thread(stop(duration, objsched)) # Commit suicide after specified no. of seconds Checkbox(objsched, mytft, mytouch, (300, 50), fillcolor = RED, callback = cbcb, args = [lstlbl[1]])
objsched.run() # Run it! 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,124 +1,117 @@
# 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) # A slider's text items lie outside its bounding box (area sensitive to touch)
from ui import touchable from ui import Touchable, get_stringsize
from TFT_io import TFT_io import TFT_io
class Slider(touchable):
class Slider(Touchable):
def __init__(self, objsched, tft, objtouch, location, font, *, height=200, width=30, divisions=10, legends=None, 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, fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None,
cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], to_string=lambda x : str(x), value=0.0): cb_end=lambda x, y : None, cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], value=0.0):
super().__init__(objsched, objtouch) super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
self.objsched = objsched
self.tft = tft
self.location = location
self.height = height
self.width = width
self.divisions = divisions self.divisions = divisions
self.legends = legends 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.slidecolor = slidecolor
self.cb_end = cb_end self.cb_end = cb_end
self.cbe_args = cbe_args self.cbe_args = cbe_args
self.cb_move = cb_move self.cb_move = cb_move
self.cbm_args = cbm_args 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.was_touched = False
self.old_text_end = False slidewidth = int(width / 1.3) & 0xfe # Ensure divisible by 2
self.slidewidth = int(width / 1.3)
self.slidewidth += self.slidewidth % 2 # Ensure divisible by 2
self.slideheight = 6 # must be divisible by 2 self.slideheight = 6 # must be divisible by 2
# We draw an odd number of pixels: # 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.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.slide_y = -1 # Invalidate old position
self.border_y = min(self.slideheight // 2, 10) # Allow space above and below slot self.value(value)
self._value = min(max(value, 0.0), 1.0) # User supplies 0-1.0
self.show()
objsched.add_thread(self.mainthread())
def show(self): def show(self):
tft = self.tft tft = self.tft
fgcolor = tft.getColor() # save old colors bw = self.draw_border() # and background if required. Result is width of border
bgcolor = tft.getBGColor() x = self.location[0] + bw
mybgcolor = bgcolor y = self.location[1] + bw + self.slideheight // 2 # Allow space above and below slot
x = self.location[0] width = self.width - 2 * bw
y = self.location[1] + self.border_y self.set_color()
if self.bgcolor is not None: height = self.pot_dimension # Height of slot
tft.setColor(self.bgcolor) dx = width / 3
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
tft.drawRectangle(x + dx, y, x + 2 * dx, y + height) tft.drawRectangle(x + dx, y, x + 2 * dx, y + height)
if self.divisions > 0: if self.divisions > 0:
dy = height // self.divisions # Tick marks dy = height / (self.divisions) # Tick marks
ytick = y
fhdelta = self.font.bits_vert // 2
for tick in range(self.divisions + 1): for tick in range(self.divisions + 1):
tft.drawHLine(x, ytick, dx) ypos = int(y + dy * tick)
tft.drawHLine(x + 2 * dx, ytick, dx) tft.drawHLine(x + 1, ypos, dx)
ytick += dy tft.drawHLine(x + 1 + 2 * dx, ypos, dx)
if self.legends is not None: # Legends if self.legends is not None: # Legends
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if len(self.legends) <= 1: if len(self.legends) <= 1:
dy = 0 dy = 0
else: else:
dy = height // (len(self.legends) -1) dy = height / (len(self.legends) -1)
yl = y + height # Start at bottom yl = y + height # Start at bottom
fhdelta = self.font.bits_vert / 2
for legend in self.legends: for legend in self.legends:
tft.setTextPos(x + width, yl - fhdelta) tft.setTextPos(x + self.width, int(yl - fhdelta))
tft.printString(legend) tft.printString(legend)
yl -= dy yl -= dy
sw = self.slidewidth # Handle slider sh = self.slideheight # Handle slider
sh = self.slideheight x0 = self.slide_x0
sliderpos = int(y + height - self._value * height) y0 = self.slide_y
if self.slidecolor is not None: x1 = self.slide_x1
tft.setColor(self.slidecolor) y1 = y0 + sh
if self.slide_x >= 0: # Restore background if self.slide_y >= 0: # Restore background
tft.setXY(self.slide_x, self.slide_y, self.slide_x + sw, self.slide_y + sh) tft.setXY(x0, y0, x1, y1)
TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes) 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 y0 = sliderpos - sh // 2
x1 = xcentre + sw // 2
y1 = sliderpos + sh // 2 y1 = sliderpos + sh // 2
tft.setXY(x0, y0, x1, y1) # Read background tft.setXY(x0, y0, x1, y1) # Read background
TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes) TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes)
self.slide_x = x0
self.slide_y = y0 self.slide_y = y0
if self.slidecolor is not None:
self.set_color(self.slidecolor)
tft.fillRectangle(x0, y0, x1, y1) # Draw slider tft.fillRectangle(x0, y0, x1, y1) # Draw slider
self.restore_color()
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)
def value(self, val=None): def value(self, val=None):
if val is None: if val is None:
return self._value return self._value
self._value = min(max(val, 0.0), 1.0) 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() self.show()
def touched(self, x, y): # If touched in bounding box, process it otherwise do nothing def touched(self, x, y): # If touched in bounding box, process it otherwise do nothing
@ -128,19 +121,111 @@ class Slider(touchable):
y1 = self.location[1] + self.height y1 = self.location[1] + self.height
if x0 <= x <= x1 and y0 <= y <= y1: if x0 <= x <= x1 and y0 <= y <= y1:
self.was_touched = True 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 def untouched(self): # User has released touchpad or touched elsewhere
if self.was_touched: if self.was_touched:
self.cb_end(self, self.cbe_args) # Callback not a bound method so pass self self.cb_end(self, self.cbe_args) # Callback not a bound method so pass self
self.was_touched = False self.was_touched = False
def mainthread(self): class HorizSlider(Touchable):
old_value = self._value def __init__(self, objsched, tft, objtouch, location, font, *, height=30, width=200, divisions=10, legends=None,
while True: fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None,
yield cb_end=lambda x, y : None, cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], value=0.0):
val = self._value super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
if val != old_value: self.divisions = divisions
old_value = val 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.cb_move(self, self.cbm_args) # Callback not a bound method so pass self
self.show() 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 font10 import font10
from tft import TFT, LANDSCAPE from tft import TFT, LANDSCAPE
from usched import Sched from usched import Sched
from touch import TOUCH from touch import TOUCH
from slider import Slider from slider import Slider
from button import Button from button import Button
from ui import CLIPPED_RECT from displays import Dial, Label
import math from ui import CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
#gc.collect() from math import pi
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)
# CALLBACKS # CALLBACKS
# cb_end occurs when user stops touching the control # cb_end occurs when user stops touching the control
@ -54,7 +38,7 @@ def callback(slider, args):
print('{} returned {}'.format(args[0], slider.value())) print('{} returned {}'.format(args[0], slider.value()))
def to_string(val): def to_string(val):
return '{:4.1f}ohms'.format(val * 10) return '{:3.1f} ohms'.format(val * 10)
def master_moved(slider, args): def master_moved(slider, args):
val = slider.value() val = slider.value()
@ -62,41 +46,70 @@ def master_moved(slider, args):
slave1.value(val) slave1.value(val)
slave2 = args[1] slave2 = args[1]
slave2.value(val) 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) # Either slave has had its slider moved (by user or by having value altered)
def slave_moved(slider, args): def slave_moved(slider, args):
val = slider.value()
dial = args[0] dial = args[0]
dial.delta = slider.value() dial.delta = val
label = args[1]
label.show(to_string(val))
def doquit(button, args): def doquit(button, args):
button.objsched.stop() 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' # '0', '1','2','3','4','5','6','7','8','9','10'
# Common arguments for all three sliders # Common arguments for all three sliders
table = {'fontcolor' : (255, 255, 255), table = {'fontcolor' : WHITE,
'legends' : ('0', '5', '10'), 'legends' : ('0', '5', '10'),
'to_string' : to_string,
'cb_end' : callback, 'cb_end' : callback,
'value' : 0.5} }
# 'border' : 2,
def test(duration = 0): def test(duration = 0):
print('Test TFT panel...') print('Test TFT panel...')
objsched = Sched() # Instantiate the scheduler objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE) mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched) mytouch = TOUCH("XPT2046", objsched, confidence=50)
mytft.backlight(100) # light on 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) height = 30, text = 'Quit', shape = CLIPPED_RECT)
dial1 = Dial(objsched, mytft, 350, 60, 50, 48) dial1 = Dial(mytft, (350, 10), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7))
dial2 = Dial(objsched, mytft, 350, 170, 50, 48) 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 y = 5
slave1 = Slider(objsched, mytft, mytouch, (80, y), font10, 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, slave2 = Slider(objsched, mytft, mytouch, (160, y), font10,
fgcolor = (0, 255, 0), cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = (dial2,), **table) fgcolor = (0, 255, 0), cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = (dial2, lstlbl[2]), **table)
Slider(objsched, mytft, mytouch, (0, y), font10, master = Slider(objsched, mytft, mytouch, (0, y), font10,
fgcolor = (255, 255, 0), cbe_args = ('Master',), cb_move = master_moved, cbm_args = (slave1, slave2), **table) 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! objsched.run() # Run it!
test() 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 CIRCLE = 1
RECTANGLE = 2 RECTANGLE = 2
CLIPPED_RECT = 3 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): def get_stringsize(s, font):
hor = 0 hor = 0
@ -17,8 +46,51 @@ def print_centered(tft, x, y, s, color, font):
tft.setTextPos(x - length // 2, y - height // 2) tft.setTextPos(x - length // 2, y - height // 2)
tft.printString(s) 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. # Base class for touch-enabled classes.
class touchable(object): class Touchable(NoTouch):
touchlist = [] touchlist = []
objtouch = None objtouch = None
@ -36,9 +108,11 @@ class touchable(object):
for obj in cls.touchlist: for obj in cls.touchlist:
obj.untouched() obj.untouched()
def __init__(self, objsched, objtouch): def __init__(self, objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border):
touchable.touchlist.append(self) super().__init__(tft, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
Touchable.touchlist.append(self)
self.enabled = True # Available to user/subclass self.enabled = True # Available to user/subclass
if touchable.objtouch is None: # Initialising class and thread self.objsched = objsched
touchable.objtouch = objtouch if Touchable.objtouch is None: # Initialising class and thread
Touchable.objtouch = objtouch
objsched.add_thread(self.touchtest()) # One thread only objsched.add_thread(self.touchtest()) # One thread only