kopia lustrzana https://github.com/peterhinch/micropython-samples
ugui.py now includes all classes. Rotary knob now supported
rodzic
ceda9894b4
commit
84c7a5df4b
|
@ -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.
|
||||||
|
|
1189
tft_gui/TFT_io.py
1189
tft_gui/TFT_io.py
Plik diff jest za duży
Load Diff
|
@ -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
|
|
|
@ -26,20 +26,16 @@ from font14 import font14
|
||||||
from tft import TFT, LANDSCAPE
|
from tft import TFT, LANDSCAPE
|
||||||
from usched import Sched
|
from usched import Sched
|
||||||
from touch import TOUCH
|
from touch import TOUCH
|
||||||
from button import Button, Buttonset, RadioButtons, Checkbox
|
from ugui import Button, Buttonset, RadioButtons, Checkbox, Label
|
||||||
from ui import CIRCLE, RECTANGLE, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
|
from ugui import CIRCLE, RECTANGLE, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
|
||||||
from displays import Label
|
|
||||||
|
|
||||||
def callback(button, args):
|
def callback(button, arg, label):
|
||||||
arg = args[0]
|
|
||||||
label = args[1]
|
|
||||||
label.show(arg)
|
label.show(arg)
|
||||||
if arg == 'Q':
|
if arg == 'Q':
|
||||||
button.objsched.stop()
|
button.objsched.stop()
|
||||||
|
|
||||||
def cbcb(checkbox, args):
|
def cbcb(checkbox, label):
|
||||||
label = args[0]
|
if checkbox.value():
|
||||||
if checkbox.value:
|
|
||||||
label.show('True')
|
label.show('True')
|
||||||
else:
|
else:
|
||||||
label.show('False')
|
label.show('False')
|
||||||
|
@ -85,11 +81,16 @@ labels = { 'width' : 70,
|
||||||
|
|
||||||
# USER TEST FUNCTION
|
# USER TEST FUNCTION
|
||||||
|
|
||||||
|
def cbtest(checkbox):
|
||||||
|
while True:
|
||||||
|
yield 3
|
||||||
|
checkbox.value(not checkbox.value())
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
print('Testing TFT...')
|
print('Testing TFT...')
|
||||||
objsched = Sched() # Instantiate the scheduler
|
objsched = Sched() # Instantiate the scheduler
|
||||||
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
||||||
mytouch = TOUCH("XPT2046", objsched)
|
mytouch = TOUCH("XPT2046", objsched, confidence = 50, margin = 50)
|
||||||
mytft.backlight(100) # light on
|
mytft.backlight(100) # light on
|
||||||
lstlbl = []
|
lstlbl = []
|
||||||
for n in range(3):
|
for n in range(3):
|
||||||
|
@ -124,14 +125,15 @@ def test():
|
||||||
for t in table4:
|
for t in table4:
|
||||||
t['args'].append(lstlbl[2])
|
t['args'].append(lstlbl[2])
|
||||||
rb.add_button(objsched, mytft, mytouch, (x, 180), font = font14, fontcolor = WHITE,
|
rb.add_button(objsched, mytft, mytouch, (x, 180), font = font14, fontcolor = WHITE,
|
||||||
fgcolor = (0, 0, 90), height = 30, **t)
|
fgcolor = (0, 0, 90), height = 40, **t)
|
||||||
x += 40
|
x += 60
|
||||||
rb.run()
|
rb.run()
|
||||||
|
|
||||||
# Checkbox
|
# Checkbox
|
||||||
Checkbox(objsched, mytft, mytouch, (300, 0), callback = cbcb, args = [lstlbl[0]])
|
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!
|
objsched.run() # Run it!
|
||||||
|
|
||||||
test()
|
test()
|
||||||
|
|
|
@ -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()
|
|
|
@ -26,44 +26,37 @@ from font10 import font10
|
||||||
from tft import TFT, LANDSCAPE
|
from tft import TFT, LANDSCAPE
|
||||||
from usched import Sched
|
from usched import Sched
|
||||||
from touch import TOUCH
|
from touch import TOUCH
|
||||||
from slider import HorizSlider
|
from ugui import HorizSlider, Button, Dial, Label, LED, Meter, CLIPPED_RECT, GREEN, RED, YELLOW, WHITE, BLUE
|
||||||
from button import Button
|
|
||||||
from displays import Dial, Label, LED, Meter
|
|
||||||
from ui import CLIPPED_RECT, GREEN, RED, YELLOW, WHITE, BLUE
|
|
||||||
import pyb
|
import pyb
|
||||||
|
|
||||||
# CALLBACKS
|
# CALLBACKS
|
||||||
# cb_end occurs when user stops touching the control
|
# cb_end occurs when user stops touching the control
|
||||||
def callback(slider, args):
|
def callback(slider, control_name):
|
||||||
print('{} returned {}'.format(args[0], slider.value()))
|
print('{} returned {}'.format(control_name, slider.value()))
|
||||||
|
|
||||||
def to_string(val):
|
def to_string(val):
|
||||||
return '{:3.1f} ohms'.format(val * 10)
|
return '{:3.1f} ohms'.format(val * 10)
|
||||||
|
|
||||||
def master_moved(slider, args):
|
def master_moved(slider, slave1, slave2, label, led):
|
||||||
val = slider.value()
|
val = slider.value()
|
||||||
slave1 = args[0]
|
|
||||||
slave1.value(val)
|
slave1.value(val)
|
||||||
slave2 = args[1]
|
|
||||||
slave2.value(val)
|
slave2.value(val)
|
||||||
label = args[2]
|
|
||||||
label.show(to_string(val))
|
label.show(to_string(val))
|
||||||
led = args[3]
|
|
||||||
if val > 0.8:
|
if val > 0.8:
|
||||||
led.on()
|
led.on()
|
||||||
else:
|
else:
|
||||||
led.off()
|
led.off()
|
||||||
|
|
||||||
# Either slave has had its slider moved (by user or by having value altered)
|
# Either slave has had its slider moved (by user or by having value altered)
|
||||||
def slave_moved(slider, args):
|
def slave_moved(slider, label):
|
||||||
val = slider.value()
|
val = slider.value()
|
||||||
if val > 0.8:
|
if val > 0.8:
|
||||||
slider.fgcolor = RED
|
slider.fgcolor = RED
|
||||||
else:
|
else:
|
||||||
slider.fgcolor = GREEN
|
slider.fgcolor = GREEN
|
||||||
label = args[0]
|
|
||||||
label.show(to_string(val))
|
label.show(to_string(val))
|
||||||
|
|
||||||
def doquit(button, args):
|
def doquit(button):
|
||||||
button.objsched.stop()
|
button.objsched.stop()
|
||||||
|
|
||||||
# USER TEST FUNCTION
|
# USER TEST FUNCTION
|
||||||
|
@ -95,11 +88,11 @@ def testmeter(meter):
|
||||||
meter.value(oldvalue)
|
meter.value(oldvalue)
|
||||||
yield 0.05
|
yield 0.05
|
||||||
|
|
||||||
def test(duration = 0):
|
def test():
|
||||||
print('Test TFT panel...')
|
print('Test TFT panel...')
|
||||||
objsched = Sched() # Instantiate the scheduler
|
objsched = Sched() # Instantiate the scheduler
|
||||||
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
||||||
mytouch = TOUCH("XPT2046", objsched, 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
|
mytft.backlight(100) # light on
|
||||||
led = LED(mytft, (420, 0), border = 2)
|
led = LED(mytft, (420, 0), border = 2)
|
||||||
meter1 = Meter(mytft, (320, 0), font=font10, legends=('0','5','10'), pointercolor = YELLOW, fgcolor = GREEN)
|
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))
|
lstlbl.append(Label(mytft, (x, 40 + 60 * n), font = font10, **labels))
|
||||||
x = 0
|
x = 0
|
||||||
slave1 = HorizSlider(objsched, mytft, mytouch, (x, 100), font10,
|
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,
|
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,
|
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(meter1))
|
||||||
objsched.add_thread(testmeter(meter2))
|
objsched.add_thread(testmeter(meter2))
|
||||||
objsched.run() # Run it!
|
objsched.run() # Run it!
|
||||||
|
|
|
@ -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()
|
|
@ -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
|
|
374
tft_gui/tft.py
374
tft_gui/tft.py
|
@ -21,23 +21,25 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
#
|
#
|
||||||
# Class supporting TFT LC-displays with a parallel Interface
|
# Some parts of the software are a port of code provided by Rinky-Dink Electronics, Henning Karlsen,
|
||||||
# First example: Controller SSD1963
|
# with the following copyright notice:
|
||||||
# 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:
|
|
||||||
## Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
|
## Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
|
||||||
## This library is free software; you can redistribute it and/or
|
## 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.
|
## modify it under the terms of the CC BY-NC-SA 3.0 license.
|
||||||
## Please see the included documents for further information.
|
## 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
|
import pyb, stm
|
||||||
from uctypes import addressof
|
from uctypes import addressof
|
||||||
from TFT_io import TFT_io
|
import TFT_io
|
||||||
|
|
||||||
# define constants
|
# define constants
|
||||||
#
|
#
|
||||||
|
@ -73,7 +75,7 @@ class TFT:
|
||||||
self.v_flip = v_flip # flip vertical
|
self.v_flip = v_flip # flip vertical
|
||||||
self.h_flip = h_flip # flip horizontal
|
self.h_flip = h_flip # flip horizontal
|
||||||
self.c_flip = 0 # flip blue/red
|
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.setColor((255, 255, 255)) # set FG color to white as can be.
|
||||||
self.setBGColor((0, 0, 0)) # set BG to black
|
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
|
# this may have to be moved to the controller specific section
|
||||||
if orientation == PORTRAIT:
|
if orientation == PORTRAIT:
|
||||||
self.setXY = TFT_io.setXY_P
|
self.setXY = TFT_io.setXY_P
|
||||||
|
self.drawPixel = TFT_io.drawPixel_P
|
||||||
else:
|
else:
|
||||||
self.setXY = TFT_io.setXY_L
|
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",
|
for pin_name in ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8",
|
||||||
"Y10", "Y11", "Y12"]:
|
"Y10", "Y11", "Y12"]:
|
||||||
pin = pyb.Pin(pin_name, pyb.Pin.OUT_PP) # set as output
|
pin = pyb.Pin(pin_name, pyb.Pin.OUT_PP) # set as output
|
||||||
|
@ -251,13 +257,12 @@ class TFT:
|
||||||
#
|
#
|
||||||
# Set character printing defaults
|
# Set character printing defaults
|
||||||
#
|
#
|
||||||
self.setTextPos(0,0)
|
|
||||||
self.setScrollArea(0, self.disp_y_size + 1, 0)
|
|
||||||
self.text_font = None
|
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
|
# 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.clrSCR() # clear the display
|
||||||
# self.backlight(100) ## switch BG LED on
|
# self.backlight(100) ## switch BG LED on
|
||||||
#
|
#
|
||||||
|
@ -311,6 +316,9 @@ class TFT:
|
||||||
def setBGColor(self, bgcolor):
|
def setBGColor(self, bgcolor):
|
||||||
self.BGcolor = bgcolor
|
self.BGcolor = bgcolor
|
||||||
self.BGcolorvect = bytearray(self.BGcolor) # prepare byte array
|
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
|
# get the color used for the draw commands
|
||||||
#
|
#
|
||||||
|
@ -322,20 +330,25 @@ class TFT:
|
||||||
def getBGColor(self):
|
def getBGColor(self):
|
||||||
return self.BGcolor
|
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
|
# Rather slow at 40µs/Pixel
|
||||||
#
|
#
|
||||||
def drawPixel(self, x, y):
|
def drawPixel_py(self, x, y, color):
|
||||||
self.setXY(x, y, x, y)
|
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.
|
# 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()
|
self.clrXY()
|
||||||
TFT_io.fillSCR_AS(self.BGcolorvect, (self.disp_x_size + 1) * (self.disp_y_size + 1))
|
TFT_io.fillSCR_AS(colorvect, (self.disp_x_size + 1) * (self.disp_y_size + 1))
|
||||||
self.text_x = self.text_y = self.scroll_start = 0
|
self.setScrollArea(0, self.disp_y_size + 1, 0)
|
||||||
self.setScrollStart(0)
|
self.setScrollStart(0)
|
||||||
|
self.setTextPos(0,0)
|
||||||
#
|
#
|
||||||
# reset the address range to fullscreen
|
# 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()
|
# 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
|
# 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:
|
if y1 == y2:
|
||||||
self.drawHLine(x1, y1, x2 - x1 + 1)
|
self.drawHLine(x1, y1, x2 - x1 + 1, color)
|
||||||
elif x1 == x2:
|
elif x1 == x2:
|
||||||
self.drawVLine(x1, y1, y2 - y1 + 1)
|
self.drawVLine(x1, y1, y2 - y1 + 1, color)
|
||||||
else:
|
else:
|
||||||
|
if color is None:
|
||||||
|
colorvect = self.colorvect
|
||||||
|
else:
|
||||||
|
colorvect = bytearray(color)
|
||||||
dx, xstep = (x2 - x1, 1) if x2 > x1 else (x1 - x2, -1)
|
dx, xstep = (x2 - x1, 1) if x2 > x1 else (x1 - x2, -1)
|
||||||
dy, ystep = (y2 - y1, 1) if y2 > y1 else (y1 - y2, -1)
|
dy, ystep = (y2 - y1, 1) if y2 > y1 else (y1 - y2, -1)
|
||||||
col, row = x1, y1
|
col, row = x1, y1
|
||||||
if dx < dy:
|
if dx < dy:
|
||||||
t = - (dy >> 1)
|
t = - (dy >> 1)
|
||||||
while True:
|
while True:
|
||||||
self.drawPixel(col, row)
|
self.drawPixel(col, row, colorvect)
|
||||||
if row == y2:
|
if row == y2:
|
||||||
return
|
return
|
||||||
row += ystep
|
row += ystep
|
||||||
|
@ -371,7 +388,7 @@ class TFT:
|
||||||
else:
|
else:
|
||||||
t = - (dx >> 1)
|
t = - (dx >> 1)
|
||||||
while True:
|
while True:
|
||||||
self.drawPixel(col, row)
|
self.drawPixel(col, row, colorvect)
|
||||||
if col == x2:
|
if col == x2:
|
||||||
return
|
return
|
||||||
col += xstep
|
col += xstep
|
||||||
|
@ -383,44 +400,52 @@ class TFT:
|
||||||
# Draw a horizontal line with 1 Pixel width, from x,y to x + l - 1, y
|
# 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
|
# 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
|
if l < 0: # negative length, swap parameters
|
||||||
l = -l
|
l = -l
|
||||||
x -= l
|
x -= l
|
||||||
self.setXY(x, y, x + l - 1, y) # set display window
|
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
|
# 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
|
# 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
|
if l < 0: # negative length, swap parameters
|
||||||
l = -l
|
l = -l
|
||||||
y -= l
|
y -= l
|
||||||
self.setXY(x, y, x, y + l - 1) # set display window
|
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
|
# Draw rectangle from x1, y1, to x2, y2
|
||||||
# Straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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:
|
if x1 > x2:
|
||||||
t = x1; x1 = x2; x2 = t
|
x1, x2 = x2, x1
|
||||||
if y1 > y2:
|
if y1 > y2:
|
||||||
t = y1; y1 = y2; y2 = t
|
y1, y2 = y2, y1
|
||||||
self.drawHLine(x1, y1, x2 - x1 + 1)
|
self.drawHLine(x1, y1, x2 - x1 + 1, color)
|
||||||
self.drawHLine(x1, y2, x2 - x1 + 1)
|
self.drawHLine(x1, y2, x2 - x1 + 1, color)
|
||||||
self.drawVLine(x1, y1, y2 - y1 + 1)
|
self.drawVLine(x1, y1, y2 - y1 + 1, color)
|
||||||
self.drawVLine(x2, y1, y2 - y1 + 1)
|
self.drawVLine(x2, y1, y2 - y1 + 1, color)
|
||||||
#
|
#
|
||||||
# Fill rectangle
|
# Fill rectangle
|
||||||
# Almost straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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:
|
if x1 > x2:
|
||||||
t = x1; x1 = x2; x2 = t
|
x1, x2 = x2, x1
|
||||||
if y1 > y2:
|
if y1 > y2:
|
||||||
t = y1; y1 = y2; y2 = t
|
y1, y2 = y2, y1
|
||||||
self.setXY(x1, y1, x2, y2) # set display window
|
self.setXY(x1, y1, x2, y2) # set display window
|
||||||
if color:
|
if color:
|
||||||
TFT_io.fillSCR_AS(bytearray(color), (x2 - x1 + 1) * (y2 - y1 + 1))
|
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
|
# Draw smooth rectangle from x1, y1, to x2, y2
|
||||||
# Straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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:
|
if x1 > x2:
|
||||||
t = x1; x1 = x2; x2 = t
|
x1, x2 = x2, x1
|
||||||
if y1 > y2:
|
if y1 > y2:
|
||||||
t = y1; y1 = y2; y2 = t
|
y1, y2 = y2, y1
|
||||||
if (x2-x1) > 4 and (y2-y1) > 4:
|
if (x2-x1) > 4 and (y2-y1) > 4:
|
||||||
self.drawPixel(x1 + 2,y1 + 1)
|
if color is None:
|
||||||
self.drawPixel(x1 + 1,y1 + 2)
|
colorvect = self.colorvect
|
||||||
self.drawPixel(x2 - 2,y1 + 1)
|
else:
|
||||||
self.drawPixel(x2 - 1,y1 + 2)
|
colorvect = bytearray(color)
|
||||||
self.drawPixel(x1 + 2,y2 - 1)
|
self.drawPixel(x1 + 2,y1 + 1, colorvect)
|
||||||
self.drawPixel(x1 + 1,y2 - 2)
|
self.drawPixel(x1 + 1,y1 + 2, colorvect)
|
||||||
self.drawPixel(x2 - 2,y2 - 1)
|
self.drawPixel(x2 - 2,y1 + 1, colorvect)
|
||||||
self.drawPixel(x2 - 1,y2 - 2)
|
self.drawPixel(x2 - 1,y1 + 2, colorvect)
|
||||||
self.drawHLine(x1 + 3, y1, x2 - x1 - 5)
|
self.drawPixel(x1 + 2,y2 - 1, colorvect)
|
||||||
self.drawHLine(x1 + 3, y2, x2 - x1 - 5)
|
self.drawPixel(x1 + 1,y2 - 2, colorvect)
|
||||||
self.drawVLine(x1, y1 + 3, y2 - y1 - 5)
|
self.drawPixel(x2 - 2,y2 - 1, colorvect)
|
||||||
self.drawVLine(x2, y1 + 3, y2 - y1 - 5)
|
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
|
# Fill smooth rectangle from x1, y1, to x2, y2
|
||||||
# Straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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:
|
if x1 > x2:
|
||||||
t = x1; x1 = x2; x2 = t
|
t = x1; x1 = x2; x2 = t
|
||||||
if y1 > y2:
|
if y1 > y2:
|
||||||
|
@ -461,22 +490,27 @@ class TFT:
|
||||||
if (x2-x1) > 4 and (y2-y1) > 4:
|
if (x2-x1) > 4 and (y2-y1) > 4:
|
||||||
for i in range(((y2 - y1) // 2) + 1):
|
for i in range(((y2 - y1) // 2) + 1):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
self.drawHLine(x1 + 3, y1 + i, x2 - x1 - 5)
|
self.drawHLine(x1 + 3, y1 + i, x2 - x1 - 5, color)
|
||||||
self.drawHLine(x1 + 3, y2 - i, x2 - x1 - 5)
|
self.drawHLine(x1 + 3, y2 - i, x2 - x1 - 5, color)
|
||||||
elif i == 1:
|
elif i == 1:
|
||||||
self.drawHLine(x1 + 2, y1 + i, x2 - x1 - 3)
|
self.drawHLine(x1 + 2, y1 + i, x2 - x1 - 3, color)
|
||||||
self.drawHLine(x1 + 2, y2 - i, x2 - x1 - 3)
|
self.drawHLine(x1 + 2, y2 - i, x2 - x1 - 3, color)
|
||||||
elif i == 2:
|
elif i == 2:
|
||||||
self.drawHLine(x1 + 1, y1 + i, x2 - x1 - 1)
|
self.drawHLine(x1 + 1, y1 + i, x2 - x1 - 1, color)
|
||||||
self.drawHLine(x1 + 1, y2 - i, x2 - x1 - 1)
|
self.drawHLine(x1 + 1, y2 - i, x2 - x1 - 1, color)
|
||||||
else:
|
else:
|
||||||
self.drawHLine(x1, y1 + i, x2 - x1 + 1)
|
self.drawHLine(x1, y1 + i, x2 - x1 + 1, color)
|
||||||
self.drawHLine(x1, y2 - i, x2 - x1 + 1)
|
self.drawHLine(x1, y2 - i, x2 - x1 + 1, color)
|
||||||
#
|
#
|
||||||
# draw a circle at x, y with radius
|
# draw a circle at x, y with radius
|
||||||
# Straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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
|
f = 1 - radius
|
||||||
ddF_x = 1
|
ddF_x = 1
|
||||||
|
@ -484,10 +518,10 @@ class TFT:
|
||||||
x1 = 0
|
x1 = 0
|
||||||
y1 = radius
|
y1 = radius
|
||||||
|
|
||||||
self.drawPixel(x, y + radius)
|
self.drawPixel(x, y + radius, colorvect)
|
||||||
self.drawPixel(x, y - radius)
|
self.drawPixel(x, y - radius, colorvect)
|
||||||
self.drawPixel(x + radius, y)
|
self.drawPixel(x + radius, y, colorvect)
|
||||||
self.drawPixel(x - radius, y)
|
self.drawPixel(x - radius, y, colorvect)
|
||||||
|
|
||||||
while x1 < y1:
|
while x1 < y1:
|
||||||
if f >= 0:
|
if f >= 0:
|
||||||
|
@ -497,47 +531,67 @@ class TFT:
|
||||||
x1 += 1
|
x1 += 1
|
||||||
ddF_x += 2
|
ddF_x += 2
|
||||||
f += ddF_x
|
f += ddF_x
|
||||||
self.drawPixel(x + x1, y + y1)
|
self.drawPixel(x + x1, y + y1, colorvect)
|
||||||
self.drawPixel(x - x1, y + y1)
|
self.drawPixel(x - x1, y + y1, colorvect)
|
||||||
self.drawPixel(x + x1, y - y1)
|
self.drawPixel(x + x1, y - y1, colorvect)
|
||||||
self.drawPixel(x - x1, y - y1)
|
self.drawPixel(x - x1, y - y1, colorvect)
|
||||||
self.drawPixel(x + y1, y + x1)
|
self.drawPixel(x + y1, y + x1, colorvect)
|
||||||
self.drawPixel(x - y1, y + x1)
|
self.drawPixel(x - y1, y + x1, colorvect)
|
||||||
self.drawPixel(x + y1, y - x1)
|
self.drawPixel(x + y1, y - x1, colorvect)
|
||||||
self.drawPixel(x - y1, y - x1)
|
self.drawPixel(x - y1, y - x1, colorvect)
|
||||||
#
|
#
|
||||||
# fill a circle at x, y with radius
|
# fill a circle at x, y with radius
|
||||||
# Straight port from the UTFT Library at Rinky-Dink Electronics
|
# 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
|
# 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
|
r_square = radius * radius * 4
|
||||||
for y1 in range (-(radius * 2), 1):
|
for y1 in range (-(radius * 2), 1):
|
||||||
y_square = y1 * y1
|
y_square = y1 * y1
|
||||||
for x1 in range (-(radius * 2), 1):
|
for x1 in range (-(radius * 2), 1):
|
||||||
if x1*x1+y_square <= r_square:
|
if x1*x1+y_square <= r_square:
|
||||||
x1i = x1//2
|
x1i = x1 // 2
|
||||||
y1i = y1//2
|
y1i = y1 // 2
|
||||||
self.drawHLine(x + x1i, y + y1i, 2 * (-x1i))
|
self.drawHLine(x + x1i, y + y1i, 2 * (-x1i), color)
|
||||||
self.drawHLine(x + x1i, y - y1i, 2 * (-x1i))
|
self.drawHLine(x + x1i, y - y1i, 2 * (-x1i), color)
|
||||||
break;
|
break;
|
||||||
#
|
#
|
||||||
# Draw a bitmap at x,y with size sx, sy
|
# Draw a bitmap at x,y with size sx, sy
|
||||||
# mode determines the type of expected data
|
# mode determines the type of expected data
|
||||||
# mode = 0: The data must contain 3 bytes/pixel red/green/blue
|
# mode = 1: The data contains 1 bit per pixel, mapped to fg/bg color
|
||||||
# mode = 1: The data must contain 2 packed bytes/pixel blue/green/red in 565 format
|
# unless a colortable is provided
|
||||||
# mode = 2: The data contains 1 bit per pixel, mapped to fg/bg color
|
# 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)
|
self.setXY(x, y, x + sx - 1, y + sy - 1)
|
||||||
if mode == 0:
|
if mode == 24:
|
||||||
TFT_io.displaySCR_AS(data, sx * sy)
|
TFT_io.displaySCR_AS(data, sx * sy)
|
||||||
elif mode == 1:
|
elif mode == 16:
|
||||||
TFT_io.displaySCR565_AS(data, sx * sy)
|
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:
|
elif mode == 2:
|
||||||
control = bytearray(self.BGcolorvect + self.colorvect + chr(self.transparency))
|
if colortable is None:
|
||||||
TFT_io.displaySCR_bitmap(data, sx*sy, control, 0)
|
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
|
# set scroll area to the region between the first and last line
|
||||||
#
|
#
|
||||||
|
@ -546,22 +600,51 @@ class TFT:
|
||||||
[(tfa >> 8) & 0xff, tfa & 0xff,
|
[(tfa >> 8) & 0xff, tfa & 0xff,
|
||||||
(vsa >> 8) & 0xff, vsa & 0xff,
|
(vsa >> 8) & 0xff, vsa & 0xff,
|
||||||
(bfa >> 8) & 0xff, bfa & 0xff]), 6)
|
(bfa >> 8) & 0xff, bfa & 0xff]), 6)
|
||||||
self.scroll_fta = tfa
|
self.scroll_tfa = tfa
|
||||||
self.scroll_vsa = vsa
|
self.scroll_vsa = vsa
|
||||||
self.scroll_bfa = bfa
|
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
|
# set the line which is displayed first
|
||||||
#
|
#
|
||||||
def setScrollStart(self, lline):
|
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
|
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
|
# Set text position
|
||||||
#
|
#
|
||||||
def setTextPos(self, x, y, clip = False, scroll = True):
|
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_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:
|
if clip and (self.text_x + clip) < self.text_width:
|
||||||
self.text_width = self.text_x + clip
|
self.text_width = self.text_x + clip
|
||||||
self.text_scroll = scroll
|
self.text_scroll = scroll
|
||||||
|
@ -569,62 +652,84 @@ class TFT:
|
||||||
# Get text position
|
# Get text position
|
||||||
#
|
#
|
||||||
def getTextPos(self):
|
def getTextPos(self):
|
||||||
return (self.text_x, self.text_y)
|
return (self.text_x, self.text_yabs)
|
||||||
#
|
#
|
||||||
# Set Text Style
|
# Set Text Style
|
||||||
#
|
#
|
||||||
def setTextStyle(self, fgcolor = None, bgcolor = None, transparency = None, font = None, gap = None):
|
def setTextStyle(self, fgcolor=None, bgcolor=None, transparency=None, font=None, gap=None):
|
||||||
if font != None:
|
if font is not None:
|
||||||
self.text_font = font
|
self.text_font = font
|
||||||
if font:
|
|
||||||
self.text_rows, self.text_cols, nchar, first = font.get_properties() #
|
self.text_rows, self.text_cols, nchar, first = font.get_properties() #
|
||||||
if transparency != None:
|
if transparency is not None:
|
||||||
self.transparency = transparency
|
self.transparency = transparency
|
||||||
if gap != None:
|
if gap is not None:
|
||||||
self.text_gap = gap
|
self.text_gap = gap
|
||||||
self.text_color = bytearray(0)
|
self.text_color = bytearray(0)
|
||||||
if bgcolor != None:
|
if bgcolor is not None:
|
||||||
self.text_color += bytearray(bgcolor)
|
self.text_bgcolor = bgcolor
|
||||||
else:
|
if fgcolor is not None:
|
||||||
self.text_color += self.BGcolorvect
|
self.text_fgcolor = fgcolor
|
||||||
if fgcolor != None:
|
if transparency is not None:
|
||||||
self.text_color += bytearray(fgcolor)
|
|
||||||
else:
|
|
||||||
self.text_color += self.colorvect
|
|
||||||
if transparency != None:
|
|
||||||
self.transparency = transparency
|
self.transparency = transparency
|
||||||
self.text_color += bytearray([self.transparency])
|
self.text_color = (bytearray(self.text_bgcolor)
|
||||||
if gap != None:
|
+ bytearray(self.text_fgcolor)
|
||||||
|
+ bytearray([self.transparency]))
|
||||||
|
if gap is not None:
|
||||||
self.text_gap = gap
|
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
|
# Check, if a new line is to be opened
|
||||||
# if yes, advance, including scrolling, and clear line, if flags is set
|
# if yes, advance, including scrolling, and clear line, if flags is set
|
||||||
|
# Obsolete?
|
||||||
#
|
#
|
||||||
def printNewline(self):
|
def printNewline(self, clear = False):
|
||||||
self.text_y += self.text_rows
|
if (self.text_yabs + self.text_rows) >= (self.scroll_tfa + self.scroll_vsa): # does the line fit?
|
||||||
if (self.text_y + self.text_rows) > self.scroll_vsa: # does the line fit?
|
self.scroll(self.text_rows) # no. scroll
|
||||||
self.text_y = 0
|
else: # Yes, just advance pointers
|
||||||
newline = self.text_rows
|
self.text_yabs += self.text_rows
|
||||||
self.setScrollStart(newline)
|
self.setTextPos(self.text_x, self.text_yabs)
|
||||||
elif self.scroll_start > 0: # Scrolling has started
|
if clear:
|
||||||
newline = (self.scroll_start + self.text_rows) % self.scroll_vsa
|
self.printClrLine(2) # clear actual line
|
||||||
self.setScrollStart(newline)
|
|
||||||
#
|
#
|
||||||
# Carriage Return
|
# Carriage Return
|
||||||
#
|
#
|
||||||
def printCR(self): # clear to end of line
|
def printCR(self): # clear to end of line
|
||||||
self.text_x = 0
|
self.text_x = 0
|
||||||
#
|
#
|
||||||
# clear to end-of-line
|
# clear line modes
|
||||||
#
|
#
|
||||||
def printClrEOL(self): # clear to end of line
|
def printClrLine(self, mode = 0): # clear to end of line/bol/line
|
||||||
self.setXY(self.text_x, self.text_y,
|
if mode == 0:
|
||||||
self.text_width - self.text_x - 1, self.text_y + self.text_rows - 1) # set display window
|
self.setXY(self.text_x, self.text_y,
|
||||||
TFT_io.fillSCR_AS(self.text_color, self.text_width * self.text_rows)
|
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
|
# 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
|
len = 0
|
||||||
for c in s:
|
for c in s:
|
||||||
cols = self.printChar(c, bg_buf)
|
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
|
# 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
|
# get the charactes pixel bitmap and dimensions
|
||||||
if self.text_font:
|
if self.text_font:
|
||||||
fontptr, rows, cols = self.text_font.get_ch(ord(c))
|
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_x + cols > self.text_width: # does the char fit on the screen?
|
||||||
if self.text_scroll:
|
if self.text_scroll:
|
||||||
self.printCR() # No, then CR
|
self.printCR() # No, then CR
|
||||||
self.printNewline() # NL: advance to the next line
|
self.printNewline(True) # NL: advance to the next line
|
||||||
self.printClrEOL() # clear to end of line
|
|
||||||
else:
|
else:
|
||||||
return 0
|
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 self.transparency: # in case of transpareny, the frame buffer content is needed
|
||||||
if not bg_buf: # buffer allocation needed?
|
if not bg_buf: # buffer allocation needed?
|
||||||
bg_buf = bytearray(pix_count * 3) # sigh...
|
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
|
TFT_io.tft_read_cmd_data_AS(0x2e, bg_buf, pix_count * 3) # read background data
|
||||||
else:
|
else:
|
||||||
bg_buf = 0 # dummy assignment, since None is not accepted
|
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
|
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
|
#advance pointer
|
||||||
self.text_x += (cols + self.text_gap)
|
self.text_x += (cols + self.text_gap)
|
||||||
return cols + self.text_gap
|
return cols + self.text_gap
|
||||||
|
|
|
@ -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)
|
118
tft_gui/ui.py
118
tft_gui/ui.py
|
@ -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
|
|
|
@ -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)
|
# The MIT License (MIT)
|
||||||
#
|
#
|
||||||
|
@ -26,38 +26,29 @@ from font10 import font10
|
||||||
from tft import TFT, LANDSCAPE
|
from tft import TFT, LANDSCAPE
|
||||||
from usched import Sched
|
from usched import Sched
|
||||||
from touch import TOUCH
|
from touch import TOUCH
|
||||||
from slider import Slider
|
from ugui import Slider, Button, Dial, Label, CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
|
||||||
from button import Button
|
|
||||||
from displays import Dial, Label
|
|
||||||
from ui import CLIPPED_RECT, WHITE, BLACK, RED, GREEN, BLUE, YELLOW, GREY
|
|
||||||
from math import pi
|
from math import pi
|
||||||
|
|
||||||
# CALLBACKS
|
# CALLBACKS
|
||||||
# cb_end occurs when user stops touching the control
|
# cb_end occurs when user stops touching the control
|
||||||
def callback(slider, args):
|
def callback(slider, device):
|
||||||
print('{} returned {}'.format(args[0], slider.value()))
|
print('{} returned {}'.format(device, slider.value()))
|
||||||
|
|
||||||
def to_string(val):
|
def to_string(val):
|
||||||
return '{:3.1f} ohms'.format(val * 10)
|
return '{:3.1f} ohms'.format(val * 10)
|
||||||
|
|
||||||
def master_moved(slider, args):
|
def master_moved(slider, slave1, slave2, label):
|
||||||
val = slider.value()
|
val = slider.value()
|
||||||
slave1 = args[0]
|
|
||||||
slave1.value(val)
|
slave1.value(val)
|
||||||
slave2 = args[1]
|
|
||||||
slave2.value(val)
|
slave2.value(val)
|
||||||
label = args[2]
|
|
||||||
label.show(to_string(val))
|
label.show(to_string(val))
|
||||||
|
|
||||||
# Either slave has had its slider moved (by user or by having value altered)
|
# Either slave has had its slider moved (by user or by having value altered)
|
||||||
def slave_moved(slider, args):
|
def slave_moved(slider, label):
|
||||||
val = slider.value()
|
val = slider.value()
|
||||||
dial = args[0]
|
|
||||||
dial.delta = val
|
|
||||||
label = args[1]
|
|
||||||
label.show(to_string(val))
|
label.show(to_string(val))
|
||||||
|
|
||||||
def doquit(button, args):
|
def doquit(button):
|
||||||
button.objsched.stop()
|
button.objsched.stop()
|
||||||
|
|
||||||
# THREADS
|
# THREADS
|
||||||
|
@ -88,11 +79,11 @@ table = {'fontcolor' : WHITE,
|
||||||
}
|
}
|
||||||
# 'border' : 2,
|
# 'border' : 2,
|
||||||
|
|
||||||
def test(duration = 0):
|
def test():
|
||||||
print('Test TFT panel...')
|
print('Test TFT panel...')
|
||||||
objsched = Sched() # Instantiate the scheduler
|
objsched = Sched() # Instantiate the scheduler
|
||||||
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
mytft = TFT("SSD1963", "LB04301", LANDSCAPE)
|
||||||
mytouch = TOUCH("XPT2046", objsched, confidence=50)
|
mytouch = TOUCH("XPT2046", objsched, confidence=50, margin = 50)
|
||||||
mytft.backlight(100) # light on
|
mytft.backlight(100) # light on
|
||||||
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = RED,
|
Button(objsched, mytft, mytouch, (400, 240), font = font10, callback = doquit, fgcolor = RED,
|
||||||
height = 30, text = 'Quit', shape = CLIPPED_RECT)
|
height = 30, text = 'Quit', shape = CLIPPED_RECT)
|
||||||
|
@ -103,11 +94,11 @@ def test(duration = 0):
|
||||||
lstlbl.append(Label(mytft, (80 * n, 240), font = font10, **labels))
|
lstlbl.append(Label(mytft, (80 * n, 240), font = font10, **labels))
|
||||||
y = 5
|
y = 5
|
||||||
slave1 = Slider(objsched, mytft, mytouch, (80, y), font10,
|
slave1 = Slider(objsched, mytft, mytouch, (80, y), font10,
|
||||||
fgcolor = (0, 255, 0), cbe_args = ('Slave1',), cb_move = slave_moved, cbm_args = (dial1, 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,
|
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,
|
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(slave1, dial1))
|
||||||
objsched.add_thread(mainthread(slave2, dial2))
|
objsched.add_thread(mainthread(slave2, dial2))
|
||||||
objsched.run() # Run it!
|
objsched.run() # Run it!
|
Ładowanie…
Reference in New Issue