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 Elements. 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.

  // Create a new style for white elements.
  white-style := Style --color=WHITE --font=SANS

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.

Weather station example screenshot
Weather station example screenshot
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