ugui.py now includes all classes. Rotary knob now supported

pull/7/head
Peter Hinch 2016-04-30 08:51:19 +01:00
rodzic ceda9894b4
commit 84c7a5df4b
12 zmienionych plików z 1953 dodań i 1385 usunięć

150
tft_gui/README.md 100644
Wyświetl plik

@ -0,0 +1,150 @@
# micropython-gui
Provides a simple touch driven event based GUI interface for the Pyboard when used with a TFT
display. The latter should be based on SSD1963 controller with XPT2046 touch controller. Such
displays are available in electronics stores and on eBay. The software is based on drivers for the
TFT and touch controller from Robert Hammelrath together with a cooperative scheduler of my own
design.
It is targeted at hardware control and display applications.
# Pre requisites
[TFT driver](https://github.com/robert-hh/SSD1963-TFT-Library-for-PyBoard.git)
[XPT2046 driver](https://github.com/robert-hh/XPT2046-touch-pad-driver-for-PyBoard.git)
[Scheduler](https://github.com/peterhinch/Micropython-scheduler.git)
Core files:
1. TFT_io.py Low level TFT driver *
2. touch.py Touch controller driver *
3. tft.py TFT driver
4. usched.py Scheduler
5. delay.py Used with the scheduler for watchdog type delays.
6. ugui.py The micro GUI library.
Optional files:
1. font10.py Font used by the test programs.
2. font14.py Font used by the test programs.
Test/demo programs:
1. vst.py A test program for vertical linear sliders.
2. hst.py Tests horizontal slider controls, meters and LED.
3. buttontest.py Pushbuttons and checkboxes.
4. knobtest.py Rotary control test.
It should be noted that by the standards of the Pyboard this is a large library. Attempts to use it
in the normal way are likely to provoke memory errors owing to heap fragmentation. It is
recommended that the core and optional files are included with the firmware as persistent bytecode.
You may also want to include any other fonts you plan to use. The first two core files listed above
cannot be included as they use inline assembler. Instructions on how to do this may be found
[here](http://forum.micropython.org/viewtopic.php?f=6&t=1776).
It is also wise to issue ctrl-D to soft reset the Pyboard before importing a module which uses the
library. The test programs require a ctrl-D before import.
Instructions on creating font files may be found in the README for the TFT driver listed above.
# Concepts
### Coordinates
In common with most displays, the top left hand corner of the display is (0, 0) with increasing
values of x to the right, and increasing values of y downward. Display objects exist within a
rectangular bounding box; in the case of touch sensitive controls this corresponds to the sensitive
region. The location of the object is defined as the coordinates of the top left hand corner of the
bounding box. Locations are defined as a 2-tuple (x, y).
### Colours
These are defined as a 3-tuple (r, g, b) with values of red, green and blue in range 0 to 255. The
interface uses the American spelling (color) throughout for consistency with the TFT library.
### Callbacks
The interface is event driven. Optional callbacks may be provided which will be executed when a
given event occurs. A callback function receives positional arguments. The first is a reference to
the object raising the callback. Subsequent arguments are user defined, and are specified as a list
of items. Note that a list rather than a tuple should be used.
# Initialisation Code
# Displays
These classes provide ways to display data and are not touch sensitive.
## Class Label
Displays text in a fixed length field. Constructor mandatory positional arguments:
1. ``tft`` The TFT object.
2. ``location`` 2-tuple defining position.
Keyword only arguments:
1. ``font`` Mandatory. Font object to use.
2. ``width`` Mandatory. The width of the object in pixels.
3. ``border`` Border width in pixels - typically 2. If omitted, no border will be drawn.
4. ``fgcolor`` Color of border. Defaults to system color.
5. ``bgcolor`` Background color of object. Defaults to system background.
6. ``fontcolor`` Text color. Defaults to system text color.
7. ``text`` Initial text. Defaults to ''.
Method:
1. ``show`` Argument: ``text``. Displays the string in the label.
## Class Dial
Displays angles in a circular dial. Angles are in radians with zero represented by a vertical
pointer. Positive angles appear as clockwise rotation of the pointer. The object can display
multiple angles using pointers of differing lengths (e.g. clock face). Constructor mandatory
positional arguments:
1. ``tft`` The TFT object.
2. ``location`` 2-tuple defining position.
Keyword only arguments (all
1. ``height`` Dimension of the square bounding box. Default 100 pixels.
2. ``fgcolor`` Color of border. Defaults to system color.
3. ``bgcolor`` Background color of object. Defaults to system background.
4. ``border`` Border width in pixels - typically 2. If omitted, no border will be drawn.
5. ``pointers`` Tuple of floats in range 0 to 0.9. Defines the length of each pointer as a
proportion of the dial diameter. Default (0.9,) i.e. one pointer.
6. ``ticks`` Defines the number of graduations around the dial. Default 4.
Method:
1. ``show`` Displays an angle. Arguments: ``angle`` (mandatory), ``pointer`` the pointer index
(default 0).
## Class LED
Displays a boolean state. Can display other information by varying the color. Constructor mandatory
positional arguments:
1. ``tft`` The TFT object.
2. ``location`` 2-tuple defining position.
Keyword only arguments:
1. ``height`` Dimension of the square bounding box. Default 30 pixels.
2. ``fgcolor`` Color of border. Defaults to system color.
3. ``bgcolor`` Background color of object. Defaults to system background.
4. ``border`` Border width in pixels - typically 2. If omitted, no border will be drawn.
5. ``color`` The color of the LED.
Methods:
1. ``off`` No arguments. Turns the LED off.
2. ``on`` Optional arguemnt ``color``. Turns the LED on. By default it will use the ``color``
specified in the constructor.
## Class Meter
This displays a single value in range 0.0 to 1.0 on a vertical linear meter. Constructor mandatory
positional arguments:
1. ``tft`` The TFT object.
2. ``location`` 2-tuple defining position.
Keyword only arguments:
1. ``height`` Dimension of the bounding box. Default 200 pixels.
2. ``width`` Dimension of the bounding box. Default 30 pixels.
3. ``font`` Font to use in any legends. Default: ``None`` No legends will be displayed.
4. ``legends`` A tuple of strings to display on the centreline of the meter. These should be
short to physically fit. They will be displayed equidistantly along the vertical scale, with
string 0 at the bottom. Default ``None``: no legends will be shown.
5. ``divisions`` Count of graduations on the meter scale. Default 10.
6. ``fgcolor`` Color of border. Defaults to system color.
7. ``bgcolor`` Background color of object. Defaults to system background.
8. ``fontcolor`` Text color. Defaults to system text color.
9. ``pointercolor`` Color of meter pointer. Defaults to ``fgcolor``.
10. ``value`` Initial value to display. Default 0.
Methods:
1.``value`` Optional argument ``val``. If set, refreshes the meter display with a new value,
otherwise returns its current value.

Plik diff jest za duży Load Diff

Wyświetl plik

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

@ -26,20 +26,16 @@ from font14 import font14
from tft import TFT, LANDSCAPE
from usched import Sched
from touch import TOUCH
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
from ugui import Button, Buttonset, RadioButtons, Checkbox, Label
from ugui import CIRCLE, RECTANGLE, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
def callback(button, args):
arg = args[0]
label = args[1]
def callback(button, arg, label):
label.show(arg)
if arg == 'Q':
button.objsched.stop()
def cbcb(checkbox, args):
label = args[0]
if checkbox.value:
def cbcb(checkbox, label):
if checkbox.value():
label.show('True')
else:
label.show('False')
@ -85,11 +81,16 @@ labels = { 'width' : 70,
# USER TEST FUNCTION
def cbtest(checkbox):
while True:
yield 3
checkbox.value(not checkbox.value())
def test():
print('Testing TFT...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched)
mytouch = TOUCH("XPT2046", objsched, confidence = 50, margin = 50)
mytft.backlight(100) # light on
lstlbl = []
for n in range(3):
@ -124,14 +125,15 @@ def test():
for t in table4:
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
fgcolor = (0, 0, 90), height = 40, **t)
x += 60
rb.run()
# 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]])
cb2 = Checkbox(objsched, mytft, mytouch, (300, 50), fillcolor = RED, callback = cbcb, args = [lstlbl[1]])
objsched.add_thread(cbtest(cb2)) # Toggle every 2 seconds
objsched.run() # Run it!
test()

Wyświetl plik

@ -1,184 +0,0 @@
# 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()

Wyświetl plik

@ -26,44 +26,37 @@ 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
from ugui import HorizSlider, Button, Dial, Label, LED, Meter, 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 callback(slider, control_name):
print('{} returned {}'.format(control_name, slider.value()))
def to_string(val):
return '{:3.1f} ohms'.format(val * 10)
def master_moved(slider, args):
def master_moved(slider, slave1, slave2, label, led):
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):
def slave_moved(slider, label):
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):
def doquit(button):
button.objsched.stop()
# USER TEST FUNCTION
@ -95,11 +88,11 @@ def testmeter(meter):
meter.value(oldvalue)
yield 0.05
def test(duration = 0):
def test():
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))
mytouch = TOUCH("XPT2046", objsched, confidence = 50, margin = 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)
@ -112,11 +105,11 @@ def test(duration = 0):
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)
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)
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)
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!

