Developer notes for micropython-gui
June 16, 2020 ยท View on GitHub
These notes are intended for those wishing to implement new controls or displays for this library.
Introduction
GUI objects comprise displays and controls. They differ in that displays don't
respond to touch whereas controls do. Displays are subclassed from the
NoTouch class. Controls are subclassed from Touchable (which is itself
subclassed from NoTouch). Understanding the functionality of these base
classes is crucial to maintaining consistency and avoiding code duplication.
GUI objects have a value method which is the principal user interface. It
accesses an underlying _value variable which may be of various types. If
called with no arguments it returns the current _value. Called with a single
argument it checks for a difference with the current _value and, if there is
one, updates _value. It updates the control using the show method outlined
below. The intention here is that the value of controls and displays can
programmatically be altered. On occasion the value method will need to be
subclassed, for example if data validation, modification or type conversion is
required.
GUI objects must implement a show method capable of being called with no
arguments. This should display the object: the underlying code will call it
without arguments when a screen is displayed (for example after a screen
change). By default it is also called without arguments when a control's value
changes. Its behaviour can be modified by NoTouch bound variables
initial_value and redraw. The aim here is to avoid the situation where a
value change redraws the entire control which can lead to flicker, especially
in the case of controls whose value changes in response to a drag. These
variables are discussed in detail below.
Touchable objects must implement a _touched method which determines how the
control responds to a touch. The method receives the pixel coordinates of the
touch (which will be within the control's bounding box).
The other user interface is the greyed_out method. Called without args it
returns the current disabled state of the control (True if disabled). Called
with a boolean (allowable only for touchable controls) it greys out (True) or
enables (False) the control, with its appearance reflecting the change.
Disabled controls should still respond to programmatic changes (calls to
value).
Overall design
The Screen class is at the core of the GUI functionality, and some
familiarity with it is necessary for designers of controls and displays. The
class is initialised by the setup method, called from tft_local.py, which
assigns a TFT (a TFT_G instance) and touch panel (TOUCH instance) to class
variables tft and objtouch.
The class maintains instance variables touchlist and displaylist. These
contain the touchable controls and the display objects on that screen. Objects
are automatically added to the correct list by the addobject class method,
which is called by the NoTouch constructor.
The Screen class has a class variable current_screen which holds the
Screen instance which is current. Currency carries two implications. When GUI
objects are instantiated, they are assigned to the current screen. And when a
Screen is displayed by a call to Screen.show, the class method finds the
correct instance in that variable; i.e. the current screen is the one visible
on the physical hardware.
The Screen class controls touch response. When the first Screen object is
instatiated a task Screen._touchtest is initiated. This runs forever and
tests whether the screen is being touched. If it is, it checks each touchable
object on the current screen to see if it is currently capable of responding to
touch. If it is, the object's _trytouch method is called. The Touchable
class has a default method which serves for most controls. If the screen is not
being touched, the task checks each touchable object on the current screen to
see if it was formerly being touched. If it was, the formerly touched status is
cleared down and the object's _untouched method is called. This enables
release callbacks to be implemented. Again a default method is provided by the
Touchable class.
The Screen class also provides class methods for changing the current screen
and reverting to the previous one. These clear the screen and call the
Screen.show class method, which redraws all objects on the newly current
screen. It does this via the object's draw_border and show methods, called
without arguments. It also supports the set_grey_style class method and the
get_tft class method. This provides access to the TFT. It accepts the
greyed-out status of the control instance as its argument and instructs the
TFT_G class of the correct greyed-out status to use.
In practice, controls and displays access the TFT by means of the tft
property of the NoTouch base class. This sets the greyed out status of the
TFT_G and returns the TFT instance.
Access to the TFT module is mediated by the TFT_G class. This is subclassed
from the TFT module's TFT class. Its purpose is to handle the colors of
greyed out controls: for consistency its graphics primitives are used
throughout the GUI in preference to those of the underlying TFT class.
class NoTouch
Constructor arguments (all mandatory, positional):
location2-tuple defining the location of the top left corner of the control in screen coords.fontFont object to use (may beNoneif control has no text elements).heightHeight in pixels. In theLabelclass it isNoneand computed in the constructor.widthWidth in pixels.fgcolor3-tuple defining the foreground color, orNone. IfNonethe screen's foreground color is used.bgcolor3-tuple defining the background color, orNone. If a color is provided the control will be filled with a background of this color (seeknobtest.py).fontcolor3-tuple defining the font color, orNone. IfNonethe screen's foreground color is used.borderInteger orNone. If an integernis provided a single pixel border will be drawn around the control, separated from it bynpixels.valueThe starting value for the contol. Data types are various, but clearly theshowmethod must be able to display them. Floats are constrained to the 0 to 1.0 limits used throughout.initial_valueTypicallyNone. This may be used to enableshowto identify the first time the control has been displayed. It is stored in theinitial_valuebound variable and otherwise unused by theTouchableandNoTouchclasses.
Bound variables:
fgcolorForeground color. See fontcolor below.bgcolorBackground color. See fontcolor below.fontcolorThese should be used in all methods, notably in constructors, as they reflect the defaults applied by the superclass where the user passedNoneto the constructor.screenThe screen object on which this instance should be drawn. This is set to the current screen by theNoTouchconstructor.redrawSet by the GUI system. When set theshowmethod must redraw the entire control. If theshowmethod is to avoid redrawing the entire control on every call, it should clear the flag down when drawing the elements which are intended to persist.locationThe following are simply storage for the constructor args described above._value_initial_valueAvailable for any purpose, notably initialisation detection.fontheightwidthfillTrue if abgcolorwas provided to the constructor.visibleFor compound cotrols only (pseudo controls consisting of more than one physical control). Currently only theButtonListsets itFalseandButtonhonours it. IfFalsethe control will be invisible and insensitive to touch._greyed_outAlwaysFalsein the case of displays (which don't respond to touch).borderBorder width in pixels. 0 if there is no border.callbackCallback function on value change. Primarily for control classes: default is a null function.argsArgs for above, default [].cb_endCallback on touch release: for control classes.cbe_argsArgs for above, default [].
Methods:
greyed_outNo args. ReturnsTrueif the control is disabled.show_if_currentNo args. Calls theshowmethod (defined in the subclass) if the control is on the current screen. Useful in customvaluemethods.valueThe default user interface. Positional argsvaldefault None,showdefaultTrue. If called without args, returns the control's current value. Ifvalis provided it checks the type: if it is afloatit is constrained to 0 <= val <= 1.0. If the value has changed it updates it, calls the callback, and calls the control'sshowmethod if it is on the current screen. The optionalshowargument is setFalseby theshowmethod where that method calls the control'svaluemethod. This prevents needless recursion.
Property:
tftReturns theTFT_Ginstance with greyed_out status set to that ofself.
class Touchable (subclass of NoTouch)
Constructor arguments (all mandatory, positional). These are as described above, except 10.
locationfontheightwidthfgcolorbgcolorfontcolorbordervaluecan_dragTrue if the control must respond to drag events.initial_value
Bound variables (not usually needed by subclasses):
As per NoTouch plus:
can_dragTrue if the control must respond to drag events. Such events cause repeated calls to the_touchedmethod.busyThis is used by controls which do not respond to drag events to ensure that the_touchedmethod of such controls is called once only. It is set by_trytouchand cleared by theScreen._touchtesttask when the touch ceases.was_touchedThis is set by the_trytouchmethod and cleared by theScreen._touchtesttask: it forms an interlock to ensure the_untouchedmethod is called once only when a touch ends.
Methods:
As perNoTouch plus:
greyed_outOverridden method, optional boolean argvaldefaultNone. If the arg is not supplied, returns the current greyed-out status. If the arg is supplied, and differs from the current status, updates the current status, redraws the control and returns the new status._untouchedCalled by theScreen._touchtesttask when the touch ceases. Calls thecb_endcallback. Can be overridden in subclasses._trytouchCalled by theScreen._touchtesttask when the touch panel is touched and the object is on the current screen and responsive to touch. If the touch is within the object's bounding box, it calls the_touchedmethod. All touchable controls must implement this method, which determines how the control should respond when touched._set_callbacksCalled from subclass constructors to set the callback functions and args.
uasyncio interface
When the first screen is displayed (using the Screen.change class method) a
coroutine Screen.monitor is started. This starts the scheduler. The task
immediately suspends, waiting on an Event (Screen.is_shutdown). The
Screen.shutdown class method may be called from user code: its only purpose
is to trigger the Event and allow the Screen.monitor task to complete. It
cancels any tasks registered by the current screen, clears down the display,
and leaves the driver and uasyncio in a state where another script can be run
(subject to RAM).
Termination of the primary Task restores the REPL.