├── .gitignore ├── DEVELOPER.md ├── HARDWARE.md ├── IMAGES.md ├── LICENSE ├── PLOT.md ├── README.md ├── bmp_to_icon.py ├── pictures ├── IMG_2441_small.JPG ├── bode.JPG ├── buttons.JPG ├── dialog.JPG ├── dials.JPG ├── horiz_slider.JPG ├── icon2.JPG ├── nan.JPG ├── nyquist.JPG └── vert_sliders2.JPG └── tft ├── demos ├── __init__.py ├── buttontest.py ├── dialog.py ├── hst.py ├── ibt.py ├── knobtest.py ├── pt.py ├── screentest.py ├── vst.py └── vtest.py ├── driver ├── TFT_io.py ├── __init__.py ├── constants.py ├── plot.py ├── tft.py ├── tft_local.py ├── touch_bytecode.py └── ugui.py ├── fonts ├── __init__.py ├── font10.py └── font14.py ├── icons ├── __init__.py ├── checkbox.py ├── flash.py ├── gauge.py ├── iconswitch.py ├── radiobutton.py ├── threestate.py └── traffic.py ├── primitives ├── __init__.py └── delay_ms.py └── widgets ├── __init__.py ├── buttons.py ├── checkbox.py ├── dial.py ├── dialog.py ├── dropdown.py ├── horiz_slider.py ├── icon_buttons.py ├── icon_gauge.py ├── knob.py ├── label.py ├── led.py ├── listbox.py ├── meter.py ├── slider.py └── vectors.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Precompiled MicroPython files 10 | *.mpy 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | #Ipython Notebook 65 | .ipynb_checkpoints 66 | -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Developer notes for micropython-gui 2 | 3 | These notes are intended for those wishing to implement new controls or 4 | displays for this library. 5 | 6 | # Introduction 7 | 8 | GUI objects comprise displays and controls. They differ in that displays don't 9 | respond to touch whereas controls do. Displays are subclassed from the 10 | `NoTouch` class. Controls are subclassed from `Touchable` (which is itself 11 | subclassed from `NoTouch`). Understanding the functionality of these base 12 | classes is crucial to maintaining consistency and avoiding code duplication. 13 | 14 | GUI objects have a `value` method which is the principal user interface. It 15 | accesses an underlying `_value` variable which may be of various types. If 16 | called with no arguments it returns the current `_value`. Called with a single 17 | argument it checks for a difference with the current `_value` and, if there is 18 | one, updates `_value`. It updates the control using the `show` method outlined 19 | below. The intention here is that the value of controls and displays can 20 | programmatically be altered. On occasion the `value` method will need to be 21 | subclassed, for example if data validation, modification or type conversion is 22 | required. 23 | 24 | GUI objects must implement a `show` method capable of being called with no 25 | arguments. This should display the object: the underlying code will call it 26 | without arguments when a screen is displayed (for example after a screen 27 | change). By default it is also called without arguments when a control's value 28 | changes. Its behaviour can be modified by `NoTouch` bound variables 29 | `initial_value` and `redraw`. The aim here is to avoid the situation where a 30 | value change redraws the entire control which can lead to flicker, especially 31 | in the case of controls whose value changes in response to a drag. These 32 | variables are discussed in detail below. 33 | 34 | Touchable objects must implement a `_touched` method which determines how the 35 | control responds to a touch. The method receives the pixel coordinates of the 36 | touch (which will be within the control's bounding box). 37 | 38 | The other user interface is the `greyed_out` method. Called without args it 39 | returns the current disabled state of the control (`True` if disabled). Called 40 | with a boolean (allowable only for touchable controls) it greys out (`True`) or 41 | enables (`False`) the control, with its appearance reflecting the change. 42 | Disabled controls should still respond to programmatic changes (calls to 43 | `value`). 44 | 45 | # Overall design 46 | 47 | The `Screen` class is at the core of the GUI functionality, and some 48 | familiarity with it is necessary for designers of controls and displays. The 49 | class is initialised by the `setup` method, called from `tft_local.py`, which 50 | assigns a TFT (a `TFT_G` instance) and touch panel (`TOUCH` instance) to class 51 | variables `tft` and `objtouch`. 52 | 53 | The class maintains instance variables `touchlist` and `displaylist`. These 54 | contain the touchable controls and the display objects on that screen. Objects 55 | are automatically added to the correct list by the `addobject` class method, 56 | which is called by the `NoTouch` constructor. 57 | 58 | The `Screen` class has a class variable `current_screen` which holds the 59 | `Screen` instance which is current. Currency carries two implications. When GUI 60 | objects are instantiated, they are assigned to the current screen. And when a 61 | `Screen` is displayed by a call to `Screen.show`, the class method finds the 62 | correct instance in that variable; i.e. the current screen is the one visible 63 | on the physical hardware. 64 | 65 | The `Screen` class controls touch response. When the first `Screen` object is 66 | instatiated a task `Screen._touchtest` is initiated. This runs forever and 67 | tests whether the screen is being touched. If it is, it checks each touchable 68 | object on the current screen to see if it is currently capable of responding to 69 | touch. If it is, the object's `_trytouch` method is called. The `Touchable` 70 | class has a default method which serves for most controls. If the screen is not 71 | being touched, the task checks each touchable object on the current screen to 72 | see if it was formerly being touched. If it was, the formerly touched status is 73 | cleared down and the object's `_untouched` method is called. This enables 74 | release callbacks to be implemented. Again a default method is provided by the 75 | `Touchable` class. 76 | 77 | The `Screen` class also provides class methods for changing the current screen 78 | and reverting to the previous one. These clear the screen and call the 79 | `Screen.show` class method, which redraws all objects on the newly current 80 | screen. It does this via the object's `draw_border` and `show` methods, called 81 | without arguments. It also supports the `set_grey_style` class method and the 82 | `get_tft` class method. This provides access to the TFT. It accepts the 83 | greyed-out status of the control instance as its argument and instructs the 84 | `TFT_G` class of the correct greyed-out status to use. 85 | 86 | In practice, controls and displays access the TFT by means of the `tft` 87 | property of the `NoTouch` base class. This sets the greyed out status of the 88 | `TFT_G` and returns the TFT instance. 89 | 90 | Access to the TFT module is mediated by the `TFT_G` class. This is subclassed 91 | from the TFT module's `TFT` class. Its purpose is to handle the colors of 92 | greyed out controls: for consistency its graphics primitives are used 93 | throughout the GUI in preference to those of the underlying `TFT` class. 94 | 95 | # class NoTouch 96 | 97 | Constructor arguments (all mandatory, positional): 98 | 99 | 1. `location` 2-tuple defining the location of the top left corner of the 100 | control in screen coords. 101 | 2. `font` Font object to use (may be `None` if control has no text elements). 102 | 3. `height` Height in pixels. In the `Label` class it is `None` and computed 103 | in the constructor. 104 | 4. `width` Width in pixels. 105 | 5. `fgcolor` 3-tuple defining the foreground color, or `None`. If `None` the 106 | screen's foreground color is used. 107 | 6. `bgcolor` 3-tuple defining the background color, or `None`. If a color is 108 | provided the control will be filled with a background of this color (see 109 | `knobtest.py`). 110 | 7. `fontcolor` 3-tuple defining the font color, or `None`. If `None` the 111 | screen's foreground color is used. 112 | 8. `border` Integer or `None`. If an integer `n` is provided a single pixel 113 | border will be drawn around the control, separated from it by `n` pixels. 114 | 9. `value` The starting value for the contol. Data types are various, but 115 | clearly the `show` method must be able to display them. Floats are constrained 116 | to the 0 to 1.0 limits used throughout. 117 | 10. `initial_value` Typically `None`. This may be used to enable `show` to 118 | identify the first time the control has been displayed. It is stored in the 119 | `initial_value` bound variable and otherwise unused by the `Touchable` and 120 | `NoTouch` classes. 121 | 122 | Bound variables: 123 | 124 | * `fgcolor` Foreground color. See fontcolor below. 125 | * `bgcolor` Background color. See fontcolor below. 126 | * `fontcolor` These should be used in all methods, notably in constructors, as 127 | they reflect the defaults applied by the superclass where the user passed 128 | `None` to the constructor. 129 | * `screen` The screen object on which this instance should be drawn. This is 130 | set to the current screen by the `NoTouch` constructor. 131 | * `redraw` Set by the GUI system. When set the `show` method must redraw the 132 | entire control. If the `show` method is to avoid redrawing the entire control 133 | on every call, it should clear the flag down when drawing the elements which 134 | are intended to persist. 135 | * `location` The following are simply storage for the constructor args 136 | described above. 137 | * `_value` 138 | * `_initial_value` Available for any purpose, notably initialisation 139 | detection. 140 | * `font` 141 | * `height` 142 | * `width` 143 | * `fill` True if a `bgcolor` was provided to the constructor. 144 | * `visible` For compound cotrols only (pseudo controls consisting of more than 145 | one physical control). Currently only the `ButtonList` sets it `False` and 146 | `Button` honours it. If `False` the control will be invisible and insensitive 147 | to touch. 148 | * `_greyed_out` Always `False` in the case of displays (which don't respond to 149 | touch). 150 | * `border` Border width in pixels. 0 if there is no border. 151 | * `callback` Callback function on value change. Primarily for control classes: 152 | default is a null function. 153 | * `args` Args for above, default []. 154 | * `cb_end` Callback on touch release: for control classes. 155 | * `cbe_args` Args for above, default []. 156 | 157 | Methods: 158 | 159 | * `greyed_out` No args. Returns `True` if the control is disabled. 160 | * `show_if_current` No args. Calls the `show` method (defined in the subclass) 161 | if the control is on the current screen. Useful in custom `value` methods. 162 | * `value` The default user interface. Positional args `val` default None, 163 | `show` default `True`. If called without args, returns the control's current 164 | value. If `val` is provided it checks the type: if it is a `float` it is 165 | constrained to 0 <= val <= 1.0. If the value has changed it updates it, calls 166 | the callback, and calls the control's `show` method if it is on the current 167 | screen. The optional `show` argument is set `False` by the `show` method where 168 | that method calls the control's `value` method. This prevents needless 169 | recursion. 170 | 171 | Property: 172 | * `tft` Returns the `TFT_G` instance with greyed_out status set to that of 173 | `self`. 174 | 175 | # class Touchable (subclass of NoTouch) 176 | 177 | Constructor arguments (all mandatory, positional). These are as described 178 | above, except 10. 179 | 180 | 1. `location` 181 | 2. `font` 182 | 3. `height` 183 | 4. `width` 184 | 5. `fgcolor` 185 | 6. `bgcolor` 186 | 7. `fontcolor` 187 | 8. `border` 188 | 9. `value` 189 | 10. `can_drag` True if the control must respond to drag events. 190 | 11. `initial_value` 191 | 192 | Bound variables (not usually needed by subclasses): 193 | 194 | As per `NoTouch` plus: 195 | * `can_drag` True if the control must respond to drag events. Such events 196 | cause repeated calls to the `_touched` method. 197 | * `busy` This is used by controls which do not respond to drag events to 198 | ensure that the `_touched` method of such controls is called once only. It is 199 | set by `_trytouch` and cleared by the `Screen._touchtest` task when the touch 200 | ceases. 201 | * `was_touched` This is set by the `_trytouch` method and cleared by the 202 | `Screen._touchtest` task: it forms an interlock to ensure the `_untouched` 203 | method is called once only when a touch ends. 204 | 205 | Methods: 206 | 207 | As per`NoTouch` plus: 208 | * `greyed_out` Overridden method, optional boolean arg `val` default `None`. 209 | If the arg is not supplied, returns the current greyed-out status. If the arg 210 | is supplied, and differs from the current status, updates the current status, 211 | redraws the control and returns the new status. 212 | * `_untouched` Called by the `Screen._touchtest` task when the touch ceases. 213 | Calls the `cb_end` callback. Can be overridden in subclasses. 214 | * `_trytouch` Called by the `Screen._touchtest` task when the touch panel is 215 | touched and the object is on the current screen and responsive to touch. If 216 | the touch is within the object's bounding box, it calls the `_touched` method. 217 | All touchable controls must implement this method, which determines how the 218 | control should respond when touched. 219 | * `_set_callbacks` Called from subclass constructors to set the callback 220 | functions and args. 221 | 222 | # uasyncio interface 223 | 224 | When the first screen is displayed (using the `Screen.change` class method) a 225 | coroutine `Screen.monitor` is started. This starts the scheduler. The task 226 | immediately suspends, waiting on an `Event` (`Screen.is_shutdown`). The 227 | `Screen.shutdown` class method may be called from user code: its only purpose 228 | is to trigger the `Event` and allow the `Screen.monitor` task to complete. It 229 | cancels any tasks registered by the current screen, clears down the display, 230 | and leaves the driver and `uasyncio` in a state where another script can be run 231 | (subject to RAM). 232 | 233 | Termination of the primary `Task` restores the REPL. 234 | -------------------------------------------------------------------------------- /HARDWARE.md: -------------------------------------------------------------------------------- 1 | # Hardware notes 2 | 3 | The tft driver (comprising ``tft.py``, ``TFT_io.py`` and ``touch.py``) was developed by Robert 4 | Hammelrath (robert-hh on Github, roberthh on the MicroPython forum). These notes aim to augment his 5 | documentation with additional hardware details. Driver sites: 6 | [TFT driver](https://github.com/robert-hh/SSD1963-TFT-Library-for-PyBoard.git) 7 | [XPT2046 driver](https://github.com/robert-hh/XPT2046-touch-pad-driver-for-PyBoard.git) 8 | 9 | The easiest way to acquire these displays is to search eBay for SSD1963. 10 | 11 | # Minimal connections for TFT display 12 | 13 | These notes are intended for users not intending to use Robert Hammelrath's excellent PCB, which 14 | has features including the ability to power down the display to conserve power when not in use. 15 | 16 | Most 4.3 inch and 5 inch displays have a 40 way 0.1 inch connector with the following pinout. 17 | Pins are usually marked on the PCB silkscreen. Pins marked - are defined as no connect. Signals in 18 | parentheses are not required by the driver and are no-connect. Most 7 inch displays use a different 19 | connector. Note that the 5 inch 800*480 display is not currently supported by the TFT driver. 20 | 21 | The table below is laid out looking at the underside of the display with the plug to the left and 22 | most of the PCB to the right. L and R denote the left and right TFT connector pins when viewed 23 | this way. Check your own display to ensure it conforms to this pinout! 24 | 25 | 26 | | Signal | Pyboard | L | R | Pyboard | Signal | 27 | |:-------:|:-------:|:---:|:---:|:-------:|:--------:| 28 | | - | | 20 | 40 | | - | 29 | | LED-A | Y3 [1]| 19 | 39 | | - | 30 | | V-LED | Note [2]| 18 | 38 | | (SD_CS) | 31 | | REST | Y9 | 17 | 37 | | (SD_DIN) | 32 | | (FCS)[3]| | 16 | 36 | | (SD_CLK) | 33 | | CS | Gnd | 15 | 35 | | (SD_DO) | 34 | | (DB15) | | 14 | 34 | Y1 | T_IRQ | 35 | | (DB14) | | 13 | 33 | Y2 | T_DO | 36 | | (DB13) | | 12 | 32 | | - | 37 | | (DB12) | | 11 | 31 | X11 | T_DIN | 38 | | (DB11) | | 10 | 30 | Gnd | T_CS | 39 | | (DB10) | | 9 | 29 | X12 | T_CLK | 40 | | (DB9) | | 8 | 28 | X8 | DB7 | 41 | | (DB8) | | 7 | 27 | X7 | DB6 | 42 | | Rd | Y10 | 6 | 26 | X6 | DB5 | 43 | | Wr | Y11 | 5 | 25 | X5 | DB4 | 44 | | Rs | Y12 | 4 | 24 | X4 | DB3 | 45 | | - | | 3 | 23 | X3 | DB2 | 46 | | 3.3V | 3.3V | 2 | 22 | X2 | DB1 | 47 | | Gnd | Gnd | 1 | 21 | X1 | DB0 | 48 | 49 | Notes: 50 | [1] Pin 19 controls backlight brightness. The TFT driver supports brightness control using PWM 51 | at 500Hz. Not all panels allow PWM in which case it would normally be wired to 3.3V. Consult panel 52 | manual. 53 | [2] Backlight power source. Some displays show no connection on pin 18. Others require 3.3V or 54 | more: consult the panel manual for the power requirements. 55 | [3] This is a chip select for an onboard 2MB Flash chip. It is unused by the driver and the chip is 56 | not fitted to all display models. 57 | 58 | The TFT driver tft.py uses pin Y3 and timer 4 for backlight brightness control. Pin 19 can be 59 | linked to 3.3V if full brightness is always required. It also uses pin Y4 for power control: with 60 | suitable hardware this enables power to be conserved by powering the display down when not in use. 61 | If the display is powered down it requires re-initialisation by calling ``tft.tft_init()``. The 62 | touch panel requires no initialisation. 63 | 64 | Signal descriptions: 65 | Signals T_* are the touch panel controller chip connections. 66 | Signals (SD_*) are for the onboard SD card which is unused. 67 | DB0-15 is the parallel bidirectional data bus (lower 8 bits only are used). 68 | Rd, Wr, Rs (read, write, command/data) are the active low bus control signals. 69 | CS is the active low chip select (always selected). 70 | REST is the controller reset. 71 | 72 | # Power 73 | 74 | I have run a 4.3 inch display from the Pyboard's 3.3V output for short periods but only when 75 | running the Pyboard from 5V. It's not recommended as the 4.3 inch display takes over 200mA at full 76 | baklight brightness which, with the Pyboard's own current draw, pushes the power dissipation of the 77 | Pyboard's regulator. 78 | 79 | One solution is to power the Pyboard and the display from a common 3.3V supply. An efficient way of 80 | doing this is to use a Pololu switch mode DC converter: part numbers S7V8F3 or D24V5F3 are 81 | suggested depending on your input voltage range. If you adopt this approach there is an issue if 82 | using a USB connection to the Pyboard. In this case the Pyboard draws power from the USB cable, and 83 | its 3.3V pin becomes an output: the Pyboard 3.3V pin should be disconnected from the Pololu 84 | regulator output, which then powers the display only. 85 | 86 | A debugging option which avoids this difficulty is to ignore USB and to use UART(1) via an FTDI 87 | adaptor, connecting only RXD, TXD and GND and powering the entire system from the 3.3V source. 88 | 89 | If the system is always to be powered by USB an option is to connect a Pololu S7V8F3 to the Pyboard 90 | Vin pin (which will be an output in this case), and use its 3.3V output to power the display. If 91 | doing this, use a good quality USB cable capable of handing the current involved. 92 | 93 | # Pyboard Free Pins 94 | 95 | ### Unused pins 96 | 97 | The following I/O pins are unused by the interface and code: 98 | 99 | Y5-Y8, X9, X10, X17-X22, P18-P21 (LED pins) 100 | Ranges are inclusive. 101 | 102 | ### Pins Y3 and Y4 103 | 104 | The TFT driver's use of these pins is optional. If not using power control hardware edit ``tft_local.py`` 105 | to initialise the display with ``power_control = False``. This will free pin Y4. 106 | 107 | Pin Y3 is configured on first use of the TFT class ``backlight()`` method: to use this pin for 108 | other purposes simply avoid calling this method. This also leaves timer(4) free for use. 109 | -------------------------------------------------------------------------------- /IMAGES.md: -------------------------------------------------------------------------------- 1 | #Sample Images 2 | 3 | These are based on the 4.3 inch display. 4 | 5 | #Puhbuttons 6 | 7 | ![Buttons](pictures/buttons.JPG) 8 | 9 | #Vertical sliders 10 | 11 | ![Vertical sliders](pictures/vert_sliders2.JPG) 12 | 13 | #Horizontal sliders 14 | 15 | ![Horizontal sliders](pictures/horiz_slider.JPG) 16 | 17 | #Rotary controls and displays 18 | 19 | ![Dials](pictures/dials.JPG) 20 | 21 | #Icon Controls 22 | 23 | ![Icon Buttons](pictures/icon2.JPG) 24 | 25 | #Modal Dialog Box 26 | 27 | ![Dialog box](pictures/dialog.JPG) 28 | 29 | #Plot Module 30 | 31 | These images are of a proof-of-concept of using the Pyboard to generate a sine 32 | wave and using the ADC's to read the response of the network under test. 33 | 34 | ##Control Panel 35 | 36 | ![Control](pictures/nan.JPG) 37 | 38 | ##Bode Plot (Cartesian graph) 39 | 40 | ![Bode](pictures/bode.JPG) 41 | 42 | ##Nyquist Plot (Polar graph) 43 | 44 | ![Nyquist](pictures/nyquist.JPG) 45 | 46 | To anyone interested in this project this was a lash-up with the network (a two 47 | pole passive filter) wired between a DAC output and an ADC input. A serious 48 | solution would require I/O electronics which I have designed but not implemented 49 | to date. The dynamic range could be substantially improved: 60dB was my target. 50 | As it stands the phase measurements are dubious when the amplitude is more than 51 | 40dB down. An auto-ranging amplifier is required. 52 | 53 | Note to any MicroPython developers. Would that the firmware supported concurrent 54 | synchronous reading of two ADC's. This would enable phase to be read from an 55 | arbitrary pair of incoming signals. Phase measurements were only possible 56 | because the signal is of a known frequency, constant for the duration of each 57 | reading. 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Peter Hinch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PLOT.md: -------------------------------------------------------------------------------- 1 | # plot module 2 | 3 | This provides a rudimentary means of displaying two dimensional Cartesian (xy) and polar graphs on 4 | TFT displays using the SSD1963 driver. It is an optional extension to the MicroPython GUI library: 5 | this should be installed, configured and tested before use. 6 | 7 | # Python files 8 | 9 | 1. plot.py The plot library 10 | 2. pt.py Test program. 11 | 12 | plot.py should be installed as frozen bytecode. 13 | 14 | # concepts 15 | 16 | ## Graph classes 17 | 18 | A user program first instantiates a graph object (``PolarGraph`` or ``CartesianGraph``). This 19 | creates an empty graph image upon which one or more curves may be plotted. Graphs are GUI display 20 | objects: they do not respond to touch. 21 | 22 | ## Curve classes 23 | 24 | The user program then instantiates one or more curves (``Curve`` or ``PolarCurve``) as appropriate 25 | to the graph. Curves may be assigned colors to distinguish them. 26 | 27 | A curve is plotted by means of a user defined ``populate`` callback. This assigns points to the 28 | curve in the order in which they are to be plotted. The curve will be displayed on the graph as a 29 | sequence of straight line segments between successive points. 30 | 31 | ## Coordinates 32 | 33 | Graph objects are sized and positioned in terms of TFT screen pixel coordinates, with (0, 0) being 34 | the top left corner of the display, with x increasing to the right and y increasing downwards. 35 | 36 | Scaling is provided on Cartesian curves enabling user defined ranges for x and 37 | y values. Points lying outside of the defined range will produce lines which 38 | are clipped at the graph boundary. 39 | 40 | Points on polar curves are defined as Python ``complex`` types and should lie 41 | within the unit circle. Points which are out of range may be plotted beyond the 42 | unit circle but will be clipped to the rectangular graph boundary. 43 | 44 | # Graph classes 45 | 46 | ## Class CartesianGraph 47 | 48 | Constructor. 49 | Mandatory positional argument: 50 | 1. ``location`` 2-tuple defining position. 51 | 52 | Keyword only arguments (all optional): 53 | * ``height`` Dimension of the bounding box. Default 250 pixels. 54 | * ``width`` Dimension of the bounding box. Default 250 pixels. 55 | * ``fgcolor`` Color of the axis lines. Defaults to WHITE. 56 | * ``bgcolor`` Background color of graph. Defaults to system background. 57 | * ``border`` Width of border. Default ``None``: no border will be drawn. 58 | * ``gridcolor`` Color of grid. Default LIGHTGREEN. 59 | * ``xdivs`` Number of divisions (grid lines) on x axis. Default 10. 60 | * ``ydivs`` Number of divisions on y axis. Default 10. 61 | * ``xorigin`` Location of origin in terms of grid divisions. Default 5. 62 | * ``yorigin`` As ``xorigin``. The default of 5, 5 with 10 grid lines on each axis puts the origin 63 | at the centre of the graph. Settings of 0, 0 would be used to plot positive values only. 64 | 65 | Method: 66 | * ``clear`` Removes all curves from the graph and re-displays the grid. 67 | 68 | ## Class PolarGraph 69 | 70 | Constructor. 71 | Mandatory positional argument: 72 | 1. ``location`` 2-tuple defining position. 73 | 74 | Keyword only arguments (all optional): 75 | * ``height`` Dimension of the square bounding box. Default 250 pixels. 76 | * ``fgcolor`` Color of foreground (the axis lines). Defaults to WHITE. 77 | * ``bgcolor`` Background color of object. Defaults to system background. 78 | * ``border`` Width of border. Default ``None``: no border will be drawn. 79 | * ``gridcolor`` Color of grid. Default LIGHTGREEN. 80 | * ``adivs`` Number of angle divisions per quadrant. Default 3. 81 | * ``rdivs`` Number radius divisions. Default 4. 82 | 83 | Method: 84 | * ``clear`` Removes all curves from the graph and re-displays the grid. 85 | 86 | # Curve classes 87 | 88 | ## class Curve 89 | 90 | The Cartesian curve constructor takes the following positional arguments: 91 | 92 | Mandatory argument: 93 | 1. ``graph`` The ``CartesianGraph`` instance. 94 | 95 | Optional arguments: 96 | 2. ``populate`` A callback function to populate the curve. See below. Default: a null function. 97 | 3. ``args`` List or tuple of arguments for ``populate`` callback. Default []. 98 | 4. ``origin`` 2-tuple containing x and y values for the origin. Default (0, 0). 99 | 5. ``excursion`` 2-tuple containing scaling values for x and y. Default (1, 1). 100 | 6. ``color`` Default YELLOW. 101 | 102 | Methods: 103 | * ``point`` Arguments x, y. Defaults ``None``. Adds a point to the curve. If a 104 | prior point exists a line will be drawn between it and the current point. If a 105 | point is out of range or if either arg is ``None`` no line will be drawn. 106 | Passing no args enables discontinuous curves to be plotted. 107 | * ``show`` No args. This can be used to redraw a curve which has been erased by the graph's 108 | ``clear`` method. In practice likely to be used when plotting changing data from sensors. 109 | 110 | The ``populate`` callback may take one or more positional arguments. The first argument is always 111 | the ``Curve`` instance. Subsequent arguments are any specified in the curve's constructor's 112 | ``args``. It should repeatedly call the curve's ``point`` method to plot the curve before 113 | returning. 114 | 115 | If ``populate`` is not provided the curve may be plotted by successive calls to the ``point`` 116 | method. This may be of use where data points are acquired in real time, and realtime plotting is 117 | required. 118 | 119 | ### Scaling 120 | 121 | To plot x values from 1000 to 4000 we would set the ``origin`` x value to 1000 and the ``excursion`` 122 | x value to 3000. The ``excursion`` values scale the plotted values to fit the corresponding axis. 123 | 124 | ## class PolarCurve 125 | 126 | The constructor takes the following positional arguments: 127 | 128 | Mandatory argument: 129 | 1. ``graph`` The ``CartesianGraph`` instance. 130 | 131 | Optional arguments: 132 | 2. ``populate`` A callback function to populate the curve. See below. Default: a null function. 133 | 3. ``args`` List or tuple of arguments for ``populate`` callback. Default []. 134 | 4. ``color`` Default YELLOW. 135 | 136 | Methods: 137 | * ``point`` Argument z, default ``None``. Normally a ``complex``. Adds a point 138 | to the curve. If a prior point exists a line will be drawn between it and the 139 | current point. If the arg is ``None`` or lies outside the unit circle no line 140 | will be drawn. Passing no args enables discontinuous curves to be plotted. 141 | * ``show`` No args. This can be used to redraw a curve which has been erased by the graph's 142 | ``clear`` method. In practice likely to be used when plotting changing data from sensors. 143 | 144 | The ``populate`` callback may take one or more positional arguments. The first argument is always 145 | the ``Curve`` instance. Subsequent arguments are any specified in the curve's constructor's 146 | ``args``. It should repeatedly call the curve's ``point`` method to plot the curve before 147 | returning. 148 | 149 | If ``populate`` is not provided the curve may be plotted by successive calls to the ``point`` 150 | method. This may be of use where data points are acquired in real time, and realtime plotting is 151 | required. 152 | 153 | ### Scaling 154 | 155 | Complex points should lie within the unit circle to be drawn within the grid. 156 | -------------------------------------------------------------------------------- /bmp_to_icon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Convert a BMP source file to Python source. 4 | 5 | # Copyright Robert Hammelrath and Peter Hinch 2016 6 | # Released under the MIT licence 7 | # Files created by any graphic tool exporting bmp files, e.g. gimp 8 | # the colour depth may be 1, 4, 8, 16, or 24 pixels, lower sizes preferred 9 | 10 | # Usage: 11 | # ./bmp_to_icon checkbox_on.bmp checkbox_off.bmp 12 | # puts files into a single Python file defaulting to icons.py (-o name can override default) 13 | # with a dictionary 'icons' indexed by a number. 14 | # The generated icon pathon script also defines a function get_icon(index) 15 | # for accessing an icon, which returns a tuple which can directly supplied 16 | # into the drawBitmap() function of the tft lib. 17 | # ------------------------- 18 | # Example: Assuming an icons file called icons.py. 19 | # then the sript usign it could look like: 20 | # 21 | # import tft 22 | # import icons 23 | # ..... 24 | # mytft = tft.TFT() 25 | # ..... 26 | # mytft.drawBitmap(x1, y1, *icons.get_icon(0)) # draw the first icon at location x1, y1 27 | # mytft.drawBitmap(x2, y2, *icons.get_icon(1)) # draw the scond icon at location x2, y2 28 | 29 | 30 | import os 31 | import argparse 32 | from struct import unpack 33 | 34 | # define symbol shared with repetive call as global 35 | icon_width = None 36 | icon_height = None 37 | icon_colortable = None 38 | icon_colors = None 39 | icon_table = [] 40 | no_icons = 0 41 | 42 | # split read, due to the bug in the SD card library, avoid reading 43 | # more than 512 bytes at once, at a performance penalty 44 | # required if the actual file position is not a multiple of 4 45 | def split_read(f, buf, n): 46 | BLOCKSIZE = 512 ## a sector 47 | mv = memoryview(buf) 48 | bytes_read = 0 49 | for i in range(0, n - BLOCKSIZE, BLOCKSIZE): 50 | bytes_read += f.readinto(mv[i:i + BLOCKSIZE]) 51 | if bytes_read < n and (n - bytes_read) <= BLOCKSIZE: 52 | bytes_read += f.readinto(mv[bytes_read:n]) 53 | return bytes_read 54 | 55 | 56 | def getname(sourcefile): 57 | return os.path.basename(os.path.splitext(sourcefile)[0]) 58 | 59 | 60 | def process(f, outfile): 61 | # 62 | global icon_width 63 | global icon_height 64 | global icon_colortable 65 | global icon_colors 66 | global icon_table 67 | global no_icons 68 | 69 | BM, filesize, res0, offset = unpack(" 0.8) 86 | 87 | # Either slave has had its slider moved (by user or by having value altered) 88 | def slave_moved(self, slider, label): 89 | val = slider.value() 90 | if val > 0.8: 91 | slider.color(RED) 92 | else: 93 | slider.color(GREEN) 94 | label.value(to_string(val)) 95 | 96 | def quit(self, button): 97 | Screen.shutdown() 98 | 99 | def cb_en_dis(self, button, disable): 100 | for item in self.lst_en_dis: 101 | item.greyed_out(disable) 102 | # Meters move linearly between random values 103 | async def test_meter(self, meter): 104 | oldvalue = 0 105 | await asyncio.sleep(0) 106 | while True: 107 | val = pyb.rng() / 2**30 108 | steps = 20 109 | delta = (val - oldvalue) / steps 110 | for _ in range(steps): 111 | oldvalue += delta 112 | meter.value(oldvalue) 113 | await asyncio.sleep_ms(100) 114 | 115 | def test(): 116 | print('Test TFT panel...') 117 | setup() 118 | Screen.change(HorizontalSliderScreen) 119 | 120 | test() 121 | -------------------------------------------------------------------------------- /tft/demos/ibt.py: -------------------------------------------------------------------------------- 1 | # ibt.py Test/demo of icon based pushbutton classes for Pybboard TFT GUI 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | import uasyncio as asyncio 8 | import gc 9 | 10 | from tft.driver.ugui import Screen 11 | from tft.driver.constants import * 12 | from tft.driver.tft_local import setup 13 | gc.collect() 14 | from tft.widgets.icon_buttons import IconButton, IconRadioButtons 15 | from tft.widgets.label import Label 16 | from tft.widgets.icon_gauge import IconGauge 17 | gc.collect() 18 | from tft.fonts import font10 19 | from tft.fonts import font14 20 | gc.collect() 21 | from tft.icons import radiobutton, checkbox, flash, threestate, iconswitch, gauge, traffic # icon files 22 | gc.collect() 23 | 24 | def lr(n): # y coordinate from logical row 25 | return 10 + 50 * n 26 | 27 | # Quit button CB 28 | def quit(button): 29 | Screen.shutdown() 30 | 31 | class IconButtonScreen(Screen): 32 | def __init__(self): 33 | super().__init__() 34 | # Label common attributes 35 | labels = { 'width' : 70, 36 | 'fontcolor' : WHITE, 37 | 'border' : 2, 38 | 'fgcolor' : RED, 39 | 'bgcolor' : (0, 40, 0), 40 | 'font' : font14, 41 | } 42 | # Static labels 43 | Label((90, 0), font = font10, value = 'Flashing buttons') 44 | Label((90, 18), font = font10, value = '(RH responds to long press)') 45 | Label((244, lr(1) + 5), font = font10, value = 'Reset radio button') 46 | Label((244, lr(2) + 5), font = font10, value = 'Reset checkbox') 47 | Label((244, lr(3) + 5), font = font10, value = 'Disable rb, checkbox') 48 | Label((370, 243), font = font14, value = 'Quit') 49 | # Dynamic labels 50 | self.lstlbl = [] 51 | for n in range(4): 52 | self.lstlbl.append(Label((400, lr(n)), **labels)) 53 | # Flashing buttons (RH one responds to long press) 54 | IconButton((10, lr(0)), icon_module = flash, flash = 1.0, 55 | callback = self.callback, args = ('A', 0)) 56 | IconButton((50, lr(0)), icon_module = flash, flash = 1.0, 57 | callback = self.callback, args = ('Short', 0), 58 | lp_callback = self.callback, lp_args = ('Long', 0), onrelease = False) 59 | # Quit button 60 | IconButton((420, 240), icon_module = radiobutton, callback = quit) 61 | # Radio buttons 62 | rb = IconRadioButtons(callback = self.callback) 63 | rb0 = rb.add_button((10, lr(1)), icon_module = radiobutton, args = ('1', 1)) 64 | rb.add_button((50, lr(1)), icon_module = radiobutton, args = ('2', 1)) 65 | rb.add_button((90, lr(1)), icon_module = radiobutton, args = ('3', 1)) 66 | rb.add_button((130, lr(1)), icon_module = radiobutton, args = ('4', 1)) 67 | # Checkbox 68 | cb = IconButton((10, lr(2)), icon_module = threestate, toggle = True, 69 | callback = self.cbcb, args =(2,)) 70 | # Traffic light state change button 71 | IconButton((10, lr(4)), icon_module = traffic, toggle = True) 72 | # Reset buttons 73 | IconButton((200, lr(1)), icon_module = radiobutton, callback = self.rb_cancel, args = (rb, rb0)) 74 | IconButton((200, lr(2)), icon_module = radiobutton, callback = self.cb_cancel, args = (cb,)) 75 | # Switch 76 | sw = IconButton((10, lr(3)), icon_module = iconswitch, callback = self.cbswitch, toggle = True, args = (3,)) 77 | # Disable Checkbox 78 | IconButton((200, lr(3)), icon_module = checkbox, toggle = True, 79 | callback = self.cb_en_dis, args =((cb, rb, sw),)) 80 | # Gauge 81 | ig = IconGauge((80, lr(5)), icon_module = gauge) 82 | self.reg_task(self.maintask(ig)) 83 | 84 | 85 | # CALLBACKS 86 | # Default CB displays text on a label 87 | def callback(self, button, arg, idx_label): 88 | self.lstlbl[idx_label].value(arg) 89 | 90 | # Checkbox CB 91 | def cbcb(self, checkbox, idx_label): 92 | self.lstlbl[idx_label].value(('False', '???', 'True')[checkbox.value()]) 93 | 94 | # CB for button 'reset radio button': test radiobutton response to value change 95 | def rb_cancel(self, button, radiobutton, rb0): 96 | radiobutton.value(rb0) 97 | 98 | # CB for button 'reset checkbox': test checkbox response to forced change 99 | def cb_cancel(self, button, checkbox): 100 | checkbox.value(0) 101 | 102 | # Switch CB 103 | def cbswitch(self, button, idx_label): 104 | self.lstlbl[idx_label].value(str(button.value())) 105 | 106 | # Enable/disable CB 107 | def cb_en_dis(self, button, itemlist): 108 | for item in itemlist: 109 | item.greyed_out(button.value() > 0) 110 | 111 | 112 | # THREAD: keep the gauge moving 113 | async def maintask(self, objgauge): 114 | INC = 0.05 115 | oldvalue = 0 116 | inc = INC 117 | await asyncio.sleep(0) 118 | while True: 119 | oldvalue += inc 120 | if oldvalue >= 1.0: 121 | oldvalue = 1.0 122 | inc = -INC 123 | elif oldvalue <= 0: 124 | oldvalue = 0 125 | inc = INC 126 | objgauge.value(oldvalue) 127 | await asyncio.sleep_ms(100) 128 | 129 | def test(): 130 | print('Testing TFT...') 131 | setup() 132 | Screen.change(IconButtonScreen) 133 | 134 | test() 135 | -------------------------------------------------------------------------------- /tft/demos/knobtest.py: -------------------------------------------------------------------------------- 1 | # knobtest.py Test/demo of Knob and Dial classes for Pybboard TFT GUI 2 | 3 | # Adapted for (and requires) uasyncio V3 4 | 5 | # Released under the MIT License (MIT). See LICENSE. 6 | # Copyright (c) 2016-2020 Peter Hinch 7 | 8 | from math import pi 9 | from tft.driver.ugui import Screen 10 | from tft.driver.constants import * 11 | from tft.driver.tft_local import setup 12 | 13 | from tft.widgets.dial import Dial 14 | from tft.widgets.knob import Knob 15 | from tft.widgets.dropdown import Dropdown 16 | from tft.widgets.listbox import Listbox 17 | from tft.widgets.buttons import Button, ButtonList 18 | from tft.widgets.label import Label 19 | 20 | from tft.fonts import font14 21 | from tft.fonts import font10 22 | 23 | class KnobScreen(Screen): 24 | def __init__(self): 25 | super().__init__() 26 | Button((390, 240), font = font14, callback = self.quit, fgcolor = RED, 27 | text = 'Quit', shape = RECTANGLE, width = 80, height = 30) 28 | self.dial = Dial((120, 0), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7)) 29 | k0 = Knob((0, 0), fgcolor = GREEN, bgcolor=(0, 0, 80), color = (168,63,63), border = 2, 30 | cb_end = self.callback, cbe_args = ['Knob1'], cb_move = self.knob_moved, cbm_args = (0,)) 31 | k1 = Knob((0, 120), fgcolor = WHITE, border = 2, arc = pi * 1.5, 32 | cb_end = self.callback, cbe_args = ['Knob2'], cb_move = self.knob_moved, cbm_args = (1,)) 33 | # Dropdown 34 | self.lbl_dd = Label((120, 120), font = font14, width = 100, border = 2, bgcolor = (0, 40, 0), fgcolor = RED) 35 | self.dropdown = Dropdown((280, 0), font = font14, width = 100, callback = self.cbdb, 36 | elements = ('Dog', 'Cat', 'Rat', 'Goat', 'Snake', 'Pig')) 37 | 38 | Button((280, 70), font = font14, callback = self.set_dropdown, fgcolor = BLUE, 39 | text = 'Reset', shape = RECTANGLE, width = 80, height = 30) # Test of set by value 40 | Button((280, 120), font = font14, callback = self.set_bytext, args = ('Snake',), fgcolor = CYAN, 41 | fontcolor = BLACK, text = 'Snake', shape = RECTANGLE, width = 80, height = 30) # test set by text 42 | # Listbox 43 | self.listbox = Listbox((370, 70), font = font14, width = 105, 44 | bgcolor = GREY, fgcolor = YELLOW, select_color = BLUE, 45 | elements = ('aardvark', 'zebra', 'armadillo', 'warthog'), 46 | callback = self.cblb) 47 | # On/Off toggle grey style 48 | self.lbl_style = Label((170, 210), font = font10, value = 'Current style: grey') 49 | bstyle = ButtonList(self.cb_style) 50 | bstyle.add_button((170, 240), font = font14, fontcolor = WHITE, height = 30, width = 90, 51 | fgcolor = RED, shape = RECTANGLE, text = 'Dim', args = (False,)) 52 | bstyle.add_button((170, 240), font = font14, fontcolor = WHITE, height = 30, width = 90, 53 | fgcolor = GREEN, shape = RECTANGLE, text = 'Grey', args = (True,)) 54 | # On/Off toggle enable/disable 55 | bs = ButtonList(self.cb_en_dis) 56 | self.lst_en_dis = (bstyle, k0, k1, self.dropdown, self.listbox) 57 | bs.add_button((280, 240), font = font14, fontcolor = BLACK, height = 30, width = 90, 58 | fgcolor = GREEN, shape = RECTANGLE, text = 'Disable', args = (True,)) 59 | bs.add_button((280, 240), font = font14, fontcolor = BLACK, height = 30, width = 90, 60 | fgcolor = RED, shape = RECTANGLE, text = 'Enable', args = (False,)) 61 | 62 | # CALLBACKS 63 | # cb_end occurs when user stops touching the control 64 | def callback(self, knob, control_name): 65 | print('{} returned {}'.format(control_name, knob.value())) 66 | 67 | def knob_moved(self, knob, pointer): 68 | val = knob.value() # range 0..1 69 | self.dial.value(2 * (val - 0.5) * pi, pointer) 70 | 71 | def quit(self, button): 72 | Screen.shutdown() 73 | 74 | def cb_en_dis(self, button, disable): 75 | for item in self.lst_en_dis: 76 | item.greyed_out(disable) 77 | 78 | def cb_style(self, button, desaturate): 79 | self.lbl_style.value(''.join(('Current style: ', 'grey' if desaturate else 'dim'))) 80 | Screen.set_grey_style(desaturate = desaturate) 81 | 82 | def cbdb(self, dropdown): 83 | self.lbl_dd.value(dropdown.textvalue()) 84 | 85 | def cblb(self, listbox): 86 | print(listbox.textvalue()) 87 | 88 | def set_dropdown(self, button): 89 | self.dropdown.value(0) 90 | 91 | def set_bytext(self, button, txt): 92 | self.dropdown.textvalue(txt) 93 | 94 | def test(): 95 | print('Test TFT panel...') 96 | setup() 97 | Screen.change(KnobScreen) 98 | 99 | test() 100 | -------------------------------------------------------------------------------- /tft/demos/pt.py: -------------------------------------------------------------------------------- 1 | # pt.py Test/demo of graph plotting extension for Pybboard TFT GUI 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | import uasyncio as asyncio 8 | from math import sin, cos, pi 9 | from cmath import rect 10 | 11 | from tft.driver.plot import PolarGraph, PolarCurve, CartesianGraph, Curve 12 | from tft.driver.ugui import Screen 13 | from tft.driver.constants import * 14 | from tft.driver.tft_local import setup 15 | 16 | from tft.widgets.label import Label 17 | from tft.widgets.buttons import Button 18 | 19 | from tft.fonts import font14 20 | from tft.fonts import font10 21 | 22 | # STANDARD BUTTONS 23 | 24 | def quitbutton(x, y): 25 | def quit(button): 26 | Screen.shutdown() 27 | return Button((x, y), height = 30, font = font14, callback = quit, fgcolor = RED, 28 | text = 'Quit', shape = RECTANGLE, width = 80) 29 | 30 | def fwdbutton(x, y, screen, text='Next'): 31 | def fwd(button, screen): 32 | Screen.change(screen) 33 | return Button((x, y), height = 30, font = font14, callback = fwd, args = [screen], fgcolor = RED, 34 | text = text, shape = RECTANGLE, width = 80) 35 | 36 | def backbutton(x, y): 37 | def back(button): 38 | Screen.back() 39 | return Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = back, 40 | fgcolor = CYAN, text = 'Back', shape = RECTANGLE, width = 80) 41 | 42 | def clearbutton(x, y, graph): 43 | def clear(button): 44 | graph.clear() 45 | return Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = clear, 46 | fgcolor = GREEN, text = 'Clear', shape = RECTANGLE, width = 80) 47 | 48 | def refreshbutton(x, y, curvelist): 49 | def refresh(button): 50 | for curve in curvelist: 51 | curve.show() 52 | return Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = refresh, 53 | fgcolor = GREEN, text = 'Refresh', shape = RECTANGLE, width = 80) 54 | 55 | # SCREEN CREATION 56 | 57 | # Tests overlaying a plot with another screen 58 | class BackScreen(Screen): 59 | def __init__(self): 60 | super().__init__() 61 | Label((0, 0), font = font10, value = 'Ensure back refreshes properly') 62 | backbutton(390, 242) 63 | 64 | # Base screen with pushbuttons to launch demos. 65 | class BaseScreen(Screen): 66 | def __init__(self): 67 | super().__init__() 68 | Label((0, 0), font = font14, value = 'plot module demo.') 69 | Label((0, 100), font = font10, value = 'RT: simulate realtime data acquisition.') 70 | Label((0, 140), font = font10, value = 'Over, Lines: test clipping.') 71 | fwdbutton(0, 242, PolarScreen, 'Polar') 72 | fwdbutton(100, 242, XYScreen, 'XY') 73 | fwdbutton(200, 242, RealtimeScreen, 'RT') 74 | fwdbutton(0, 200, PolarORScreen, 'Over') 75 | fwdbutton(100, 200, DiscontScreen, 'Lines') 76 | quitbutton(390, 242) 77 | 78 | # Simple polar plot. 79 | class PolarScreen(Screen): 80 | def __init__(self): 81 | super().__init__() 82 | backbutton(390, 242) 83 | fwdbutton(390, 0, BackScreen) 84 | g = PolarGraph((10, 10), border = 4) 85 | clearbutton(390, 70, g) 86 | curve = PolarCurve(g, self.populate) 87 | refreshbutton(390, 140, (curve,)) 88 | 89 | def populate(self, curve): 90 | def f(theta): 91 | return rect(sin(3 * theta), theta) # complex 92 | nmax = 150 93 | for n in range(nmax + 1): 94 | theta = 2 * pi * n / nmax 95 | curve.point(f(theta)) 96 | 97 | # Test clipping 98 | class PolarORScreen(Screen): 99 | def __init__(self): 100 | super().__init__() 101 | backbutton(390, 242) 102 | fwdbutton(390, 0, BackScreen) 103 | g = PolarGraph((10, 10), border = 4) 104 | clearbutton(390, 70, g) 105 | curve = PolarCurve(g, self.populate, (1,)) 106 | curve1 = PolarCurve(g, self.populate, (rect(1, pi/5),), color=RED) 107 | refreshbutton(390, 140, (curve, curve1)) 108 | 109 | def populate(self, curve, rot): 110 | def f(theta): 111 | return rect(1.15*sin(5 * theta), theta)*rot # complex 112 | nmax = 150 113 | for n in range(nmax + 1): 114 | theta = 2 * pi * n / nmax 115 | curve.point(f(theta)) 116 | 117 | # Simple Cartesian plot with asymmetric axis and two curves. 118 | class XYScreen(Screen): 119 | def __init__(self): 120 | super().__init__() 121 | backbutton(390, 242) 122 | fwdbutton(390, 0, BackScreen) 123 | g = CartesianGraph((10, 10), yorigin = 2) # Asymmetric y axis 124 | clearbutton(390, 70, g) 125 | curve1 = Curve(g, self.populate_1, (lambda x : x**3 + x**2 -x,)) # args demo 126 | curve2 = Curve(g, self.populate_2, color = RED) 127 | refreshbutton(390, 140, (curve1, curve2)) 128 | 129 | def populate_1(self, curve, func): 130 | x = -1 131 | while x < 1.01: 132 | y = func(x) 133 | curve.point(x, y) 134 | x += 0.1 135 | 136 | def populate_2(self, curve): 137 | x = -1 138 | while x < 1.01: 139 | y = x**2 140 | curve.point(x, y) 141 | x += 0.1 142 | 143 | # Test of discontinuous curves and those which provoke clipping 144 | class DiscontScreen(Screen): 145 | def __init__(self): 146 | super().__init__() 147 | backbutton(390, 242) 148 | fwdbutton(390, 0, BackScreen) 149 | g = CartesianGraph((10, 10)) 150 | clearbutton(390, 70, g) 151 | curve1 = Curve(g, self.populate_1, (1.1,)) 152 | curve2 = Curve(g, self.populate_1, (1.05,), color=RED) 153 | curve3 = Curve(g, self.populate_3, color=BLUE) 154 | refreshbutton(390, 140, (curve1, curve2, curve3)) 155 | 156 | def populate_3(self, curve): 157 | for x, y in ((-2, -0.2), (-2, 0.2), (-0.2, -2), (0.2, -2), (2, 0.2), (2, -0.2), (0.2, 2), (-0.2, 2)): 158 | curve.point(x, y) 159 | curve.point(0,0) 160 | curve.point() 161 | 162 | def populate_1(self, curve, mag): 163 | theta = 0 164 | delta = pi/32 165 | while theta <= 2 * pi: 166 | curve.point(mag*sin(theta), mag*cos(theta)) 167 | theta += delta 168 | 169 | # Simulate slow real time data acquisition and plotting 170 | class RealtimeScreen(Screen): 171 | def __init__(self): 172 | super().__init__() 173 | self.aqu_task = None 174 | backbutton(390, 242) 175 | fwdbutton(390, 0, BackScreen) 176 | cartesian_graph = CartesianGraph((10, 10)) 177 | self.clearbutton(390, 70, cartesian_graph) 178 | curve = Curve(cartesian_graph, self.populate) 179 | refreshbutton(390, 140, (curve,)) 180 | 181 | def populate(self, curve): 182 | self.aqu_task = asyncio.create_task(self.acquire(curve)) 183 | self.reg_task(self.aqu_task, True) 184 | 185 | async def acquire(self, curve): 186 | x = -1 187 | await asyncio.sleep(0) 188 | while x < 1.01: 189 | y = max(1 - x * x, 0) # possible precision issue 190 | curve.point(x, y ** 0.5) 191 | x += 0.05 192 | await asyncio.sleep_ms(250) 193 | x = 1 194 | while x > -1.01: 195 | y = max(1 - x * x, 0) 196 | curve.point(x, -(y ** 0.5)) 197 | x -= 0.05 198 | await asyncio.sleep_ms(250) 199 | 200 | def clearbutton(self, x, y, graph): 201 | def clear(button): 202 | self.aqu_task.cancel() 203 | graph.clear() 204 | return Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = clear, 205 | fgcolor = GREEN, text = 'Clear', shape = RECTANGLE, width = 80) 206 | 207 | 208 | def pt(): 209 | print('Testing plot module...') 210 | setup() 211 | Screen.change(BaseScreen) 212 | 213 | pt() 214 | -------------------------------------------------------------------------------- /tft/demos/screentest.py: -------------------------------------------------------------------------------- 1 | # screentest.py Test/demo of multiple screens for Pybboard TFT GUI 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | 8 | import uasyncio as asyncio 9 | from math import pi 10 | 11 | from tft.driver.constants import * 12 | from tft.driver.tft_local import setup 13 | from tft.driver.ugui import Screen 14 | 15 | from tft.widgets.knob import Knob 16 | from tft.widgets.dial import Dial 17 | from tft.widgets.label import Label 18 | from tft.widgets.buttons import Button, RadioButtons, ButtonList 19 | from tft.widgets.meter import Meter 20 | from tft.widgets.slider import Slider 21 | from tft.widgets.checkbox import Checkbox 22 | from tft.widgets.led import LED 23 | 24 | from tft.fonts import font14 25 | from tft.fonts import font10 26 | 27 | def to_string(val): 28 | return '{:3.1f} ohms'.format(val * 10) 29 | 30 | # STANDARD BUTTONS 31 | 32 | def quitbutton(x, y): 33 | def quit(button): 34 | Screen.shutdown() 35 | Button((x, y), height = 30, font = font14, callback = quit, fgcolor = RED, 36 | text = 'Quit', shape = RECTANGLE, width = 80) 37 | 38 | def fwdbutton(x, y, cls_screen, text='Next'): 39 | def fwd(button): 40 | Screen.change(cls_screen) 41 | Button((x, y), height = 30, font = font14, callback = fwd, fgcolor = RED, 42 | text = text, shape = RECTANGLE, width = 80) 43 | 44 | def backbutton(x, y): 45 | def back(button): 46 | Screen.back() 47 | Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = back, 48 | fgcolor = CYAN, text = 'Back', shape = RECTANGLE, width = 80) 49 | 50 | # SCREEN CREATION 51 | 52 | # Demo of on_open and on_hide methods 53 | class BackScreen(Screen): 54 | def __init__(self): 55 | super().__init__() 56 | Label((0, 0), font = font14, value = 'Ensure back refreshes properly') 57 | backbutton(390, 242) 58 | self.open_arg = 'Opening' 59 | self.hide_arg = 'Hiding' 60 | 61 | def on_open(self): 62 | print(self.open_arg) 63 | 64 | def on_hide(self): 65 | print(self.hide_arg) 66 | 67 | class TaskScreen(Screen): 68 | def __init__(self): 69 | super().__init__() 70 | self.tasks = [] # Control cancellation explicitly 71 | Label((0, 0), font = font14, value = 'Green dial runs only') 72 | Label((0, 30), font = font14, value = 'when screen is visible') 73 | Label((0, 120), font = font14, value = "Yellow dial's value is") 74 | Label((0, 150), font = font14, value = 'computed continuously.') 75 | self.dial1 = Dial((350, 10), fgcolor = GREEN, border = 2, pointers = (0.9, 0.7)) 76 | self.dial2 = Dial((350, 120), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7)) 77 | self.pause = False # asyncio can't pause coros so handle at application level 78 | self.tasks.append(asyncio.create_task(self.mainthread(self.dial1, True))) 79 | self.tasks.append(asyncio.create_task(self.mainthread(self.dial2))) 80 | 81 | fwdbutton(0, 242, BackScreen) 82 | self.backbutton(390, 242) 83 | 84 | def on_open(self): 85 | print('Start green dial') 86 | self.pause = False 87 | 88 | def on_hide(self): 89 | print('Stop green dial') 90 | self.pause = True 91 | 92 | async def mainthread(self, dial, can_pause=False): 93 | angle = 0 94 | await asyncio.sleep(0) 95 | while True: 96 | await asyncio.sleep_ms(200) 97 | if not (can_pause and self.pause): 98 | delta = 0.2 99 | angle += pi * 2 * delta / 10 100 | dial.value(angle) 101 | dial.value(angle /10, 1) 102 | 103 | def backbutton(self, x, y): 104 | def back(button): 105 | for task in self.tasks: 106 | task.cancel() 107 | self.tasks = [] 108 | Screen.back() 109 | Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = back, 110 | fgcolor = CYAN, text = 'Back', shape = RECTANGLE, width = 80) 111 | 112 | class BaseScreen(Screen): 113 | def __init__(self): 114 | super().__init__() 115 | Label((0, 0), font = font14, value = 'Multiple screen demonstration.') 116 | fwdbutton(0, 242, KnobScreen, 'Knobs') 117 | fwdbutton(100, 242, SliderScreen, 'Sliders') 118 | fwdbutton(200, 242, AssortedScreen, 'Various') 119 | fwdbutton(0, 100, TaskScreen, 'Tasks') 120 | quitbutton(390, 242) 121 | 122 | class KnobScreen(Screen): 123 | def __init__(self): 124 | super().__init__() 125 | backbutton(390, 242) 126 | fwdbutton(0, 242, BackScreen) 127 | labels = { 'width' : 70, 128 | 'fontcolor' : WHITE, 129 | 'border' : 2, 130 | 'fgcolor' : RED, 131 | 'bgcolor' : (0, 40, 0), 132 | } 133 | 134 | lstlbl = [] 135 | for n in range(2): 136 | lstlbl.append(Label((120, 120 + 40 * n), font = font10, **labels)) 137 | lbl_1 = Label((120, 120), font = font10, **labels) 138 | lbl_2 = Label((120, 160), font = font10, **labels) 139 | meter1 = Meter((320, 0), font=font10, legends=('0','5','10'), pointercolor = YELLOW, fgcolor = GREEN) 140 | dial1 = Dial((120, 0), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7)) 141 | Knob((0, 0), fgcolor = GREEN, bgcolor=(0, 0, 80), color = (168,63,63), border = 2, 142 | cb_end = self.callback, cbe_args = ['Knob1'], cb_move = self.knob_moved, cbm_args = [dial1, 0, lbl_1, meter1]) 143 | Knob((0, 120), fgcolor = WHITE, border = 2, cb_move = self.knob_moved, cbm_args = [dial1, 1, lbl_2], 144 | cb_end = self.callback, cbe_args = ['Knob2'], arc = pi * 1.5) 145 | 146 | def callback(self, knob, control_name): 147 | print('{} returned {}'.format(control_name, knob.value())) 148 | 149 | def knob_moved(self, knob, dial, pointer, label, meter=None): 150 | val = knob.value() # range 0..1 151 | dial.value(2 * (val - 0.5) * pi, pointer) 152 | label.value('{:3.1f}'.format(val)) 153 | if meter is not None: 154 | meter.value(val) 155 | 156 | class SliderScreen(Screen): 157 | def __init__(self): 158 | super().__init__() 159 | # Common args for the labels 160 | labels = { 'width' : 70, 161 | 'fontcolor' : WHITE, 162 | 'border' : 2, 163 | 'fgcolor' : RED, 164 | 'bgcolor' : (0, 40, 0), 165 | } 166 | # Common arguments for all three sliders 167 | table = {'fontcolor' : WHITE, 168 | 'legends' : ('0', '5', '10'), 169 | } 170 | backbutton(390, 242) 171 | fwdbutton(0, 242, BackScreen) 172 | self.lstlbl = [] 173 | for n in range(3): 174 | self.lstlbl.append(Label((80 * n, 215), font = font10, **labels)) 175 | y = 5 176 | self.slave1 = Slider((80, y), font = font10, 177 | fgcolor = GREEN, cb_end = self.callback, cbe_args = ('Slave1',), cb_move = self.slave_moved, cbm_args = (1,), **table) 178 | self.slave2 = Slider((160, y), font = font10, 179 | fgcolor = GREEN, cb_end = self.callback, cbe_args = ('Slave2',), cb_move = self.slave_moved, cbm_args = (2,), **table) 180 | master = Slider((0, y), font = font10, 181 | fgcolor = YELLOW, cb_end = self.callback, cbe_args = ('Master',), cb_move = self.master_moved, 182 | cbm_args = (0,), value=0.5, border = 2, **table) 183 | 184 | # cb_end occurs when user stops touching the control 185 | def callback(self, slider, device): 186 | print('{} returned {}'.format(device, slider.value())) 187 | 188 | def master_moved(self, slider, idx_label): 189 | val = slider.value() 190 | self.slave1.value(val) 191 | self.slave2.value(val) 192 | self.lstlbl[idx_label].value(to_string(val)) 193 | 194 | # Either slave has had its slider moved (by user or by having value altered) 195 | def slave_moved(self, slider, idx_label): 196 | val = slider.value() 197 | self.lstlbl[idx_label].value(to_string(val)) 198 | 199 | class AssortedScreen(Screen): 200 | def __init__(self): 201 | super().__init__() 202 | labels = { 'width' : 70, 203 | 'fontcolor' : WHITE, 204 | 'border' : 2, 205 | 'fgcolor' : RED, 206 | 'bgcolor' : (0, 40, 0), 207 | 'font' : font14, 208 | } 209 | radiobuttons = [ 210 | {'text' : '1', 'args' : ['1']}, 211 | {'text' : '2', 'args' : ['2']}, 212 | {'text' : '3', 'args' : ['3']}, 213 | {'text' : '4', 'args' : ['4']}, 214 | ] 215 | buttonlist = [ 216 | {'fgcolor' : GREEN, 'shape' : CLIPPED_RECT, 'text' : 'Start', 'args' : ['Live']}, 217 | {'fgcolor' : RED, 'shape' : CLIPPED_RECT, 'text' : 'Stop', 'args' : ['Die']}, 218 | ] 219 | 220 | backbutton(390, 242) 221 | fwdbutton(0, 242, BackScreen) 222 | lstlbl = [] 223 | for n in range(4): 224 | lstlbl.append(Label((350, 40 * n), **labels)) 225 | self.led = LED((440, 0), border = 2) 226 | Checkbox((300, 0), callback = self.cbcb, args = [lstlbl[0], False]) 227 | Checkbox((300, 40), fillcolor = RED, callback = self.cbcb, args = [lstlbl[1], True]) 228 | 229 | # On/Off toggle 230 | x = 1 231 | bs = ButtonList(self.callback) 232 | bs0 = None 233 | for t in buttonlist: # Buttons overlay each other at same location 234 | t['args'].append(lstlbl[2]) 235 | button = bs.add_button((x, 120), font = font14, fontcolor = BLACK, **t) 236 | if bs0 is None: 237 | bs0 = button 238 | # Radio buttons 239 | x = 1 240 | rb = RadioButtons(BLUE, self.callback) # color of selected button 241 | for t in radiobuttons: 242 | t['args'].append(lstlbl[3]) 243 | button = rb.add_button((x, 180), font = font14, fontcolor = WHITE, 244 | fgcolor = (0, 0, 90), height = 40, width = 40, **t) 245 | x += 60 246 | 247 | def callback(self, button, arg, label): # Callback for radio button 248 | label.value(arg) 249 | 250 | def cbcb(self, checkbox, label, test): # Callback for checkbox 251 | if test: 252 | self.led.value(checkbox.value()) 253 | else: 254 | color = RED if checkbox.value() else YELLOW 255 | self.led.color(color) 256 | if checkbox.value(): 257 | label.value('True') 258 | else: 259 | label.value('False') 260 | 261 | def test(): 262 | print('Test TFT panel...') 263 | setup() 264 | Screen.change(BaseScreen) # Run it! 265 | 266 | test() 267 | -------------------------------------------------------------------------------- /tft/demos/vst.py: -------------------------------------------------------------------------------- 1 | # vst.py Demo/test program for vertical slider class for Pyboard TFT GUI 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | import uasyncio as asyncio 8 | from math import pi 9 | 10 | from tft.driver.constants import * 11 | from tft.driver.tft_local import setup 12 | from tft.driver.ugui import Screen 13 | 14 | from tft.widgets.dial import Dial 15 | from tft.widgets.label import Label 16 | from tft.widgets.buttons import Button, ButtonList 17 | from tft.widgets.slider import Slider 18 | 19 | from tft.fonts import font14 20 | from tft.fonts import font10 21 | 22 | 23 | def to_string(val): 24 | return '{:3.1f} ohms'.format(val * 10) 25 | 26 | def quit(button): 27 | Screen.shutdown() 28 | 29 | 30 | class VerticalSliderScreen(Screen): 31 | def __init__(self): 32 | super().__init__() 33 | # Common args for the labels 34 | labels = { 'width' : 70, 35 | 'fontcolor' : WHITE, 36 | 'border' : 2, 37 | 'fgcolor' : RED, 38 | 'bgcolor' : (0, 40, 0), 39 | } 40 | # Common args for all three sliders 41 | table = {'fontcolor' : WHITE, 42 | 'legends' : ('0', '5', '10'), 43 | 'cb_end' : self.callback, 44 | } 45 | btnquit = Button((390, 240), font = font14, callback = quit, fgcolor = RED, 46 | text = 'Quit', shape = RECTANGLE, width = 80, height = 30) 47 | self.dial1 = Dial((350, 10), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7)) 48 | self.dial2 = Dial((350, 120), fgcolor = YELLOW, border = 2, pointers = (0.9, 0.7)) 49 | self.lstlbl = [] 50 | for n in range(3): 51 | self.lstlbl.append(Label((80 * n, 240), font = font10, **labels)) 52 | y = 5 53 | self.slave1 = Slider((80, y), font = font10, 54 | fgcolor = GREEN, cbe_args = ('Slave1',), cb_move = self.slave_moved, cbm_args = (1,), **table) 55 | self.slave2 = Slider((160, y), font = font10, 56 | fgcolor = GREEN, cbe_args = ('Slave2',), cb_move = self.slave_moved, cbm_args = (2,), **table) 57 | master = Slider((0, y), font = font10, 58 | fgcolor = YELLOW, cbe_args = ('Master',), cb_move = self.master_moved, value=0.5, border = 2, **table) 59 | self.reg_task(self.thread1()) 60 | self.reg_task(self.thread2()) 61 | # On/Off toggle: enable/disable quit button and one slider 62 | bs = ButtonList(self.cb_en_dis) 63 | lst_en_dis = [self.slave1, btnquit] 64 | button = bs.add_button((280, 240), font = font14, fontcolor = BLACK, height = 30, width = 90, 65 | fgcolor = GREEN, shape = RECTANGLE, text = 'Disable', args = [True, lst_en_dis]) 66 | button = bs.add_button((280, 240), font = font14, fontcolor = BLACK, height = 30, width = 90, 67 | fgcolor = RED, shape = RECTANGLE, text = 'Enable', args = [False, lst_en_dis]) 68 | 69 | # CALLBACKS 70 | # cb_end occurs when user stops touching the control 71 | def callback(self, slider, device): 72 | print('{} returned {}'.format(device, slider.value())) 73 | 74 | def master_moved(self, slider): 75 | val = slider.value() 76 | self.slave1.value(val) 77 | self.slave2.value(val) 78 | self.lstlbl[0].value(to_string(val)) 79 | 80 | # Either slave has had its slider moved (by user or by having value altered) 81 | def slave_moved(self, slider, idx): 82 | val = slider.value() 83 | self.lstlbl[idx].value(to_string(val)) 84 | 85 | def cb_en_dis(self, button, disable, itemlist): 86 | for item in itemlist: 87 | item.greyed_out(disable) 88 | 89 | # THREADS 90 | async def thread1(self): 91 | angle = 0 92 | while True: 93 | await asyncio.sleep_ms(100) 94 | delta = self.slave1.value() 95 | angle += pi * 2 * delta / 10 96 | self.dial1.value(angle) 97 | self.dial1.value(angle /10, 1) 98 | 99 | async def thread2(self): 100 | angle = 0 101 | while True: 102 | await asyncio.sleep_ms(100) 103 | delta = self.slave2.value() 104 | angle += pi * 2 * delta / 10 105 | self.dial2.value(angle) 106 | self.dial2.value(angle /10, 1) 107 | 108 | def test(): 109 | print('Test TFT panel...') 110 | setup() 111 | Screen.set_grey_style(desaturate = False) # dim 112 | Screen.change(VerticalSliderScreen) # Run it! 113 | 114 | test() 115 | -------------------------------------------------------------------------------- /tft/demos/vtest.py: -------------------------------------------------------------------------------- 1 | # vtest.py Test/demo of VectorDial 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2019-2020 Peter Hinch 5 | 6 | # Updated for uasyncio V3 7 | 8 | import urandom 9 | import time 10 | from cmath import rect, pi 11 | import uasyncio as asyncio 12 | 13 | from tft.driver.ugui import Screen 14 | from tft.driver.constants import * 15 | from tft.driver.tft_local import setup 16 | 17 | from tft.widgets.buttons import Button, ButtonList 18 | from tft.widgets.label import Label 19 | from tft.widgets.vectors import Pointer, VectorDial 20 | 21 | from tft.fonts import font10, font14 22 | 23 | def quitbutton(x=399, y=242): 24 | def quit(button): 25 | Screen.shutdown() 26 | Button((x, y), height = 30, font = font14, callback = quit, fgcolor = RED, 27 | text = 'Quit', shape = RECTANGLE, width = 80) 28 | 29 | def fwdbutton(x, y, cls_screen, text='Next'): 30 | def fwd(button): 31 | Screen.change(cls_screen) 32 | Button((x, y), height = 30, font = font14, callback = fwd, fgcolor = RED, 33 | text = text, shape = RECTANGLE, width = 100) 34 | 35 | def backbutton(x=399, y=242): 36 | def back(button): 37 | Screen.back() 38 | Button((x, y), height = 30, font = font14, fontcolor = BLACK, callback = back, 39 | fgcolor = CYAN, text = 'Back', shape = RECTANGLE, width = 80) 40 | 41 | class BackScreen(Screen): 42 | def __init__(self): 43 | super().__init__() 44 | Label((0, 0), font = font14, value = 'Ensure back refreshes properly') 45 | backbutton() 46 | 47 | # Create a random vector. Interpolate between current vector and the new one. 48 | # Change pointer color dependent on magnitude. 49 | async def ptr_test(dial): 50 | ptr = Pointer(dial) 51 | v = 0j 52 | steps = 20 # No. of interpolation steps 53 | grv = lambda : urandom.getrandbits(16) / 2**15 - 1 # Random: range -1.0 to +1.0 54 | while True: 55 | v1 = grv() + 1j * grv() # Random vector 56 | dv = (v1 - v) / steps # Interpolation vector 57 | for _ in range(steps): 58 | v += dv 59 | mag = abs(v) 60 | if mag < 0.3: 61 | ptr.value(v, BLUE) 62 | elif mag < 0.7: 63 | ptr.value(v, GREEN) 64 | else: 65 | ptr.value(v, RED) 66 | await asyncio.sleep_ms(200) 67 | 68 | # Analog clock demo. Note this could also be achieved using the Dial class. 69 | async def aclock(dial, lbldate, lbltim): 70 | uv = lambda phi : rect(1, phi) # Return a unit vector of phase phi 71 | days = ('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 72 | 'Sunday') 73 | months = ('January', 'February', 'March', 'April', 'May', 'June', 'July', 74 | 'August', 'September', 'October', 'November', 'December') 75 | 76 | hrs = Pointer(dial) 77 | mins = Pointer(dial) 78 | secs = Pointer(dial) 79 | 80 | hstart = 0 + 0.7j # Pointer lengths. Position at top. 81 | mstart = 0 + 1j 82 | sstart = 0 + 1j 83 | 84 | while True: 85 | t = time.localtime() 86 | hrs.value(hstart * uv(-t[3] * pi/6 - t[4] * pi / 360), CYAN) 87 | mins.value(mstart * uv(-t[4] * pi/30), CYAN) 88 | secs.value(sstart * uv(-t[5] * pi/30), RED) 89 | lbltim.value('{:02d}.{:02d}.{:02d}'.format(t[3], t[4], t[5])) 90 | lbldate.value('{} {} {} {}'.format(days[t[6]], t[2], months[t[1] - 1], t[0])) 91 | await asyncio.sleep(1) 92 | 93 | class VScreen(Screen): 94 | def __init__(self): 95 | super().__init__() 96 | labels = {'fontcolor' : WHITE, 97 | 'border' : 2, 98 | 'fgcolor' : RED, 99 | 'bgcolor' : DARKGREEN, 100 | 'font' : font10, 101 | } 102 | 103 | fwdbutton(0, 242, BackScreen, 'Forward') 104 | quitbutton() 105 | # Set up random vector display with two pointers 106 | dial = VectorDial((0, 0), height = 200, ticks = 12, fgcolor = YELLOW, arrow = True) 107 | self.reg_task(ptr_test(dial)) 108 | self.reg_task(ptr_test(dial)) 109 | # Set up clock display: instantiate labels 110 | lbldate = Label((240, 210), width = 239, **labels) 111 | lbltim = Label((240, 235), width = 80, **labels) 112 | dial = VectorDial((240, 0), height = 200, ticks = 12, fgcolor = GREEN, pip = GREEN) 113 | self.reg_task(aclock(dial, lbldate, lbltim)) 114 | 115 | def test(): 116 | print('Test TFT panel...') 117 | setup() 118 | Screen.change(VScreen) 119 | 120 | test() 121 | -------------------------------------------------------------------------------- /tft/driver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-tft-gui/11837ce83df6b122a33bad1b472e9da5a1e40c22/tft/driver/__init__.py -------------------------------------------------------------------------------- /tft/driver/constants.py: -------------------------------------------------------------------------------- 1 | # constants.py Micropython GUI library for TFT displays: useful constants 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | CIRCLE = 1 8 | RECTANGLE = 2 9 | CLIPPED_RECT = 3 10 | 11 | WHITE = (255, 255, 255) 12 | BLACK = (0, 0, 0) 13 | RED = (255, 0, 0) 14 | GREEN = (0, 255, 0) 15 | BLUE = (0, 0, 255) 16 | YELLOW = (255, 255, 0) 17 | GREY = (100, 100, 100) 18 | MAGENTA = (255, 0, 255) 19 | CYAN = (0, 255, 255) 20 | LIGHTGREEN = (0, 80, 0) 21 | DARKGREEN = (0, 40, 0) 22 | LIGHTBLUE = (0, 0, 80) 23 | -------------------------------------------------------------------------------- /tft/driver/plot.py: -------------------------------------------------------------------------------- 1 | # plot.py Graph plotting extension for Pybboard TFT GUI 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import NoTouch, dolittle, Screen 8 | from tft.driver.constants import * 9 | from math import pi 10 | from cmath import rect 11 | from micropython import const 12 | 13 | # Line clipping outcode bits 14 | _TOP = const(1) 15 | _BOTTOM = const(2) 16 | _LEFT = const(4) 17 | _RIGHT = const(8) 18 | # Bounding box for line clipping 19 | _XMAX = const(1) 20 | _XMIN = const(-1) 21 | _YMAX = const(1) 22 | _YMIN = const(-1) 23 | 24 | 25 | class Curve(object): 26 | @staticmethod 27 | def _outcode(x, y): 28 | oc = _TOP if y > 1 else 0 29 | oc |= _BOTTOM if y < -1 else 0 30 | oc |= _RIGHT if x > 1 else 0 31 | oc |= _LEFT if x < -1 else 0 32 | return oc 33 | 34 | def __init__(self, graph, populate=dolittle, args=[], origin=(0, 0), excursion=(1, 1), color=YELLOW): 35 | self.graph = graph 36 | self.populate = populate 37 | self.callback_args = args 38 | self.origin = origin 39 | self.excursion = excursion 40 | self.color = color 41 | self.graph.addcurve(self) 42 | self.lastpoint = None 43 | self.newpoint = None 44 | 45 | def point(self, x=None, y=None): 46 | if x is None or y is None: 47 | self.newpoint = None 48 | self.lastpoint = None 49 | return 50 | 51 | self.newpoint = self._scale(x, y) # In-range points scaled to +-1 bounding box 52 | if self.lastpoint is None: # Nothing to plot. Save for next line. 53 | self.lastpoint = self.newpoint 54 | return 55 | 56 | res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box 57 | if res is not None: # Ignore lines which don't intersect 58 | self.graph.line(res[0:2], res[2:5], self.color) 59 | self.lastpoint = self.newpoint # Scaled but not clipped 60 | 61 | # Cohen–Sutherland line clipping algorithm 62 | # If self.newpoint and self.lastpoint are valid clip them so that both lie 63 | # in +-1 range. If both are outside the box return None. 64 | def _clip(self, x0, y0, x1, y1): 65 | oc1 = self._outcode(x0, y0) 66 | oc2 = self._outcode(x1, y1) 67 | while True: 68 | if not oc1 | oc2: # OK to plot 69 | return x0, y0, x1, y1 70 | if oc1 & oc2: # Nothing to do 71 | return 72 | oc = oc1 if oc1 else oc2 73 | if oc & _TOP: 74 | x = x0 + (_YMAX - y0)*(x1 - x0)/(y1 - y0) 75 | y = _YMAX 76 | elif oc & _BOTTOM: 77 | x = x0 + (_YMIN - y0)*(x1 - x0)/(y1 - y0) 78 | y = _YMIN 79 | elif oc & _RIGHT: 80 | y = y0 + (_XMAX - x0)*(y1 - y0)/(x1 - x0) 81 | x = _XMAX 82 | elif oc & _LEFT: 83 | y = y0 + (_XMIN - x0)*(y1 - y0)/(x1 - x0) 84 | x = _XMIN 85 | if oc is oc1: 86 | x0, y0 = x, y 87 | oc1 = self._outcode(x0, y0) 88 | else: 89 | x1, y1 = x, y 90 | oc2 = self._outcode(x1, y1) 91 | 92 | def show(self): 93 | self.graph.addcurve(self) # May have been removed by clear() 94 | self.lastpoint = None 95 | self.populate(self, *self.callback_args) 96 | 97 | def _scale(self, x, y): # Scale to +-1.0 98 | x0, y0 = self.origin 99 | xr, yr = self.excursion 100 | xs = (x - x0) / xr 101 | ys = (y - y0) / yr 102 | return xs, ys 103 | 104 | class PolarCurve(Curve): # Points are complex 105 | def __init__(self, graph, populate=dolittle, args=[], color=YELLOW): 106 | super().__init__(graph, populate, args, color=color) 107 | 108 | def point(self, z=None): 109 | if z is None: 110 | self.newpoint = None 111 | self.lastpoint = None 112 | return 113 | 114 | self.newpoint = self._scale(z.real, z.imag) # In-range points scaled to +-1 bounding box 115 | if self.lastpoint is None: # Nothing to plot. Save for next line. 116 | self.lastpoint = self.newpoint 117 | return 118 | 119 | res = self._clip(*(self.lastpoint + self.newpoint)) # Clip to +-1 box 120 | if res is not None: # At least part of line was in box 121 | self.graph.rline(res, self.color) 122 | #start = self.lastpoint[0] + self.lastpoint[1]*1j 123 | #end = self.newpoint[0] + self.newpoint[1]*1j 124 | #self.graph.line(start, end, self.color) 125 | self.lastpoint = self.newpoint # Scaled but not clipped 126 | 127 | 128 | class Graph(object): 129 | def __init__(self, location, height, width, gridcolor): 130 | border = self.border # border width 131 | self.x0 = self.location[0] + border 132 | self.x1 = self.location[0] + self.width - border 133 | self.y0 = self.location[1] + border 134 | self.y1 = self.location[1] + self.height - border 135 | self.gridcolor = gridcolor 136 | self.curves = set() 137 | 138 | def addcurve(self, curve): 139 | self.curves.add(curve) 140 | 141 | def clear(self): 142 | tft = Screen.get_tft() 143 | self.curves = set() 144 | tft.fill_rectangle(self.x0, self.y0, self.x1, self.y1, self.bgcolor) 145 | self.show() 146 | 147 | class CartesianGraph(NoTouch, Graph): 148 | def __init__(self, location, *, height=250, width = 250, fgcolor=WHITE, bgcolor=None, border=None, 149 | gridcolor=LIGHTGREEN, xdivs=10, ydivs=10, xorigin=5, yorigin=5): 150 | NoTouch.__init__(self, location, None, height, width, fgcolor, bgcolor, None, border, None, None) 151 | Graph.__init__(self, location, height, width, gridcolor) 152 | self.xdivs = xdivs 153 | self.ydivs = ydivs 154 | self.xorigin = xorigin 155 | self.yorigin = yorigin 156 | height -= 2 * self.border 157 | width -= 2 * self.border 158 | self.x_axis_len = max(xorigin, xdivs - xorigin) * width / xdivs # Max distance from origin in pixels 159 | self.y_axis_len = max(yorigin, ydivs - yorigin) * height / ydivs 160 | self.xp_origin = self.x0 + xorigin * width / xdivs # Origin in pixels 161 | self.yp_origin = self.y0 + (ydivs - yorigin) * height / ydivs 162 | 163 | def show(self): 164 | tft = self.tft 165 | x0 = self.x0 166 | x1 = self.x1 167 | y0 = self.y0 168 | y1 = self.y1 169 | if self.ydivs > 0: 170 | height = y1 - y0 171 | dy = height / (self.ydivs) # Y grid line 172 | for line in range(self.ydivs + 1): 173 | color = self.fgcolor if line == self.yorigin else self.gridcolor 174 | ypos = int(y1 - dy * line) 175 | tft.draw_hline(x0, ypos, x1 - x0, color) 176 | if self.xdivs > 0: 177 | width = x1 - x0 178 | dx = width / (self.xdivs) # X grid line 179 | for line in range(self.xdivs + 1): 180 | color = self.fgcolor if line == self.xorigin else self.gridcolor 181 | xpos = int(x0 + dx * line) 182 | tft.draw_vline(xpos, y0, y1 - y0, color) 183 | for curve in self.curves: 184 | curve.show() 185 | 186 | def line(self, start, end, color): # start and end relative to origin and scaled -1 .. 0 .. +1 187 | xs = int(self.xp_origin + start[0] * self.x_axis_len) 188 | ys = int(self.yp_origin - start[1] * self.y_axis_len) 189 | xe = int(self.xp_origin + end[0] * self.x_axis_len) 190 | ye = int(self.yp_origin - end[1] * self.y_axis_len) 191 | self.tft.drawLine(xs, ys, xe, ye, color) 192 | 193 | class PolarGraph(NoTouch, Graph): 194 | def __init__(self, location, *, height=250, fgcolor=WHITE, bgcolor=None, border=None, 195 | gridcolor=LIGHTGREEN, adivs=3, rdivs=4): 196 | NoTouch.__init__(self, location, None, height, height, fgcolor, bgcolor, None, border, None, None) 197 | Graph.__init__(self, location, height, height, gridcolor) 198 | self.adivs = 2 * adivs # No. of divisions of Pi radians 199 | self.rdivs = rdivs # No. of divisions of radius 200 | height -= 2 * self.border 201 | self.radius = int(height / 2) # Unit: pixels 202 | self.xp_origin = self.x0 + self.radius # Origin in pixels 203 | self.yp_origin = self.y0 + self.radius 204 | 205 | def show(self): 206 | tft = self.tft 207 | x0 = self.x0 208 | y0 = self.y0 209 | radius = self.radius 210 | diam = 2 * radius 211 | if self.rdivs > 0: 212 | for r in range(1, self.rdivs + 1): 213 | tft.draw_circle(self.xp_origin, self.yp_origin, int(radius * r / self.rdivs), self.gridcolor) 214 | if self.adivs > 0: 215 | v = complex(1) 216 | m = rect(1, pi / self.adivs) 217 | for _ in range(self.adivs): 218 | self.cline(-v, v, self.gridcolor) 219 | v *= m 220 | tft.draw_vline(x0 + radius, y0, diam, self.fgcolor) 221 | tft.draw_hline(x0, y0 + radius, diam, self.fgcolor) 222 | for curve in self.curves: 223 | curve.show() 224 | 225 | def cline(self, start, end, color): # start and end are complex, 0 <= magnitude <= 1 226 | xs = int(self.xp_origin + start.real * self.radius) 227 | ys = int(self.yp_origin - start.imag * self.radius) 228 | xe = int(self.xp_origin + end.real * self.radius) 229 | ye = int(self.yp_origin - end.imag * self.radius) 230 | self.tft.draw_line(xs, ys, xe, ye, color) 231 | 232 | def rline(self, vect, color): # start and end relative to origin and scaled -1 .. 0 .. +1 233 | height = self.radius # Unit: pixels 234 | xs = int(self.xp_origin + vect[0] * height) 235 | ys = int(self.yp_origin - vect[1] * height) 236 | xe = int(self.xp_origin + vect[2] * height) 237 | ye = int(self.yp_origin - vect[3] * height) 238 | self.tft.drawLine(xs, ys, xe, ye, color) 239 | -------------------------------------------------------------------------------- /tft/driver/tft_local.py: -------------------------------------------------------------------------------- 1 | # tft_local.py Configuration for Pybboard TFT GUI 2 | 3 | # This file is intended for definition of the local hardware. It's also a 4 | # convenient place to store constants used on a project such as colors. 5 | 6 | # Released under the MIT License (MIT). See LICENSE. 7 | # Copyright (c) 2016-2020 Peter Hinch 8 | 9 | from tft.driver.tft import LANDSCAPE 10 | from tft.driver.touch_bytecode import TOUCH 11 | from tft.driver.ugui import Screen, TFT_G 12 | 13 | def setup(): 14 | tft = TFT_G("SSD1963", "LB04301", LANDSCAPE) 15 | touch = TOUCH("XPT2046", True, confidence = 50, margin = 50) 16 | # (-3886,-0.1287,-3812,-0.132,-3797,-0.07685,-3798,-0.07681)) 17 | tft.backlight(100) # light on: remove this line if you don't have backlight control hardware 18 | Screen.setup(tft, touch) 19 | -------------------------------------------------------------------------------- /tft/driver/touch_bytecode.py: -------------------------------------------------------------------------------- 1 | # touch_bytecode.py 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2016 Robert Hammelrath (c) 2016-2020 Peter Hinch 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in 16 | # all copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | # THE SOFTWARE. 25 | # 26 | # Class supporting the resisitve touchpad of TFT LC-displays 27 | # First example: Controller XPT2046 28 | # It uses Y5..Y8 of PyBoard 29 | # 30 | import pyb, stm 31 | import uasyncio as asyncio 32 | 33 | # define constants 34 | # 35 | PCB_VERSION = 2 36 | 37 | #if PCB_VERSION == 1: 38 | # CONTROL_PORT = stm.GPIOB 39 | # T_CLOCK = const(1 << 15) ## Y8 = B15 40 | # T_DOUT = const(1 << 14) ## Y7 = B14 41 | # T_DIN = const(1 << 13) ## Y6 = B13 42 | # T_IRQ = const(1 << 12) ## Y5 = B12 43 | 44 | if PCB_VERSION == 2: 45 | CONTROL_PORT = stm.GPIOC 46 | T_CLOCK = const(1 << 5) ## X12 = C5 47 | T_DOUT = const(1 << 4) ## X11 = C4 48 | T_DIN = const(1 << 7) ## Y2 = C7 49 | T_IRQ = const(1 << 6) ## Y1 = C6 50 | 51 | # T_CS is not used and must be hard tied to GND 52 | 53 | T_GETX = const(0xd0) ## 12 bit resolution 54 | T_GETY = const(0x90) ## 12 bit resolution 55 | T_GETZ1 = const(0xb8) ## 8 bit resolution 56 | T_GETZ2 = const(0xc8) ## 8 bit resolution 57 | # 58 | X_LOW = const(10) ## lowest reasonable X value from the touchpad 59 | Y_HIGH = const(4090) ## highest reasonable Y value 60 | 61 | class TOUCH: 62 | # 63 | # Init just sets the PIN's to In / out as required 64 | # async: set True if asynchronous operation intended 65 | # confidence: confidence level - number of consecutive touches with a margin smaller than the given level 66 | # which the function will sample until it accepts it as a valid touch 67 | # margin: Difference from mean centre at which touches are considered at the same position 68 | # delay: Delay between samples in ms. (n/a if asynchronous) 69 | # 70 | DEFAULT_CAL = (-3917, -0.127, -3923, -0.1267, -3799, -0.07572, -3738, -0.07814) 71 | def __init__(self, controller = "XPT2046", asyn = False, *, confidence = 5, margin = 50, delay = 10, calibration = None): 72 | if PCB_VERSION == 1: 73 | self.pin_clock = pyb.Pin("Y8", pyb.Pin.OUT_PP) 74 | self.pin_clock.value(0) 75 | self.pin_d_out = pyb.Pin("Y7", pyb.Pin.OUT_PP) 76 | self.pin_d_in = pyb.Pin("Y6", pyb.Pin.IN) 77 | self.pin_irq = pyb.Pin("Y5", pyb.Pin.IN) 78 | else: 79 | self.pin_clock = pyb.Pin("X11", pyb.Pin.OUT_PP) 80 | self.pin_clock.value(0) 81 | self.pin_d_out = pyb.Pin("X12", pyb.Pin.OUT_PP) 82 | self.pin_d_in = pyb.Pin("Y1", pyb.Pin.IN) 83 | self.pin_irq = pyb.Pin("Y2", pyb.Pin.IN) 84 | # set default values 85 | self.ready = False 86 | self.touched = False 87 | self.x = 0 88 | self.y = 0 89 | self.buf_length = 0 90 | cal = TOUCH.DEFAULT_CAL if calibration is None else calibration 91 | self.asynchronous = False 92 | self.touch_parameter(confidence, margin, delay, cal) 93 | if asyn: 94 | self.asynchronous = True 95 | asyncio.create_task(self._main_thread()) 96 | 97 | # set parameters for get_touch() 98 | # res: Resolution in bits of the returned values, default = 10 99 | # confidence: confidence level - number of consecutive touches with a margin smaller than the given level 100 | # which the function will sample until it accepts it as a valid touch 101 | # margin: Difference from mean centre at which touches are considered at the same position 102 | # delay: Delay between samples in ms. 103 | # 104 | def touch_parameter(self, confidence = 5, margin = 50, delay = 10, calibration = None): 105 | if not self.asynchronous: # Ignore attempts to change on the fly. 106 | confidence = max(min(confidence, 25), 5) 107 | if confidence != self.buf_length: 108 | self.buff = [[0,0] for x in range(confidence)] 109 | self.buf_length = confidence 110 | self.delay = max(min(delay, 100), 5) 111 | margin = max(min(margin, 100), 1) 112 | self.margin = margin * margin # store the square value 113 | if calibration: 114 | self.calibration = calibration 115 | 116 | # get_touch(): Synchronous use. get a touch value; Parameters: 117 | # 118 | # initital: Wait for a non-touch state before getting a sample. 119 | # True = Initial wait for a non-touch state 120 | # False = Do not wait for a release 121 | # wait: Wait for a touch or not? 122 | # False: Do not wait for a touch and return immediately 123 | # True: Wait until a touch is pressed. 124 | # raw: Setting whether raw touch coordinates (True) or normalized ones (False) are returned 125 | # setting the calibration vector to (0, 1, 0, 1, 0, 1, 0, 1) result in a identity mapping 126 | # timeout: Longest time (ms, or None = 1 hr) to wait for a touch or release 127 | # 128 | # Return (x,y) or None 129 | # 130 | def get_touch(self, initial = True, wait = True, raw = False, timeout = None): 131 | if self.asynchronous: 132 | return None # Should only be called in synhronous mode 133 | if timeout == None: 134 | timeout = 3600000 # set timeout to 1 hour 135 | # 136 | if initial: ## wait for a non-touch state 137 | sample = True 138 | while sample and timeout > 0: 139 | sample = self.raw_touch() 140 | pyb.delay(self.delay) 141 | timeout -= self.delay 142 | if timeout <= 0: # after timeout, return None 143 | return None 144 | # 145 | buff = self.buff 146 | buf_length = self.buf_length 147 | buffptr = 0 148 | nsamples = 0 149 | while timeout > 0: 150 | if nsamples == buf_length: 151 | meanx = sum([c[0] for c in buff]) // buf_length 152 | meany = sum([c[1] for c in buff]) // buf_length 153 | dev = sum([(c[0] - meanx)**2 + (c[1] - meany)**2 for c in buff]) / buf_length 154 | if dev <= self.margin: # got one; compare against the square value 155 | if raw: 156 | return (meanx, meany) 157 | else: 158 | return self.do_normalize((meanx, meany)) 159 | # get a new value 160 | sample = self.raw_touch() # get a touch 161 | if sample == None: 162 | if not wait: 163 | return None 164 | nsamples = 0 # Invalidate buff 165 | else: 166 | buff[buffptr] = sample # put in buff 167 | buffptr = (buffptr + 1) % buf_length 168 | nsamples = min(nsamples +1, buf_length) 169 | pyb.delay(self.delay) 170 | timeout -= self.delay 171 | return None 172 | 173 | # Asynchronous use: this thread maintains self.x and self.y 174 | async def _main_thread(self): 175 | buff = self.buff 176 | buf_length = self.buf_length 177 | buffptr = 0 178 | nsamples = 0 179 | await asyncio.sleep(0) 180 | while True: 181 | if nsamples == buf_length: 182 | meanx = sum([c[0] for c in buff]) // buf_length 183 | meany = sum([c[1] for c in buff]) // buf_length 184 | dev = sum([(c[0] - meanx)**2 + (c[1] - meany)**2 for c in buff]) / buf_length 185 | if dev <= self.margin: # got one; compare against the square value 186 | self.ready = True 187 | self.x, self.y = self.do_normalize((meanx, meany)) 188 | sample = self.raw_touch() # get a touch 189 | if sample == None: 190 | self.touched = False 191 | self.ready = False 192 | nsamples = 0 # Invalidate buff 193 | else: 194 | self.touched = True 195 | buff[buffptr] = sample # put in buff 196 | buffptr = (buffptr + 1) % buf_length 197 | nsamples = min(nsamples + 1, buf_length) 198 | await asyncio.sleep(0) 199 | 200 | # Asynchronous get_touch 201 | def get_touch_async(self): 202 | if self.ready: 203 | self.ready = False 204 | return self.x, self.y 205 | return None 206 | # 207 | # do_normalize(touch) 208 | # calculate the screen coordinates from the touch values, using the calibration values 209 | # touch must be the tuple return by get_touch 210 | # 211 | def do_normalize(self, touch): 212 | xmul = self.calibration[3] + (self.calibration[1] - self.calibration[3]) * (touch[1] / 4096) 213 | xadd = self.calibration[2] + (self.calibration[0] - self.calibration[2]) * (touch[1] / 4096) 214 | ymul = self.calibration[7] + (self.calibration[5] - self.calibration[7]) * (touch[0] / 4096) 215 | yadd = self.calibration[6] + (self.calibration[4] - self.calibration[6]) * (touch[0] / 4096) 216 | x = int((touch[0] + xadd) * xmul) 217 | y = int((touch[1] + yadd) * ymul) 218 | return (x, y) 219 | # 220 | # raw_touch(tuple) 221 | # raw read touch. Returns (x,y) or None 222 | # 223 | def raw_touch(self): 224 | x = self.touch_talk(T_GETX, 12) 225 | y = self.touch_talk(T_GETY, 12) 226 | if x > X_LOW and y < Y_HIGH: # touch pressed? 227 | return (x, y) 228 | else: 229 | return None 230 | # 231 | # Send a command to the touch controller and wait for the response 232 | # cmd is the command byte 233 | # int is the expected size of return data bits 234 | # port is the gpio base port 235 | # 236 | # Straight down coding of the data sheet's timing diagram 237 | # This is the slow bytecode implementations 238 | # Clock low & high cycles must last at least 200ns 239 | # At the moment it is set to about 25us each, 240 | # 1050µs total at 168 MHz clock rate. 241 | # Total net time for a 12 bit sample: ~ 1050 µs 242 | # 243 | def touch_talk(self, cmd, bits): 244 | 245 | global CONTROL_PORT 246 | gpio_bsr = CONTROL_PORT + stm.GPIO_BSRRL 247 | gpio_idr = CONTROL_PORT + stm.GPIO_IDR 248 | # 249 | # now shift the command out, which is 8 bits 250 | # data is sampled at the low-> high transient 251 | # 252 | stm.mem16[gpio_bsr + 2] = T_CLOCK # Empty clock cycle before start, maybe obsolete 253 | mask = 0x80 # high bit first 254 | for i in range(8): 255 | stm.mem16[gpio_bsr + 2] = T_CLOCK # set clock low in the beginning 256 | if cmd & mask: 257 | stm.mem16[gpio_bsr + 0] = T_DOUT # set data bit high 258 | else: 259 | stm.mem16[gpio_bsr + 2] = T_DOUT # set data bit low 260 | for i in range(1): pass #delay 261 | stm.mem16[gpio_bsr + 0] = T_CLOCK # set clock high 262 | mask >>= 1 263 | stm.mem16[gpio_bsr + 2] = T_CLOCK | T_DOUT# Another clock & data, low 264 | stm.mem16[gpio_bsr + 0] = T_CLOCK # clock High 265 | # 266 | # now shift the data in, which is 8 or 12 bits 267 | # data is sampled after the high->low transient 268 | # 269 | result = 0 270 | for i in range(bits): 271 | stm.mem16[gpio_bsr + 2] = T_CLOCK # Clock low 272 | if stm.mem16[gpio_idr + 0] & T_DIN: # get data 273 | bit = 1 274 | else: 275 | bit = 0 276 | result = (result << 1) | bit # shift data in 277 | stm.mem16[gpio_bsr + 0] = T_CLOCK # Clock high 278 | # 279 | # another clock cycle, maybe obsolete 280 | # 281 | stm.mem16[gpio_bsr + 2] = T_CLOCK # Another clock toggle, low 282 | stm.mem16[gpio_bsr + 0] = T_CLOCK # clock High 283 | stm.mem16[gpio_bsr + 2] = T_CLOCK # Clock low 284 | # now we're ready to leave 285 | return result 286 | 287 | -------------------------------------------------------------------------------- /tft/fonts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-tft-gui/11837ce83df6b122a33bad1b472e9da5a1e40c22/tft/fonts/__init__.py -------------------------------------------------------------------------------- /tft/fonts/font10.py: -------------------------------------------------------------------------------- 1 | # Code generated by font-to-py.py. 2 | # Font: FreeSans.ttf 3 | version = '0.1' 4 | 5 | def height(): 6 | return 17 7 | 8 | def max_width(): 9 | return 17 10 | 11 | def hmap(): 12 | return True 13 | 14 | def reverse(): 15 | return False 16 | 17 | def monospaced(): 18 | return False 19 | 20 | _font =\ 21 | b'\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 22 | b'\x00\x00\x00\x06\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x80'\ 23 | b'\x00\xc0\x00\x00\x00\x00\x06\x00\x00\xf0\xf0\xf0\xa0\x00\x00\x00'\ 24 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x19'\ 25 | b'\x00\x19\x00\x13\x00\x7f\x80\x12\x00\x32\x00\x32\x00\xff\x80\x26'\ 26 | b'\x00\x24\x00\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x10'\ 27 | b'\x00\x3c\x00\x56\x00\xd3\x00\xd3\x00\xd0\x00\xd0\x00\x3c\x00\x17'\ 28 | b'\x00\x13\x00\xd3\x00\xd6\x00\x7c\x00\x10\x00\x00\x00\x00\x00\x00'\ 29 | b'\x00\x0f\x00\x00\x00\x78\x20\xcc\x40\xcc\x80\xcc\x80\xc9\x00\x31'\ 30 | b'\x00\x02\x78\x04\xcc\x04\xcc\x08\xcc\x08\xcc\x10\x78\x00\x00\x00'\ 31 | b'\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x1e\x00\x33\x00\x33\x00\x33'\ 32 | b'\x00\x1e\x00\x18\x00\x74\xc0\xe6\xc0\xc3\x80\xc1\x80\xe3\x80\x3c'\ 33 | b'\x40\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\xc0\xc0\xc0\x80'\ 34 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x10\x20'\ 35 | b'\x20\x60\x40\xc0\xc0\xc0\xc0\xc0\xc0\x40\x60\x20\x30\x10\x00\x06'\ 36 | b'\x00\x80\xc0\x40\x60\x20\x30\x30\x30\x30\x30\x30\x20\x60\x40\xc0'\ 37 | b'\x80\x00\x07\x00\x20\xa8\x70\x50\x50\x00\x00\x00\x00\x00\x00\x00'\ 38 | b'\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 39 | b'\x00\x00\x00\x30\x00\x30\x00\x30\x00\xfc\x00\x30\x00\x30\x00\x30'\ 40 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00'\ 41 | b'\x00\x00\x00\x00\x00\x00\x00\xc0\x40\x40\x80\x00\x06\x00\x00\x00'\ 42 | b'\x00\x00\x00\x00\x00\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x04'\ 43 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00'\ 44 | b'\x00\x00\x05\x00\x08\x08\x10\x10\x10\x20\x20\x20\x40\x40\x40\x80'\ 45 | b'\x80\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\x42\x00\xc3'\ 46 | b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x42\x00\x66\x00\x3c'\ 47 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x10\x00\x30'\ 48 | b'\x00\xf0\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30\x00\x30'\ 49 | b'\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ 50 | b'\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\x03\x00\x06\x00\x0c\x00\x38'\ 51 | b'\x00\x60\x00\x40\x00\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00'\ 52 | b'\x00\x09\x00\x00\x00\x7c\x00\xe7\x00\xc3\x00\x03\x00\x02\x00\x1c'\ 53 | b'\x00\x07\x00\x03\x00\x03\x00\xc3\x00\xe6\x00\x3c\x00\x00\x00\x00'\ 54 | b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x0c\x00\x0c\x00\x1c\x00\x2c'\ 55 | b'\x00\x2c\x00\x4c\x00\x8c\x00\x8c\x00\xfe\x00\x0c\x00\x0c\x00\x0c'\ 56 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x7e\x00\x40'\ 57 | b'\x00\x40\x00\x80\x00\xbc\x00\xe6\x00\x03\x00\x03\x00\x03\x00\xc3'\ 58 | b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00'\ 59 | b'\x00\x3c\x00\x66\x00\x43\x00\xc0\x00\xc0\x00\xfc\x00\xe6\x00\xc3'\ 60 | b'\x00\xc3\x00\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00'\ 61 | b'\x00\x09\x00\x00\x00\xff\x00\x03\x00\x02\x00\x06\x00\x04\x00\x0c'\ 62 | b'\x00\x08\x00\x18\x00\x18\x00\x10\x00\x30\x00\x30\x00\x00\x00\x00'\ 63 | b'\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc3'\ 64 | b'\x00\x66\x00\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00\x3c'\ 65 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x3c\x00\x66'\ 66 | b'\x00\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc2'\ 67 | b'\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00'\ 68 | b'\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00'\ 69 | b'\x04\x00\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\xc0\x40'\ 70 | b'\x40\x80\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03'\ 71 | b'\x00\x0e\x00\x38\x00\xc0\x00\xe0\x00\x38\x00\x07\x00\x01\x00\x00'\ 72 | b'\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x00'\ 73 | b'\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\xff\x00\x00'\ 74 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00'\ 75 | b'\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x70\x00\x1c\x00\x03\x00\x07'\ 76 | b'\x00\x1c\x00\xe0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09'\ 77 | b'\x00\x3c\x00\xc7\x00\xc3\x00\x03\x00\x03\x00\x06\x00\x0c\x00\x08'\ 78 | b'\x00\x18\x00\x18\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00'\ 79 | b'\x00\x00\x00\x11\x00\x07\xe0\x00\x0c\x38\x00\x30\x0c\x00\x20\x06'\ 80 | b'\x00\x63\xb7\x00\x4c\x73\x00\xcc\x63\x00\xd8\x63\x00\xd8\x63\x00'\ 81 | b'\xd8\x46\x00\xdc\xce\x00\x6f\x78\x00\x30\x00\x00\x18\x00\x00\x0f'\ 82 | b'\xe0\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x06\x00\x0e\x00\x0b\x00'\ 83 | b'\x1b\x00\x1b\x00\x11\x80\x31\x80\x31\x80\x3f\xc0\x60\xc0\x60\x40'\ 84 | b'\x40\x60\xc0\x60\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xfe\x00'\ 85 | b'\xc3\x80\xc1\x80\xc1\x80\xc1\x80\xc3\x00\xfe\x00\xc1\x80\xc0\xc0'\ 86 | b'\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 87 | b'\x0c\x00\x1f\x80\x30\xc0\x60\x60\x40\x60\xc0\x00\xc0\x00\xc0\x00'\ 88 | b'\xc0\x00\xc0\x00\x40\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00'\ 89 | b'\x00\x00\x00\x00\x0c\x00\xff\x00\xc1\x80\xc0\xc0\xc0\x60\xc0\x60'\ 90 | b'\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\x60\xc0\xc0\xc1\x80\xff\x00'\ 91 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xff\x00\xc0\x00\xc0\x00'\ 92 | b'\xc0\x00\xc0\x00\xc0\x00\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ 93 | b'\xc0\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x00'\ 94 | b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\xc0\x00\xc0\x00'\ 95 | b'\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 96 | b'\x0d\x00\x0f\xc0\x30\x60\x60\x30\x60\x00\xc0\x00\xc0\x00\xc1\xf0'\ 97 | b'\xc0\x30\xc0\x30\x60\x30\x60\x70\x30\xf0\x0f\x10\x00\x00\x00\x00'\ 98 | b'\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 99 | b'\xc0\xc0\xff\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 100 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\xc0\xc0\xc0\xc0\xc0\xc0'\ 101 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x09\x00\x06\x00\x06'\ 102 | b'\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\xc6'\ 103 | b'\x00\xc6\x00\xc4\x00\x78\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ 104 | b'\x00\xc0\xc0\xc1\x80\xc3\x00\xc6\x00\xcc\x00\xd8\x00\xfc\x00\xe6'\ 105 | b'\x00\xc6\x00\xc3\x00\xc1\x80\xc1\x80\xc0\xc0\x00\x00\x00\x00\x00'\ 106 | b'\x00\x00\x00\x0a\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ 107 | b'\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xfe\x00\x00'\ 108 | b'\x00\x00\x00\x00\x00\x00\x00\x0e\x00\xe0\x38\xe0\x38\xf0\x78\xf0'\ 109 | b'\x78\xd0\x58\xd8\xd8\xd8\xd8\xc8\x98\xcd\x98\xcd\x98\xc5\x18\xc7'\ 110 | b'\x18\xc7\x18\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xe0\x60\xe0'\ 111 | b'\x60\xf0\x60\xd0\x60\xd8\x60\xcc\x60\xc4\x60\xc6\x60\xc3\x60\xc3'\ 112 | b'\x60\xc1\xe0\xc0\xe0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0d'\ 113 | b'\x00\x1f\x80\x30\xc0\x60\x60\xe0\x60\xc0\x30\xc0\x30\xc0\x30\xc0'\ 114 | b'\x30\xc0\x30\xe0\x60\x60\x60\x30\xc0\x1f\x80\x00\x00\x00\x00\x00'\ 115 | b'\x00\x00\x00\x0b\x00\xff\x00\xc1\x80\xc0\xc0\xc0\xc0\xc0\xc0\xc1'\ 116 | b'\x80\xff\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\x00'\ 117 | b'\x00\x00\x00\x00\x00\x00\x00\x0d\x00\x1f\x80\x30\xc0\x60\x60\xe0'\ 118 | b'\x60\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xc0\x30\xe1\x60\x61\xe0\x30'\ 119 | b'\xc0\x1f\xe0\x00\x20\x00\x00\x00\x00\x00\x00\x0c\x00\xff\x00\xc1'\ 120 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x80\xff\x00\xc1\xc0\xc0\xc0\xc0'\ 121 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x0b'\ 122 | b'\x00\x3f\x00\x61\x80\xc0\xc0\xc0\x00\xc0\x00\x60\x00\x3e\x00\x07'\ 123 | b'\x80\x01\xc0\xc0\xc0\xc0\xc0\x61\x80\x3f\x00\x00\x00\x00\x00\x00'\ 124 | b'\x00\x00\x00\x0b\x00\xff\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18'\ 125 | b'\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x00'\ 126 | b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 127 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x61'\ 128 | b'\x80\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0b\x00\xc0\x60\x40'\ 129 | b'\x40\x60\xc0\x60\xc0\x20\x80\x31\x80\x31\x80\x11\x00\x1b\x00\x0b'\ 130 | b'\x00\x0a\x00\x0e\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10'\ 131 | b'\x00\xc1\x83\xc1\x82\x42\x86\x62\xc6\x62\xc6\x62\x44\x24\x44\x24'\ 132 | b'\x6c\x34\x2c\x3c\x28\x18\x38\x18\x38\x18\x18\x00\x00\x00\x00\x00'\ 133 | b'\x00\x00\x00\x0b\x00\x60\x40\x20\xc0\x31\x80\x19\x00\x1b\x00\x0e'\ 134 | b'\x00\x06\x00\x0e\x00\x1b\x00\x11\x80\x31\x80\x60\xc0\x40\x60\x00'\ 135 | b'\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x40\x60\x60\x60\x30\xc0\x30'\ 136 | b'\xc0\x19\x80\x0d\x00\x0f\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06'\ 137 | b'\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\xff\x80\x01'\ 138 | b'\x80\x03\x00\x06\x00\x06\x00\x0c\x00\x18\x00\x18\x00\x30\x00\x60'\ 139 | b'\x00\x60\x00\xc0\x00\xff\x80\x00\x00\x00\x00\x00\x00\x00\x00\x05'\ 140 | b'\x00\xe0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 141 | b'\xc0\xe0\x05\x00\x80\x80\x40\x40\x40\x20\x20\x20\x10\x10\x10\x08'\ 142 | b'\x08\x00\x00\x00\x00\x05\x00\xe0\x60\x60\x60\x60\x60\x60\x60\x60'\ 143 | b'\x60\x60\x60\x60\x60\x60\x60\xe0\x08\x00\x00\x30\x30\x78\x48\x48'\ 144 | b'\x84\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00'\ 145 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 146 | b'\x00\x00\x00\x00\x00\x00\x00\xff\xc0\x00\x00\x00\x00\x00\x00\x04'\ 147 | b'\x00\xc0\x40\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 148 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7c\x00\xc6\x00'\ 149 | b'\x06\x00\x06\x00\x7e\x00\xc6\x00\xc6\x00\xce\x00\x77\x00\x00\x00'\ 150 | b'\x00\x00\x00\x00\x00\x00\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00'\ 151 | b'\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xc1\x80\xe3\x00'\ 152 | b'\xde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 153 | b'\x00\x00\x00\x00\x3c\x00\x66\x00\xc3\x00\xc0\x00\xc0\x00\xc0\x00'\ 154 | b'\xc3\x00\x66\x00\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00'\ 155 | b'\x03\x00\x03\x00\x03\x00\x03\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ 156 | b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x00\x00\x00\x00\x00\x00'\ 157 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x00\x66\x00'\ 158 | b'\xc3\x00\xc3\x00\xff\x00\xc0\x00\xc3\x00\x66\x00\x3c\x00\x00\x00'\ 159 | b'\x00\x00\x00\x00\x00\x00\x05\x00\x30\x60\x60\x60\xf0\x60\x60\x60'\ 160 | b'\x60\x60\x60\x60\x60\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00'\ 161 | b'\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ 162 | b'\x00\x67\x00\x3b\x00\x03\x00\x03\x00\xc6\x00\x7c\x00\x09\x00\xc0'\ 163 | b'\x00\xc0\x00\xc0\x00\xc0\x00\xde\x00\xe3\x00\xc3\x00\xc3\x00\xc3'\ 164 | b'\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00'\ 165 | b'\x00\x04\x00\xc0\x00\x00\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 166 | b'\x00\x00\x00\x00\x04\x00\x60\x00\x00\x00\x60\x60\x60\x60\x60\x60'\ 167 | b'\x60\x60\x60\x60\x60\x60\xc0\x09\x00\xc0\x00\xc0\x00\xc0\x00\xc0'\ 168 | b'\x00\xc6\x00\xcc\x00\xd8\x00\xf8\x00\xe8\x00\xcc\x00\xc6\x00\xc6'\ 169 | b'\x00\xc3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\xc0\xc0\xc0'\ 170 | b'\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x00\x00\x0e\x00'\ 171 | b'\x00\x00\x00\x00\x00\x00\x00\x00\xdd\xe0\xe7\x30\xc6\x30\xc6\x30'\ 172 | b'\xc6\x30\xc6\x30\xc6\x30\xc6\x30\xc6\x30\x00\x00\x00\x00\x00\x00'\ 173 | b'\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00\xe3\x00'\ 174 | b'\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x00\x00'\ 175 | b'\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ 176 | b'\x3c\x00\x66\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\x66\x00'\ 177 | b'\x3c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x00\x00\x00\x00'\ 178 | b'\x00\x00\x00\x00\xde\x00\xe3\x00\xc1\x80\xc1\x80\xc1\x80\xc1\x80'\ 179 | b'\xc1\x80\xe3\x00\xde\x00\xc0\x00\xc0\x00\xc0\x00\x00\x00\x0a\x00'\ 180 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x3b\x00\x67\x00\xc3\x00\xc3\x00'\ 181 | b'\xc3\x00\xc3\x00\xc3\x00\x67\x00\x3b\x00\x03\x00\x03\x00\x03\x00'\ 182 | b'\x00\x00\x06\x00\x00\x00\x00\x00\xd8\xe0\xc0\xc0\xc0\xc0\xc0\xc0'\ 183 | b'\xc0\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x7c\xc6\xc0\xc0\x70'\ 184 | b'\x0e\xc6\xc6\x7c\x00\x00\x00\x00\x05\x00\x00\x00\x60\x60\xf0\x60'\ 185 | b'\x60\x60\x60\x60\x60\x60\x70\x00\x00\x00\x00\x09\x00\x00\x00\x00'\ 186 | b'\x00\x00\x00\x00\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3\x00\xc3'\ 187 | b'\x00\xc3\x00\xc7\x00\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08'\ 188 | b'\x00\x00\x00\x00\x00\xc3\x43\x62\x66\x26\x34\x3c\x18\x18\x00\x00'\ 189 | b'\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\x30\x46\x30'\ 190 | b'\x47\x20\x6f\x20\x69\x60\x29\x60\x29\xc0\x39\xc0\x10\xc0\x00\x00'\ 191 | b'\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x42\x66\x34\x18'\ 192 | b'\x18\x1c\x24\x66\x43\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\xc3'\ 193 | b'\x42\x42\x66\x24\x24\x3c\x18\x18\x18\x10\x30\x60\x08\x00\x00\x00'\ 194 | b'\x00\x00\xfe\x0c\x08\x18\x30\x60\x40\xc0\xfe\x00\x00\x00\x00\x06'\ 195 | b'\x00\x30\x60\x60\x60\x60\x60\x60\xe0\xc0\xe0\x60\x60\x60\x60\x60'\ 196 | b'\x60\x30\x04\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0'\ 197 | b'\xc0\xc0\xc0\xc0\x00\x06\x00\xc0\x60\x60\x60\x60\x60\x60\x70\x30'\ 198 | b'\x70\x60\x60\x60\x60\x60\x60\xc0\x09\x00\x00\x00\x00\x00\x00\x00'\ 199 | b'\x00\x00\x00\x00\x00\x00\x62\x00\x9e\x00\x00\x00\x00\x00\x00\x00'\ 200 | b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 201 | 202 | _index =\ 203 | b'\x00\x00\x13\x00\x26\x00\x39\x00\x5d\x00\x81\x00\xa5\x00\xc9\x00'\ 204 | b'\xdc\x00\xef\x00\x02\x01\x15\x01\x39\x01\x4c\x01\x5f\x01\x72\x01'\ 205 | b'\x85\x01\xa9\x01\xcd\x01\xf1\x01\x15\x02\x39\x02\x5d\x02\x81\x02'\ 206 | b'\xa5\x02\xc9\x02\xed\x02\x00\x03\x13\x03\x37\x03\x5b\x03\x7f\x03'\ 207 | b'\xa3\x03\xd8\x03\xfc\x03\x20\x04\x44\x04\x68\x04\x8c\x04\xb0\x04'\ 208 | b'\xd4\x04\xf8\x04\x0b\x05\x2f\x05\x53\x05\x77\x05\x9b\x05\xbf\x05'\ 209 | b'\xe3\x05\x07\x06\x2b\x06\x4f\x06\x73\x06\x97\x06\xbb\x06\xdf\x06'\ 210 | b'\x03\x07\x27\x07\x4b\x07\x6f\x07\x82\x07\x95\x07\xa8\x07\xbb\x07'\ 211 | b'\xdf\x07\xf2\x07\x16\x08\x3a\x08\x5e\x08\x82\x08\xa6\x08\xb9\x08'\ 212 | b'\xdd\x08\x01\x09\x14\x09\x27\x09\x4b\x09\x5e\x09\x82\x09\xa6\x09'\ 213 | b'\xca\x09\xee\x09\x12\x0a\x25\x0a\x38\x0a\x4b\x0a\x6f\x0a\x82\x0a'\ 214 | b'\xa6\x0a\xb9\x0a\xcc\x0a\xdf\x0a\xf2\x0a\x05\x0b\x18\x0b\x3c\x0b'\ 215 | 216 | _mvfont = memoryview(_font) 217 | 218 | def _chr_addr(ordch): 219 | offset = 2 * (ordch - 32) 220 | return int.from_bytes(_index[offset:offset + 2], 'little') 221 | 222 | def get_ch(ch): 223 | ordch = ord(ch) 224 | ordch = ordch if ordch >= 32 and ordch <= 126 else ord('?') 225 | offset = _chr_addr(ordch) 226 | width = int.from_bytes(_font[offset:offset + 2], 'little') 227 | next_offs = _chr_addr(ordch +1) 228 | return _mvfont[offset + 2:next_offs], 17, width 229 | 230 | -------------------------------------------------------------------------------- /tft/icons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-tft-gui/11837ce83df6b122a33bad1b472e9da5a1e40c22/tft/icons/__init__.py -------------------------------------------------------------------------------- /tft/icons/checkbox.py: -------------------------------------------------------------------------------- 1 | 2 | # Code generated by bmp_to_icon.py 3 | from uctypes import addressof 4 | 5 | _icons = { 6 | 0: ( 7 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 8 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 9 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 10 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 11 | b'\xff\xff\xff\xff\xee\xaa\xaa\xaa\xaa\xaa\xaa\xee\xff\xff\xff\xff' 12 | b'\xff\xff\xff\xea\x64\x44\x44\x44\x44\x44\x44\x46\xae\xff\xff\xff' 13 | b'\xff\xff\xfe\xa4\x6e\xff\xff\xff\xff\xff\xff\xe6\x4a\xef\xff\xff' 14 | b'\xff\xff\xfa\x4a\xff\xff\xff\xff\xff\xff\xff\xff\xa4\xaf\xff\xff' 15 | b'\xff\xff\xe6\x6f\xff\xff\xff\xff\xff\xff\xff\xff\xf6\x6e\xff\xff' 16 | b'\xff\xff\xe4\xef\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x4e\xff\xff' 17 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 18 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 19 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 20 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 21 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 22 | b'\xff\xff\xa4\xfe\xee\xee\xee\xee\xee\xee\xee\xee\xef\x4a\xff\xff' 23 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 24 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 25 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 26 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 27 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 28 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 29 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 30 | b'\xff\xff\xe4\x6e\xee\xee\xee\xee\xee\xee\xee\xee\xe6\x4e\xff\xff' 31 | b'\xff\xff\xfa\x4a\xee\xee\xee\xee\xee\xee\xee\xee\xa4\xaf\xff\xff' 32 | b'\xff\xff\xfe\x64\x6e\xee\xee\xee\xee\xee\xee\xe6\x46\xef\xff\xff' 33 | b'\xff\xff\xff\xea\x44\x44\x44\x44\x44\x44\x44\x44\xae\xff\xff\xff' 34 | b'\xff\xff\xff\xfe\xaa\xa6\x66\x66\x66\x66\x6a\xaa\xef\xff\xff\xff' 35 | b'\xff\xff\xff\xff\xee\xee\xee\xee\xee\xee\xee\xee\xff\xff\xff\xff' 36 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 37 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 38 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 39 | ), 40 | 1: ( 41 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x61\x16\xff' 42 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x10\x01\xff' 43 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf2\x00\x01\xff' 44 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x40\x00\x04\xff' 45 | b'\xff\xff\xff\xff\xea\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x2f\xff' 46 | b'\xff\xff\xff\xea\x43\x33\x33\x33\x33\x33\x33\x31\x00\x01\xef\xff' 47 | b'\xff\xff\xfe\x63\x6d\xff\xff\xff\xff\xff\xff\x40\x00\x06\xff\xff' 48 | b'\xff\xff\xfa\x36\xdc\xcc\xcc\xcc\xcc\xcc\xc9\x00\x00\x2f\xff\xff' 49 | b'\xff\xff\xe4\x6d\xcc\xcc\xcc\xcc\xcc\xcc\xc2\x00\x00\x6e\xff\xff' 50 | b'\xff\xff\xa3\xac\xcc\xcc\xcc\xcc\xcc\xcc\x40\x00\x04\x4a\xff\xff' 51 | b'\xff\xff\xa3\xcc\xcc\xcc\xcc\xcc\xcc\xc9\x00\x00\x2d\x3a\xff\xff' 52 | b'\xff\xff\xa3\xcb\xbb\xbb\xbb\xbb\xbb\xb2\x00\x00\x9c\x3a\xff\xff' 53 | b'\xff\xff\xa3\xcb\xb4\x11\x4b\xbb\xbb\x40\x00\x04\xcc\x3a\xff\xff' 54 | b'\xff\xff\xa3\xbb\xb1\x00\x05\xbb\xb5\x00\x00\x2c\xbb\x3a\xff\xff' 55 | b'\xff\xff\xa3\xb8\x81\x00\x01\x58\x82\x00\x00\x9b\x8b\x3a\xff\xff' 56 | b'\xff\xff\xa3\xb8\x84\x00\x00\x18\x30\x00\x04\xb8\x8b\x3a\xff\xff' 57 | b'\xff\xff\xa3\x87\x7b\x30\x00\x02\x00\x00\x2b\x77\x78\x3a\xff\xff' 58 | b'\xff\xff\xa3\x87\x77\xb2\x00\x00\x00\x00\x98\x77\x78\x3a\xff\xff' 59 | b'\xff\xff\xa3\x87\x77\x8b\x10\x00\x00\x04\x87\x77\x78\x3a\xff\xff' 60 | b'\xff\xff\xa3\x87\x77\x7b\x91\x00\x00\x2b\x87\x77\x78\x3a\xff\xff' 61 | b'\xff\xff\xa3\xb8\x88\x88\xb5\x00\x00\x9b\x88\x88\x8b\x3a\xff\xff' 62 | b'\xff\xff\xa3\xb8\x88\x88\x8c\x41\x14\xc8\x88\x88\x8b\x3a\xff\xff' 63 | b'\xff\xff\xa3\x9b\xbb\xbb\xbb\xcc\xcc\xbb\xbb\xbb\xb9\x3a\xff\xff' 64 | b'\xff\xff\xe4\x6c\xbb\xbb\xbb\xcc\xdc\xbb\xbb\xbb\xc6\x4e\xff\xff' 65 | b'\xff\xff\xea\x36\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\x63\xae\xff\xff' 66 | b'\xff\xff\xfe\x63\x69\xdd\xdd\xdd\xdd\xdd\xdd\x96\x36\xef\xff\xff' 67 | b'\xff\xff\xff\xe6\x43\x33\x33\x33\x33\x33\x33\x34\x6e\xff\xff\xff' 68 | b'\xff\xff\xff\xfe\xaa\x66\x66\x66\x66\x66\x66\xaa\xef\xff\xff\xff' 69 | b'\xff\xff\xff\xff\xee\xee\xee\xee\xee\xee\xee\xee\xff\xff\xff\xff' 70 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 71 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 72 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 73 | ), 74 | } 75 | 76 | colortable = { 77 | 0: ( 78 | b'\x35\x1a\x00\x04\x48\x2e\x14\x00\x66\x4c\x32\x00\x84\x6e\x6d\x00' 79 | b'\xa6\x94\x85\x00\xdb\xb7\x91\x00\xc2\xb6\xaf\x00\xf7\xc0\x83\x00' 80 | b'\xf9\xd2\xa1\x00\xe3\xcf\xb4\x00\xd6\xce\xcc\x00\xfa\xde\xb7\x00' 81 | b'\xfd\xe7\xc7\x00\xf4\xed\xdd\x00\xe8\xeb\xe9\x00\xfc\xff\xfc\x00'), 82 | 1: ( 83 | b'\x1a\x0d\x00\x02\x24\x17\x0a\x00\x33\x26\x19\x00\x42\x37\x36\x00' 84 | b'\x53\x4a\x42\x00\x6d\x5b\x48\x00\x61\x5b\x57\x00\x7b\x60\x41\x00' 85 | b'\x7c\x69\x50\x00\x71\x67\x5a\x00\x6b\x67\x66\x00\x7d\x6f\x5b\x00' 86 | b'\x7e\x73\x63\x00\x7a\x76\x6e\x00\x74\x75\x74\x00\x7e\x7f\x7e\x00'), 87 | } 88 | width = 32 89 | height = 32 90 | colors = 4 91 | 92 | def get_icon(icon_index = 0, color_index = 0): 93 | return width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index]) 94 | 95 | def draw(x, y, icon_index, draw_fct, color_index = 0): 96 | draw_fct(x - width//2, y - height // 2, width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index])) 97 | -------------------------------------------------------------------------------- /tft/icons/flash.py: -------------------------------------------------------------------------------- 1 | 2 | # Code generated by bmp_to_icon.py 3 | from uctypes import addressof 4 | 5 | _icons = { 6 | 0: ( 7 | b'\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xc0\x00\x00\x03\xff\xff' 8 | b'\xff\xfc\x00\xaa\xaa\x00\x3f\xff\xff\xf0\x0a\xaa\xaa\xa0\x0f\xff' 9 | b'\xff\xc0\xaa\xaa\xaa\xaa\x03\xff\xff\x02\xaa\xaa\xaa\xaa\x80\xff' 10 | b'\xfc\x0a\xaa\xaa\xaa\xaa\xa0\x3f\xf0\x2a\xaa\xaa\xaa\xaa\xa8\x0f' 11 | b'\xf0\xaa\xaa\xaa\xaa\xaa\xaa\x0f\xc0\xaa\xaa\xaa\xaa\xaa\xaa\x03' 12 | b'\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83' 13 | b'\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0' 14 | b'\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0' 15 | b'\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0' 16 | b'\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0' 17 | b'\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83' 18 | b'\xc0\xaa\xaa\xaa\xaa\xaa\xaa\x03\xf0\xaa\xaa\xaa\xaa\xaa\xaa\x0f' 19 | b'\xf0\x2a\xaa\xaa\xaa\xaa\xa8\x0f\xfc\x0a\xaa\xaa\xaa\xaa\xa0\x3f' 20 | b'\xff\x02\xaa\xaa\xaa\xaa\x80\xff\xff\xc0\xaa\xaa\xaa\xaa\x03\xff' 21 | b'\xff\xf0\x0a\xaa\xaa\xa0\x0f\xff\xff\xfc\x00\xaa\xaa\x00\x3f\xff' 22 | b'\xff\xff\xc0\x00\x00\x03\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff' 23 | ), 24 | 1: ( 25 | b'\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xc0\x00\x00\x03\xff\xff' 26 | b'\xff\xfc\x00\xaa\xaa\x00\x3f\xff\xff\xf0\x0a\xaa\xaa\xa0\x0f\xff' 27 | b'\xff\xc0\xaa\xaa\xaa\xaa\x03\xff\xff\x02\xaa\xaa\xaa\xaa\x80\xff' 28 | b'\xfc\x0a\xaa\xaa\xaa\xaa\xa0\x3f\xf0\x2a\xaa\xaa\xaa\xaa\xa8\x0f' 29 | b'\xf0\xaa\xaa\xaa\xaa\xaa\xaa\x0f\xc0\xaa\xaa\xaa\xaa\xaa\xaa\x03' 30 | b'\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83' 31 | b'\x0a\xaa\xaa\xa5\x6a\xaa\xaa\xa0\x0a\xaa\xaa\x95\x5a\xaa\xaa\xa0' 32 | b'\x0a\xaa\xaa\x55\x56\xaa\xaa\xa0\x0a\xaa\xaa\x55\x56\xaa\xaa\xa0' 33 | b'\x0a\xaa\xaa\x55\x56\xaa\xaa\xa0\x0a\xaa\xaa\x95\x5a\xaa\xaa\xa0' 34 | b'\x0a\xaa\xaa\xa5\x6a\xaa\xaa\xa0\x0a\xaa\xaa\xaa\xaa\xaa\xaa\xa0' 35 | b'\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83\xc2\xaa\xaa\xaa\xaa\xaa\xaa\x83' 36 | b'\xc0\xaa\xaa\xaa\xaa\xaa\xaa\x03\xf0\xaa\xaa\xaa\xaa\xaa\xaa\x0f' 37 | b'\xf0\x2a\xaa\xaa\xaa\xaa\xa8\x0f\xfc\x0a\xaa\xaa\xaa\xaa\xa0\x3f' 38 | b'\xff\x02\xaa\xaa\xaa\xaa\x80\xff\xff\xc0\xaa\xaa\xaa\xaa\x03\xff' 39 | b'\xff\xf0\x0a\xaa\xaa\xa0\x0f\xff\xff\xfc\x00\xaa\xaa\x00\x3f\xff' 40 | b'\xff\xff\xc0\x00\x00\x03\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff' 41 | ), 42 | } 43 | 44 | colortable = { 45 | 0: ( 46 | b'\x00\x00\x00\x02\x32\x32\x7b\x00\xee\xee\xcd\x00\xff\xff\xff\x00'), 47 | 1: ( 48 | b'\x00\x00\x00\x01\x19\x19\x3d\x00\x77\x77\x66\x00\x7f\x7f\x7f\x00'), 49 | } 50 | width = 32 51 | height = 32 52 | colors = 2 53 | 54 | def get_icon(icon_index = 0, color_index = 0): 55 | return width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index]) 56 | 57 | def draw(x, y, icon_index, draw_fct, color_index = 0): 58 | draw_fct(x - width//2, y - height // 2, width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index])) 59 | -------------------------------------------------------------------------------- /tft/icons/iconswitch.py: -------------------------------------------------------------------------------- 1 | 2 | # Code generated by bmp_to_icon.py 3 | from uctypes import addressof 4 | 5 | _icons = { 6 | 0: ( 7 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 8 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfd\x97\x77\x55' 9 | b'\x55\x55\x55\x55\x55\x55\x55\x55\x55\x77\x7b\xdf\xff\xff\xff\xff' 10 | b'\xff\xff\xff\xfb\x79\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99' 11 | b'\x99\x99\x99\x97\xbf\xff\xff\xff\xff\xff\xfe\x79\x99\x77\x77\x77' 12 | b'\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x99\x99\x97\xef\xff\xff' 13 | b'\xff\xff\xd7\x99\x77\xbd\xee\xdb\x77\x9b\xbb\xbb\xbb\xbb\xbb\xbb' 14 | b'\xbb\xbb\xbb\xb9\x99\x7d\xff\xff\xff\xfe\x7b\x77\xbe\xee\xee\xee' 15 | b'\xed\x77\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xb9\xb7\xef\xff' 16 | b'\xff\xf7\xb7\x7e\xed\xee\xee\xed\xde\xe7\x7b\xbb\xbb\xbb\xbb\xbb' 17 | b'\xbb\xbb\xbb\xbb\xbb\x9b\x7f\xff\xff\xb9\x97\xee\xdd\xee\xee\xed' 18 | b'\xdd\xee\x79\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xb9\x9b\xff' 19 | b'\xff\x7b\x5b\xed\xdb\xde\xee\xdb\xdd\xde\xb5\xbb\xbb\xbb\xbb\xbb' 20 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xb7\xff\xfb\x99\x7e\xed\xdb\xbe\xee\xbb' 21 | b'\xbd\xdd\xe7\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xb9\xbf' 22 | b'\xf9\xb7\x9e\xdd\xbb\xbd\xee\xbb\xbd\xee\xe9\x7b\xbb\xbb\xbb\xbb' 23 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x9f\xf7\xb5\xde\xee\xdb\xbb\xed\xbd' 24 | b'\xee\xee\xed\x5b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7f' 25 | b'\xf7\xb3\xee\xee\xee\xed\xdd\xee\xee\xee\xee\x3b\xbb\xbb\xbb\xbb' 26 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7f\xf7\xb3\xee\xee\xee\xee\xdd\xdd' 27 | b'\xee\xee\xee\x3b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7f' 28 | b'\xf7\xb5\xde\xee\xee\xdb\xde\xbb\xbd\xde\xed\x5b\xbb\xbb\xbb\xbb' 29 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7f\xf9\xb7\x9e\xee\xdb\xbb\xde\xdb' 30 | b'\xbb\xdd\xe9\x7b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x9f' 31 | b'\xfb\x99\x5e\xdd\xdb\xbb\xee\xeb\xbd\xdd\xe5\x9b\xbb\xbb\xbb\xbb' 32 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xb9\xbf\xff\x7b\x3b\xed\xdd\xbd\xee\xed' 33 | b'\xbd\xde\xb3\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xb7\xff' 34 | b'\xff\xb9\x93\xee\xdd\xdd\xee\xee\xdd\xee\x39\xbb\xbb\xbb\xbb\xbb' 35 | b'\xbb\xbb\xbb\xbb\xbb\xbb\x9b\xff\xff\xf7\xb7\x5e\xed\xde\xee\xee' 36 | b'\xee\xe5\x7b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7f\xff' 37 | b'\xff\xfe\x7b\x72\xbe\xee\xee\xee\xeb\x27\xbb\xbb\xbb\xbb\xbb\xbb' 38 | b'\xbb\xbb\xbb\xbb\xbb\xb7\xef\xff\xff\xff\xd7\xb9\x25\x9d\xee\xd9' 39 | b'\x52\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x7d\xff\xff' 40 | b'\xff\xff\xfe\x79\xb7\x52\x00\x25\x9b\xbb\xbb\xbb\xbb\xbb\xbb\xbb' 41 | b'\xbb\xbb\xbb\xbb\x99\xef\xff\xff\xff\xff\xff\xfb\x79\xbb\xbb\xbb' 42 | b'\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x97\xbf\xff\xff\xff' 43 | b'\xff\xff\xff\xff\xfd\xb9\x77\x77\x77\x77\x77\x77\x77\x77\x77\x77' 44 | b'\x77\x77\x9b\xdf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 45 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 46 | ), 47 | 1: ( 48 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 49 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfa\x61\x11\x11' 50 | b'\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x16\xaf\xff\xff\xff\xff' 51 | b'\xff\xff\xff\xfa\x11\x46\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66' 52 | b'\x66\x66\x64\x11\xaf\xff\xff\xff\xff\xff\xfc\x11\x66\x66\x88\x88' 53 | b'\x88\x88\x88\x88\x88\x88\x88\x86\x41\x11\x14\x46\x11\xcf\xff\xff' 54 | b'\xff\xff\xa1\x66\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x86\x14' 55 | b'\xad\xee\xda\x41\x46\x1a\xff\xff\xff\xfc\x16\x68\x88\x88\x88\x88' 56 | b'\x88\x88\x88\x88\x88\x88\x41\xce\xee\xee\xee\xec\x14\x61\xcf\xff' 57 | b'\xff\xe1\x66\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x84\x4e\xed' 58 | b'\xee\xee\xed\xde\xe4\x46\x1e\xff\xff\x64\x68\x88\x88\x88\x88\x88' 59 | b'\x88\x88\x88\x88\x88\x61\xee\xdd\xee\xee\xdd\xdd\xee\x14\x46\xff' 60 | b'\xfd\x16\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x1c\xed\xdc' 61 | b'\xde\xee\xdc\xcd\xde\xc1\x61\xef\xfa\x46\x88\x88\x88\x88\x88\x88' 62 | b'\x88\x88\x88\x88\x84\x4e\xdd\xdc\xcd\xee\xcc\xcc\xdd\xe4\x44\xaf' 63 | b'\xf4\x66\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x84\xae\xdd\xcc' 64 | b'\xcd\xee\xcc\xcd\xee\xea\x46\x4f\xf1\x68\x88\x88\x88\x88\x88\x88' 65 | b'\x88\x88\x88\x88\x81\xce\xed\xdc\xcc\xdd\xcd\xee\xee\xed\x16\x1f' 66 | b'\xf1\x68\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x81\xee\xee\xee' 67 | b'\xed\xdd\xee\xee\xee\xee\x16\x1f\xf1\x68\x88\x88\x88\x88\x88\x88' 68 | b'\x88\x88\x88\x88\x81\xee\xee\xee\xee\xdd\xdd\xee\xee\xee\x16\x1f' 69 | b'\xf1\x68\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x81\xce\xee\xee' 70 | b'\xdc\xde\xcc\xcd\xde\xec\x16\x1f\xf4\x66\x88\x88\x88\x88\x88\x88' 71 | b'\x88\x88\x88\x88\x84\xae\xee\xdc\xcc\xde\xdc\xcc\xdd\xea\x46\x4f' 72 | b'\xfa\x46\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x86\x4e\xdd\xdc' 73 | b'\xcc\xee\xec\xcd\xdd\xe4\x44\xaf\xfd\x16\x88\x88\x88\x88\x88\x88' 74 | b'\x88\x88\x88\x88\x88\x1a\xed\xdd\xcd\xee\xed\xcd\xde\xc1\x61\xdf' 75 | b'\xff\x64\x68\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x61\xde\xdd' 76 | b'\xdd\xee\xee\xdd\xed\x14\x46\xff\xff\xe1\x66\x88\x88\x88\x88\x88' 77 | b'\x88\x88\x88\x88\x88\x84\x4d\xed\xde\xee\xee\xde\xd4\x46\x1e\xff' 78 | b'\xff\xfc\x16\x68\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x41\xce' 79 | b'\xee\xee\xee\xec\x14\x61\xcf\xff\xff\xff\xa1\x66\x68\x88\x88\x88' 80 | b'\x88\x88\x88\x88\x88\x88\x86\x14\xac\xee\xca\x41\x46\x1a\xff\xff' 81 | b'\xff\xff\xfc\x11\x66\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x86' 82 | b'\x41\x11\x14\x46\x11\xdf\xff\xff\xff\xff\xff\xfa\x11\x66\x66\x66' 83 | b'\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x66\x11\xaf\xff\xff\xff' 84 | b'\xff\xff\xff\xff\xfa\x61\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11' 85 | b'\x11\x11\x16\xaf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 86 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 87 | ), 88 | } 89 | 90 | colortable = { 91 | 0: ( 92 | b'\x51\x54\x52\x04\x9d\x6a\x33\x00\x62\x65\x63\x00\x6a\x6d\x6b\x00' 93 | b'\xad\x7c\x4c\x00\x76\x79\x77\x00\xbb\x8d\x60\x00\x8c\x8f\x8d\x00' 94 | b'\xca\x9a\x64\x00\xae\xb1\xaf\x00\xd0\xb5\x9b\x00\xc5\xc9\xc7\x00' 95 | b'\xda\xcd\xc0\x00\xe2\xe1\xdc\x00\xec\xee\xec\x00\xfc\xff\xfd\x00'), 96 | 1: ( 97 | b'\x28\x2a\x29\x02\x4e\x35\x19\x00\x31\x32\x31\x00\x35\x36\x35\x00' 98 | b'\x56\x3e\x26\x00\x3b\x3c\x3b\x00\x5d\x46\x30\x00\x46\x47\x46\x00' 99 | b'\x65\x4d\x32\x00\x57\x58\x57\x00\x68\x5a\x4d\x00\x62\x64\x63\x00' 100 | b'\x6d\x66\x60\x00\x71\x70\x6e\x00\x76\x77\x76\x00\x7e\x7f\x7e\x00'), 101 | } 102 | width = 48 103 | height = 26 104 | colors = 4 105 | 106 | def get_icon(icon_index = 0, color_index = 0): 107 | return width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index]) 108 | 109 | def draw(x, y, icon_index, draw_fct, color_index = 0): 110 | draw_fct(x - width//2, y - height // 2, width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index])) 111 | -------------------------------------------------------------------------------- /tft/icons/radiobutton.py: -------------------------------------------------------------------------------- 1 | 2 | # Code generated by bmp_to_icon.py 3 | from uctypes import addressof 4 | 5 | _icons = { 6 | 0: ( 7 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 8 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 9 | b'\xff\xff\xff\xff\xff\xfe\xdd\xda\xad\xdd\xef\xff\xff\xff\xff\xff' 10 | b'\xff\xff\xff\xff\xfe\xda\x54\x44\x44\x45\xad\xef\xff\xff\xff\xff' 11 | b'\xff\xff\xff\xff\xd5\x45\xad\xef\xfe\xda\x54\x5d\xff\xff\xff\xff' 12 | b'\xff\xff\xff\xea\x44\xaf\xff\xff\xff\xff\xfa\x44\xae\xff\xff\xff' 13 | b'\xff\xff\xfe\xa4\xaf\xff\xff\xff\xff\xff\xff\xfa\x4a\xef\xff\xff' 14 | b'\xff\xff\xfa\x4a\xff\xff\xff\xff\xff\xff\xff\xff\xa4\xaf\xff\xff' 15 | b'\xff\xff\xd4\xaf\xff\xff\xff\xff\xff\xff\xff\xff\xfa\x4d\xff\xff' 16 | b'\xff\xfe\x54\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x45\xef\xff' 17 | b'\xff\xfd\x4a\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xa4\xdf\xff' 18 | b'\xff\xea\x5f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf5\xae\xff' 19 | b'\xff\xd5\xaf\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfa\x5d\xff' 20 | b'\xff\xd4\xde\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xed\x4d\xff' 21 | b'\xff\xa4\xde\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xed\x4a\xff' 22 | b'\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff' 23 | b'\xff\xa4\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\x4a\xff' 24 | b'\xff\xa4\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\x4a\xff' 25 | b'\xff\xd4\xad\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xda\x4d\xff' 26 | b'\xff\xd4\xad\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xda\x4d\xff' 27 | b'\xff\xe5\x5e\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xe5\x5e\xff' 28 | b'\xff\xfa\x4a\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xa4\xaf\xff' 29 | b'\xff\xfd\x54\xed\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xde\x45\xdf\xff' 30 | b'\xff\xfe\xa4\xae\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xea\x4a\xef\xff' 31 | b'\xff\xff\xda\x4a\xee\xdd\xdd\xdd\xdd\xdd\xdd\xee\xa4\xad\xff\xff' 32 | b'\xff\xff\xfd\x54\xae\xee\xee\xee\xee\xee\xee\xea\x45\xdf\xff\xff' 33 | b'\xff\xff\xfe\xda\x44\xae\xee\xee\xee\xee\xea\x44\xad\xef\xff\xff' 34 | b'\xff\xff\xff\xed\xa5\x45\xad\xde\xed\xda\x54\x5a\xde\xff\xff\xff' 35 | b'\xff\xff\xff\xfe\xdd\xa5\x44\x44\x44\x44\x5a\xdd\xef\xff\xff\xff' 36 | b'\xff\xff\xff\xff\xfe\xdd\xaa\xaa\xaa\xaa\xdd\xef\xff\xff\xff\xff' 37 | b'\xff\xff\xff\xff\xff\xfe\xed\xdd\xdd\xde\xef\xff\xff\xff\xff\xff' 38 | b'\xff\xff\xff\xff\xff\xff\xff\xfe\xef\xff\xff\xff\xff\xff\xff\xff' 39 | ), 40 | 1: ( 41 | b'\xff\xff\xff\xff\xfe\xee\xec\xcc\xcc\xce\xee\xef\xff\xff\xff\xff' 42 | b'\xff\xff\xff\xfe\xee\xcc\xbb\xbb\xbb\xbb\xcc\xee\xef\xff\xff\xff' 43 | b'\xff\xff\xff\xee\xcb\xb9\x87\x77\x77\x78\x9b\xbc\xee\xff\xff\xff' 44 | b'\xff\xff\xee\xcc\xb8\x77\x33\x22\x22\x33\x77\x8b\xcc\xee\xff\xff' 45 | b'\xff\xfe\xec\xb9\x73\x32\x6b\xcc\xcc\xb6\x23\x37\x9b\xce\xef\xff' 46 | b'\xff\xfe\xcb\x87\x32\xbc\xcc\xcc\xcc\xcc\xcb\x23\x78\xbc\xef\xff' 47 | b'\xff\xec\xb8\x73\x6c\xcc\xcc\xbb\xbb\xcc\xcc\xc6\x37\x8b\xce\xff' 48 | b'\xfe\xec\x97\x36\xcc\xcb\xbb\xbb\xbb\xbb\xbc\xcc\x63\x79\xce\xef' 49 | b'\xfe\xcb\x73\x6c\xcb\xbb\xbb\xbb\xbb\xbb\xbb\xbc\xc6\x37\xbc\xef' 50 | b'\xee\xb8\x32\xcb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbc\x23\x8b\xee' 51 | b'\xec\xb7\x39\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x93\x7b\xce' 52 | b'\xec\x83\x2b\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xb2\x38\xce' 53 | b'\xeb\x83\x69\x99\x99\x99\x96\x10\x01\x69\x99\x99\x99\x96\x38\xbe' 54 | b'\xcb\x73\x89\x99\x99\x99\x60\x00\x00\x06\x99\x99\x99\x96\x37\xbc' 55 | b'\xcb\x72\x88\x88\x88\x88\x10\x00\x00\x01\x88\x88\x88\x88\x27\xbc' 56 | b'\xcb\x72\x88\x88\x88\x88\x00\x00\x00\x00\x88\x88\x88\x88\x27\xbc' 57 | b'\xcb\x72\x77\x77\x77\x77\x00\x00\x00\x00\x77\x77\x77\x77\x27\xbc' 58 | b'\xcb\x72\x77\x77\x77\x77\x10\x00\x00\x01\x77\x77\x77\x77\x27\xbc' 59 | b'\xcb\x73\x77\x77\x77\x77\x60\x00\x00\x06\x77\x77\x77\x77\x37\xbc' 60 | b'\xeb\x83\x77\x77\x77\x77\x96\x10\x01\x69\x77\x77\x77\x77\x38\xbe' 61 | b'\xec\x83\x38\x88\x88\x88\x8b\xbb\xbb\xb8\x88\x88\x88\x83\x38\xce' 62 | b'\xec\xb7\x37\x88\x88\x88\x88\xbb\xbb\x88\x88\x88\x88\x73\x7b\xce' 63 | b'\xee\xb8\x32\x99\x99\x99\x99\x99\x99\x99\x99\x99\x99\x23\x8b\xee' 64 | b'\xfe\xcb\x73\x69\x99\x99\x99\x99\x99\x99\x99\x99\x96\x37\xbc\xef' 65 | b'\xfe\xec\x97\x36\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\x63\x79\xce\xef' 66 | b'\xff\xec\xb8\x73\x6b\xbb\xbb\xbb\xbb\xbb\xbb\xb6\x37\x8b\xce\xff' 67 | b'\xff\xfe\xcb\x87\x32\x9c\xcc\xcc\xcc\xcc\xc9\x23\x78\xbc\xef\xff' 68 | b'\xff\xfe\xec\xb9\x73\x32\x6b\xbc\xcb\xb6\x23\x37\x9b\xce\xef\xff' 69 | b'\xff\xff\xee\xcc\xb8\x77\x33\x22\x22\x33\x77\x8b\xcc\xee\xff\xff' 70 | b'\xff\xff\xff\xee\xcb\xb9\x87\x77\x77\x78\x9b\xbc\xee\xff\xff\xff' 71 | b'\xff\xff\xff\xfe\xee\xcc\xbb\xbb\xbb\xbb\xcc\xee\xef\xff\xff\xff' 72 | b'\xff\xff\xff\xff\xfe\xee\xec\xcc\xcc\xce\xee\xef\xff\xff\xff\xff' 73 | ), 74 | } 75 | 76 | colortable = { 77 | 0: ( 78 | b'\x37\x1c\x00\x04\x73\x55\x37\x00\xb1\x83\x65\x00\xc3\x8f\x61\x00' 79 | b'\x98\x9b\x99\x00\xab\xae\xac\x00\xd7\xb6\x93\x00\xf0\xba\x80\x00' 80 | b'\xf7\xcc\x9b\x00\xf7\xd5\xab\x00\xd0\xd2\xce\x00\xf8\xde\xbd\x00' 81 | b'\xfa\xeb\xda\x00\xea\xed\xeb\x00\xf5\xf6\xf3\x00\xfc\xff\xfd\x00'), 82 | 1: ( 83 | b'\x1b\x0e\x00\x02\x39\x2a\x1b\x00\x58\x41\x32\x00\x61\x47\x30\x00' 84 | b'\x4c\x4d\x4c\x00\x55\x57\x56\x00\x6b\x5b\x49\x00\x78\x5d\x40\x00' 85 | b'\x7b\x66\x4d\x00\x7b\x6a\x55\x00\x68\x69\x67\x00\x7c\x6f\x5e\x00' 86 | b'\x7d\x75\x6d\x00\x75\x76\x75\x00\x7a\x7b\x79\x00\x7e\x7f\x7e\x00'), 87 | } 88 | width = 32 89 | height = 32 90 | colors = 4 91 | 92 | def get_icon(icon_index = 0, color_index = 0): 93 | return width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index]) 94 | 95 | def draw(x, y, icon_index, draw_fct, color_index = 0): 96 | draw_fct(x - width//2, y - height // 2, width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index])) 97 | -------------------------------------------------------------------------------- /tft/icons/threestate.py: -------------------------------------------------------------------------------- 1 | 2 | # Code generated by bmp_to_icon.py 3 | from uctypes import addressof 4 | 5 | _icons = { 6 | 0: ( 7 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 8 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 9 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 10 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 11 | b'\xff\xff\xff\xff\xee\xaa\xaa\xaa\xaa\xaa\xaa\xee\xff\xff\xff\xff' 12 | b'\xff\xff\xff\xea\x64\x44\x44\x44\x44\x44\x44\x46\xae\xff\xff\xff' 13 | b'\xff\xff\xfe\xa4\x6e\xff\xff\xff\xff\xff\xff\xe6\x4a\xef\xff\xff' 14 | b'\xff\xff\xfa\x4a\xff\xff\xff\xff\xff\xff\xff\xff\xa4\xaf\xff\xff' 15 | b'\xff\xff\xe6\x6f\xff\xff\xff\xff\xff\xff\xff\xff\xf6\x6e\xff\xff' 16 | b'\xff\xff\xe4\xef\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x4e\xff\xff' 17 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 18 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 19 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 20 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 21 | b'\xff\xff\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\xff\xff' 22 | b'\xff\xff\xa4\xfe\xee\xee\xee\xee\xee\xee\xee\xee\xef\x4a\xff\xff' 23 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 24 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 25 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 26 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 27 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 28 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 29 | b'\xff\xff\xa4\xee\xee\xee\xee\xee\xee\xee\xee\xee\xee\x4a\xff\xff' 30 | b'\xff\xff\xe4\x6e\xee\xee\xee\xee\xee\xee\xee\xee\xe6\x4e\xff\xff' 31 | b'\xff\xff\xfa\x4a\xee\xee\xee\xee\xee\xee\xee\xee\xa4\xaf\xff\xff' 32 | b'\xff\xff\xfe\x64\x6e\xee\xee\xee\xee\xee\xee\xe6\x46\xef\xff\xff' 33 | b'\xff\xff\xff\xea\x44\x44\x44\x44\x44\x44\x44\x44\xae\xff\xff\xff' 34 | b'\xff\xff\xff\xfe\xaa\xa6\x66\x66\x66\x66\x6a\xaa\xef\xff\xff\xff' 35 | b'\xff\xff\xff\xff\xee\xee\xee\xee\xee\xee\xee\xee\xff\xff\xff\xff' 36 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 37 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 38 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 39 | ), 40 | 1: ( 41 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 42 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 43 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 44 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 45 | b'\xff\xff\xff\xff\xea\xaa\xaa\xaa\xaa\xaa\xaa\xae\xff\xff\xff\xff' 46 | b'\xff\xff\xff\xfa\x43\x33\x33\x33\x33\x33\x33\x34\xaf\xff\xff\xff' 47 | b'\xff\xff\xff\x63\x6d\xff\xff\xff\xff\xff\xff\xd6\x36\xff\xff\xff' 48 | b'\xff\xff\xfa\x36\xdc\xcc\xcc\xcc\xcc\xcc\xcc\xcd\x63\xaf\xff\xff' 49 | b'\xff\xff\xe4\x6d\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xd6\x4e\xff\xff' 50 | b'\xff\xff\xa3\xac\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xca\x3a\xff\xff' 51 | b'\xff\xff\xa3\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\x3a\xff\xff' 52 | b'\xff\xff\xa3\xcb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbc\x3a\xff\xff' 53 | b'\xff\xff\xa3\xcb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbc\x3a\xff\xff' 54 | b'\xff\xff\xa3\xb8\x88\x88\x88\x88\x88\x88\x88\x88\x8b\x3a\xff\xff' 55 | b'\xff\xff\xa3\xb8\x88\x00\x00\x00\x00\x00\x00\x88\x8b\x3a\xff\xff' 56 | b'\xff\xff\xa3\x88\x88\x00\x00\x00\x00\x00\x00\x88\x88\x3a\xff\xff' 57 | b'\xff\xff\xa3\x87\x77\x00\x00\x00\x00\x00\x00\x77\x78\x3a\xff\xff' 58 | b'\xff\xff\xa3\x87\x77\x00\x00\x00\x00\x00\x00\x77\x78\x3a\xff\xff' 59 | b'\xff\xff\xa3\x87\x77\xbb\xbb\xbb\xbb\xbb\xbb\x77\x78\x3a\xff\xff' 60 | b'\xff\xff\xa3\x87\x77\xbb\xbb\xbb\xbb\xbb\xbb\x77\x78\x3a\xff\xff' 61 | b'\xff\xff\xa3\x88\x88\x88\x88\x88\x88\x88\x88\x88\x88\x3a\xff\xff' 62 | b'\xff\xff\xa3\xb8\x88\x88\x88\x88\x88\x88\x88\x88\x8b\x3a\xff\xff' 63 | b'\xff\xff\xa3\xab\x88\x88\x88\x88\x88\x88\x88\x88\xbb\x3a\xff\xff' 64 | b'\xff\xff\xe4\x6c\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xbb\xc6\x4e\xff\xff' 65 | b'\xff\xff\xfa\x36\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\x63\xaf\xff\xff' 66 | b'\xff\xff\xfe\x63\x6a\xdd\xdd\xdd\xdd\xdd\xdd\xa6\x36\xef\xff\xff' 67 | b'\xff\xff\xff\xe6\x43\x33\x33\x33\x33\x33\x33\x34\x6e\xff\xff\xff' 68 | b'\xff\xff\xff\xfe\xaa\x66\x66\x66\x66\x66\x66\xaa\xef\xff\xff\xff' 69 | b'\xff\xff\xff\xff\xee\xee\xee\xee\xee\xee\xee\xee\xff\xff\xff\xff' 70 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 71 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 72 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 73 | ), 74 | 2: ( 75 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x61\x16\xff' 76 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\x10\x01\xff' 77 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf2\x00\x01\xff' 78 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x40\x00\x04\xff' 79 | b'\xff\xff\xff\xff\xea\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x2f\xff' 80 | b'\xff\xff\xff\xea\x43\x33\x33\x33\x33\x33\x33\x31\x00\x01\xef\xff' 81 | b'\xff\xff\xfe\x63\x6d\xff\xff\xff\xff\xff\xff\x40\x00\x06\xff\xff' 82 | b'\xff\xff\xfa\x36\xdc\xcc\xcc\xcc\xcc\xcc\xc9\x00\x00\x2f\xff\xff' 83 | b'\xff\xff\xe4\x6d\xcc\xcc\xcc\xcc\xcc\xcc\xc2\x00\x00\x6e\xff\xff' 84 | b'\xff\xff\xa3\xac\xcc\xcc\xcc\xcc\xcc\xcc\x40\x00\x04\x4a\xff\xff' 85 | b'\xff\xff\xa3\xcc\xcc\xcc\xcc\xcc\xcc\xc9\x00\x00\x2d\x3a\xff\xff' 86 | b'\xff\xff\xa3\xcb\xbb\xbb\xbb\xbb\xbb\xb2\x00\x00\x9c\x3a\xff\xff' 87 | b'\xff\xff\xa3\xcb\xb4\x11\x4b\xbb\xbb\x40\x00\x04\xcc\x3a\xff\xff' 88 | b'\xff\xff\xa3\xbb\xb1\x00\x05\xbb\xb5\x00\x00\x2c\xbb\x3a\xff\xff' 89 | b'\xff\xff\xa3\xb8\x81\x00\x01\x58\x82\x00\x00\x9b\x8b\x3a\xff\xff' 90 | b'\xff\xff\xa3\xb8\x84\x00\x00\x18\x30\x00\x04\xb8\x8b\x3a\xff\xff' 91 | b'\xff\xff\xa3\x87\x7b\x30\x00\x02\x00\x00\x2b\x77\x78\x3a\xff\xff' 92 | b'\xff\xff\xa3\x87\x77\xb2\x00\x00\x00\x00\x98\x77\x78\x3a\xff\xff' 93 | b'\xff\xff\xa3\x87\x77\x8b\x10\x00\x00\x04\x87\x77\x78\x3a\xff\xff' 94 | b'\xff\xff\xa3\x87\x77\x7b\x91\x00\x00\x2b\x87\x77\x78\x3a\xff\xff' 95 | b'\xff\xff\xa3\xb8\x88\x88\xb5\x00\x00\x9b\x88\x88\x8b\x3a\xff\xff' 96 | b'\xff\xff\xa3\xb8\x88\x88\x8c\x41\x14\xc8\x88\x88\x8b\x3a\xff\xff' 97 | b'\xff\xff\xa3\x9b\xbb\xbb\xbb\xcc\xcc\xbb\xbb\xbb\xb9\x3a\xff\xff' 98 | b'\xff\xff\xe4\x6c\xbb\xbb\xbb\xcc\xdc\xbb\xbb\xbb\xc6\x4e\xff\xff' 99 | b'\xff\xff\xea\x36\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\x63\xae\xff\xff' 100 | b'\xff\xff\xfe\x63\x69\xdd\xdd\xdd\xdd\xdd\xdd\x96\x36\xef\xff\xff' 101 | b'\xff\xff\xff\xe6\x43\x33\x33\x33\x33\x33\x33\x34\x6e\xff\xff\xff' 102 | b'\xff\xff\xff\xfe\xaa\x66\x66\x66\x66\x66\x66\xaa\xef\xff\xff\xff' 103 | b'\xff\xff\xff\xff\xee\xee\xee\xee\xee\xee\xee\xee\xff\xff\xff\xff' 104 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 105 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 106 | b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' 107 | ), 108 | } 109 | 110 | colortable = { 111 | 0: ( 112 | b'\x35\x1a\x00\x04\x48\x2e\x14\x00\x66\x4c\x32\x00\x84\x6e\x6d\x00' 113 | b'\xa6\x94\x85\x00\xdb\xb7\x91\x00\xc2\xb6\xaf\x00\xf7\xc0\x83\x00' 114 | b'\xf9\xd2\xa1\x00\xe3\xcf\xb4\x00\xd6\xce\xcc\x00\xfa\xde\xb7\x00' 115 | b'\xfd\xe7\xc7\x00\xf4\xed\xdd\x00\xe8\xeb\xe9\x00\xfc\xff\xfc\x00'), 116 | 1: ( 117 | b'\x1a\x0d\x00\x02\x24\x17\x0a\x00\x33\x26\x19\x00\x42\x37\x36\x00' 118 | b'\x53\x4a\x42\x00\x6d\x5b\x48\x00\x61\x5b\x57\x00\x7b\x60\x41\x00' 119 | b'\x7c\x69\x50\x00\x71\x67\x5a\x00\x6b\x67\x66\x00\x7d\x6f\x5b\x00' 120 | b'\x7e\x73\x63\x00\x7a\x76\x6e\x00\x74\x75\x74\x00\x7e\x7f\x7e\x00'), 121 | } 122 | width = 32 123 | height = 32 124 | colors = 4 125 | 126 | def get_icon(icon_index = 0, color_index = 0): 127 | return width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index]) 128 | 129 | def draw(x, y, icon_index, draw_fct, color_index = 0): 130 | draw_fct(x - width//2, y - height // 2, width, height, addressof(_icons[icon_index]), colors, addressof(colortable[color_index])) 131 | -------------------------------------------------------------------------------- /tft/primitives/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py for primitives 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | try: 8 | import uasyncio as asyncio 9 | except ImportError: 10 | import asyncio 11 | 12 | 13 | async def _g(): 14 | pass 15 | type_coro = type(_g()) 16 | 17 | # If a callback is passed, run it and return. 18 | # If a coro is passed initiate it and return. 19 | # coros are passed by name i.e. not using function call syntax. 20 | def launch(func, tup_args): 21 | res = func(*tup_args) 22 | if isinstance(res, type_coro): 23 | res = asyncio.create_task(res) 24 | return res 25 | -------------------------------------------------------------------------------- /tft/primitives/delay_ms.py: -------------------------------------------------------------------------------- 1 | # delay_ms.py 2 | 3 | # Copyright (c) 2018-2020 Peter Hinch 4 | # Released under the MIT License (MIT) - see LICENSE file 5 | # Rewritten for uasyncio V3. Allows stop time to be brought forwards. 6 | 7 | import uasyncio as asyncio 8 | from utime import ticks_add, ticks_diff, ticks_ms 9 | from micropython import schedule 10 | from . import launch 11 | # Usage: 12 | # from primitives.delay_ms import Delay_ms 13 | 14 | class Delay_ms: 15 | verbose = False # verbose and can_alloc retained to avoid breaking code. 16 | def __init__(self, func=None, args=(), can_alloc=True, duration=1000): 17 | self._func = func 18 | self._args = args 19 | self._duration = duration # Default duration 20 | self._tstop = None # Stop time (ms). None signifies not running. 21 | self._tsave = None # Temporary storage for stop time 22 | self._ktask = None # timer task 23 | self._retrn = None # Return value of launched callable 24 | self._do_trig = self._trig # Avoid allocation in .trigger 25 | 26 | def stop(self): 27 | if self._ktask is not None: 28 | self._ktask.cancel() 29 | 30 | def trigger(self, duration=0): # Update end time 31 | now = ticks_ms() 32 | if duration <= 0: # Use default set by constructor 33 | duration = self._duration 34 | self._retrn = None 35 | is_running = self() 36 | tstop = self._tstop # Current stop time 37 | # Retriggering normally just updates ._tstop for ._timer 38 | self._tstop = ticks_add(now, duration) 39 | # Identify special case where we are bringing the end time forward 40 | can = is_running and duration < ticks_diff(tstop, now) 41 | if not is_running or can: 42 | schedule(self._do_trig, can) 43 | 44 | def _trig(self, can): 45 | if can: 46 | self._ktask.cancel() 47 | self._ktask = asyncio.create_task(self._timer(can)) 48 | 49 | def __call__(self): # Current running status 50 | return self._tstop is not None 51 | 52 | running = __call__ 53 | 54 | def rvalue(self): 55 | return self._retrn 56 | 57 | async def _timer(self, restart): 58 | if restart: # Restore cached end time 59 | self._tstop = self._tsave 60 | try: 61 | twait = ticks_diff(self._tstop, ticks_ms()) 62 | while twait > 0: # Must loop here: might be retriggered 63 | await asyncio.sleep_ms(twait) 64 | twait = ticks_diff(self._tstop, ticks_ms()) 65 | if self._func is not None: # Timed out: execute callback 66 | self._retrn = launch(self._func, self._args) 67 | finally: 68 | self._tsave = self._tstop # Save in case we restart. 69 | self._tstop = None # timer is stopped 70 | -------------------------------------------------------------------------------- /tft/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhinch/micropython-tft-gui/11837ce83df6b122a33bad1b472e9da5a1e40c22/tft/widgets/__init__.py -------------------------------------------------------------------------------- /tft/widgets/buttons.py: -------------------------------------------------------------------------------- 1 | # Adapted for (and requires) uasyncio V3 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2016-2020 Peter Hinch 5 | # buttons.py For TFT driver. 6 | # Adapted for (and requires) uasyncio V3 7 | 8 | # Released under the MIT License (MIT). See LICENSE. 9 | # Copyright (c) 2016-2020 Peter Hinch 10 | 11 | import uasyncio as asyncio 12 | from tft.driver.ugui import Touchable, dolittle, print_centered 13 | from tft.primitives.delay_ms import Delay_ms 14 | from tft.driver.constants import * 15 | 16 | # *********** PUSHBUTTON CLASSES *********** 17 | 18 | # Button coordinates relate to bounding box (BB). x, y are of BB top left corner. 19 | # likewise width and height refer to BB, regardless of button shape 20 | # If font is None button will be rendered without text 21 | 22 | class Button(Touchable): 23 | lit_time = 1000 24 | long_press_time = 1000 25 | def __init__(self, location, *, font, shape=CIRCLE, height=50, width=50, fill=True, 26 | fgcolor=None, bgcolor=None, fontcolor=None, litcolor=None, text='', 27 | callback=dolittle, args=[], onrelease=True, lp_callback=None, lp_args=[]): 28 | super().__init__(location, font, height, width, fgcolor, bgcolor, fontcolor, None, False, text, None) 29 | self.shape = shape 30 | self.radius = height // 2 31 | self.fill = fill 32 | self.litcolor = litcolor 33 | self.text = text 34 | self.callback = callback 35 | self.callback_args = args 36 | self.onrelease = onrelease 37 | self.lp_callback = lp_callback 38 | self.lp_args = lp_args 39 | self.lp = False # Long press not in progress 40 | self.orig_fgcolor = fgcolor 41 | if self.litcolor is not None: 42 | self.delay = Delay_ms(self.shownormal) 43 | self.litcolor = litcolor if self.fgcolor is not None else None 44 | 45 | def show(self): 46 | tft = self.tft 47 | x = self.location[0] 48 | y = self.location[1] 49 | if not self.visible: # erase the button 50 | tft.usegrey(False) 51 | tft.fill_rectangle(x, y, x + self.width, y + self.height, self.bgcolor) 52 | return 53 | if self.shape == CIRCLE: # Button coords are of top left corner of bounding box 54 | x += self.radius 55 | y += self.radius 56 | if self.fill: 57 | tft.fill_circle(x, y, self.radius, self.fgcolor) 58 | else: 59 | tft.draw_circle(x, y, self.radius, self.fgcolor) 60 | if self.font is not None and len(self.text): 61 | print_centered(tft, x, y, self.text, self.fontcolor, self.font) 62 | else: 63 | x1 = x + self.width 64 | y1 = y + self.height 65 | if self.shape == RECTANGLE: # rectangle 66 | if self.fill: 67 | tft.fill_rectangle(x, y, x1, y1, self.fgcolor) 68 | else: 69 | tft.draw_rectangle(x, y, x1, y1, self.fgcolor) 70 | if self.font is not None and len(self.text): 71 | print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font) 72 | elif self.shape == CLIPPED_RECT: # clipped rectangle 73 | if self.fill: 74 | tft.fill_clipped_rectangle(x, y, x1, y1, self.fgcolor) 75 | else: 76 | tft.draw_clipped_rectangle(x, y, x1, y1, self.fgcolor) 77 | if self.font is not None and len(self.text): 78 | print_centered(tft, (x + x1) // 2, (y + y1) // 2, self.text, self.fontcolor, self.font) 79 | 80 | def shownormal(self): 81 | self.fgcolor = self.orig_fgcolor 82 | self.show_if_current() 83 | 84 | def _touched(self, x, y): # Process touch 85 | if self.litcolor is not None: 86 | self.fgcolor = self.litcolor 87 | self.show() # must be on current screen 88 | self.delay.trigger(Button.lit_time) 89 | if self.lp_callback is not None: 90 | asyncio.create_task(self.longpress()) 91 | if not self.onrelease: 92 | self.callback(self, *self.callback_args) # Callback not a bound method so pass self 93 | 94 | def _untouched(self): 95 | self.lp = False 96 | if self.onrelease: 97 | self.callback(self, *self.callback_args) # Callback not a bound method so pass self 98 | 99 | async def longpress(self): 100 | self.lp = True 101 | await asyncio.sleep_ms(self.long_press_time) 102 | if self.lp: 103 | self.lp_callback(self, *self.lp_args) 104 | 105 | # Group of buttons, typically at same location, where pressing one shows 106 | # the next e.g. start/stop toggle or sequential select from short list 107 | class ButtonList: 108 | def __init__(self, callback=dolittle): 109 | self.user_callback = callback 110 | self.lstbuttons = [] 111 | self.current = None # No current button 112 | self._greyed_out = False 113 | 114 | def add_button(self, *args, **kwargs): 115 | button = Button(*args, **kwargs) 116 | self.lstbuttons.append(button) 117 | active = self.current is None # 1st button added is active 118 | button.visible = active 119 | button.callback = self._callback 120 | if active: 121 | self.current = button 122 | return button 123 | 124 | def value(self, button=None): 125 | if button is not None and button is not self.current: 126 | old = self.current 127 | new = button 128 | self.current = new 129 | old.visible = False 130 | old.show() 131 | new.visible = True 132 | new.show() 133 | self.user_callback(new, *new.callback_args) 134 | return self.current 135 | 136 | def greyed_out(self, val=None): 137 | if val is not None and self._greyed_out != val: 138 | self._greyed_out = val 139 | for button in self.lstbuttons: 140 | button.greyed_out(val) 141 | self.current.show() 142 | return self._greyed_out 143 | 144 | def _callback(self, button, *args): 145 | old = button 146 | old_index = self.lstbuttons.index(button) 147 | new = self.lstbuttons[(old_index + 1) % len(self.lstbuttons)] 148 | self.current = new 149 | old.visible = False 150 | old.show() 151 | new.visible = True 152 | new.busy = True # Don't respond to continued press 153 | new.show() 154 | self.user_callback(new, *args) # user gets button with args they specified 155 | 156 | # Group of buttons at different locations, where pressing one shows 157 | # only current button highlighted and oes callback from current one 158 | class RadioButtons: 159 | def __init__(self, highlight, callback=dolittle, selected=0): 160 | self.user_callback = callback 161 | self.lstbuttons = [] 162 | self.current = None # No current button 163 | self.highlight = highlight 164 | self.selected = selected 165 | self._greyed_out = False 166 | 167 | def add_button(self, *args, **kwargs): 168 | button = Button(*args, **kwargs) 169 | self.lstbuttons.append(button) 170 | button.callback = self._callback 171 | active = len(self.lstbuttons) == self.selected + 1 172 | button.fgcolor = self.highlight if active else button.orig_fgcolor 173 | if active: 174 | self.current = button 175 | return button 176 | 177 | def value(self, button=None): 178 | if button is not None and button is not self.current: 179 | self._callback(button, *button.callback_args) 180 | return self.current 181 | 182 | def greyed_out(self, val=None): 183 | if val is not None and self._greyed_out != val: 184 | self._greyed_out = val 185 | for button in self.lstbuttons: 186 | button.greyed_out(val) 187 | return self._greyed_out 188 | 189 | def _callback(self, button, *args): 190 | for but in self.lstbuttons: 191 | if but is button: 192 | but.fgcolor = self.highlight 193 | self.current = button 194 | else: 195 | but.fgcolor = but.orig_fgcolor 196 | but.show() 197 | self.user_callback(button, *args) # user gets button with args they specified 198 | -------------------------------------------------------------------------------- /tft/widgets/checkbox.py: -------------------------------------------------------------------------------- 1 | # checkbox.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import Touchable, dolittle 8 | from tft.driver.constants import * 9 | 10 | class Checkbox(Touchable): 11 | def __init__(self, location, *, height=30, fillcolor=None, 12 | fgcolor=None, bgcolor=None, callback=dolittle, args=[], value=False, border=None): 13 | super().__init__(location, None, height, height, fgcolor, bgcolor, None, border, False, value, None) 14 | super()._set_callbacks(callback, args) 15 | self.fillcolor = fillcolor 16 | 17 | def show(self): 18 | if self._initial_value is None: 19 | self._initial_value = True 20 | value = self._value # As passed to ctor 21 | if value is None: 22 | self._value = False # special case: don't execute callback on initialisation 23 | else: 24 | self._value = not value # force redraw 25 | self.value(value) 26 | return 27 | self._show() 28 | 29 | def _show(self): 30 | tft = self.tft 31 | bw = self.border 32 | x = self.location[0] + bw 33 | y = self.location[1] + bw 34 | height = self.height - 2 * bw 35 | x1 = x + height 36 | y1 = y + height 37 | if self._value: 38 | if self.fillcolor is not None: 39 | tft.fill_rectangle(x, y, x1, y1, self.fillcolor) 40 | else: 41 | tft.fill_rectangle(x, y, x1, y1, self.bgcolor) 42 | tft.draw_rectangle(x, y, x1, y1, self.fgcolor) 43 | if self.fillcolor is None and self._value: 44 | tft.draw_line(x, y, x1, y1, self.fgcolor) 45 | tft.draw_line(x, y1, x1, y, self.fgcolor) 46 | 47 | def _touched(self, x, y): # Was touched 48 | self.value(not self._value) # Upddate and refresh 49 | 50 | -------------------------------------------------------------------------------- /tft/widgets/dial.py: -------------------------------------------------------------------------------- 1 | 2 | # dial.py For TFT driver. 3 | # Adapted for (and requires) uasyncio V3 4 | 5 | # Released under the MIT License (MIT). See LICENSE. 6 | # Copyright (c) 2016-2020 Peter Hinch 7 | 8 | import math 9 | from tft.driver.ugui import NoTouch 10 | from tft.driver.constants import * 11 | 12 | # class displays angles. Angle 0 is vertical, +ve increments are clockwise. 13 | class Dial(NoTouch): 14 | def __init__(self, location, *, height=100, fgcolor=None, bgcolor=None, border=None, pointers=(0.9,), ticks=4): 15 | NoTouch.__init__(self, location, None, height, height, fgcolor, bgcolor, None, border, 0, 0) # __super__ provoked Python bug 16 | border = self.border # border width 17 | radius = height / 2 - border 18 | self.radius = radius 19 | self.ticks = ticks 20 | self.xorigin = location[0] + border + radius 21 | self.yorigin = location[1] + border + radius 22 | self.pointers = tuple(z * self.radius for z in pointers) # Pointer lengths 23 | self.angles = [None for _ in pointers] 24 | self.new_value = None 25 | 26 | def show(self): 27 | tft = self.tft 28 | ticks = self.ticks 29 | radius = self.radius 30 | ticklen = 0.1 * radius 31 | for tick in range(ticks): 32 | theta = 2 * tick * math.pi / ticks 33 | x_start = int(self.xorigin + radius * math.sin(theta)) 34 | y_start = int(self.yorigin - radius * math.cos(theta)) 35 | x_end = int(self.xorigin + (radius - ticklen) * math.sin(theta)) 36 | y_end = int(self.yorigin - (radius - ticklen) * math.cos(theta)) 37 | tft.draw_line(x_start, y_start, x_end, y_end, self.fgcolor) 38 | tft.draw_circle(self.xorigin, self.yorigin, radius, self.fgcolor) 39 | for idx, ang in enumerate(self.angles): 40 | if ang is not None: 41 | self._drawpointer(ang, idx, self.bgcolor) # erase old 42 | if self.new_value is not None: 43 | self.angles[self.new_value[1]] = self.new_value[0] 44 | self.new_value = None 45 | 46 | for idx, ang in enumerate(self.angles): 47 | if ang is not None: 48 | self._drawpointer(ang, idx, self.fgcolor) 49 | 50 | def value(self, angle, pointer=0): 51 | if pointer > len(self.pointers): 52 | raise ValueError('pointer index out of range') 53 | self.new_value = [angle, pointer] 54 | self.show_if_current() 55 | 56 | def _drawpointer(self, radians, pointer, color): 57 | tft = self.tft 58 | length = self.pointers[pointer] 59 | x_end = int(self.xorigin + length * math.sin(radians)) 60 | y_end = int(self.yorigin - length * math.cos(radians)) 61 | tft.draw_line(int(self.xorigin), int(self.yorigin), x_end, y_end, color) 62 | 63 | -------------------------------------------------------------------------------- /tft/widgets/dialog.py: -------------------------------------------------------------------------------- 1 | # dialog.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import Aperture, Screen, get_stringsize 8 | from tft.driver.constants import * 9 | from tft.widgets.label import Label 10 | from tft.widgets.buttons import Button 11 | 12 | # Enables parameterised creation of button-based dialog boxes. See dialog.py 13 | 14 | class DialogBox(Aperture): 15 | def __init__(self, font, *, elements, location=(20, 20), label=None, 16 | bgcolor=DARKGREEN, buttonwidth=25, closebutton=True): 17 | height = 150 18 | spacing = 20 19 | buttonwidth = max(max([get_stringsize(x[0], font)[0] for x in elements]) + 4, buttonwidth) 20 | buttonheight = max(get_stringsize('x', font)[1], 25) 21 | nelements = len(elements) 22 | width = spacing + (buttonwidth + spacing) * nelements 23 | if label is not None: 24 | width = max(width, get_stringsize(label, font)[0] + 2 * spacing) 25 | super().__init__(location, height, width, bgcolor = bgcolor) 26 | x = self.location[0] + spacing # Coordinates relative to physical display 27 | gap = 0 28 | if nelements > 1: 29 | gap = ((width - 2 * spacing) - nelements * buttonwidth) // (nelements - 1) 30 | y = self.location[1] + self.height - buttonheight - 10 31 | if label is not None: 32 | Label((x, self.location[1] + 50), font = font, value = label) 33 | for text, color in elements: 34 | Button((x, y), height = buttonheight, width = buttonwidth, font = font, fontcolor = BLACK, fgcolor = color, 35 | text = text, shape = RECTANGLE, 36 | callback = self.back, args = (text,)) 37 | x += buttonwidth + gap 38 | if closebutton: 39 | x, y = get_stringsize('X', font) 40 | size = max(x, y, 25) 41 | Button((self.location[0] + width - (size + 1), self.location[1] + 1), height = size, width = size, font = font, 42 | fgcolor = RED, text = 'X', shape = RECTANGLE, 43 | callback = self.back, args = ('Close',)) 44 | 45 | def back(self, button, text): 46 | Aperture.value(text) 47 | Screen.back() 48 | -------------------------------------------------------------------------------- /tft/widgets/dropdown.py: -------------------------------------------------------------------------------- 1 | # buttons.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | # dropdown.py For TFT driver. 7 | # Adapted for (and requires) uasyncio V3 8 | 9 | # Released under the MIT License (MIT). See LICENSE. 10 | # Copyright (c) 2016-2020 Peter Hinch 11 | 12 | from tft.driver.ugui import Screen, Aperture, Touchable, dolittle, print_left 13 | from tft.driver.constants import * 14 | from tft.widgets.listbox import Listbox 15 | 16 | class _ListDialog(Aperture): 17 | def __init__(self, location, dropdown, width): 18 | border = 1 # between Aperture border and list 19 | dd = dropdown 20 | font = dd.font 21 | elements = dd.elements 22 | entry_height = font.height() + 2 # Allow a pixel above and below text 23 | height = entry_height * len(elements) + 2 * border 24 | lb_location = location[0] + border, location[1] + border 25 | lb_width = width - 2 * border 26 | super().__init__(location, height, width) 27 | self.listbox = Listbox(lb_location, font = font, elements = elements, width = lb_width, 28 | border = None, fgcolor = dd.fgcolor, bgcolor = dd.bgcolor, 29 | fontcolor = dd.fontcolor, select_color = dd.select_color, 30 | value = dd.value(), callback = self.callback) 31 | self.dropdown = dd 32 | 33 | def callback(self, obj_listbox): 34 | if obj_listbox._initial_value is not None: # a touch has occurred 35 | val = obj_listbox.textvalue() 36 | Screen.back() 37 | if self.dropdown is not None: # Part of a Dropdown 38 | self.dropdown.value(obj_listbox.value()) # Update it 39 | 40 | class Dropdown(Touchable): 41 | def __init__(self, location, *, font, elements, width=250, value=0, 42 | fgcolor=None, bgcolor=None, fontcolor=None, select_color=LIGHTBLUE, 43 | callback=dolittle, args=[]): 44 | border = 2 45 | self.entry_height = font.height() + 2 # Allow a pixel above and below text 46 | height = self.entry_height + 2 * border 47 | super().__init__(location, font, height, width, fgcolor, bgcolor, fontcolor, border, False, value, None) 48 | super()._set_callbacks(callback, args) 49 | self.select_color = select_color 50 | self.elements = elements 51 | 52 | def show(self): 53 | tft = self.tft 54 | bw = self.border 55 | clip = self.width - (self.height + 2 * bw) 56 | x, y = self.location[0], self.location[1] 57 | self._draw(tft, x, y) 58 | if self._value is not None: 59 | print_left(tft, x + bw, y + bw + 1, self.elements[self._value], self.fontcolor, self.font, clip) 60 | 61 | def textvalue(self, text=None): # if no arg return current text 62 | if text is None: 63 | return self.elements[self._value] 64 | else: # set value by text 65 | try: 66 | v = self.elements.index(text) 67 | except ValueError: 68 | v = None 69 | else: 70 | if v != self._value: 71 | self.value(v) 72 | return v 73 | 74 | def _draw(self, tft, x, y): 75 | self.fill = True 76 | self.draw_border() 77 | tft.draw_vline(x + self.width - self.height, y, self.height, self.fgcolor) 78 | xcentre = x + self.width - self.height // 2 # Centre of triangle 79 | ycentre = y + self.height // 2 80 | halflength = (self.height - 8) // 2 81 | length = halflength * 2 82 | if length > 0: 83 | tft.draw_hline(xcentre - halflength, ycentre - halflength, length, self.fgcolor) 84 | tft.draw_line(xcentre - halflength, ycentre - halflength, xcentre, ycentre + halflength, self.fgcolor) 85 | tft.draw_line(xcentre + halflength, ycentre - halflength, xcentre, ycentre + halflength, self.fgcolor) 86 | 87 | def _touched(self, x, y): 88 | if len(self.elements) > 1: 89 | location = self.location[0], self.location[1] + self.height + 1 90 | args = (location, self, self.width - self.height) 91 | Screen.change(_ListDialog, args = args) 92 | -------------------------------------------------------------------------------- /tft/widgets/horiz_slider.py: -------------------------------------------------------------------------------- 1 | from tft.driver.ugui import Touchable, dolittle, get_stringsize 2 | from tft.driver import TFT_io 3 | # horiz_slider.py For TFT driver. 4 | # Adapted for (and requires) uasyncio V3 5 | 6 | # Released under the MIT License (MIT). See LICENSE. 7 | # Copyright (c) 2016-2020 Peter Hinch 8 | 9 | from tft.driver.constants import * 10 | from tft.widgets.label import Label 11 | 12 | class HorizSlider(Touchable): 13 | def __init__(self, location, *, font=None, height=30, width=200, divisions=10, legends=None, 14 | fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None, 15 | cb_end=dolittle, cbe_args=[], cb_move=dolittle, cbm_args=[], value=0.0): 16 | height &= 0xfe # ensure divisible by 2 17 | super().__init__(location, font, height, width, fgcolor, bgcolor, fontcolor, border, True, None, value) 18 | self.divisions = divisions 19 | self.legends = legends if font is not None else None 20 | self.slidecolor = slidecolor 21 | super()._set_callbacks(cb_move, cbm_args, cb_end, cbe_args) 22 | slideheight = int(height / 1.3) & 0xfe # Ensure divisible by 2 23 | self.slidewidth = 6 # must be divisible by 2 24 | # We draw an odd number of pixels: 25 | self.slidebytes = (slideheight + 1) * (self.slidewidth + 1) * 3 26 | self.slidebuf = bytearray(self.slidebytes) 27 | b = self.border 28 | self.pot_dimension = self.width - 2 * (b + self.slidewidth // 2) 29 | height = self.height - 2 * b 30 | ycentre = self.location[1] + b + height // 2 31 | self.slide_y0 = ycentre - slideheight // 2 32 | self.slide_y1 = ycentre + slideheight // 2 # slide Y coordinates 33 | self.slide_x = None # invalidate: slide has not yet been drawn 34 | # Prevent Label objects being added to display list when already there. 35 | self.drawn = False 36 | 37 | def show(self): 38 | tft = self.tft 39 | bw = self.border 40 | height = self.height - 2 * bw 41 | width = self.pot_dimension # Length of slot 42 | x = self.location[0] + bw + self.slidewidth // 2 # Allow space left and right slot for slider at extremes 43 | y = self.location[1] + bw 44 | if self._value is None or self.redraw: # Initialising 45 | self.redraw = False 46 | self.render_slide(tft, self.bgcolor) # Erase slide if it exists 47 | dy = height // 2 - 2 # slot is 4 pixels wide 48 | tft.draw_rectangle(x, y + dy, x + width, y + height - dy, self.fgcolor) 49 | if self.divisions > 0: 50 | dx = width / (self.divisions) # Tick marks 51 | for tick in range(self.divisions + 1): 52 | xpos = int(x + dx * tick) 53 | tft.draw_vline(xpos, y + 1, dy, self.fgcolor) # TODO Why is +1 fiddle required here? 54 | tft.draw_vline(xpos, y + 2 + height // 2, dy, self.fgcolor) # Add half slot width 55 | 56 | # Legends: if redrawing, they are already on the Screen's display list 57 | if self.legends is not None and not self.drawn: 58 | if len(self.legends) <= 1: 59 | dx = 0 60 | else: 61 | dx = width / (len(self.legends) -1) 62 | xl = x 63 | font = self.font 64 | for legend in self.legends: 65 | offset = get_stringsize(legend, self.font)[0] / 2 66 | loc = int(xl - offset), y - self.font.height() - bw - 1 67 | Label(loc, font = font, fontcolor = self.fontcolor, value = legend) 68 | xl += dx 69 | self.save_background(tft) 70 | if self._value is None: 71 | self.value(self._initial_value, show = False) # prevent recursion 72 | 73 | self.render_bg(tft) 74 | self.slide_x = self.update(tft) # Reflect new value in slider position 75 | self.save_background(tft) 76 | color = self.slidecolor if self.slidecolor is not None else self.fgcolor 77 | self.render_slide(tft, color) 78 | self.drawn = True 79 | 80 | def update(self, tft): 81 | x = self.location[0] + self.border + self.slidewidth // 2 82 | sliderpos = int(x + self._value * self.pot_dimension) 83 | return sliderpos - self.slidewidth // 2 84 | 85 | def slide_coords(self): 86 | return self.slide_x, self.slide_y0, self.slide_x + self.slidewidth, self.slide_y1 87 | 88 | def save_background(self, tft): # Read background under slide 89 | if self.slide_x is not None: 90 | tft.setXY(*self.slide_coords()) 91 | TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes) 92 | 93 | def render_bg(self, tft): 94 | if self.slide_x is not None: 95 | tft.setXY(*self.slide_coords()) 96 | TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes) 97 | 98 | def render_slide(self, tft, color): 99 | if self.slide_x is not None: 100 | tft.fill_rectangle(*self.slide_coords(), color = color) 101 | 102 | def color(self, color): 103 | if color != self.fgcolor: 104 | self.fgcolor = color 105 | self.redraw = True 106 | self.show_if_current() 107 | 108 | def _touched(self, x, y): # Touched in bounding box. A drag will call repeatedly. 109 | self.value((x - self.location[0]) / self.pot_dimension) 110 | -------------------------------------------------------------------------------- /tft/widgets/icon_buttons.py: -------------------------------------------------------------------------------- 1 | # buttons.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | # icon_buttons.py For TFT driver. 7 | # Adapted for (and requires) uasyncio V3 8 | 9 | # Released under the MIT License (MIT). See LICENSE. 10 | # Copyright (c) 2016-2020 Peter Hinch 11 | 12 | import uasyncio as asyncio 13 | from tft.driver.ugui import Touchable, dolittle 14 | from tft.driver.constants import * 15 | from tft.primitives.delay_ms import Delay_ms 16 | 17 | # Button/checkbox whose appearance is defined by icon bitmaps 18 | 19 | class IconButton(Touchable): 20 | long_press_time = 1000 21 | def __init__(self, location, *, icon_module, flash=0, toggle=False, state=0, 22 | callback=dolittle, args=[], onrelease=True, lp_callback=None, lp_args=[]): 23 | self.draw = icon_module.draw 24 | self.num_icons = len(icon_module._icons) 25 | super().__init__(location, None, icon_module.height, 26 | icon_module.width, None, None, None, None, False, 0, 0) 27 | self.callback = callback 28 | self.callback_args = args 29 | self.onrelease = onrelease 30 | self.lp_callback = lp_callback 31 | self.lp_args = lp_args 32 | self.lp = False # Long press not in progress 33 | self.flash = int(flash * 1000) # Compatibility 34 | self.toggle = toggle 35 | if state >= self.num_icons or state < 0: 36 | raise ugui_exception('Invalid icon index {}'.format(state)) 37 | self.state = state 38 | if self.flash > 0: 39 | if self.num_icons < 2: 40 | raise ugui_exception('Need > 1 icon for flashing button') 41 | self.delay = Delay_ms(self._show, (0,)) 42 | 43 | def show(self): 44 | self._show(self.state) 45 | 46 | def _show(self, state): 47 | tft = self.tft 48 | self.state = state 49 | x = self.location[0] + self.width // 2 # Centre relative 50 | y = self.location[1] + self.height // 2 51 | color_idx = 1 if self.greyed_out() else 0 52 | try: 53 | self.draw(x, y, state, tft.drawBitmap, color_idx) # TODO bodge to deal with old style icons ????? 54 | except TypeError: 55 | self.draw(x, y, state, tft.drawBitmap) 56 | 57 | def value(self, val=None): 58 | if val is not None: 59 | val = int(val) 60 | if val >= self.num_icons or val < 0: 61 | raise ugui_exception('Invalid icon index {}'.format(val)) 62 | if val != self.state: 63 | self._show(val) 64 | self.callback(self, *self.callback_args) # Callback not a bound method so pass self 65 | return self.state 66 | 67 | def _touched(self, x, y): # Process touch 68 | if self.flash > 0: 69 | self._show(1) 70 | self.delay.trigger(self.flash) 71 | elif self.toggle: 72 | self.state = (self.state + 1) % self.num_icons 73 | self._show(self.state) 74 | if self.lp_callback is not None: 75 | asyncio.create_task(self.longpress()) 76 | if not self.onrelease: 77 | self.callback(self, *self.callback_args) # Callback not a bound method so pass self 78 | 79 | def _untouched(self): 80 | self.lp = False 81 | if self.onrelease: 82 | self.callback(self, *self.callback_args) # Callback not a bound method so pass self 83 | 84 | async def longpress(self): 85 | self.lp = True 86 | await asyncio.sleep_ms(self.long_press_time) 87 | if self.lp: 88 | self.lp_callback(self, *self.lp_args) 89 | 90 | # Group of buttons at different locations, where pressing one shows 91 | # only current button highlighted and does callback from current one 92 | class IconRadioButtons: 93 | def __init__(self, callback=dolittle, selected=0): 94 | self.user_callback = callback 95 | self.setbuttons = set() 96 | self.selected = selected 97 | self._greyed_out = False 98 | 99 | def add_button(self, *args, **kwargs): 100 | if self.selected == len(self.setbuttons): 101 | kwargs['state'] = 1 102 | else: 103 | kwargs['state'] = 0 104 | button = IconButton(*args, **kwargs) # Create and show 105 | self.setbuttons.add(button) 106 | button.callback = self._callback 107 | return button 108 | 109 | def value(self, but=None): 110 | if but is not None: 111 | if but not in self.setbuttons: 112 | raise ugui_exception('Button not a member of this radio button') 113 | else: 114 | if but.value() == 0: 115 | self._callback(but, *but.callback_args) 116 | resultset = {x for x in self.setbuttons if x.state ==1} 117 | assert len(resultset) == 1, 'We have > 1 button selected' 118 | return resultset.pop() 119 | 120 | def greyed_out(self, val=None): 121 | if val is not None and self._greyed_out != val: 122 | self._greyed_out = val 123 | for button in self.setbuttons: 124 | button.greyed_out(val) 125 | return self._greyed_out 126 | 127 | def _callback(self, button, *args): 128 | for but in self.setbuttons: 129 | if but is button: 130 | but._show(1) 131 | else: 132 | but._show(0) 133 | self.user_callback(button, *args) # Args for button just pressed 134 | 135 | -------------------------------------------------------------------------------- /tft/widgets/icon_gauge.py: -------------------------------------------------------------------------------- 1 | # icon_gauge.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import NoTouch 8 | from tft.driver.constants import * 9 | 10 | class IconGauge(NoTouch): 11 | def __init__(self, location, *, icon_module, initial_icon=0): 12 | NoTouch.__init__(self, location, None, icon_module.height, icon_module.width, None, None, None, None, 13 | initial_icon / len(icon_module._icons), None) 14 | self.draw = icon_module.draw 15 | self.num_icons = len(icon_module._icons) 16 | self.state = initial_icon 17 | 18 | def show(self): 19 | tft = self.tft 20 | x = self.location[0] 21 | y = self.location[1] 22 | self.draw(x, y, self.state, tft.drawBitmap) 23 | 24 | def icon(self, icon_index): # select icon by index 25 | if icon_index >= self.num_icons or icon_index < 0: 26 | raise ugui_exception('Invalid icon index {}'.format(icon_index)) 27 | else: 28 | self.state = int(icon_index) 29 | self.show_if_current() 30 | 31 | def _value_change(self, show): 32 | self.state = min(int(self._value * self.num_icons), self.num_icons -1) 33 | self.show_if_current() 34 | -------------------------------------------------------------------------------- /tft/widgets/knob.py: -------------------------------------------------------------------------------- 1 | # knob.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | import math 8 | from tft.driver.ugui import Touchable, dolittle, TWOPI 9 | from tft.driver.constants import * 10 | 11 | # *********** CONTROL KNOB CLASS *********** 12 | 13 | class Knob(Touchable): 14 | def __init__(self, location, *, height=100, arc=TWOPI, ticks=9, value=0.0, 15 | fgcolor=None, bgcolor=None, color=None, border=None, 16 | cb_end=dolittle, cbe_args=[], cb_move=dolittle, cbm_args=[]): 17 | Touchable.__init__(self, location, None, height, height, fgcolor, bgcolor, None, border, True, None, value) 18 | border = self.border # Geometry: border width 19 | radius = height / 2 - border 20 | self.arc = min(max(arc, 0), TWOPI) # Usable angle of control 21 | self.radius = radius 22 | self.xorigin = location[0] + border + radius 23 | self.yorigin = location[1] + border + radius 24 | self.ticklen = 0.1 * radius 25 | self.pointerlen = radius - self.ticklen - 5 26 | self.ticks = max(ticks, 2) # start and end of travel 27 | super()._set_callbacks(cb_move, cbm_args, cb_end, cbe_args) 28 | self._old_value = None # data: invalidate 29 | self.color = color 30 | 31 | def show(self): 32 | tft = self.tft 33 | if self._value is None or self.redraw: # Initialising 34 | self.redraw = False 35 | arc = self.arc 36 | ticks = self.ticks 37 | radius = self.radius 38 | ticklen = self.ticklen 39 | for tick in range(ticks): 40 | theta = (tick / (ticks - 1)) * arc - arc / 2 41 | x_start = int(self.xorigin + radius * math.sin(theta)) 42 | y_start = int(self.yorigin - radius * math.cos(theta)) 43 | x_end = int(self.xorigin + (radius - ticklen) * math.sin(theta)) 44 | y_end = int(self.yorigin - (radius - ticklen) * math.cos(theta)) 45 | tft.draw_line(x_start, y_start, x_end, y_end, self.fgcolor) 46 | if self.color is not None: 47 | tft.fill_circle(self.xorigin, self.yorigin, radius - ticklen, self.color) 48 | tft.draw_circle(self.xorigin, self.yorigin, radius - ticklen, self.fgcolor) 49 | tft.draw_circle(self.xorigin, self.yorigin, radius - ticklen - 3, self.fgcolor) 50 | if self._value is None: 51 | self.value(self._initial_value, show = False) 52 | 53 | if self._old_value is not None: # An old pointer needs erasing 54 | if self.greyed_out() and tft.skeleton(): 55 | tft.usegrey(False) # greyed out 'skeleton' style 56 | color = tft.getBGColor() # erase to screen background 57 | else: 58 | color = self.bgcolor if self.color is None else self.color # Fill color 59 | self._drawpointer(self._old_value, color) # erase old 60 | self.tft # Reset Screen greyed-out status 61 | 62 | self._drawpointer(self._value, self.fgcolor) # draw new 63 | self._old_value = self._value # update old 64 | 65 | def _touched(self, x, y): # Touched in bounding box. A drag will call repeatedly. 66 | dy = self.yorigin - y 67 | dx = x - self.xorigin 68 | if (dx**2 + dy**2) / self.radius**2 < 0.5: 69 | return # vector too short 70 | alpha = math.atan2(dx, dy) # axes swapped: orientate relative to vertical 71 | arc = self.arc 72 | alpha = min(max(alpha, -arc / 2), arc / 2) + arc / 2 73 | self.value(alpha / arc) 74 | 75 | def _drawpointer(self, value, color): 76 | tft = self.tft 77 | arc = self.arc 78 | length = self.pointerlen 79 | angle = value * arc - arc / 2 80 | x_end = int(self.xorigin + length * math.sin(angle)) 81 | y_end = int(self.yorigin - length * math.cos(angle)) 82 | tft.draw_line(int(self.xorigin), int(self.yorigin), x_end, y_end, color) 83 | 84 | -------------------------------------------------------------------------------- /tft/widgets/label.py: -------------------------------------------------------------------------------- 1 | # label.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import NoTouch, get_stringsize, print_left 8 | from tft.driver.constants import * 9 | 10 | class Label(NoTouch): 11 | def __init__(self, location, *, font, border=None, width=None, fgcolor=None, bgcolor=None, fontcolor=None, value=None): 12 | if width is None: 13 | if value is None: 14 | raise ValueError('If label value unspecified, must define the width') 15 | width, _ = get_stringsize(value, font) 16 | super().__init__(location, font, None, width, fgcolor, bgcolor, fontcolor, border, value, None) 17 | self.height = self.font.height() 18 | self.height += 2 * self.border # Height determined by font and border 19 | 20 | def show(self): 21 | tft = self.tft 22 | bw = self.border 23 | x = self.location[0] 24 | y = self.location[1] 25 | tft.fill_rectangle(x + bw, y + bw, x + self.width - bw, y + self.height - bw, self.bgcolor) 26 | if self._value is not None: 27 | print_left(tft, x + bw, y + bw, self._value, self.fontcolor, self.font, self.width - 2 * bw) 28 | -------------------------------------------------------------------------------- /tft/widgets/led.py: -------------------------------------------------------------------------------- 1 | # led.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import NoTouch 8 | from tft.driver.constants import * 9 | 10 | class LED(NoTouch): 11 | def __init__(self, location, *, border=None, height=30, fgcolor=None, bgcolor=None, color=RED): 12 | super().__init__(location, None, height, height, fgcolor, bgcolor, None, border, False, False) 13 | self._value = False 14 | self._color = color 15 | self.radius = (self.height - 2 * self.border) / 2 16 | self.x = location[0] + self.radius + self.border 17 | self.y = location[1] + self.radius + self.border 18 | 19 | def show(self): 20 | tft = self.tft 21 | color = self._color if self._value else BLACK 22 | tft.fill_circle(int(self.x), int(self.y), int(self.radius), color) 23 | tft.draw_circle(int(self.x), int(self.y), int(self.radius), self.fgcolor) 24 | 25 | def color(self, color): 26 | self._color = color 27 | self.show_if_current() 28 | 29 | -------------------------------------------------------------------------------- /tft/widgets/listbox.py: -------------------------------------------------------------------------------- 1 | # listbox.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import Touchable, dolittle, print_left 8 | from tft.driver.constants import * 9 | 10 | # *********** LISTBOX CLASS *********** 11 | 12 | class Listbox(Touchable): 13 | def __init__(self, location, *, font, elements, width=250, value=0, border=2, 14 | fgcolor=None, bgcolor=None, fontcolor=None, select_color=LIGHTBLUE, 15 | callback=dolittle, args=[]): 16 | self.entry_height = font.height() + 2 # Allow a pixel above and below text 17 | bw = border if border is not None else 0 # Replicate Touchable ctor's handling of self.border 18 | height = self.entry_height * len(elements) + 2 * bw 19 | super().__init__(location, font, height, width, fgcolor, bgcolor, fontcolor, border, False, value, None) 20 | super()._set_callbacks(callback, args) 21 | self.select_color = select_color 22 | fail = False 23 | try: 24 | self.elements = [s for s in elements if type(s) is str] 25 | except: 26 | fail = True 27 | else: 28 | fail = len(self.elements) == 0 29 | if fail: 30 | raise ValueError('elements must be a list or tuple of one or more strings') 31 | if value >= len(self.elements): 32 | value = 0 33 | self._value = value # No callback until user touches 34 | 35 | def show(self): 36 | tft = self.tft 37 | bw = self.border 38 | clip = self.width - 2 * bw 39 | length = len(self.elements) 40 | x = self.location[0] 41 | y = self.location[1] 42 | xs = x + bw # start and end of text field 43 | xe = x + self.width - 2 * bw 44 | tft.fill_rectangle(xs, y + 1, xe, y - 1 + self.height - 2 * bw, self.bgcolor) 45 | for n in range(length): 46 | ye = y + n * self.entry_height 47 | if n == self._value: 48 | tft.fill_rectangle(xs, ye + 1, xe, ye + self.entry_height - 1, self.select_color) 49 | print_left(tft, xs, ye + 1, self.elements[n], self.fontcolor, self.font, clip) 50 | 51 | def textvalue(self, text=None): # if no arg return current text 52 | if text is None: 53 | return self.elements[self._value] 54 | else: # set value by text 55 | try: 56 | v = self.elements.index(text) 57 | except ValueError: 58 | v = None 59 | else: 60 | if v != self._value: 61 | self.value(v) 62 | return v 63 | 64 | def _touched(self, x, y): 65 | dy = y - (self.location[1]) 66 | self._initial_value = dy // self.entry_height 67 | 68 | def _untouched(self): 69 | if self._initial_value is not None: 70 | self._value = -1 # Force update on every touch 71 | self.value(self._initial_value, show = True) 72 | self._initial_value = None 73 | -------------------------------------------------------------------------------- /tft/widgets/meter.py: -------------------------------------------------------------------------------- 1 | # meter.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import NoTouch, print_centered 8 | from tft.driver import TFT_io 9 | from tft.driver.constants import * 10 | 11 | class Meter(NoTouch): 12 | def __init__(self, location, *, font=None, height=200, width=30, 13 | fgcolor=None, bgcolor=None, pointercolor=None, fontcolor=None, 14 | divisions=10, legends=None, value=0): 15 | border = 5 if font is None else 1 + font.height() / 2 16 | NoTouch.__init__(self, location, font, height, width, fgcolor, bgcolor, fontcolor, border, value, None) # super() provoked Python bug 17 | border = self.border # border width 18 | self.ptrbytes = 3 * (self.width + 1) # 3 bytes per pixel 19 | self.ptrbuf = bytearray(self.ptrbytes) #??? 20 | self.x0 = self.location[0] 21 | self.x1 = self.location[0] + self.width 22 | self.y0 = self.location[1] + border + 2 23 | self.y1 = self.location[1] + self.height - border 24 | self.divisions = divisions 25 | self.legends = legends 26 | self.pointercolor = pointercolor if pointercolor is not None else self.fgcolor 27 | self.ptr_y = None # Invalidate old position 28 | 29 | def show(self): 30 | tft = self.tft 31 | width = self.width 32 | dx = 5 33 | x0 = self.x0 34 | x1 = self.x1 35 | y0 = self.y0 36 | y1 = self.y1 37 | height = y1 - y0 38 | if self.divisions > 0: 39 | dy = height / (self.divisions) # Tick marks 40 | for tick in range(self.divisions + 1): 41 | ypos = int(y0 + dy * tick) 42 | tft.draw_hline(x0, ypos, dx, self.fgcolor) 43 | tft.draw_hline(x1 - dx, ypos, dx, self.fgcolor) 44 | 45 | if self.legends is not None and self.font is not None: # Legends 46 | if len(self.legends) <= 1: 47 | dy = 0 48 | else: 49 | dy = height / (len(self.legends) -1) 50 | yl = self.y1 # Start at bottom 51 | for legend in self.legends: 52 | print_centered(tft, int(self.x0 + self.width /2), int(yl), legend, self.fontcolor, self.font) 53 | yl -= dy 54 | 55 | if self.ptr_y is not None: # Restore background if it was saved 56 | tft.setXY(x0, self.ptr_y, x1, self.ptr_y) 57 | TFT_io.tft_write_data_AS(self.ptrbuf, self.ptrbytes) 58 | self.ptr_y = int(self.y1 - self._value * height) # y position of slider 59 | tft.setXY(x0, self.ptr_y, x1, self.ptr_y) # Read background 60 | TFT_io.tft_read_cmd_data_AS(0x2e, self.ptrbuf, self.ptrbytes) 61 | tft.draw_hline(x0, self.ptr_y, width, self.pointercolor) # Draw pointer 62 | 63 | -------------------------------------------------------------------------------- /tft/widgets/slider.py: -------------------------------------------------------------------------------- 1 | # slider.py For TFT driver. 2 | # Adapted for (and requires) uasyncio V3 3 | 4 | # Released under the MIT License (MIT). See LICENSE. 5 | # Copyright (c) 2016-2020 Peter Hinch 6 | 7 | from tft.driver.ugui import Touchable, dolittle 8 | from tft.driver import TFT_io 9 | from tft.driver.constants import * 10 | from tft.widgets.label import Label 11 | # A slider's text items lie outside its bounding box (area sensitive to touch) 12 | 13 | class Slider(Touchable): 14 | def __init__(self, location, *, font=None, height=200, width=30, divisions=10, legends=None, 15 | fgcolor=None, bgcolor=None, fontcolor=None, slidecolor=None, border=None, 16 | cb_end=dolittle, cbe_args=[], cb_move=dolittle, cbm_args=[], value=0.0): 17 | width &= 0xfe # ensure divisible by 2 18 | super().__init__(location, font, height, width, fgcolor, bgcolor, fontcolor, border, True, None, value) 19 | self.divisions = divisions 20 | self.legends = legends if font is not None else None 21 | self.slidecolor = slidecolor 22 | super()._set_callbacks(cb_move, cbm_args, cb_end, cbe_args) 23 | slidewidth = int(width / 1.3) & 0xfe # Ensure divisible by 2 24 | self.slideheight = 6 # must be divisible by 2 25 | # We draw an odd number of pixels: 26 | self.slidebytes = (self.slideheight + 1) * (slidewidth + 1) * 3 27 | self.slidebuf = bytearray(self.slidebytes) 28 | b = self.border 29 | self.pot_dimension = self.height - 2 * (b + self.slideheight // 2) 30 | width = self.width - 2 * b 31 | xcentre = self.location[0] + b + width // 2 32 | self.slide_x0 = xcentre - slidewidth // 2 33 | self.slide_x1 = xcentre + slidewidth // 2 # slide X coordinates 34 | self.slide_y = None # Invalidate slide position 35 | # Prevent Label objects being added to display list when already there. 36 | self.drawn = False 37 | 38 | def show(self): 39 | tft = self.tft 40 | bw = self.border 41 | width = self.width - 2 * bw 42 | height = self.pot_dimension # Height of slot 43 | x = self.location[0] + bw 44 | y = self.location[1] + bw + self.slideheight // 2 # Allow space above and below slot 45 | if self._value is None or self.redraw: # Initialising 46 | self.redraw = False 47 | self.render_slide(tft, self.bgcolor) # Erase slide if it exists 48 | dx = width // 2 - 2 49 | tft.draw_rectangle(x + dx, y, x + width - dx, y + height, self.fgcolor) 50 | if self.divisions > 0: 51 | dy = height / (self.divisions) # Tick marks 52 | for tick in range(self.divisions + 1): 53 | ypos = int(y + dy * tick) 54 | tft.draw_hline(x + 1, ypos, dx, self.fgcolor) 55 | tft.draw_hline(x + 2 + width // 2, ypos, dx, self.fgcolor) # Add half slot width 56 | 57 | # Legends: if redrawing, they are already on the Screen's display list 58 | if self.legends is not None and not self.drawn: 59 | if len(self.legends) <= 1: 60 | dy = 0 61 | else: 62 | dy = height / (len(self.legends) -1) 63 | yl = y + height # Start at bottom 64 | fhdelta = self.font.height() / 2 65 | font = self.font 66 | for legend in self.legends: 67 | loc = (x + self.width, int(yl - fhdelta)) 68 | Label(loc, font = font, fontcolor = self.fontcolor, value = legend) 69 | yl -= dy 70 | self.save_background(tft) 71 | if self._value is None: 72 | self.value(self._initial_value, show = False) # Prevent recursion 73 | self.render_bg(tft) 74 | self.slide_y = self.update(tft) # Reflect new value in slider position 75 | self.save_background(tft) 76 | color = self.slidecolor if self.slidecolor is not None else self.fgcolor 77 | self.render_slide(tft, color) 78 | self.drawn = True 79 | 80 | def update(self, tft): 81 | y = self.location[1] + self.border + self.slideheight // 2 82 | sliderpos = int(y + self.pot_dimension - self._value * self.pot_dimension) 83 | return sliderpos - self.slideheight // 2 84 | 85 | def slide_coords(self): 86 | return self.slide_x0, self.slide_y, self.slide_x1, self.slide_y + self.slideheight 87 | 88 | def save_background(self, tft): # Read background under slide 89 | if self.slide_y is not None: 90 | tft.setXY(*self.slide_coords()) 91 | TFT_io.tft_read_cmd_data_AS(0x2e, self.slidebuf, self.slidebytes) 92 | 93 | def render_bg(self, tft): 94 | if self.slide_y is not None: 95 | tft.setXY(*self.slide_coords()) 96 | TFT_io.tft_write_data_AS(self.slidebuf, self.slidebytes) 97 | 98 | def render_slide(self, tft, color): 99 | if self.slide_y is not None: 100 | tft.fill_rectangle(*self.slide_coords(), color = color) 101 | 102 | def color(self, color): 103 | if color != self.fgcolor: 104 | self.fgcolor = color 105 | self.redraw = True 106 | self.show_if_current() 107 | 108 | def _touched(self, x, y): # Touched in bounding box. A drag will call repeatedly. 109 | self.value((self.location[1] + self.height - y) / self.pot_dimension) 110 | 111 | -------------------------------------------------------------------------------- /tft/widgets/vectors.py: -------------------------------------------------------------------------------- 1 | # vectors.py Extension to ugui providing vector display 2 | 3 | # Released under the MIT License (MIT). See LICENSE. 4 | # Copyright (c) 2019 Peter Hinch 5 | 6 | import cmath 7 | from tft.driver.ugui import Screen, NoTouch 8 | from tft.driver.constants import * 9 | 10 | conj = lambda v : v.real - v.imag * 1j # Complex conjugate 11 | 12 | # Draw a vector in complex coordinates. Origin and end are complex. 13 | # End is relative to origin. 14 | def pline(tft, origin, vec, color): 15 | xs, ys = origin.real, origin.imag 16 | tft.draw_line(round(xs), round(ys), round(xs + vec.real), round(ys - vec.imag), color) 17 | 18 | # Draw an arrow; origin and vec are complex, scalar lc defines length of chevron. 19 | # cw and ccw are unit vectors of +-3pi/4 radians for chevrons. 20 | def arrow(tft, origin, vec, lc, color): 21 | ccw = cmath.exp(3j * cmath.pi/4) # Unit vectors 22 | cw = cmath.exp(-3j * cmath.pi/4) 23 | length, theta = cmath.polar(vec) 24 | uv = cmath.rect(1, theta) # Unit rotation vector 25 | start = -vec 26 | if length > 3 * lc: # If line is long 27 | ds = cmath.rect(lc, theta) 28 | start += ds # shorten to allow for length of tail chevrons 29 | chev = lc + 0j 30 | pline(tft, origin, vec, color) # Origin to tip 31 | pline(tft, origin, start, color) # Origin to tail 32 | pline(tft, origin + conj(vec), chev*ccw*uv, color) # Tip chevron 33 | pline(tft, origin + conj(vec), chev*cw*uv, color) 34 | if length > lc: # Confusing appearance of very short vectors with tail chevron 35 | pline(tft, origin + conj(start), chev*ccw*uv, color) # Tail chevron 36 | pline(tft, origin + conj(start), chev*cw*uv, color) 37 | 38 | # Vector display 39 | class Pointer: 40 | def __init__(self, dial): 41 | dial.vectors.add(self) 42 | self.dial = dial 43 | self.color = BLACK # SYS_FGCOLOR 44 | self.val = 0j 45 | 46 | def value(self, v=None, color=None): 47 | if isinstance(color, tuple): 48 | self.color = color 49 | dial = self.dial 50 | if v is not None: 51 | if isinstance(v, complex): 52 | l = cmath.polar(v)[0] 53 | newval = v /l if l > 1 else v # Max length = 1.0 54 | else: 55 | raise ValueError('Pointer value must be complex.') 56 | if v != self.val and dial.screen is Screen.current_screen: 57 | self.show(newval) 58 | self.val = newval 59 | dial.show_if_current() 60 | return self.val 61 | 62 | def show(self, newval=None): 63 | dial = self.dial 64 | tft = dial.tft 65 | color = self.color 66 | vor = dial.vor # Dial's origin as a vector 67 | r = dial.radius * (1 - dial.TICKLEN) 68 | if dial.arrow: 69 | if newval is None: # Refresh 70 | arrow(tft, vor, r * self.val, 5, color) 71 | else: 72 | arrow(tft, vor, r * self.val, 5, dial.bgcolor) 73 | arrow(tft, vor, r * newval, 5, color) 74 | else: 75 | if newval is None: # Refresh 76 | pline(tft, vor, r * self.val, color) 77 | else: 78 | pline(tft, vor, r * self.val, dial.bgcolor) # Erase old 79 | pline(tft, vor, r * newval, color) 80 | 81 | 82 | class VectorDial(NoTouch): 83 | TICKLEN = 0.1 84 | def __init__(self, location, *, height=100, fgcolor=None, bgcolor=None, border=None, 85 | ticks=4, arrow=False, pip=None): 86 | super().__init__(location, None, height, height, fgcolor, bgcolor, None, border, 0, 0) 87 | self.arrow = arrow 88 | self.pip = self.fgcolor if pip is None else pip 89 | border = self.border # border width 90 | radius = height / 2 - border 91 | self.radius = radius 92 | self.ticks = ticks 93 | self.xorigin = location[0] + border + radius 94 | self.yorigin = location[1] + border + radius 95 | self.vor = self.xorigin + 1j * self.yorigin # Origin as a vector 96 | self.vectors = set() 97 | self.drawn = False 98 | 99 | def show(self): 100 | # cache bound variables 101 | tft = self.tft 102 | ticks = self.ticks 103 | radius = self.radius 104 | xo = self.xorigin 105 | yo = self.yorigin 106 | vor = self.vor 107 | if self.redraw: # An overlaying screen has closed. Force redraw. 108 | self.redraw = False 109 | self.drawn = False 110 | if not self.drawn: 111 | self.drawn = True 112 | vtstart = (1 - self.TICKLEN) * radius + 0j # start of tick 113 | vtick = self.TICKLEN * radius + 0j # tick 114 | vrot = cmath.exp(2j * cmath.pi/ticks) # unit rotation 115 | for _ in range(ticks): 116 | pline(tft, vor + conj(vtstart), vtick, self.fgcolor) 117 | vtick *= vrot 118 | vtstart *= vrot 119 | tft.draw_circle(xo, yo, radius, self.fgcolor) 120 | 121 | vshort = 1000 # Length of shortest vector 122 | for v in self.vectors: 123 | val = v.value() * radius # val is complex 124 | vshort = min(vshort, cmath.polar(val)[0]) 125 | v.show() 126 | if isinstance(self.pip, tuple) and vshort > 9: 127 | tft.fill_circle(xo, yo, 3, self.pip) 128 | --------------------------------------------------------------------------------