Wyświetl plik

@ -0,0 +1,35 @@
from font10 import font10
from tft import TFT, LANDSCAPE
from usched import Sched
from touch import TOUCH
from ugui import Knob, Dial, Label, Button, WHITE, YELLOW, GREEN, RED, CLIPPED_RECT
from math import pi
# CALLBACKS
# cb_end occurs when user stops touching the control
def callback(knob, control_name):
print('{} returned {}'.format(control_name, knob.value()))
def knob_moved(knob, dial):
val = knob.value() # range 0..1
dial.show(2 * (val - 0.5) * pi)
def doquit(button):
button.objsched.stop()
def test():
print('Test TFT panel...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched, confidence = 50, margin = 50)
mytft.backlight(100) # light on
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = RED,
height = 30, text = 'Quit', shape = CLIPPED_RECT)
dial1 = Dial(mytft, (120, 0), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7))
Knob(objsched, mytft, mytouch, (0, 0), fgcolor = GREEN, bgcolor=(0, 0, 80), color = (168,63,63), border = 2,
cb_end = callback, cbe_args = ['Knob1'], cb_move = knob_moved, cbm_args = [dial1]) #, arc = pi * 1.5)
Knob(objsched, mytft, mytouch, (0, 120), fgcolor = WHITE, border = 2,
cb_end = callback, cbe_args = ['Knob2'], arc = pi * 1.5)
objsched.run() # Run it!
test()

Wyświetl plik

