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

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

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.

Read Also
T-Watch. Mix the LVGL and TFT_eSPI libraries in the same ESP32 project

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 menu
WiFi wifi wifi
Bluetooth bluetooth bluetooth
Accelerometer accel accel
GPS gps gps
Settings settings settings
Clock clock clock 1
Music music music
Tools 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.

Read Also
T-Watch. Display XBM (TFT_eSPI) and C++ (LVGL) images. ESP32, Arduino compatible

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.

  • The top of the main page is occupied by a status bar. The left icon provides access to the scrolling menu to access a page of the application. The menu on the right is identical. Each option is preceded by a 30×30 pixel monochrome icon.
  • In the center of the page, there is an icon that allows access to the full page menu
  • Each page of the app simply displays the title in the status bar


ttgo t-watch page navigation menu demo tft_espi

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:

  • Origin x, y of the image
  • Image point array in XBM format (const unsigned char)
  • The width and height of the image
  • Icon color
  • Background color. In general, it is identical to the bottom of the screen. It is enough to invert the colors in the event of erroneous rendering.

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

ttgo t-watch tft_espi menu full screen

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.

logo github

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]

Thanks for your reading

Did you like this project ? Don't miss any more projects by subscribing to our weekly newsletter!

Are you having a problem with this topic?

Maybe someone has already found the solution, visit the forum before asking your question
1 Comment
  1. Is there any way to update some text every second inside the clock page?
    maybe an interrupt, or an non-ending while loop?
    or any other method?

Leave a Reply

Read more
DIY Projects
DIY Projects
%d bloggers like this: