Display

The Toit SDK includes support for writing text and printing icons 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 reading Toit 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 -> TwoColorPixelDisplay:
  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_ID: throw "No SSD1306 display found"

  driver := SSD1306 (bus.device 0x3c)

  return TwoColorPixelDisplay driver

Display architecture

A display will be an instance of one of the following classes, which have most of the same methods:

TwoColorPixelDisplay for black/white displays.

ThreeColorPixelDisplay for black/white/red displays (usually e-paper).

FourGrayPixelDisplay for 4-tone grayscale displays (usually e-paper).

TrueColorPixelDisplay for color displays like the ones supported by the color-tft driver.

A display maintains a list of objects that it is currently displaying. These objects are called Textures. They are mutable objects that represent a shape on the display. For example a geometric shape or a piece of text. Together, the textures added to a display describe a scene.

To update the display, you add or remove textures, or modify the existing textures. 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 textures for your given color model into your namespace with an import like import pixel_display.true_color show *

Write with built-in sans font

Let us start with a simple example using a text texture. 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.

In this example we build up a 'scene' consisting of only one text texture, and immediately render it to the screen with draw. Afterwards we exit the program, which 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.texture show *
import pixel_display.two_color show *  // Provides WHITE and BLACK.
import pixel_display show *

import .get_display  // Import file in current project, see above.

sans ::= Font.get "sans10"
display ::= get_display

main:
  context := display.context --landscape --color=BLACK --font=sans
  display.text context 60 50 "Hello from Toit!"
  display.draw

When creating a texture 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 graphics context.

In the above example we created a context that uses a landscape orientation and coordinates, draws in black, and uses the built-in sans10 font for text objects. Often, contexts are created from other contexts using the with method:

  // Create a new context for white textures.
  white_context := context.with --color=WHITE

Using additional fonts

Import specific fonts from font.x11_100dpi with:

import font show *
import font.x11_100dpi.sans.sans_10_bold
import font.x11_100dpi.sans.sans_24_bold
import pixel_display.texture show*
import pixel_display.two_color show WHITE BLACK
import pixel_display show TwoColorPixelDisplay

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 ::= get_display

  sans_10 := display.context
    --landscape
    --font = sans_10_bold
    --color = BLACK
    --alignment = TEXT_TEXTURE_ALIGN_RIGHT

  sans_24 := display.context
    --landscape
    --font = sans_24_font
    --color = BLACK
    --alignment = TEXT_TEXTURE_ALIGN_CENTER

  context := display.context --landscape --color=BLACK
  display.text sans_10 40 30 "Hello from"
  display.text sans_24 18 65 "TOITWARE"
  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 TwoColorPixelDisplay
// Use "toit 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 := get_display
  context := display.context --landscape
  icon := display.icon context 63 85 icons.HUMAN_SCOOTER
  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 a pubsub stream for its weather information, and uses the local time to update a digital clock on the same display.

Weather station example screenshot
Weather station example screenshot
import encoding.json
import font show *
import font.x11_100dpi.sans.sans_14_bold
import monitor show Mutex
import pubsub

import pictogrammers_icons.size_48 as icons

import pixel_display.two_color show *
import pixel_display.texture show *
import pixel_display show TwoColorPixelDisplay

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 threads 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

display := get_display

main:
  sans_14_font ::= Font [
    sans_14_bold.ASCII,  // Regular characters.
    sans_14_bold.LATIN_1_SUPPLEMENT,  // Degree symbol.
  ]

  // Build a scene with the metods `add`, `text`, `icon`,
  // and `filled_rectangle`.

  display.background = BLACK
  context := display.context
    --landscape
    --color=WHITE
    --font=sans_14_font
  black_context := context.with --color=BLACK

  // White circle as background of weather icon.  We are just
  // using the window to draw a circle here, not as an actual
  // window with its own textures.
  DIAMETER ::= 56
  CORNER_RADIUS ::= DIAMETER / 2
  display.add
    RoundedCornerWindow 68 4 DIAMETER DIAMETER
      context.transform
      CORNER_RADIUS
      WHITE
  // Icon is added after the white dot so it is in a higher layer.
  icon_texture :=
    display.icon black_context 72 48 icons.WEATHER_CLOUDY

  // Temperature is black on white.
  display.filled_rectangle context 0 0 64 32
  temperature_context :=
    black_context.with --alignment=TEXT_TEXTURE_ALIGN_CENTER
  temperature_texture :=
    display.text temperature_context 32 23 "??°C"

  // Time is white on the black background, aligned by the
  // center so it looks right relative to the temperature
  // without having to zero-pad the hours.
  time_context := context.with --alignment=TEXT_TEXTURE_ALIGN_CENTER
  time_texture := display.text time_context 32 53 "??:??"

  // The scene is built, now start some tasks that will update
  // the display.

  task:: clock_task time_texture
  task:: weather_task icon_texture temperature_texture

weather_task weather_icon/IconTexture temperature_texture/TextTexture:
  pubsub.subscribe "weather" --auto_acknowledge: | message |
    map := json.decode message.payload
    code := map["wmo_4501"]
    temp := map["temperature_c"]
    display_mutex.do:
      weather_icon.icon = WMO_4501_ICONS[code]
      temperature_texture.text = "$(%.1f temp)°C"
      display.draw

clock_task time_texture:
  while true:
    now := (Time.now).local
    display_mutex.do:
      // H:MM or HH:MM depending on time of day.
      time_texture.text = "$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