@ -1,231 +0,0 @@
# 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, 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, 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
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) * (slidewidth + 1) * 3
self.slidebuf = bytearray(self.slidebytes)
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.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
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
for tick in range(self.divisions + 1):
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)
yl = y + height # Start at bottom
fhdelta = self.font.bits_vert / 2
for legend in self.legends:
tft.setTextPos(x + self.width, int(yl - fhdelta))
tft.printString(legend)
yl -= dy
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)
sliderpos = int(y + height - self._value * height)
y0 = sliderpos - sh // 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_y = y0
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((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
class HorizSlider(Touchable):
def __init__(self, objsched, tft, objtouch, location, font, *, height=30, width=200, divisions=10, legends=None,
fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None,
cb_end=lambda x, y : None, cbe_args=[], cb_move=lambda x, y : None, cbm_args=[], value=0.0):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
self.divisions = divisions
self.legends = legends
self.slidecolor = slidecolor
self.cb_end = cb_end
self.cbe_args = cbe_args
self.cb_move = cb_move
self.cbm_args = cbm_args
self.was_touched = False
slideheight = int(height / 1.3) & 0xfe # Ensure divisible by 2
self.slidewidth = 6 # must be divisible by 2
# We draw an odd number of pixels:
self.slidebytes = (slideheight + 1) * (self.slidewidth + 1) * 3
self.slidebuf = bytearray(self.slidebytes)
self._old_value = -1 # Invalidate
b = self.border
self.pot_dimension = self.width - 2 * (b + self.slidewidth // 2)
height = self.height - 2 * b
ycentre = self.location[1] + b + height // 2
self.slide_y0 = ycentre - slideheight // 2
self.slide_y1 = ycentre + slideheight // 2 # slide Y coordinates
self.slide_x = -1 # Invalidate old position
self.value(value)
def show(self):
tft = self.tft
bw = self.draw_border() # and background if required. Result is width of border
x = self.location[0] + bw + self.slidewidth // 2 # Allow space left and right slot for slider at extremes
y = self.location[1] + bw
height = self.height - 2 * bw
self.set_color()
width = self.pot_dimension # Length of slot
dy = height / 3
ycentre = y + height // 2
tft.drawRectangle(x, y + dy, x + width, y + 2 * dy)
if self.divisions > 0:
dx = width / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
xpos = int(x + dx * tick)
tft.drawVLine(xpos, y + 1, dy) # TODO Why is +1 fiddle required here?
tft.drawVLine(xpos, y + 1 + 2 * dy, dy) # and here
if self.legends is not None: # Legends
tft.setTextStyle(self.fontcolor, None, 2, self.font)
if len(self.legends) <= 1:
dx = 0
else:
dx = width / (len(self.legends) -1)
xl = x
for legend in self.legends:
offset = get_stringsize(legend, self.font)[0] / 2
tft.setTextPos(int(xl - offset), y - self.font.bits_vert) # Arbitrary left shift should be char width /2
tft.printString(legend)
xl += dx
sw = self.slidewidth # Handle slider
x0 = self.slide_x
y0 = self.slide_y0
x1 = x0 + sw
y1 = self.slide_y1
if self.slide_x >= 0: # Restore background
tft.setXY(x0, y0, x1, y1)
TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes)
sliderpos = int(x + self._value * width)
x0 = sliderpos - sw // 2
x1 = sliderpos + sw // 2
tft.setXY(x0, y0, x1, y1) # Read background
TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes)
self.slide_x = x0
if self.slidecolor is not None:
self.set_color(self.slidecolor)
tft.fillRectangle(x0, y0, x1, y1) # Draw slider
self.restore_color()
def value(self, val=None):
if val is None:
return self._value
self._value = min(max(val, 0.0), 1.0)
if self._value != self._old_value:
self._old_value = self._value
self.cb_move(self, self.cbm_args) # Callback not a bound method so pass self
self.show()
def touched(self, x, y): # If touched in bounding box, process it otherwise do nothing
x0 = self.location[0]
x1 = self.location[0] + self.width
y0 = self.location[1]
y1 = self.location[1] + self.height
if x0 <= x <= x1 and y0 <= y <= y1:
self.was_touched = True
self.value((x - x0) / self.pot_dimension)
def untouched(self): # User has released touchpad or touched elsewhere
if self.was_touched:
self.cb_end(self, self.cbe_args) # Callback not a bound method so pass self
self.was_touched = False

Wyświetl plik

@ -21,23 +21,25 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# Class supporting TFT LC-displays with a parallel Interface
# First example: Controller SSD1963
# It uses X1..X8 for data and Y3, Y9, Y10, Y11 and Y12 for control signals.
# The minimal connection just for writes is X1..X8 for data, Y9 for /Reset. Y11 for /WR and Y12 for /RS
# Then LED and /CS must be hard tied to Vcc and GND, and /RD is not used.
#
# Some parts of the software are a port of code provided by Rinky-Dink Electronics, Henning Karlsen,
# with the following copyright notice:
# Some parts of the software are a port of code provided by Rinky-Dink Electronics, Henning Karlsen,
# with the following copyright notice:
#
## Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
## This library is free software; you can redistribute it and/or
## modify it under the terms of the CC BY-NC-SA 3.0 license.
## Please see the included documents for further information.
## This library is free software; you can redistribute it and/or
## modify it under the terms of the CC BY-NC-SA 3.0 license.
## Please see the included documents for further information.
#
# Class supporting TFT LC-displays with a parallel Interface
# First example: Controller SSD1963 with a 4.3" or 7" display
#
# The minimal connection is:
# X1..X8 for data, Y9 for /Reset, Y10 for /RD, Y11 for /WR and Y12 for /RS
# Then LED must be hard tied to Vcc and /CS to GND.
#
import pyb, stm
from uctypes import addressof
from TFT_io import TFT_io
import TFT_io
# define constants
#
@ -73,7 +75,7 @@ class TFT:
self.v_flip = v_flip # flip vertical
self.h_flip = h_flip # flip horizontal
self.c_flip = 0 # flip blue/red
self.rc_flip = 0 # flip row/column (does not seem to work)
self.rc_flip = 0 # flip row/column
self.setColor((255, 255, 255)) # set FG color to white as can be.
self.setBGColor((0, 0, 0)) # set BG to black
@ -90,9 +92,13 @@ class TFT:
# this may have to be moved to the controller specific section
if orientation == PORTRAIT:
self.setXY = TFT_io.setXY_P
self.drawPixel = TFT_io.drawPixel_P
else:
self.setXY = TFT_io.setXY_L
# ----------
self.drawPixel = TFT_io.drawPixel_L
self.swapbytes = TFT_io.swapbytes
self.swapcolors = TFT_io.swapcolors
# ----------
for pin_name in ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8",
"Y10", "Y11", "Y12"]:
pin = pyb.Pin(pin_name, pyb.Pin.OUT_PP) # set as output
@ -251,13 +257,12 @@ class TFT:
#
# Set character printing defaults
#
self.setTextPos(0,0)
self.setScrollArea(0, self.disp_y_size + 1, 0)
self.text_font = None
self.setTextStyle(None, None, 0, None, 0)
self.setTextStyle(self.color, self.BGcolor, 0, None, 0)
#
# Init done. clear Screen and switch BG LED on
#
self.text_x = self.text_y = self.text_yabs = 0
self.clrSCR() # clear the display
# self.backlight(100) ## switch BG LED on
#
@ -311,6 +316,9 @@ class TFT:
def setBGColor(self, bgcolor):
self.BGcolor = bgcolor
self.BGcolorvect = bytearray(self.BGcolor) # prepare byte array
self.BMPcolortable = bytearray([self.BGcolorvect[2], # create colortable
self.BGcolorvect[1], self.BGcolorvect[0],0,
self.colorvect[2], self.colorvect[1], self.colorvect[0],0])
#
# get the color used for the draw commands
#
@ -322,20 +330,25 @@ class TFT:
def getBGColor(self):
return self.BGcolor
#
# Draw a single pixel at location x, y
# Draw a single pixel at location x, y with color
# Rather slow at 40µs/Pixel
#
def drawPixel(self, x, y):
def drawPixel_py(self, x, y, color):
self.setXY(x, y, x, y)
TFT_io.displaySCR_AS(self.colorvect, 1) #
TFT_io.displaySCR_AS(color, 1) #
#
# clear screen, set it to BG color.
#
def clrSCR(self):
def clrSCR(self, color = None):
if color is None:
colorvect = self.BGcolorvect
else:
colorvect = bytearray(color)
self.clrXY()
TFT_io.fillSCR_AS(self.BGcolorvect, (self.disp_x_size + 1) * (self.disp_y_size + 1))
self.text_x = self.text_y = self.scroll_start = 0
TFT_io.fillSCR_AS(colorvect, (self.disp_x_size + 1) * (self.disp_y_size + 1))
self.setScrollArea(0, self.disp_y_size + 1, 0)
self.setScrollStart(0)
self.setTextPos(0,0)
#
# reset the address range to fullscreen
#
@ -348,19 +361,23 @@ class TFT:
# Draw a line from x1, y1 to x2, y2 with the color set by setColor()
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawLine(self, x1, y1, x2, y2):
def drawLine(self, x1, y1, x2, y2, color = None):
if y1 == y2:
self.drawHLine(x1, y1, x2 - x1 + 1)
self.drawHLine(x1, y1, x2 - x1 + 1, color)
elif x1 == x2:
self.drawVLine(x1, y1, y2 - y1 + 1)
self.drawVLine(x1, y1, y2 - y1 + 1, color)
else:
if color is None:
colorvect = self.colorvect
else:
colorvect = bytearray(color)
dx, xstep = (x2 - x1, 1) if x2 > x1 else (x1 - x2, -1)
dy, ystep = (y2 - y1, 1) if y2 > y1 else (y1 - y2, -1)
col, row = x1, y1
if dx < dy:
t = - (dy >> 1)
while True:
self.drawPixel(col, row)
self.drawPixel(col, row, colorvect)
if row == y2:
return
row += ystep
@ -371,7 +388,7 @@ class TFT:
else:
t = - (dx >> 1)
while True:
self.drawPixel(col, row)
self.drawPixel(col, row, colorvect)
if col == x2:
return
col += xstep
@ -383,44 +400,52 @@ class TFT:
# Draw a horizontal line with 1 Pixel width, from x,y to x + l - 1, y
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawHLine(self, x, y, l): # draw horiontal Line
def drawHLine(self, x, y, l, color = None): # draw horiontal Line
if color is None:
colorvect = self.colorvect
else:
colorvect = bytearray(color)
if l < 0: # negative length, swap parameters
l = -l
x -= l
self.setXY(x, y, x + l - 1, y) # set display window
TFT_io.fillSCR_AS(self.colorvect, l)
TFT_io.fillSCR_AS(colorvect, l)
#
# Draw a vertical line with 1 Pixel width, from x,y to x, y + l - 1
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawVLine(self, x, y, l): # draw horiontal Line
def drawVLine(self, x, y, l, color = None): # draw horiontal Line
if color is None:
colorvect = self.colorvect
else:
colorvect = bytearray(color)
if l < 0: # negative length, swap parameters
l = -l
y -= l
self.setXY(x, y, x, y + l - 1) # set display window
TFT_io.fillSCR_AS(self.colorvect, l)
TFT_io.fillSCR_AS(colorvect, l)
#
# Draw rectangle from x1, y1, to x2, y2
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawRectangle(self, x1, y1, x2, y2):
def drawRectangle(self, x1, y1, x2, y2, color = None):
if x1 > x2:
t = x1; x1 = x2; x2 = t
x1, x2 = x2, x1
if y1 > y2:
t = y1; y1 = y2; y2 = t
self.drawHLine(x1, y1, x2 - x1 + 1)
self.drawHLine(x1, y2, x2 - x1 + 1)
self.drawVLine(x1, y1, y2 - y1 + 1)
self.drawVLine(x2, y1, y2 - y1 + 1)
y1, y2 = y2, y1
self.drawHLine(x1, y1, x2 - x1 + 1, color)
self.drawHLine(x1, y2, x2 - x1 + 1, color)
self.drawVLine(x1, y1, y2 - y1 + 1, color)
self.drawVLine(x2, y1, y2 - y1 + 1, color)
#
# Fill rectangle
# Almost straight port from the UTFT Library at Rinky-Dink Electronics
#
def fillRectangle(self, x1, y1, x2, y2, color = None):
def fillRectangle(self, x1, y1, x2, y2, color=None):
if x1 > x2:
t = x1; x1 = x2; x2 = t
x1, x2 = x2, x1
if y1 > y2:
t = y1; y1 = y2; y2 = t
y1, y2 = y2, y1
self.setXY(x1, y1, x2, y2) # set display window
if color:
TFT_io.fillSCR_AS(bytearray(color), (x2 - x1 + 1) * (y2 - y1 + 1))
@ -431,29 +456,33 @@ class TFT:
# Draw smooth rectangle from x1, y1, to x2, y2
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawClippedRectangle(self, x1, y1, x2, y2):
def drawClippedRectangle(self, x1, y1, x2, y2, color = None):
if x1 > x2:
t = x1; x1 = x2; x2 = t
x1, x2 = x2, x1
if y1 > y2:
t = y1; y1 = y2; y2 = t
y1, y2 = y2, y1
if (x2-x1) > 4 and (y2-y1) > 4:
self.drawPixel(x1 + 2,y1 + 1)
self.drawPixel(x1 + 1,y1 + 2)
self.drawPixel(x2 - 2,y1 + 1)
self.drawPixel(x2 - 1,y1 + 2)
self.drawPixel(x1 + 2,y2 - 1)
self.drawPixel(x1 + 1,y2 - 2)
self.drawPixel(x2 - 2,y2 - 1)
self.drawPixel(x2 - 1,y2 - 2)
self.drawHLine(x1 + 3, y1, x2 - x1 - 5)
self.drawHLine(x1 + 3, y2, x2 - x1 - 5)
self.drawVLine(x1, y1 + 3, y2 - y1 - 5)
self.drawVLine(x2, y1 + 3, y2 - y1 - 5)
if color is None:
colorvect = self.colorvect
else:
colorvect = bytearray(color)
self.drawPixel(x1 + 2,y1 + 1, colorvect)
self.drawPixel(x1 + 1,y1 + 2, colorvect)
self.drawPixel(x2 - 2,y1 + 1, colorvect)
self.drawPixel(x2 - 1,y1 + 2, colorvect)
self.drawPixel(x1 + 2,y2 - 1, colorvect)
self.drawPixel(x1 + 1,y2 - 2, colorvect)
self.drawPixel(x2 - 2,y2 - 1, colorvect)
self.drawPixel(x2 - 1,y2 - 2, colorvect)
self.drawHLine(x1 + 3, y1, x2 - x1 - 5, colorvect)
self.drawHLine(x1 + 3, y2, x2 - x1 - 5, colorvect)
self.drawVLine(x1, y1 + 3, y2 - y1 - 5, colorvect)
self.drawVLine(x2, y1 + 3, y2 - y1 - 5, colorvect)
#
# Fill smooth rectangle from x1, y1, to x2, y2
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def fillClippedRectangle(self, x1, y1, x2, y2):
def fillClippedRectangle(self, x1, y1, x2, y2, color = None):
if x1 > x2:
t = x1; x1 = x2; x2 = t
if y1 > y2:
@ -461,22 +490,27 @@ class TFT:
if (x2-x1) > 4 and (y2-y1) > 4:
for i in range(((y2 - y1) // 2) + 1):
if i == 0:
self.drawHLine(x1 + 3, y1 + i, x2 - x1 - 5)
self.drawHLine(x1 + 3, y2 - i, x2 - x1 - 5)
self.drawHLine(x1 + 3, y1 + i, x2 - x1 - 5, color)
self.drawHLine(x1 + 3, y2 - i, x2 - x1 - 5, color)
elif i == 1:
self.drawHLine(x1 + 2, y1 + i, x2 - x1 - 3)
self.drawHLine(x1 + 2, y2 - i, x2 - x1 - 3)
self.drawHLine(x1 + 2, y1 + i, x2 - x1 - 3, color)
self.drawHLine(x1 + 2, y2 - i, x2 - x1 - 3, color)
elif i == 2:
self.drawHLine(x1 + 1, y1 + i, x2 - x1 - 1)
self.drawHLine(x1 + 1, y2 - i, x2 - x1 - 1)
self.drawHLine(x1 + 1, y1 + i, x2 - x1 - 1, color)
self.drawHLine(x1 + 1, y2 - i, x2 - x1 - 1, color)
else:
self.drawHLine(x1, y1 + i, x2 - x1 + 1)
self.drawHLine(x1, y2 - i, x2 - x1 + 1)
self.drawHLine(x1, y1 + i, x2 - x1 + 1, color)
self.drawHLine(x1, y2 - i, x2 - x1 + 1, color)
#
# draw a circle at x, y with radius
# Straight port from the UTFT Library at Rinky-Dink Electronics
#
def drawCircle(self, x, y, radius):
def drawCircle(self, x, y, radius, color = None):
if color is None:
colorvect = self.colorvect
else:
colorvect = bytearray(color)
f = 1 - radius
ddF_x = 1
@ -484,10 +518,10 @@ class TFT:
x1 = 0
y1 = radius
self.drawPixel(x, y + radius)
self.drawPixel(x, y - radius)
self.drawPixel(x + radius, y)
self.drawPixel(x - radius, y)
self.drawPixel(x, y + radius, colorvect)
self.drawPixel(x, y - radius, colorvect)
self.drawPixel(x + radius, y, colorvect)
self.drawPixel(x - radius, y, colorvect)
while x1 < y1:
if f >= 0:
@ -497,47 +531,67 @@ class TFT:
x1 += 1
ddF_x += 2
f += ddF_x
self.drawPixel(x + x1, y + y1)
self.drawPixel(x - x1, y + y1)
self.drawPixel(x + x1, y - y1)
self.drawPixel(x - x1, y - y1)
self.drawPixel(x + y1, y + x1)
self.drawPixel(x - y1, y + x1)
self.drawPixel(x + y1, y - x1)
self.drawPixel(x - y1, y - x1)
self.drawPixel(x + x1, y + y1, colorvect)
self.drawPixel(x - x1, y + y1, colorvect)
self.drawPixel(x + x1, y - y1, colorvect)
self.drawPixel(x - x1, y - y1, colorvect)
self.drawPixel(x + y1, y + x1, colorvect)
self.drawPixel(x - y1, y + x1, colorvect)
self.drawPixel(x + y1, y - x1, colorvect)
self.drawPixel(x - y1, y - x1, colorvect)
#
# fill a circle at x, y with radius
# Straight port from the UTFT Library at Rinky-Dink Electronics
# Instead of caluclating x = sqrt(r*r - y*y), it searches the x
# Instead of calculating x = sqrt(r*r - y*y), it searches the x
# for r*r = x*x + x*x
#
def fillCircle(self, x, y, radius):
def fillCircle(self, x, y, radius, color = None):
r_square = radius * radius * 4
for y1 in range (-(radius * 2), 1):
y_square = y1 * y1
for x1 in range (-(radius * 2), 1):
if x1*x1+y_square <= r_square:
x1i = x1//2
y1i = y1//2
self.drawHLine(x + x1i, y + y1i, 2 * (-x1i))
self.drawHLine(x + x1i, y - y1i, 2 * (-x1i))
x1i = x1 // 2
y1i = y1 // 2
self.drawHLine(x + x1i, y + y1i, 2 * (-x1i), color)
self.drawHLine(x + x1i, y - y1i, 2 * (-x1i), color)
break;
#
# Draw a bitmap at x,y with size sx, sy
# mode determines the type of expected data
# mode = 0: The data must contain 3 bytes/pixel red/green/blue
# mode = 1: The data must contain 2 packed bytes/pixel blue/green/red in 565 format
# mode = 2: The data contains 1 bit per pixel, mapped to fg/bg color
# mode = 1: The data contains 1 bit per pixel, mapped to fg/bg color
# unless a colortable is provided
# mode = 2: The data contains 2 bit per pixel; a colortable with 4 entries must be provided
# mode = 4: The data contains 4 bit per pixel;
# a colortable with 16 entries must be provided
# mode = 8: The data contains 8 bit per pixel;
# a colortable with 256 entries must be provided
# mode = 16: The data must contain 2 packed bytes/pixel red/green/blue in 565 format
# mode = 24: The data must contain 3 bytes/pixel red/green/blue
#
def drawBitmap(self, x, y, sx, sy, data, mode = 0):
def drawBitmap(self, x, y, sx, sy, data, mode = 24, colortable = None):
self.setXY(x, y, x + sx - 1, y + sy - 1)
if mode == 0:
if mode == 24:
TFT_io.displaySCR_AS(data, sx * sy)
elif mode == 1:
elif mode == 16:
TFT_io.displaySCR565_AS(data, sx * sy)
elif mode == 1:
if colortable is None:
colortable = self.BMPcolortable # create colortable
TFT_io.displaySCR_bmp(data, sx*sy, 1, colortable)
elif mode == 2:
control = bytearray(self.BGcolorvect + self.colorvect + chr(self.transparency))
TFT_io.displaySCR_bitmap(data, sx*sy, control, 0)
if colortable is None:
return
TFT_io.displaySCR_bmp(data, sx*sy, 2, colortable)
elif mode == 4:
if colortable is None:
return
TFT_io.displaySCR_bmp(data, sx*sy, 4, colortable)
elif mode == 8:
if colortable is None:
return
TFT_io.displaySCR_bmp(data, sx*sy, 8, colortable)
#
# set scroll area to the region between the first and last line
#
@ -546,22 +600,51 @@ class TFT:
[(tfa >> 8) & 0xff, tfa & 0xff,
(vsa >> 8) & 0xff, vsa & 0xff,
(bfa >> 8) & 0xff, bfa & 0xff]), 6)
self.scroll_fta = tfa
self.scroll_tfa = tfa
self.scroll_vsa = vsa
self.scroll_bfa = bfa
self.setScrollStart(self.scroll_tfa)
x, y = self.getTextPos()
self.setTextPos(x, y) # realign pointers
#
# get scroll area of the region between the first and last line
#
def getScrollArea(self):
return self.scroll_tfa, self.scroll_vsa, self.scroll_bfa
#
# set the line which is displayed first
#
def setScrollStart(self, lline):
TFT_io.tft_cmd_data_AS(0x37, bytearray([(lline >> 8) & 0xff, lline & 0xff]), 2)
self.scroll_start = lline # store the logical first line
TFT_io.tft_cmd_data_AS(0x37, bytearray([(lline >> 8) & 0xff, lline & 0xff]), 2)
#
# get the line which is displayed first
#
def getScrollStart(self):
return self.scroll_start # get the logical first line
#
# Scroll vsa up/down by a number of pixels
#
def scroll(self, pixels):
line = ((self.scroll_start - self.scroll_tfa + pixels) % self.scroll_vsa
+ self.scroll_tfa)
self.setScrollStart(line) # set the new line
#
# Set text position
#
def setTextPos(self, x, y, clip = False, scroll = True):
self.text_width, self.text_height = self.getScreensize()
self.text_width, self.text_height = self.getScreensize() ## height possibly wrong
self.text_x = x
self.text_y = y
if self.scroll_tfa <= y < (self.scroll_tfa + self.scroll_vsa): # in scroll area ? check later for < or <=
# correct position relative to scroll start
self.text_y = (y + self.scroll_start - self.scroll_tfa)
if self.text_y >= (self.scroll_tfa + self.scroll_vsa):
self.text_y -= self.scroll_vsa
else: # absolute
self.text_y = y
self.text_yabs = y
# Hint: self.text_yabs = self.text_y - self.scroll_start) % self.scroll_vsa + self.scroll_tfa)
if clip and (self.text_x + clip) < self.text_width:
self.text_width = self.text_x + clip
self.text_scroll = scroll
@ -569,62 +652,84 @@ class TFT:
# Get text position
#
def getTextPos(self):
return (self.text_x, self.text_y)
return (self.text_x, self.text_yabs)
#
# Set Text Style
#
def setTextStyle(self, fgcolor = None, bgcolor = None, transparency = None, font = None, gap = None):
if font != None:
def setTextStyle(self, fgcolor=None, bgcolor=None, transparency=None, font=None, gap=None):
if font is not None:
self.text_font = font
if font:
self.text_rows, self.text_cols, nchar, first = font.get_properties() #
if transparency != None:
if transparency is not None:
self.transparency = transparency
if gap != None:
if gap is not None:
self.text_gap = gap
self.text_color = bytearray(0)
if bgcolor != None:
self.text_color += bytearray(bgcolor)
else:
self.text_color += self.BGcolorvect
if fgcolor != None:
self.text_color += bytearray(fgcolor)
else:
self.text_color += self.colorvect
if transparency != None:
if bgcolor is not None:
self.text_bgcolor = bgcolor
if fgcolor is not None:
self.text_fgcolor = fgcolor
if transparency is not None:
self.transparency = transparency
self.text_color += bytearray([self.transparency])
if gap != None:
self.text_color = (bytearray(self.text_bgcolor)
+ bytearray(self.text_fgcolor)
+ bytearray([self.transparency]))
if gap is not None:
self.text_gap = gap
#
# Get Text Style: return (color, bgcolor, font, transpareny, gap)
#
def getTextStyle(self):
return (self.text_color[3:6], self.text_color[0:3],
self.transparency, self.text_font, self.text_gap)
#
# Check, if a new line is to be opened
# if yes, advance, including scrolling, and clear line, if flags is set
# Obsolete?
#
def printNewline(self):
self.text_y += self.text_rows
if (self.text_y + self.text_rows) > self.scroll_vsa: # does the line fit?
self.text_y = 0
newline = self.text_rows
self.setScrollStart(newline)
elif self.scroll_start > 0: # Scrolling has started
newline = (self.scroll_start + self.text_rows) % self.scroll_vsa
self.setScrollStart(newline)
def printNewline(self, clear = False):
if (self.text_yabs + self.text_rows) >= (self.scroll_tfa + self.scroll_vsa): # does the line fit?
self.scroll(self.text_rows) # no. scroll
else: # Yes, just advance pointers
self.text_yabs += self.text_rows
self.setTextPos(self.text_x, self.text_yabs)
if clear:
self.printClrLine(2) # clear actual line
#
# Carriage Return
#
def printCR(self): # clear to end of line
self.text_x = 0
#
# clear to end-of-line
# clear line modes
#
def printClrEOL(self): # clear to end of line
self.setXY(self.text_x, self.text_y,
self.text_width - self.text_x - 1, self.text_y + self.text_rows - 1) # set display window
TFT_io.fillSCR_AS(self.text_color, self.text_width * self.text_rows)
def printClrLine(self, mode = 0): # clear to end of line/bol/line
if mode == 0:
self.setXY(self.text_x, self.text_y,
self.text_width - 1, self.text_y + self.text_rows - 1) # set display window
TFT_io.fillSCR_AS(self.text_color, (self.text_width - self.text_x + 1) * self.text_rows)
elif mode == 1 and self.text_x > 0:
self.setXY(0, self.text_y,
self.text_x - 1, self.text_y + self.text_rows - 1) # set display window
TFT_io.fillSCR_AS(self.text_color, (self.text_x - 1) * self.text_rows)
elif mode == 2:
self.setXY(0, self.text_y,
self.text_width - 1, self.text_y + self.text_rows - 1) # set display window
TFT_io.fillSCR_AS(self.text_color, self.text_width * self.text_rows)
#
# clear sreen modes
#
def printClrSCR(self): # clear Area set by setScrollArea
self.setXY(0, self.scroll_tfa,
self.text_width - 1, self.scroll_tfa + self.scroll_vsa) # set display window
TFT_io.fillSCR_AS(self.text_color, self.text_width * self.scroll_vsa)
self.setScrollStart(self.scroll_tfa)
self.setTextPos(0, self.scroll_tfa)
#
# Print string s, returning the length of the printed string in pixels
#
def printString(self, s, bg_buf = None):
def printString(self, s, bg_buf=None):
len = 0
for c in s:
cols = self.printChar(c, bg_buf)
@ -635,7 +740,7 @@ class TFT:
#
# Print string c using the given char bitmap at location x, y, returning the width of the printed char in pixels
#
def printChar(self, c, bg_buf = None):
def printChar(self, c, bg_buf=None):
# get the charactes pixel bitmap and dimensions
if self.text_font:
fontptr, rows, cols = self.text_font.get_ch(ord(c))
@ -646,11 +751,10 @@ class TFT:
if self.text_x + cols > self.text_width: # does the char fit on the screen?
if self.text_scroll:
self.printCR() # No, then CR
self.printNewline() # NL: advance to the next line
self.printClrEOL() # clear to end of line
self.printNewline(True) # NL: advance to the next line
else:
return 0
# set data arrays & XY-Range
# Retrieve Background data if transparency is required
if self.transparency: # in case of transpareny, the frame buffer content is needed
if not bg_buf: # buffer allocation needed?
bg_buf = bytearray(pix_count * 3) # sigh...
@ -658,9 +762,9 @@ class TFT:
TFT_io.tft_read_cmd_data_AS(0x2e, bg_buf, pix_count * 3) # read background data
else:
bg_buf = 0 # dummy assignment, since None is not accepted
# print char
# Set XY range & print char
self.setXY(self.text_x, self.text_y, self.text_x + cols - 1, self.text_y + rows - 1) # set area
TFT_io.displaySCR_bitmap(fontptr, pix_count, self.text_color, bg_buf) # display char!
TFT_io.displaySCR_charbitmap(fontptr, pix_count, self.text_color, bg_buf) # display char!
#advance pointer
self.text_x += (cols + self.text_gap)
return cols + self.text_gap

734
tft_gui/ugui.py 100644
Wyświetl plik

@ -0,0 +1,734 @@
# 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 TFT_io
import math
from delay import Delay
TWOPI = 2 * math.pi
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
for c in s:
_, vert, cols = font.get_ch(ord(c))
hor += cols
return hor, vert
def print_centered(tft, x, y, s, color, font):
length, height = get_stringsize(s, font)
tft.setTextStyle(color, None, 2, 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 restore_color(self): # Restore to system default
self.tft.setColor(NoTouch.old_color)
# Base class for touch-enabled classes.
class Touchable(NoTouch):
touchlist = []
objtouch = None
@classmethod
def touchtest(cls): # Singleton thread tests all touchable instances
mytouch = cls.objtouch
while True:
yield
if mytouch.ready:
x, y = mytouch.get_touch_async()
for obj in cls.touchlist:
if obj.enabled:
obj.trytouch(x, y)
elif not mytouch.touched:
for obj in cls.touchlist:
if obj.was_touched:
obj.was_touched = False # Call untouched once only
obj.busy = False
obj.untouched()
def __init__(self, objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border, can_drag):
super().__init__(tft, location, font, height, width, fgcolor, bgcolor, fontcolor, border)
Touchable.touchlist.append(self)
self.can_drag = can_drag
self.busy = False
self.enabled = True # Available to user/subclass
self.was_touched = False
self.objsched = objsched
if Touchable.objtouch is None: # Initialising class and thread
Touchable.objtouch = objtouch
objsched.add_thread(self.touchtest()) # One thread only
def trytouch(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
if not self.busy or self.can_drag:
self.touched(x, y) # Called repeatedly for draggable objects
self.busy = True # otherwise once only
def untouched(self): # Default if not defined in subclass
pass
# *********** DISPLAYS: NON-TOUCH CLASSES FOR DATA DISPLAY ***********
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]
tft.fillRectangle(x + bw, y + bw, x + self.width - bw, y + self.height - bw, self.bgcolor)
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. Angle 0 is vertical, +ve increments are clockwise.
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]
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, self.fgcolor)
tft.drawCircle(self.xorigin, self.yorigin, radius, self.fgcolor)
self.restore_color()
def show(self, angle, pointer=0):
tft = self.tft
if self.angles[pointer] is not None:
self.drawpointer(self.angles[pointer], pointer, self.bgcolor) # erase old
self.drawpointer(angle, pointer, self.fgcolor) # draw new
self.angles[pointer] = angle # update old
self.restore_color()
def drawpointer(self, radians, pointer, color):
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, color)
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.tft.fillCircle(int(self.x), int(self.y), int(self.radius), color)
self.tft.drawCircle(int(self.x), int(self.y), int(self.radius), self.fgcolor)
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
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, self.fgcolor)
tft.drawHLine(x1 - dx, ypos, dx, self.fgcolor)
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
tft.drawHLine(x0, y0, width, self.pointercolor) # 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()
# *********** PUSHBUTTON AND CHECKBOX CLASSES ***********
# 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, *, font, shape=CIRCLE, height=50, width=50, fill=True,
fgcolor=None, bgcolor=None, fontcolor=None, litcolor=None, text='', show=True, callback=lambda *_ : None,
args=[]):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, None, False)
self.shape = shape
self.radius = height // 2
self.fill = fill
self.litcolor = litcolor
self.text = text
self.callback = callback
self.callback_args = args
self.orig_fgcolor = fgcolor
if self.litcolor is not None:
self.delay = Delay(objsched, self.shownormal)
self.visible = True # ditto
self.litcolor = litcolor if self.fgcolor is not None else None
if show:
self.show()
def show(self):
tft = self.tft
x = self.location[0]
y = self.location[1]
if not self.visible: # erase the button
tft.fillRectangle(x, y, x + self.width, y + self.height, self.bgcolor)
self.restore_color()
return
if self.shape == CIRCLE: # Button coords are of top left corner of bounding box
x += self.radius
y += self.radius
if self.fill:
tft.fillCircle(x, y, self.radius, self.fgcolor)
else:
tft.drawCircle(x, y, self.radius, self.fgcolor)
if self.font is not None and len(self.text):
print_centered(tft, x, y, self.text, self.fontcolor, self.font)
else:
x1 = x + self.width
y1 = y + self.height
if self.shape == RECTANGLE: # rectangle
if self.fill:
tft.fillRectangle(x, y, x1, y1, self.fgcolor)
else:
tft.drawRectangle(x, y, x1, y1, self.fgcolor)
if self.font is not None and len(self.text):
print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font)
elif self.shape == CLIPPED_RECT: # clipped rectangle
if self.fill:
tft.fillClippedRectangle(x, y, x1, y1, self.fgcolor)
else:
tft.drawClippedRectangle(x, y, x1, y1, self.fgcolor)
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)
self.restore_color()
def shownormal(self):
self.fgcolor = self.orig_fgcolor
self.show()
def touched(self, x, y): # Process touch
if self.litcolor is not None:
self.fgcolor = self.litcolor
self.show()
self.delay.trigger(1)
self.callback(self, *self.callback_args) # Callback not a bound method so pass self
class Buttons(object):
def __init__(self, user_callback):
self.user_callback = user_callback
self.lstbuttons = []
def add_button(self, *args, **kwargs):
kwargs['show'] = False
self.lstbuttons.append(Button(*args, **kwargs))
# Group of buttons, typically at same location, where pressing one shows
# the next e.g. start/stop toggle or sequential select from short list
class Buttonset(Buttons):
def __init__(self, user_callback):
super().__init__(user_callback)
def run(self):
for idx, button in enumerate(self.lstbuttons):
if idx:
button.visible = False # Only button zero visible and sensitive
button.enabled = False
button.callback_args.append(idx)
button.callback = self.callback
self.lstbuttons[0].show()
def callback(self, button, *args):
button_no = args[-1]
old = self.lstbuttons[button_no]
new = self.lstbuttons[(button_no + 1) % len(self.lstbuttons)]
old.enabled = False
old.visible = False
old.show()
new.enabled = True
new.visible = True
new.busy = True # Don't respond to continued press
new.show()
self.user_callback(new, *args[:-1]) # user gets button with args they specified
# Group of buttons at different locations, where pressing one shows
# only current button highlighted and oes callback from current one
class RadioButtons(Buttons):
def __init__(self, user_callback, highlight, selected=0):
super().__init__(user_callback)
self.highlight = highlight
self.selected = selected
def run(self):
for idx, button in enumerate(self.lstbuttons):
if idx == self.selected: # Initial selection
button.fgcolor = self.highlight
else:
button.fgcolor = button.orig_fgcolor
button.show()
button.callback = self.callback
def callback(self, button, *args):
for but in self.lstbuttons:
if but is button:
but.fgcolor = self.highlight
else:
but.fgcolor = but.orig_fgcolor
but.show()
self.user_callback(button, *args) # user gets button with args they specified
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, False)
self.callback = callback
self.callback_args = args
self.fillcolor = fillcolor
if value is None:
self._value = False # special case: don't execute callback on initialisation
self.show()
else:
self._value = not value
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
y = self.location[1] + bw
height = self.height - 2 * bw
x1 = x + height
y1 = y + height
if self._value:
if self.fillcolor is not None:
tft.fillRectangle(x, y, x1, y1, self.fillcolor)
else:
tft.fillRectangle(x, y, x1, y1, self.bgcolor)
tft.drawRectangle(x, y, x1, y1, self.fgcolor)
if self.fillcolor is None and self._value:
tft.drawLine(x, y, x1, y1, self.fgcolor)
tft.drawLine(x, y1, x1, y, self.fgcolor)
def value(self, val=None):
if val is None:
return self._value
val = bool(val)
if val != self._value:
self._value = val
self.callback(self, *self.callback_args) # Callback not a bound method so pass self
self.show()
def touched(self, x, y): # Was touched
self.value(not self._value) # Upddate and refresh
# *********** SLIDER CLASSES ***********
# A slider's text items lie outside its bounding box (area sensitive to touch)
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, border=None,
cb_end=lambda *_ : None, cbe_args=[], cb_move=lambda *_ : None, cbm_args=[], value=0.0):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border, True)
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
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) * (slidewidth + 1) * 3
self.slidebuf = bytearray(self.slidebytes)
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.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
y = self.location[1] + bw + self.slideheight // 2 # Allow space above and below slot
width = self.width - 2 * bw
height = self.pot_dimension # Height of slot
dx = width / 3
tft.drawRectangle(x + dx, y, x + 2 * dx, y + height, self.fgcolor)
if self.divisions > 0:
dy = height / (self.divisions) # Tick marks
for tick in range(self.divisions + 1):
ypos = int(y + dy * tick)
tft.drawHLine(x + 1, ypos, dx, self.fgcolor)
tft.drawHLine(x + 1 + 2 * dx, ypos, dx, self.fgcolor)
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)
yl = y + height # Start at bottom
fhdelta = self.font.bits_vert / 2
for legend in self.legends:
tft.setTextPos(x + self.width, int(yl - fhdelta))
tft.printString(legend)
yl -= dy
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)
sliderpos = int(y + height - self._value * height)
y0 = sliderpos - sh // 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_y = y0
color = self.slidecolor if self.slidecolor is not None else self.fgcolor
tft.fillRectangle(x0, y0, x1, y1, color) # 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): # Touched in bounding box. A drag will call repeatedly.
self.value((self.location[1] + self.height - y) / self.pot_dimension)
def untouched(self): # User has released touchpad or touched elsewhere
self.cb_end(self, *self.cbe_args) # Callback not a bound method so pass self
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 *_ : None, cbe_args=[], cb_move=lambda *_ : None, cbm_args=[], value=0.0):
super().__init__(objsched, tft, objtouch, location, font, height, width, fgcolor, bgcolor, fontcolor, border, True)
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
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
width = self.pot_dimension # Length of slot
dy = height / 3
ycentre = y + height // 2
tft.drawRectangle(x, y + dy, x + width, y + 2 * dy, self.fgcolor)
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, self.fgcolor) # TODO Why is +1 fiddle required here?
tft.drawVLine(xpos, y + 1 + 2 * dy, dy, self.fgcolor) # 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
color = self.slidecolor if self.slidecolor is not None else self.fgcolor
tft.fillRectangle(x0, y0, x1, y1, color) # 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): # Touched in bounding box. A drag will call repeatedly.
self.value((x - self.location[0]) / self.pot_dimension)
def untouched(self): # User has released touchpad or touched elsewhere
self.cb_end(self, *self.cbe_args) # Callback not a bound method so pass self
# *********** CONTROL KNOB CLASS ***********
class Knob(Touchable):
def __init__(self, objsched, tft, objtouch, location, *, height=100, arc=TWOPI, ticks=9, value=0.0,
fgcolor=None, bgcolor=None, color=None, border=None,
cb_end=lambda *_ : None, cbe_args=[], cb_move=lambda *_ : None, cbm_args=[]):
Touchable.__init__(self, objsched, tft, objtouch, location, None, height, height, fgcolor, bgcolor, None, border, True)
border = self.border # Geometry: border width
radius = height / 2 - border
arc = min(max(arc, 0), TWOPI)
self.arc = arc # Usable angle of control
self.radius = radius
self.xorigin = location[0] + border + radius
self.yorigin = location[1] + border + radius
ticklen = 0.1 * radius
self.pointerlen = radius - ticklen - 5
ticks = max(ticks, 2) # start and end of travel
self.cb_end = cb_end # Callbacks
self.cbe_args = cbe_args
self.cb_move = cb_move
self.cbm_args = cbm_args
self._old_value = None # data: invalidate
self._value = -1
self.color = color
for tick in range(ticks):
theta = (tick / (ticks - 1)) * arc - arc / 2
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, self.fgcolor)
if color is not None:
tft.fillCircle(self.xorigin, self.yorigin, radius - ticklen, color)
tft.drawCircle(self.xorigin, self.yorigin, radius - ticklen, self.fgcolor)
tft.drawCircle(self.xorigin, self.yorigin, radius - ticklen - 3, self.fgcolor)
self.value(value) # Cause the object to be displayed and callback to be triggered
self.restore_color()
def show(self):
tft = self.tft
if self._old_value is not None:
color = self.bgcolor if self.color is None else self.color
self.drawpointer(self._old_value, color) # erase old
self.drawpointer(self._value, self.fgcolor) # draw new
self._old_value = self._value # update old
self.restore_color()
def value(self, val=None):
if val is None:
return self._value
val = min(max(val, 0.0), 1.0)
if val != self._value:
self._value = val # Update value for callback
self.cb_move(self, *self.cbm_args) # Callback not a bound method so pass self
self.show()
def touched(self, x, y): # Touched in bounding box. A drag will call repeatedly.
dy = self.yorigin - y
dx = x - self.xorigin
if (dx**2 + dy**2) / self.radius**2 < 0.5:
return # vector too short
alpha = math.atan2(dx, dy) # axes swapped: orientate relative to vertical
arc = self.arc
alpha = min(max(alpha, -arc / 2), arc / 2) + arc / 2
self.value(alpha /arc)
def untouched(self): # User has released touchpad or touched elsewhere
self.cb_end(self, *self.cbe_args) # Callback not a bound method so pass self
def drawpointer(self, value, color):
arc = self.arc
length = self.pointerlen
angle = value * arc - arc / 2
x_end = int(self.xorigin + length * math.sin(angle))
y_end = int(self.yorigin - length * math.cos(angle))
self.tft.drawLine(int(self.xorigin), int(self.yorigin), x_end, y_end, color)

Wyświetl plik

@ -1,118 +0,0 @@
# 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
for c in s:
_, vert, cols = font.get_ch(ord(c))
hor += cols
return hor, vert
def print_centered(tft, x, y, s, color, font):
length, height = get_stringsize(s, font)
tft.setTextStyle(color, None, 2, 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(NoTouch):
touchlist = []
objtouch = None
@classmethod
def touchtest(cls): # Singleton thread tests all touchable instances
mytouch = cls.objtouch
while True:
yield
if mytouch.ready:
x, y = mytouch.get_touch_async()
for obj in cls.touchlist:
if obj.enabled:
obj.touched(x, y)
elif not mytouch.touched:
for obj in cls.touchlist:
obj.untouched()
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
self.objsched = objsched
if Touchable.objtouch is None: # Initialising class and thread
Touchable.objtouch = objtouch
objsched.add_thread(self.touchtest()) # One thread only

Wyświetl plik

@ -1,4 +1,4 @@
# slidetest.py Demo/test program for vertical slider class for Pyboard TFT GUI
# vst.py Demo/test program for vertical slider class for Pyboard TFT GUI
# The MIT License (MIT)
#
@ -26,38 +26,29 @@ 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 displays import Dial, Label
from ui import CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
from ugui import Slider, Button, Dial, Label, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
from math import pi
# CALLBACKS
# cb_end occurs when user stops touching the control
def callback(slider, args):
print('{} returned {}'.format(args[0], slider.value()))
def callback(slider, device):
print('{} returned {}'.format(device, slider.value()))
def to_string(val):
return '{:3.1f} ohms'.format(val * 10)
def master_moved(slider, args):
def master_moved(slider, slave1, slave2, label):
val = slider.value()
slave1 = args[0]
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):
def slave_moved(slider, label):
val = slider.value()
dial = args[0]
dial.delta = val
label = args[1]
label.show(to_string(val))
def doquit(button, args):
def doquit(button):
button.objsched.stop()
# THREADS
@ -88,11 +79,11 @@ table = {'fontcolor' : WHITE,
}
# 'border' : 2,
def test(duration = 0):
def test():
print('Test TFT panel...')
objsched = Sched() # Instantiate the scheduler
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
mytouch = TOUCH("XPT2046", objsched, confidence=50)
mytouch = TOUCH("XPT2046", objsched, confidence=50, margin = 50)
mytft.backlight(100) # light on
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = RED,
height = 30, text = 'Quit', shape = CLIPPED_RECT)
@ -103,11 +94,11 @@ def test(duration = 0):
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, lstlbl[1]), **table)
fgcolor = GREEN, cbe_args = ('Slave1',), cb_move = slave_moved, cbm_args = [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, lstlbl[2]), **table)
fgcolor = GREEN, cbe_args = ('Slave2',), cb_move = slave_moved, cbm_args = [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)
fgcolor = YELLOW, 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!