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 OLED’s and full colour TFT displays using the Adafruit_GFX library.

In order to achieve such a wide range of displays the renderer describes things in terms of menu items, active positions, editors and dialogs. This allows each renderer to convert these concepts into something that works for its needs.

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.

Common functions on most renderers

  • Type: BaseMenuRenderer in BaseRenderers.h
  • Type: BaseGraphicalRenderer in graphics/BaseGraphicalRenderer.h
  • Type: GraphicsDeviceRenderer in graphics/GraphicsDeviceRenderer.h

Most renderers are based on the same base classes, and have common functionality. Other than for Uno, all other renderers are based on at least BaseGraphicalRenderer.

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.

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.

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);

// 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):


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:

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 it’s 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.