By dave | September 12, 2019

Rendering with TcMenu to TFT, LCD and OLED

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

Most renderers are based on the same base class, and have common functionality.

See the reference documentation for base renderer

Presenting a dialog to the user

Type: BaseDialog in BaseDialog.h

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

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

    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

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

Overridable graphical rendering - TcMenu 2.0

In TcMenu 2.0 we have added the ability to override the way that renderers draw in several ways. Firstly, lets start with a discussion of what’s possible.

  • Icons can be used in place of actionable items, they are setup on a per menu-item basis.
  • The colors, padding, orientation, and font for a menu item are now customisable at three levels: Default, Sub-menu, Item.
  • The grid position of an item can be configured, in that each row can have a grid of columns.

This flexible configuration based rendering is made possible by a display factory. The display factory stores all the grids, icons and drawing properties in high performance btree lists that are optimised for reading. We’ll go through each below but first there’s a couple of types you will come across often that we’ll discuss here.

Along with this overview, please see the reference documentation for more complete details, they will not be repeated here:

struct Coord - coordinates

Stores an X and Y coordinate in a 32bit bit-packed struct. It supports copy construction and assignment. You can directly access x and y.

Coord myCoord(10, 4);     // create a coord with x=10 and y=4
Coord copyCoord(myCoord); // copy myCoord into copyCoord
int x = myCoord.x;
int y = myCoord.y;

struct MenuPadding - item padding

Works similar to padding in HTML, You can directly access the top, bottom, left, right values.

MenuPadding equalPadding(4);                        // all sides have padding of 4
MenuPadding equalPadding(top, right, bottom, left); // per side padding.

Drawing in grid positions

We can draw a menu item at any grid and row position, just be careful that the row definition comes before the natural menu row, otherwise the row will already be taken and can’t be overridden. There are three ways to draw with this type of renderer:

  • Fully on the fly, don’t configure any grid positions, every item will be on a new row by default.
  • Partial overriding of the grid positions
  • Full overriding of grid positions for every menu item in a given submenu.

Let’s say we want menuBtn1 and menuBtn2 in a single row, here’s how we do it, further let’s say that we want them to justify center without the value being presented:

// We define grid positions for menuBtn1 and menuBtn2, with a grid width of 2.
factory.addGridPosition(&menuBtn1, GridPosition(GridPosition:: DRAW_TEXTUAL_ITEM,
                                                GridPosition::JUSTIFY_CENTER_NO_VALUE, 2, 1, 4, 45));
factory.addGridPosition(&menuBtn2, GridPosition(GridPosition:: DRAW_TEXTUAL_ITEM,
                                                GridPosition::JUSTIFY_CENTER_NO_VALUE, 2, 2, 4, 45));

Adding icons for actionable items (requires V2.0)

In order to be able to render as an icon we first need to tell our display properties about the icon, we do this by registering an icon with an association to a particular menu item. Let’s say for example that we had a sub-menu called menuSettings that we wanted to draw as an icon. First we would add the icon to the cache:

// step 1, get the graphics factory
auto & factory = renderer.getGraphicsPropertiesFactory();
// step 2, add an icon to the image cache
Coord iconSize(32, 32);
factory.addImageToCache(DrawableIcon(menuSettings.getId(), iconSize, DrawableIcon::ICON_XBITMAP, 
                        settingsIconData, optionalSelectedIcon));
// step 3, tell the renderer that the settings icon should be in a grid of three, column 1, 45 high, drawn as icon 
factory.addGridPosition(&menuSettings, GridPosition(GridPosition::DRAW_AS_ICON_ONLY,
                                GridPosition::JUSTIFY_CENTER_NO_VALUE, 3, 1, 4, 45));

Changing the drawing parameters (requires V2.0)

You can also change the font, color, padding, size and default justification at three levels:

  • Default settings when no others match
  • SubMenu level, when there is no match at item level
  • Item level, where there is a match on menu item

To change the defaults use setDrawingPropertiesDefault on the factory, providing the component type and the drawing settings.

To change the properties for all in a submenu use setDrawingPropertiesForItem on the factory, providing the component type and drawing settings.

To change the properties for a single item use setDrawingPropertiesAllInSub on the factory, providing the component type and drawing settings.

Here is an example that provides an override for a specific item, it is overridden to display as a title, provides a custom palette, padding, spacing, font and height:

color_t specialPalette[] { ILI9341_WHITE, ILI9341_RED, ILI9341_BLACK, ILI9341_BLUE};
factory.setDrawingPropertiesForItem(ItemDisplayProperties::COMPTYPE_TITLE, menuStatus.getId(), specialPalette,
                                    MenuPadding(4), nullptr, 4, 10, 30,
                                    GridPosition::JUSTIFY_CENTER_WITH_VALUE );

Back to tcMenu main page

Other pages within this category

comments powered by Disqus

We use cookies to analyse traffic and to personalise content. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Please see our privacy policy should you need more information or wish to adjust your settings.