By dave | September 12, 2019

Rendering menus to TFT, LCD and OLED using tcMenu

TcMenu supports a wide range of rendering devices, from HD44780 based units using our LiquidCrystal fork through to mono OLEDs and full colour TFT displays using Adafruit_GFX and TFT_eSPI library. Over to the left you see an example of rendering to OLED device with title widgets.

You can also easily take over the display to draw your own screen at any time. This is such a large subject, it deserves a page of its own.

How the menu application looks on the device

How a menu will look on the device will largely depend on which display is used. However, there are a few common features of all displays. They can generally all have a title, and the title can nearly always contain title widgets. Title widgets provide a way to present the graphical state of something within the system in a small icon, the most common would be the signal strength indicator, or a connection status icon. An example showing this is presented below:

application with title containing widgets and item rows

The basic layout of a menu application

Common functions on most renderers

In order to allow for a wide range of displays, we provide multiple extension points within the rendering class hierarchy, and keep as much functionality as possible in the core.

Class diagram showing rendering class, doxygen has a more accessible version

Class Diagram showing nearly all rendering classes

From the above diagram we can see that most graphical and LCD displays (except Uno cases) extend from at least the BaseGraphicalRenderer. And in fact all the true graphical displays extend from GraphicsDeviceRenderer and then have a custom drawable. The benefit of GraphicsDeviceRenderer is that does all the complex logic, and the drawable just has to implement the drawing glue code that calls into the library.

Renderer integration into the sketch

In all cases the display plugins will create a global variable called renderer in your sketch. It will be at least of type MenuRenderer meaning that you can rely on an absolute base set of functionality. In most cases it will be of BaseGraphicalRenderer or GraphicsDeviceRenderer so you will be able to rely on nearly all functions being available.

Usually, the renderer is initialised during menu setup and this starts a task manager timer task that calls the display back frequently to check if anything needs drawing. It is this task that keeps the screen up-to-date.

Presenting a dialog to the user

  • Type: BaseDialog in BaseDialog.h - for Uno low memory renderers
  • Type: MenuBasedDialog in BaseDialog.h - for all other renderers

It is also possible to present a simple dialog for either information, or Yes / No question onto almost all supported displays. Along with this you can optionally decide to let the dialog show on any remote connections, taking over the top of the display similar to an Alert Box.

Doing something on dialog completion

Firstly, if we are interested in the outcome of the dialog, we must give the dialog a function to call us back upon when complete:

// this method is called when the dialog is dismissed.
void onDialogFinished(ButtonType btnPressed, void* /*userdata*/) {        
    if(btnPressed != BTNTYPE_OK) {
        // do something if OK was pressed.

Creating a regular dialog

Next we get hold of the dialog and initialise it. Here we choose a dialog with OK and CANCEL as options:

BaseDialog* dlg = renderer.getDialog();
dlg->setButtons(BTNTYPE_OK, BTNTYPE_CANCEL, 1);

If you are using a menu based dialog (nearly all except Uno renderers), you can get hold of it directly without casting using:

withMenuDialogIfAvailable([](MenuBasedDialog* dlg) {
    // code that relies on the menu based dialog.

Now we call the show method to make the dialog take over the screen

// for all cases and versions, you call show providing a character arry in progmem.
dlg->show(pgmHeaderText, remoteAllowed, onDialogFinished); // true = shows on remote sessions.
  • pgmHeaderText is the header text for the dialog
  • remoteAllowed true if should appear on all remote controls
  • onDialogFinished an optional callback when a button is pressed to dismiss the dialog

There are also newer versions of show that have additional features:

// additionally for newer versions you can provide header text that is located in RAM
void showRam(const char* headerRam, bool allowRemote, CompletedHandlerFn completedHandler = NULL);

Lastly we can copy some text into the second line of the dialog (not program memory):


Showing a dialog as a controller.

Controllers allow you far more control over a dialog, you can not only add additional menu items and buttons to the dialog, but you can also be informed when dialog buttons are pressed before the dialog ends, and be informed when it is initialising.

// and you can even use a controller, to provide additional functionality when buttons are
// pressed or change the text for buttons
void showController(bool allowRemote, BaseDialogController* controller);

Where the BaseDialogController interface is implemented as follows:

class MyController : public BaseDialogController {
    void initialiseAndGetHeader(BaseDialog* dialog, char* buffer, size_t bufferSize) override {
        // Here we should initialise the controller and fill in the buffer provided with the title

    void dialogDismissed(ButtonType buttonType) override {
        // this is called when the dialog is dismissed, the button that was clicked is provided.

    bool dialogButtonPressed(int buttonNum) override {
        // this is called when a dialog button is pressed, returning true will continue default processing,
        // returning false prevents additional default processing
    void copyCustomButtonText(int buttonNumber, char* buffer, size_t bufferSize) override {
        // this will be used to get the text for a button, fill in the buffer with the text.

You can add additional menu items of any type to the dialog, you can even add more buttons, additional buttons should be of this type [].

 void insertMenuItem(MenuItem* item);

Lastly we can copy some text into the second line of the dialog (not program memory):


Touch screen support

The easiest way to use touch support, is from tcMenuDesigner where it can be automatically added to appropriate display devices, this just explains how designer adds touch support for those who want more information, or wish to do it manually.

Touch screen support is added in two steps, first you must tell the renderer that you’re going to use a touch screen:

void setHasTouchInterface(bool hasTouch);

Then you must create a touch screen manager and encoder, here we show for resistive touch screen but there are many other options:

using namespace iotouch;
ResistiveTouchInterrogator touchInterrogator(xplus, xminus, yplus, yminus);
MenuTouchScreenManager touchScreen(touchInterrogator, renderer, rotation);

Then in your setup method:

menuMgr.initWithoutInput(renderer, &rootMenuItem());

TitleWidget for presenting state in icon form

For all displays including LiquidCrystal we support the concept of title widgets. Title widgets allow a small icon to be presented in the title area that can have a number of states. A non exhaustive list of examples of this:

  • Wifi signal strength indicator (see the esp8266 example).
  • Current Connection status icon (see many of the examples).
  • Power or battery indicator.

Here’s an example OLED display showing two widgets:

OLED display showing tcMenu with title widgets

Screen shot of menu showing title widgets - upper right

Each TitleWidget has an array of icons that represent the states. Each icon should first be defined:

const uint8_t iconData1[] PROGMEM = { 0, 0, 0 etc };
const uint8_t iconData2[] PROGMEM = { 0, 0, 0 etc };

Following this, we then define the array of icons, the icons are in XBM format (Xbitmap), GIMP can export in this format:

const uint8_t* const iconsData[] PROGMEM = { iconData1, iconData2 };

Lastly we then define a TitleWidget that represents this icon state:

TitleWidget iconsWidget(iconsData, numOfIcons, width, height [, &optionalNextWidget]);

The optionalNextWidget is a pointer to the next widget, if you only have one you don’t provide it.

Now we set the first widget as follows:


To change the state of a widget, simply call its setter method:


Default icon sets

There are default icons for both WiFi strength and connection state included that work for a wide range of displays.

Icons for low resolution displays such as Nokia 5110:

#include "stockIcons/wifiAndConnectionIcons8x7.h"

Icons for higher resolution displays such as TFT and OLED:

#include "stockIcons/wifiAndConnectionIcons16x10.h"

Icons for 5x8 LiquidCrystal / HD44780 displays:

#include "stockIcons/wifiAndConnectionIconsLCD.h"

In each file there are two icon sets defined.

// a set of wifi icons, 0 is not connected, 1..4 are low to high strength
const uint8_t* const iconsWifi[] PROGMEM = { iconWifiNotConnected, iconWifiLowSignal, iconWifiMedSignal, iconWifiStrongSignal, iconWifiBestSignal };

// a boolean not connected (0) or connected (1) icon    
const uint8_t* const iconsConnection[] PROGMEM = { iconDisconnected, iconConnected };

For some sizes (but not all) we also define an icon for ethernet state (wired connection):

const uint8_t* const iconsEthernetConnection[] PROGMEM = { iconEthernetNone, iconEthernetConn };

Back to tcMenu main page

Other pages within this category

comments powered by Disqus

This site uses cookies to analyse traffic, serve ads by Google AdSense (non-personalized in EEA/UK), and to record consent. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Our privacy policy applies to all pages on our site

Should you need further guidance on how to proceed: External link for information about cookie management.

Send a message

Please use the forum for help with UI & libraries.

This message will be securely transmitted to our servers.