DIY Projects

T-Watch. Menu, pages, navigation between screens with TFT_eSPI

ttgo t-wacth tft-espi menu page screen navigation exampe esp32 arduino code

In this tutorial we will learn how to create menus (and scrolling menus) and navigate between the pages of an application using the TFT_eSPI library. This tutorial is primarily intended for ESP32 TTGO T-Watch watches from LilyGo. With a few small adaptations of the code, you can use it for other Arduino projects with a classic TFT screen.


The TFT_eSPI library is very easy to use for creating menus and interface elements.


It is entirely possible to use the TFT_eSPI and LVGL library together in an application project for T-Watch.

Activate the TFT_eSPI library

By default, the TFT_eSPI library is integrated and activated by the LilyGoWatch library. This is also the case regardless of the model of T-Watch you use. So there is nothing more to do.

At the programming level, it is easier to declare a pointer to the object at the start of the project. This way you won’t need to re-declare the class each time to access the functions of the TFT_eSPI library

TTGOClass *ttgo; // Pointer to the API of the watch devices
TFT_eSPI *tft;   // Pointer to the functions of the TFT_eSPI library

In the setup, we create the ttgo and tft objects that we place in the pointers created at the start of the project

void setup(){  
  ttgo = TTGOClass::getWatch();
  tft = ttgo->tft;

Now you can access the screen management functions anywhere in the project.

How to port code from an Arduino project with the TFT_eSPI library?

It is quite possible to port code developed for an Arduino and adapt it to the T-Watch.

It will simply be necessary to replace all the calls to the functions which are made with the operator point (.) By the operator arrow (->).

Prepare the XBM icons (images)

First of all, it is necessary to convert the images to XBM format of each icon, for each resolution. Indeed, the TFT_eSPI library does not offer any function to resize the images.

You will find on the project’s GitHub repository several icons in PNG and XBM formats that you can use in your projects. They are available in several resolutions.

Icon File name 80×80 32×32 30×30
Menu main
WiFi wifi
Bluetooth bluetooth
Accelerometer accel
GPS gps
Settings settings
Clock clock
Music music
Tools tools
Exit exit

It is very easy to make your own monochrome icons by converting an image to XBM format. Read this tutorial which explains in detail how to do it.

Organization of the pages and menus of the T-Watch application

Obviously, we are not going to develop the entire application in this tutorial. We will focus on the menus and navigation between the pages (screens) of the application here.


Create a full page menu

The first menu that we are going to create is a menu that is displayed in full screen. It consists of 4 buttons. Here, to exit the menu, an icon is reserved for this use. We could also use a button or the accelerometer (shake to go back for example).

To create a full-page menu, all you have to do is draw the 4 buttons in their position using the drawXBitmap() method whose parameters are as follows:

We stay in a while loop until the exitMenu variable changes to False .

By testing the position of the finger on the screen returned by the getTouch() function , we can determine which button has been touched and return the corresponding index.

int toolPageMenu(){
  tft->fillScreen(TFT_BLACK);  // Fill background in black 
  tft->drawXBitmap(25, 25, wifi, icon_width, icon_height, TFT_WHITE, TFT_BLACK);
  tft->drawXBitmap(130, 25, bluetooth, icon_width, icon_height, TFT_WHITE, TFT_BLACK);
  tft->drawXBitmap(25, 130, gps, icon_width, icon_height, TFT_WHITE, TFT_BLACK);
  tft->drawXBitmap(130, 130, menuexit, icon_width, icon_height, TFT_WHITE, TFT_BLACK);

  int16_t x, y, tx, ty;

  int mSelect = 0; 
  // Keep the TFT_eSPI page open until the user touches an option
  boolean exitMenu = false;   
  while (!exitMenu) {
    // Wait for the user to touch the screen
    if (ttgo->getTouch(x, y)) { 
      while (ttgo->getTouch(tx, ty)) {} 
      Serial.printf("toolPageMenu clicked %u %u\n", x, y);
      int index = 0;
      if ( x < w / 2 && y < h / 2 ) { mSelect = 1; exitMenu = true; } else if ( x > w / 2 && y < h / 2 ) {
        mSelect = 2;
        exitMenu = true;
      } else if ( x < w / 2 && y > h / 2 ) {
        mSelect = 6;
        exitMenu = true;
      } else {
        mSelect = 0;
        exitMenu = true;
  menuOpened = false;

  // Returns the index of the desired page
  return mSelect;

Here is what we get with this function

Scrolling menu with icon on the left (or not)

Unlike the LVGL library, the TFT_eSPI library only “allows” (it’s already a lot!) To draw simple geometric shapes. To create a vertical scrolling menu, the trick is to redraw the menu when you touch the top or bottom of the screen.

The displayScrollingMenu() function allows you to monitor user action on the screen. Here, the menu consists of three cells. The cell labels are retrieved using a pointer. As soon as the user touches a line of the menu, it is rebuilt by modifying the position of the mSelect cursor using the createMenuList() or createMenuListIcon() methods .

int displayScrollingMenu(){
  int16_t x, y, tx, ty;
  int mSelect = menuSelected; // The currently highlighted app
  boolean exitMenu = false;   // used to stay in the menu until user selects app
  refreshMenu(menuSelected); // display vertical scrolling menu 
  while (!exitMenu) {
    if (ttgo->getTouch(x, y)) { // If you have touched something...
      while (ttgo->getTouch(tx, ty)) {} // wait until you stop touching
      //Serial.printf("Menu y %u \n", y);
      //Serial.printf("Height %u \n", tft->height());

      // The user moves to the bottom of the menu

      if (y >= 160 ) { 
        mSelect += 1;
        if (mSelect == appMax) mSelect = 0;
      // User moves to the top of the menu
      if (y <= 80 ) { 
        mSelect -= 1;
        if (mSelect < 0) mSelect = appMax - 1; refreshMenu(mSelect); } // The user has selected the central option, we exit the menu and open the corresponding page if (y > 80 && y <160 ) { exitMenu = true; } } delay(5); } //Return with mSelect containing the desired mode ttgo->tft->fillScreen(TFT_BLACK);
  menuOpened = false;

  return mSelect;

The createMenuListIcon() method creates a vertical scrolling menu. Each option is preceded by the application icon. The icon is nothing more than an XBM image drawn with the drawXBitmap() method as before.

void createMenuListIcon(int mSel) {
  menuOpened = true;
  int curSel = 0;
  // Create blue background with a black line in the middle of the screen 
  ttgo->tft->fillRect(0, 80, 240, 80, TFT_BLACK);

  // First line of the scrolling menu 
  if (mSel == 0) curSel = appMax - 1;
  else curSel = mSel - 1;
  ttgo->tft->drawXBitmap(10, 20, getMenuIcon(curSel), 32, 32, TFT_WHITE, TFT_BLUE);
  ttgo->tft->setCursor(50, 30);

  // Central line (selected option) 
  ttgo->tft->drawXBitmap(10, 100, getMenuIcon(mSel), 32, 32, TFT_WHITE, TFT_BLACK);
  ttgo->tft->setCursor(50, 110);  // 40

  // Last line 
  if (mSel == appMax - 1) curSel = 0;
  else curSel = mSel + 1;

  ttgo->tft->drawXBitmap(10, 180, getMenuIcon(curSel), 32, 32, TFT_WHITE, TFT_BLUE);
  ttgo->tft->setCursor(50, 190);

How to navigate between pages, pass and return parameters?

Here too, the TFT_eSPI library does not offer any advanced solution for managing the pages of the ESP32 application. In fact when we go from one screen to another on the watch, we only rebuild the screen from scratch each time.

Each page (screen) is a simple C++ function. When you want to pass parameters to a screen, you just need to declare variables or use global variables (beware of bugs!).

For example here, we pass the index of the displayed page which will allow its title to be retrieved. To create the new screen, we start by erasing it with the desired background color (here in black).

Read this article which details the basic elements of the TFT_eSPI library

void createPage(int pageindex){
  tft->fillScreen(TFT_BLACK);  // Fill the screen background in black
  tft->drawString(appName[pageindex], menuSize + 2 * menuPadding, menuPadding, 1);
  tft->drawFastHLine(0, menuSize + menuPadding, w, TFT_DARKGREY);

Since a screen is a simple function, we can simply return parameters to the return command. This is what we do for example to select a menu option.

int displayScrollingMenu(){
  return mSelect;

Complete project code

The full code for the project is available on GitHub only.

The source code has been tested on the T-Watch Touch 2019

As well as on the T-Watch 2020

T-Watch and expansion boards


To conclude this article, here is a small demonstration video. Even if the creation of the menus is a bit DIY with TFT_eSPI, the rendering is efficient and quick to do.



2020/11/25 Publication of the article


Click to rate this post!
[Total: 1 Average: 5]
Exit mobile version