micropython-micro-gui



This is a lightweight, portable, MicroPython GUI library for displays having
drivers subclassed from framebuf. Written in Python it runs under a standard
MicroPython firmware build. Options for data input comprise:
- Two pushbuttons: limited capabilities with some widgets unusable for input.
- Three pushbuttons with full capability.
- Five pushbuttons: full capability, less “modal” interface.
- A switch-based navigation joystick: another way to implement five buttons.
- Via two pushbuttons and a rotary encoder such as
this one. An intuitive interface.
- On ESP32 physical buttons may be replaced with touchpads.
It is larger and more complex than nano-gui owing to the support for input.
It enables switching between screens and launching modal windows. Widgets are
a substantial superset of nano-gui widgets.
It is compatible with all display drivers for
nano-gui so is portable
to a wide range of displays. It is also portable between hosts.

Raspberry Pico with an ILI9341 from eBay.

TTGO T-Display. A joystick switch and an SIL resistor make a simple inexpensive
and WiFi-capable system.

micro_gui now has limited support for ePaper.
Rationale
Touch GUI’s are supported by micropython-touch.
This GUI provides an alternative for displays without a touch overlay. A
non-touch solution avoids the need for calibration and can also save cost. Cheap
Chinese touch displays often marry a good display to a poor touch overlay. It
can make sense to use such a screen with micro-gui, ignoring the touch overlay.
For touch support it is worth spending money on a good quality device (for
example Adafruit).
The micro-gui input options work well and can yield inexpensive solutions. A
network-connected board with a 135x240 color display can be built for under £20
($20?) using the
TTGO T-Display. The
test board shown above has a 320x240 display from eBay with a Pi Pico and has a
component cost of well below £20.
The following are similar GUI repos with differing objectives.
- nano-gui Extremely low
RAM usage but display-only with no provision for input.
- LCD160cr Touch GUI
for the official display.
- RA8875 Touch GUI for
displays with RA8875 controller. Supports large displays, e.g. from Adafruit.
- SSD1963 Touch GUI for
displays based on SSD1963 and XPT2046. High performance on large displays due
to the parallel interface. Specific to STM hosts.
LVGL is a pretty icon-based GUI library. It is written in C
with MicroPython bindings; consequently it requires the build system for your
target and a C device driver (unless you can acquire a suitable binary).
Project status
April 2024: Add screen replace feature for non-tree navigation.
Sept 2023: Add “encoder only” mode suggested by @eudoxos.
April 2023: Add limited ePaper support, grid widget, calendar and epaper demos.
Now requires firmware >= V1.20.
July 2022: Add ESP32 touch pad support.
June 2022: Add QRMap and
BitMap widgets.
March 2022: Add latency control for hosts with
SPIRAM.
February 2022: Supports use with only three buttons devised by Bart Cerneels.
Simplified widget import. Existing users should replace the entire gui tree.
Code has been tested on ESP32, ESP32-S2, ESP32-S3, Pi Pico and Pyboard. This is
under development so check for updates.
0. Contents
- Basic concepts Including “Hello world” script.
1.1 Coordinates The GUI’s coordinate system.
1.2 Screen Window and Widget objects Basic GUI classes.
1.3 Fonts
1.4 Navigation Options for hardware. How the GUI navigates between widgets.
1.4.1 Encoder-only mode Using only an encoder for navigation.
1.5 Hardware definition How to configure your hardware.
1.6 Quick hardware check Testing the hardware config. Please do this first.
1.7 Installation Installing the library.
1.8 Performance and hardware notes
1.9 Firmware and dependencies
1.10 Supported hosts and displays
1.11 Files Discussion of the files in the library.
1.11.1 Demos Simple demos showing coding techniques.
1.11.2 Test scripts GUI tests, some needing larger displays
1.12 Floating Point Widgets How to input floating point data.
- Usage Application design.
2.1 Program structure and operation A simple demo of navigation and use.
2.2 Callbacks
2.3 Colors
2.3.1 Monochrome displays
- The ssd and display objects
3.1 SSD class Instantiation in hardware_setup.
3.2 Display class Instantiation in hardware_setup.py.
3.2.1 Encoder usage
3.2.2 Encoder only mode
- Screen class Full screen window.
4.1 Class methods
4.2 Constructor
4.3 Callback methods Methods which run in response to events.
4.4 Method Optional interface to asyncio code.
4.5 Class variable Control latency caused by garbage collection.
4.6 Usage Accessing data created in a screen.
- Window class
5.1 Constructor
5.2 Class method
5.3 Popup windows
- Widgets Displayable objects.
6.1 Label widget Single line text display.
6.1.1 Grid widget A spreadsheet-like array of labels.
6.2 LED widget Display Boolean values.
6.3 Checkbox widget Enter Boolean values.
6.4 Button and CloseButton widgets Pushbutton emulation.
6.5 ButtonList object Pushbuttons with multiple states.
6.6 RadioButtons object One-of-N pushbuttons.
6.7 Listbox widget
6.8 Dropdown widget Dropdown lists.
6.9 DialogBox class Pop-up modal dialog boxes.
6.10 Textbox widget Scrolling text display.
6.11 Meter widget Display floats on an analog meter, with data driven callbacks.
6.11.1 Region class
6.12 Slider and HorizSlider widgets Linear potentiometer float data entry and display
6.13 Scale widget High precision float entry and display.
6.14 ScaleLog widget Wide dynamic range float entry and display.
6.15 Dial widget Display multiple vectors.
6.16 Knob widget Rotary potentiometer float entry.
6.17 Adjuster widget Space saving way to enter floats.
6.18 Menu class
6.19 BitMap widget Draw bitmaps from files.
6.20 QRMap widget Draw QR codes created by uQR.
- Graph plotting Widgets for Cartesian and polar graphs.
7.1 Concepts
7.1.1 Graph classes
7.1.2 Curve classes
7.1.3 Coordinates
7.2 Graph classes
7.2.1 Class CartesianGraph
7.2.2 Class PolarGraph
7.3 Curve classes
7.3.1 Class Curve
7.3.2 Class PolarCurve
7.4 Class TSequence Plotting realtime, time sequential data.
- ESP32 touch pads Replacing buttons with touch pads.
- Realtime applications Accommodating tasks requiring fast RT performance.
- ePaper displays Guidance on using ePaper displays.
Appendix 1 Application design Tab order, button layout, encoder interface, use of graphics primitives, more on callbacks.
Appendix 2 Freezing bytecode Optional way to save RAM.
Appendix 3 Cross compiling Another way to save RAM.
1. Basic concepts
Internally micro-gui uses asyncio. It presents a conventional callback
based interface; knowledge of asyncio is not required for its use. Display
refresh is handled automatically. Widgets are drawn using graphics primitives
rather than icons. This makes them efficiently scalable and minimises RAM usage
compared to icon-based graphics. It also facilitates the provision of extra
visual information. For example the color of all or part of a widget may be
changed programmatically, for example to highlight an overrange condition.
There is limited support for
icons
in pushbuttons via icon fonts, also via the BitMap widget.
The following, taken from gui.demos.simple.py, is a complete application. It
shows a message and has “Yes” and “No” buttons which trigger a callback.
import hardware_setup # Create a display instance
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
# from gui.core.writer import Writer # Monochrome display
from gui.core.writer import CWriter
# Font for CWriter or Writer
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
# wri = Writer(ssd, arial10, verbose=False) # Monochrome display
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 50
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Simple demo: button presses print to REPL.')
Screen.change(BaseScreen) # A class is passed here, not an instance.
test()
Notes:
- Monochrome displays use the
Writer class rather than CWriter to
render fonts, as per the commented-out code above.
- Hardware is defined by a single small file
hardware_setup.py which the
user must edit.
1.1 Coordinates
These are defined as row and col values where row==0 and col==0
corresponds to the top left most pixel. Rows increase downwards and columns
increase to the right. The graph plotting widget uses normal mathematical
conventions within graphs.
A Screen is a window which occupies the entire display. A Screen can
overlay another, replacing all its contents. When closed, the Screen below is
re-displayed. This default method of navigation results in a tree structure of
Screen instances where the screen below retains state. An alternative allows
a Screen to replace another, allowing Screen instances to be navigated in an
arbitrary way. For example a set of Screen instances might be navigated in a
circular fashion. The penalty is that, to save RAM, state is not retained when a
Screen is replaced
A Window is a subclass of Screen but is smaller, with size and location
attributes. It can overlay part of an underlying Screen and is typically used
for dialog boxes. Window objects are modal: a Window can overlay a Screen
but cannot overlay another Window.
A Widget is an object capable of displaying data. Some are also capable of
data input: such a widget is defined as active. A passive widget can only
display data. An active widget can acquire focus. The widget with focus
is able to respond to user input. See navigation.
Widget objects have dimensions defined as height and width. The space
requred by them exceeds these dimensions by two pixels all round. This is
because micro-gui displays a surrounding white border to show which object
currently has focus. Thus to place a Widget at the extreme top left, row
and col values should be 2.
1.3 Fonts
Python font files are in the gui/fonts directory. The easiest way to conserve
RAM is to freeze them which is highly recommended. In doing so the directory
structure must be maintained.
To create alternatives, Python fonts may be generated from industry standard
font files with
font_to_py.py. The
-x option for horizontal mapping must be specified. If fixed pitch rendering
is required -f is also required. Supplied examples are:
arial10.py Variable pitch Arial. 10 pixels high.
arial35.py Arial 35 high.
arial_50.py Arial 50 high.
courier20.py Fixed pitch Courier, 20 high.
font6.py FreeSans 14 high.
font10.py FreeSans 17 high.
freesans20.py FreeSans 20 high.
The directory gui/fonts/bitmaps is only required for the bitmap.py demo.
1.4 Navigation
The GUI requires from 2 to 5 pushbuttons for control. These are:
Next Move to the next widget.
Select Operate the currently selected widget.
Prev Move to the previous widget.
Increase Move within the widget (i.e. adjust its value).
Decrease Move within the widget.
An alternative is to replace buttons 4 and 5 with a quadrature encoder knob
such as this one. That device has a
switch which operates when the knob is pressed: this may be wired for the
Select button. This provides the most intuitive operation.
Many widgets such as Pushbutton or Checkbox objects require only the
Select button to operate: it is possible to design an interface with a subset
of micro-gui widgets which requires only the first two buttons. With three
buttons all widgets may be used without restriction.
Widgets such as Listbox objects, dropdown lists (Dropdown), and those for
floating point data entry can use the Increase and Decrease buttons (or an
encoder) to select a data item or to adjust the linear value. If three buttons
are provided, the GUI will enter “adjust” mode in response to a double-click
of Select. In this mode Prev and Next act to decrease and increase the
widget’s value. A further double-click restores normal navigation. This is
discussed in Floating Point Widgets.
The currently selected Widget is identified by a white border: the focus
moves between widgets via Next and Prev. Only active Widget instances
(those that can accept input) can receive the focus. Widgets are defined as
active or passive in the constructor, and this status cannot be changed. In
some cases the state can be specified as a constructor arg, but other widgets
have a predefined state. An active widget can be disabled and re-enabled at
runtime. A disabled active widget is shown “greyed-out” and cannot accept the
focus until re-enabled.
1.4.1 Encoder only mode
This uses a rotary encoder with a built-in pushbutton as the sole means of
navigation, a mode suggested by @eudoxos. By default, turning the dial moves
the currency between widgets; the widget with the focus has a white border.
Widgets for numeric entry such as sliders and scales may be put into “adjust”
mode with a double click. In that mode turning the dial adjusts the widget.
Floating Point Widgets can enter
“precision” adjustment mode with a long press of the button. “Adjust” and
“precision” modes are cleared with a short button press.
This mode works well and its use is quite intuitive. Navigation by turning a
dial makes it particularly useful when a screen has a large number of widgets.
1.5 Hardware definition
A file hardware_setup.py must exist in the GUI root directory. This defines
the connections to the display, the display driver, and pins used for the
pushbuttons. Example files may be found in the setup_examples directory.
Further examples (without pin definitions) are in this
nano-gui directory.
The following is a typical example for a Raspberry Pi Pico driving an ILI9341
display:
from machine import Pin, SPI, freq
import gc
from drivers.ili93xx.ili9341 import ILI9341 as SSD
freq(250_000_000) # RP2 overclock
# Create and export an SSD instance
pdc = Pin(8, Pin.OUT, value=0) # Arbitrary pins
prst = Pin(9, Pin.OUT, value=1)
pcs = Pin(10, Pin.OUT, value=1)
spi = SPI(0, baudrate=30_000_000)
gc.collect() # Precaution before instantiating framebuf
# Instantiate display and assign to ssd. For args see display drivers doc.
ssd = SSD(spi, pcs, pdc, prst, usd=True)
# The following import must occur after ssd is instantiated.
from gui.core.ugui import Display, quiet
# quiet()
# Define control buttons
nxt = Pin(19, Pin.IN, Pin.PULL_UP) # Move to next control
sel = Pin(16, Pin.IN, Pin.PULL_UP) # Operate current control
prev = Pin(18, Pin.IN, Pin.PULL_UP) # Move to previous control
increase = Pin(20, Pin.IN, Pin.PULL_UP) # Increase control's value
decrease = Pin(17, Pin.IN, Pin.PULL_UP) # Decrease control's value
# Create a Display instance and assign to display.
display = Display(ssd, nxt, sel, prev, increase, decrease)
Where an encoder replaces the increase and decrease buttons, only the final
line needs to be changed to provide an extra arg:
display = Display(ssd, nxt, sel, prev, increase, decrease, 4)
The final arg specifies the sensitivity of the attached encoder, the higher the
value the more the knob has to be turned for a desired effect. A value of 1
provides the highest sensitivity, being the native rate of the encoder. Many
encoders have mechanical detents: a value of 4 matches the click rate of most
devices.
The commented-out quiet() line provides a means of suppressing diagnostic
messages.
Instantiation of SSD and Display classes is detailed in
section 3.
Display drivers are
documented here.
1.6 Quick hardware check
The following may be pasted at the REPL to verify correct connection to the
display. It also confirms that hardware_setup.py is specifying a suitable
display driver.
from hardware_setup import ssd # Create a display instance
from gui.core.colors import *
ssd.fill(0)
ssd.line(0, 0, ssd.width - 1, ssd.height - 1, GREEN) # Green diagonal corner-to-corner
ssd.rect(0, 0, 15, 15, RED) # Red square at top left
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE) # Blue square at bottom right
ssd.show()
1.7 Installation
Please ensure device firmware is up to date. Clone the repo to the PC with:
$ git clone https://github.com/peterhinch/micropython-micro-gui
$ cd micropython-micro-gui
In the micropython-micro-gui directory edit hardware_setup.py to match the
hardware in use.
The official
mpremote
tool is recommended. Install with:
$ pip3 install mpremote
There are several options for installation
- Using mpremote to run the GUI demos via the PC without installing.
- Subtractive. Installing the entire GUI, then (optionally) removing unused
components.
- Additive. Installing a minimal subset and manually adding extra components.
- Using frozen bytecode.
Testing without installing
The easy way to start is to use mpremote which allows a directory on your PC
to be mounted on the host. In this way the filesystem on the host is left
unchanged. This is at some cost in loading speed, especially on ESP32. In the
micropython-micro-gui directory run:
$ mpremote mount .
This should provide a REPL. Run the minimal demo:
>>> import gui.demos.simple
If this runs the hardware is correctly configured and other demos should run.
Installing a display driver
It is necessary to install a display driver prior to any GUI installation. On
networked hardware a display driver may be installed as follows (example is for
ST7789):
>>> mip.install("github:peterhinch/micropython-nano-gui/drivers/st7789")
The last part of the addresss (st7789) is the name of the directory holding
drivers for the display in use. In cases where the directory holds more than
one driver all will be installed. Unused drivers may be deleted.
Install using mpremote on the PC as follows:
$ mpremote mip install "github:peterhinch/micropython-nano-gui/drivers/st7789"
Full installation (subtractive)
The entire GUI is large. It is possible to install it all from the PC clone by
issuing:
$ cd micropython-micro-gui
$ mpremote cp -r gui :
$ mpremote cp hardware_setup.py :
This is rather profligate with Flash storage. There is great scope for
discarding unused fonts, demos and widgets. As an alternative to installing
everything and pruning, an additive approach may be used where a minimal subset
is installed with extra fonts and widgets being added as required.
Minimal installation (additive)
This installs a subset adequate to run the simple.py demo. It comprises:

Note that mip and mpremote mip install to /lib/ which therefore becomes
the root of the above tree. The subset is installed with (on the device):
>>> mip.install("github:peterhinch/micropython-micro-gui")
or (on the PC):
$ mpremote mip install "github:peterhinch/micropython-micro-gui"
In both cases the edited hardware_setup.py must be copied from the PC:
$ cd micropython-micro-gui
$ mpremote cp hardware_setup.py :
When adding components the directory structure must be maintained. For example,
in the micropython-micro-gui directory:
$ mpremote cp gui/fonts/font10.py :/gui/fonts/
$ mpremote cp gui/widgets/checkbox.py :/gui/widgets/
Freezing bytecode
There is scope for speeding loading and saving RAM by using frozen bytecode.
The entire gui tree may be frozen but the directory structure must be
maintained. For reasons that are unclear freezing display drivers may not
work. For fexibility, consider keeping hardware_setup.py in the filesystem.
See Appendix 2 Freezing bytecode.
RAM usage
Running the linked_sliders demo, the code uses about 23,000 bytes with frozen
bytecode and 55,000 bytes without. To this must be added the size of the frame
buffer. This can readily be calculated. For example in the case of the ILI9341
(a 240x320 pixel unit whose driver uses 4-bit color) the buffer size is
240x320/2 = 38,400 bytes.
A Pico shows ~182000 bytes free with no code running. With linked_sliders
running on an ILI9341 display, it shows 120,896 bytes free with frozen
bytecode and 88,640 bytes free without.
With multi-pixel displays the size of the frame buffer can prevent the GUI from
compiling. If frozen bytecode is impractical, consider cross-compiling. See
Appendix 3 Cross compiling.
Speed
The consequence of inadequate speed is that brief button presses can be missed.
This is because display update blocks for tens of milliseconds, during which
time the pushbuttons are not polled. This can be an issue in displays with a
large number of pixels, multi-byte colors and/or slow SPI clock rates. In high
resolution cases the device driver has specfic asyncio support whereby the
driver yields to the scheduler a few times during the refresh.Currently this
exists on ILI9486, ILI9341 and ST7789 (e.g. TTGO T-Display). By my calculations
and measurements this should be unnecessary on other drivers, but please report
any tendency to miss button presses and I will investigate.
This may be mitigated by two approaches:
- Clocking the SPI bus as fast as possible. This is discussed in
the drivers doc.
- Clocking the host fast (
machine.freq).
On ESP32 (including the TTGO T-Display) note that pins 36-39 are input-only and
do not have pullup support: if these are used for pushbutton input, physical
pullups to 3.3V should be used.
See ref.
On a Pyboard 1.1 with 320x240 ili9341 display it was necessary to use frozen
bytecode: in this configuration running the various.py demo there was 29K of
free RAM. Note that, at 37.5KiB, this display is the worst-case in terms of
RAM usage. A smaller display or a Pyboard D would offer more headroom. Frozen
bytecode was also necessary on an RP2 running an ILI9486: a 480x320 display
requires a 76,800 byte frame buffer.
1.9 Firmware and dependencies
Firmware should be V1.17 or later. The source tree includes all dependencies.
These are listed to enable users to check for newer versions or to read docs:
- writer.py
Provides text rendering of Python font files.
- SSD1306 driver.
A copy of the official driver for OLED displays using the SSD1306 chip is
provided. The link is to the official file.
- Synchronisation primitives.
The link is to my
asyncio support repo.
- PCD8544/Nokia 5110.
Displays based on the Nokia 5110 (PCD8544 chip) require this driver. It is not
provided in this repo. The link is to its source.
1.10 Supported hosts and displays
Development was done using a Raspberry Pi Pico connected to a cheap ILI9341
320x240 display. I have also tested a TTGO T-Display (an ESP32 host) and a
Pyboard. Code is written with portability as an aim, but MicroPython configs
vary between platforms and I can’t guarantee that every widget will work on
every platform. For example, some use the cmath module which may be absent on
some builds.
Supported displays are as per
the nano-gui list.
In general ePaper and Sharp displays are unlikely to yield good results because
of slow and visually intrusive refreshing. However there is an exception: the
Waveshare pico_epaper_42. See
10. ePaper displays.
Display drivers are documented here.
1.11 Files
Display drivers may be found in the drivers directory. These are copies of
those in nano-gui, included for convenience. Note the file
drivers/boolpalette.py, required by all color drivers.
The system is organised as a Python package with the root being gui. Core
files in gui/core are:
colors.py Constants including colors and shapes.
ugui.py The start GUI code.
writer.py Supports the Writer and CWriter classes.
The gui/primitives directory contains the following files:
pushbutton.py Interface to physical pushbuttons and ESP32 touch pads.
delay_ms.py A software triggerable timer.
encoder.py Driver for a quadrature encoder. This offers an alternative
interface - see Appendix 1.
The gui/demos directory contains a variety of demos and tests described
below.
1.11.1 Demos
Demos are run by issuing (for example):
>>> import gui.demos.simple
If shut down cleanly with the “close” button a demo can be re-run with (e.g.):
gui.demos.simple.test()
Before running a different demo the host should be reset (ctrl-d) to clear RAM.
These will run on screens of 128x128 pixels or above. The initial ones are
minimal and aim to demonstrate a single technique.
simple.py Minimal demo discussed below. Button presses print to REPL.
checkbox.py A Checkbox controlling an LED.
slider.py A Slider whose color varies with its value.
slider_label.py A Slider updating a Label. Good for trying precision
mode.
linked_sliders.py One Slider updating two others, and a coding “wrinkle”
required for doing this.
dropdown.py A dropdown list (with scrolling) updates a Label.
listbox.py A listbox with scrolling.
dialog.py DialogBox demo. Illustrates the screen change mechanism.
screen_change.py A Pushbutton causing a screen change using a re-usable
“forward” button.
screen_replace.py A more complex (non-tree) screen layout.
primitives.py Use of graphics primitives.
aclock.py An analog clock using the Dial vector display. Also shows
screen layout using widget metrics. Has a simple asyncio task.
tbox.py Text boxes and user-controlled scrolling.
tstat.py A demo of the Meter class with data sensitive regions.
menu.py A multi-level menu.
adjuster.py Simple demo of the Adjuster control.
adjust_vec.py A pair of Adjusters vary a vector.
bitmap.py Demo of the BitMap widget showing a changing image. (See widget
docs).
qrcode.py Display a QR code. Requires the uQR module.
calendar.py Demo of grid widget.
epaper.py Warts-and-all demo for an ePaper display. Currently the only
supported display is the
Waveshare pico_epaper_42
with Pico or other host.
1.11.2 Test scripts
These more complex demos are run in the same way by issuing (for example):
>>> import gui.demos.active
Some of these require larger screens. Required sizes are specified as
(height x width).
active.py Demonstrates active controls providing floating point input
(240x320).
plot.py Graph plotting (128x200).
screens.py Listbox, dropdown and dialog boxes (128x240).
various.py Assorted widgets including the different types of pushbutton
(240x320).
vtest.py Clock and compass styles of vector display (240x320).
calendar.py Demo of grid control (240x320 - but could be reduced).
Some applications need to adjust a data value with an extremely large dynamic
range. This is the ratio of the data value’s total range to the smallest
adjustment that can be made. The mechanism currently implemented enables a
precision of 0.05%.
Floating point widgets respond to a brief press of the increase or decrease
buttons by adjusting the value by a small amount. A continued press causes the
value to be repeatedly adjusted, with the amount of the adjustment increasing
with time. This enables the entire range of the control to be accessed quickly,
while allowing small changes of 0.5%. This works well. In many cases the level
of precision will suffice. An encoder provides similar performance.
Fine adjustments may be achieved by pressing the select button for at least
one second. The GUI will respond by changing the border color from white
(i.e. has focus) to yellow. In this mode a brief press of increase or
decrease or small movement of an encoder will have a reduced effect (0.05%).
Fine mode may be cancelled by pressing select or by moving the focus to
another control. This also works in three-button mode, with Next and Prev
performing the adjustments.
In the case of slider and knob controls the precision of fine mode exceeds that
of the visual appearance of the widget: fine changes can be too small to see.
Options are to use the Scale widget or to have a
linked Label showing the widget’s exact value.
The callback runs whenever the widget’s value changes. This causes the callback
to run repeatedly while the user adjusts the widget. This is required if there
is a linked Label to update.
2. Usage
2.1 Program structure and operation
The following is a minimal script (found in gui.demos.simple.py) which will
run on a minimal system with a small display and two pushbuttons. Commented out
code shows changes for monochrome displays.
The demo provides two Button widgets with “Yes” and “No” legends. It may be
run by issuing at the REPL:
>>> import gui.demos.simple
Note that the import of hardware_setup.py is the first line of code. This is
because the frame buffer is created here, with a need for a substantial block
of contiguous RAM.
import hardware_setup # Instantiate display, setup color LUT (if present)
from gui.core.ugui import Screen, ssd
from gui.widgets import Label, Button, CloseButton
# from gui.core.writer import Writer # Monochrome display
from gui.core.writer import CWriter
# Font for CWriter
import gui.fonts.arial10 as arial10
from gui.core.colors import *
class BaseScreen(Screen):
def __init__(self):
def my_callback(button, arg):
print('Button pressed', arg)
super().__init__()
# wri = Writer(ssd, arial10, verbose=False)
wri = CWriter(ssd, arial10, GREEN, BLACK, verbose=False)
col = 2
row = 2
Label(wri, row, col, 'Simple Demo')
row = 20
Button(wri, row, col, text='Yes', callback=my_callback, args=('Yes',))
col += 60
Button(wri, row, col, text='No', callback=my_callback, args=('No',))
CloseButton(wri) # Quit the application
def test():
print('Testing micro-gui...')
Screen.change(BaseScreen)
test()
Note how the Next pushbutton moves the focus between the two buttons and the
“X” close button. The focus does not move to the “Simple Demo” widget because
it is not active: a Label cannot accept user input. Pushing the Select
pushbutton while the focus is on a Pushbutton causes the callback to run.
Applications start by performing Screen.change() to a user-defined Screen
object. This must be subclassed from the GUI’s Screen class. Note that
Screen.change accepts a class name, not a class instance.
The user defined BaseScreen class constructor instantiates all widgets to be
displayed and typically associates them with callback functions - which may be
bound methods. Screens typically have a CloseButton widget. This is a special
Pushbutton subclass which displays as an “X” at the top right corner of the
physical display and closes the current screen, showing the one below. If used
on the bottom level Screen (as above) it closes the application.
The CWriter instance wri associates a widget with a font. Constructors for
all widgets have three mandatory positional args. These are a CWriter
instance followed by row and col. These args are followed by a number of
optional keyword args. These have (hopefully) sensible defaults enabling you to
get started easily. Monochrome displays use the simpler Writer class.
2.2 Callbacks
The interface is event driven. Widgets may have optional callbacks which will
be executed when a given event occurs. Events occur when a widget’s properties
are changed programmatically, and also (in the case of active widgets) in
response to user input.
A callback function receives positional arguments. The first is a reference to
the object raising the callback. Subsequent arguments are user defined, and are
specified as a tuple or list of items. Callbacks and their argument lists are
optional: a default null function and empty tuple are provided. Callbacks may
optionally be written as bound methods. This facilitates communication between
widgets.
When writing callbacks take care to ensure that the correct number of arguments
are passed, bearing in mind the first arg described above. An incorrect
argument count results in puzzling tracebacks which appear to implicate the GUI
code. This is because it is the GUI which actually executes the callbacks.
Callbacks should complete quickly. See
Appendix 1 Application design for
discussion of this.
2.3 Colors
The file gui/core/colors.py defines a set of color constants which may be
used with any display driver. This section describes how to change these or
to create additional colors. Most of the color display drivers define colors
as 8-bit or larger values. For the larger displays 4-bit drivers are provided
with the aim of conserving RAM.
In the 4-bit case colors are assigned to a lookup table (LUT) with 16 entries.
The frame buffer stores 4-bit color values, which are converted to the correct
color depth for the hardware when the display is refreshed. Of the 16 possible
colors 13 are assigned in gui/core/colors.py, leaving color numbers 12, 13
and 14 free.
The following code is portable between displays and creates a user defined
color PALE_YELLOW.
from gui.core.colors import * # Imports the create_color function
PALE_YELLOW = create_color(12, 150, 150, 0) # index, r, g, b
If a 4-bit driver is in use, the color rgb(150, 150, 0) will be assigned to
“spare” color number 12. Any color number in range `0 <= n
README 内容过长,已截取前 64 KB。完整内容请前往 GitHub 仓库查看。
评论