Display
The Toit SDK includes support for simple user interfaces on displays. We highlight in this section the basic functions of display use. You can also learn more about the different fonts available in the SDK by studying the font basics.
These examples assume you have installed the pixel_display package into your project. See the Packages quick start guide for information on getting started with packages.
You will also need at least one display driver, for example the SSD1306 driver
which is also available as a package, called ssd1306
. A color TFT driver that supports many common displays is available
in the toit-color-tft and can be installed as the color-tft
package.
Getting the display driver and PixelDisplay object
Both the color-tft
and ssd1306
packages have some helpful examples which you can see by cloning the
github repos linked above. We will assume that you have a get_display.toit
file in your project, which
might look something like the following. Details will depend on your hardware configuration, in particular which
pins and bus you are using to connect your display.
Example contents of get_display.toit
import gpio import i2c import ssd1306 show * import pixel-display show * get-display -> PixelDisplay: scl := gpio.Pin 4 sda := gpio.Pin 5 bus := i2c.Bus --sda=sda --scl=scl --frequency=800_000 devices := bus.scan if not devices.contains Ssd1306.I2C-ADDRESS: throw "No SSD1306 display found" driver := Ssd1306.i2c (bus.device Ssd1306.I2C-ADDRESS) return PixelDisplay.two-color driver // Alternatively: return PixelDisplay.two-color --portrait --inverted driver
Display architecture
A display will be an subclass of PixelDisplay
. These are created with named
constructors.
PixelDisplay.two-color
for black/white displays.
PixelDisplay.three-color
for black/white/red displays (usually e-paper).
PixelDisplay.four-gray
for 4-tone grayscale displays (usually e-paper).
PixelDisplay.true-color
for color displays like the ones supported by the color-tft
driver.
A display maintains a tree of objects that it is currently displaying.
These objects are called Element
s. As in HTML, one of the popular
elements is called Div
and it is primarily used to group other
elements. The main element for displaying text is called Label
.
Elements are mutable objects that represent a shape on the display. For example a geometric shape or a piece of text. Together, the elements added to a display describe a scene.
To update the display, you add or remove elements, or modify the
existing elements. When you have made your changes you update the
physical display by calling the draw
method on the display object.
Normally you will import all the common elements
into your namespace with an import like import pixel-display show *
The true-color
displays use web-like six-digit hex color codes in the style
0xRRGGBB, while other displays have useful constants like BLACK
,
and WHITE
, which can be imported with import pixel-display.two-color show *
.
Write with built-in sans font
Let us start with a simple example using a Label element. In this example we use the sans10 font, which is a simple ASCII-only font that is built in to the Toit SDK and does not need to be imported with a package.
We build up a 'scene' consisting of only one text
element, and immediately render it to the screen with draw
.
Afterwards we exit the program, which (depending on the driver)
leaves the image on the screen.
In this example there is no way to update the scene later, since
the program has terminated.
import font show * import pixel-display show * import pixel-display.two-color show * // Provides WHITE and BLACK. import .get-display // Import file in current project, see above. SANS ::= Font.get "sans10" // Edit get-display.toit to get a rotated display. DISPLAY ::= get-display STYLE := Style --color=BLACK --font=SANS main: DISPLAY.background = WHITE DISPLAY.add Label --x=60 --y=50 --text="Hello from Toit!" --style=STYLE DISPLAY.draw sleep --ms=1000
When creating an element to add to the scene there are a huge number of parameters to specify. This includes the color, the orientation, the font, and the coordinate system. We combine almost all these parameters into an immutable object called a style.
In the above example we created a style that draws in black, and uses the built-in sans10 font for text objects. Often styles are used with elements using maps that are reminiscent of CSS.
Using additional fonts
More fonts are available in the packages font-x11-adobe
,
font-clear
, font-clearly-u
, and font-tiny
packages.
Once you have installed the package in your project,
import specific fonts from font-x11-adobe
with:
import font show * import font-x11-adobe.sans-10-bold import font-x11-adobe.sans-24-bold import pixel-display show * import pixel-display.two-color show WHITE BLACK import .get-display // Import file in current project, see above. main: SANS-10-BOLD ::= Font [ sans-10-bold.ASCII, sans-10-bold.LATIN-1-SUPPLEMENT] SANS-24-FONT ::= Font [ sans-24-bold.ASCII, sans-24-bold.LATIN-1-SUPPLEMENT] display/PixelDisplay ::= get-display sans-10 := Style --font = SANS-10-BOLD --color = BLACK --align-right sans-24 := Style --font = SANS-24-FONT --color = BLACK --align-center [ Label --style=sans-10 --x=40 --y=30 --text="Hello from", Label --style=sans-24 --x=18 --y=65 --text="TOITWARE", ].do: display.add it display.draw
Learn more about all available fonts here.
Using icons
All Material Design icons are available for use in Toit programs.
import pixel-display show * import pixel-display.two-color show BLACK WHITE // Use "jag pkg install pictogrammers_icons" to get this package. import pictogrammers-icons.size-48 as icons import .get-display // Import file in current project, see above. main: display/PixelDisplay := get-display display.background = BLACK // For this label we use `--icon` instead of `--text`. display.add Label --x=63 --y=85 --icon=icons.HUMAN-SCOOTER --color=WHITE display.draw
Updating the display
In order to update the display we can build up a scene, then create a loop that updates the scene as new data arrives. Here is a bigger example that subscribes to an MQTT topic for its weather information, and uses the local time to update a digital clock on the same display.
This example also shows the use of a style built up with maps, to let
multiple elements be styled with the same parameters. We can use
the syntax --id="my-name"
to given an element a name, much like
the HTML syntax <div id="my-name">
. Similarly, we can give an element a
class with --classes=["my-class"]
, which is analogous to
the HTML syntax <div class="my-class">
. We don't use --class
because
that is a reserved keyword in Toit.
A composite style like this for the entire display must be applied to the
display with display.set-styles [STYLE]
before the display is drawn.
import encoding.json import font show * import font-x11-adobe.sans-14-bold import monitor show Mutex import mqtt import pictogrammers-icons.size-48 as icons import pixel-display.two-color show * import pixel-display show * import .get-display // Import file in current project, see above. // Search for icon names on https://materialdesignicons.com/ // (hover over icons to get names). WMO-4501-ICONS ::= [ icons.WEATHER-SUNNY, icons.WEATHER-CLOUDY, icons.WEATHER-SNOWY, icons.WEATHER-SNOWY-HEAVY, icons.WEATHER-FOG, icons.WEATHER-PARTLY-RAINY, icons.WEATHER-RAINY, icons.WEATHER-SNOWY, icons.WEATHER-PARTLY-RAINY, icons.WEATHER-LIGHTNING, ] // We don't want separate tasks updating the display at the // same time, so this mutex is used to ensure the tasks only // have access one at a time. display-mutex := Mutex WIDTH ::= 128 HEIGHT ::= 64 display := get-display main: sans-14-font ::= Font [ sans-14-bold.ASCII, // Regular characters. sans-14-bold.LATIN-1-SUPPLEMENT, // Degree symbol. ] // Circle as background of weather icon. DIAMETER ::= 56 CORNER-RADIUS ::= DIAMETER / 2 display.background = BLACK STYLE ::= Style --class-map = { "top": Style --x=0 --y=0 --w=WIDTH --h=HEIGHT --background=BLACK --border=(RoundedCornerBorder --radius=8), "rounded": Style --x=68 --y=4 --w = DIAMETER --h = DIAMETER --border = RoundedCornerBorder --radius=CORNER-RADIUS --background = WHITE, "temp-box": Style --x=0 --y=0 --w=64 --h=32 --background=WHITE, "clock-box": Style --x=0 --y=32 --w=64 --h=32 --background=BLACK, } --id-map = { "icon": Style --x=(DIAMETER / 2) --y=(16 + DIAMETER / 2) --color=BLACK --align-center, "temp": Style --x=32 --y=23 --font=sans-14-font --color=BLACK --align-center, "time": Style --x=32 --y=23 --font=sans-14-font --color=WHITE --align-center, } // The Div element takes a list as a parameter, which allows you to // specify the children that it contains. This lets us build up // a display with a syntactic tree of elements, and since Toit does // not have a `new` keyword the result is a bit like a DSL. display.add Div.clipping --classes=["top"] [ Div.clipping --classes=["rounded"] [ Label --id="icon", ], Div --classes=["temp-box"] [ Label --id="temp", ], Div --classes=["clock-box"] [ Label --id="time", ], ] // This applies the styles to all the elements that have been added to the // display. display.set-styles [STYLE] task --background:: clock-task (display.get-element-by-id "time") task --background:: weather-task (display.get-element-by-id "icon") (display.get-element-by-id "temp") weather-task weather-icon/Label temperature-element/Label: client := mqtt.Client --host="127.0.0.1" client.start --client-id="toit-client-id" client.subscribe "weather":: | topic/string payload/ByteArray | map := json.decode payload code := map["wmo_4501"] temp := map["temperature_c"] display-mutex.do: weather-icon.icon = WMO-4501-ICONS[code] temperature-element.label = "$(%.1f temp)°C" display.draw clock-task time-element/Label: while true: now := (Time.now).local display-mutex.do: // H:MM or HH:MM depending on time of day. time-element.label = "$now.h:$(%02d now.m)" display.draw // Sleep this task until the next whole minute. sleep-time := 60 - now.s sleep --ms=sleep-time*1000