kopia lustrzana https://github.com/peterhinch/micropython-samples
TFT GUI version 0.1
rodzic
d2f4e9e79b
commit
ceda9894b4
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue