How to create a user menu structure

Here's an example of an icon:

const uint16_t l100_d_mki[700] = {

  0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xce79, 0x31a6, 0x18e3, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
  0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x8c51, 0x0000, 0x39e7, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,

// yadayadayada...

  0xffff, 0xb596, 0x4228, 0x1082, 0x0000, 0x2124, 0x4228, 0xef5d, 0xffff, 0xffff, 0x4a49, 0x0000, 0x2124, 0x5acb, 0x0861, 0x0000, 0x9492, 0xffff, 0xffff, 0x4a49, 0x0000, 0x2124, 0x5acb, 0x0861, 0x0000, 0x9492, 0xffff, 0xffff,
  0xffff, 0x9492, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xdefb, 0xffff, 0xffff, 0xf79e, 0x52aa, 0x0000, 0x0000, 0x0861, 0x8c51, 0xffff, 0xffff, 0xffff, 0xf79e, 0x52aa, 0x0000, 0x0000, 0x0861, 0x8c51, 0xffff, 0xffff, 0xffff
};

The icons are all in 16-bit color. I have found that only anti-aliased icons in 16-bit color give me the kind of display quality that I want. And with the ESP32's comparatively vast memory, there aren't many constraints to this approach.

BulldogLowell:
Depending on how your data is structured, using a string for the values can be easier to work with and you may be able to overload a displayfunction depending on wether that particular spot on the display is a reference to a float or to an int

Let's see... the "big numbers" on the screen will all have between one and four digits. Things like engine oil temperature will have a range between -20°C and +170°C (no plus sign for positive temperatures) and will be gathered from the sensors as integers (tapping into the car's own oil temp sensor, for example, which is a simple thermistor, via analogRead).

Fuel consumption will be given either as miles per gallon or liters per 100 km. Both will probably be displayed as floats with one decimal, and that decimal will be rounded to the nearest 0.5.

I am using a custom made text printing function which handles any and all text on the screen. It allows some basic text formatting functionalities, like align (left, center, right), margins, letter spacing and defining the width of empty spaces:

#ifndef FONT_LIBRARY_H
#define FONT_LIBRARY_H

#include <stdint.h>
#include <string.h>

#include "fonts_def.h"

// Main text printing function
void printText(const char *myText, const fontPointer* font, byte align, byte margin, int y_start, int letterSpacing, int emptySpaceWidth, uint16_t blank_color) {

  int textLength = strlen(myText);

  const uint16_t *font_data = font->fontData;
  const fontParam* font_param = font->fontParamData;

  int characterTableLength = font->charsCount;
  int startX = 0;
  int y_height = font->fontHeight;

  /*  y parameter is calculated from bottom pixel row.
      This makes screen layouts with different fonts
      much easier.
  */

  int startY = y_start - y_height;
  uint32_t startOffset;
  uint32_t endOffset;
  bool matchFound = false;
  int xWidthCumul = 0;
  uint32_t charParams[textLength][2];

  for (int i = 0; i < textLength; i++) {
    startOffset = 0;
    endOffset = 0;
    matchFound = false;

    for (int j = 0; j < characterTableLength; j++) {
      //Looking up matching character in character table
      char currentChar = font_param[j].character[0];

      int x_width = font_param[j].characterWidth;
      endOffset += font_param[j].dataSize;

      if (myText[i] == currentChar) {
        // if match found...
        matchFound = true;

        // Calculating start offset to give us starting block of character in font data pane
        startOffset = endOffset - font_param[j].dataSize;

        /*  calculating x-axis "pixel length" of text array and
            putting data for each character in a character parameters array
            We need this in order to calculate the x starting position of a text array
        */
        charParams[i][0] = startOffset;
        charParams[i][1] = x_width;

        break;
      }
      if (!matchFound) {

        charParams[i][0] = -1;
        charParams[i][1] = emptySpaceWidth;
      }
    }
    xWidthCumul += (charParams[i][1] + letterSpacing);
  }

  // Subtracting letter spacing one time for last character of our text array
  xWidthCumul -= letterSpacing;

  // Calculating X starting point based on specified alignment
  // align left
  if (align == 0) startX = margin;
  // align center
  if (align == 1) startX = (int) ((128 - xWidthCumul) / 2);
  // align right
  if (align == 2) startX = 128 - xWidthCumul - margin;

  uint32_t startOffsetNow;
  int k = 0;

  // Draw blank rectangle in specified color first to eliminate remnants of previous text
  tft.fillRect(startX, startY, (startX + xWidthCumul), y_height, blank_color);

  while (k < textLength) {

    // Go through the charParams array one-by-one and print out characters
    startOffsetNow = charParams[k][0];
    if (charParams[k][0] != -1) tft.pushImage(startX, startY, charParams[k][1], y_height, &font_data[startOffsetNow]);

    // Move forward to next character's position
    startX += (charParams[k][1] + letterSpacing);
    k++;

    // Break if no data present
    if (startOffsetNow == NULL) break;
  }
}

// Float printing
void printFloat(float floatValue, const fontPointer* ffont, byte falign, byte fmargin, int fy_start, int fletterSpacing, int femptySpaceWidth, byte fplusminus, uint16_t fblank_color)
{

  char floatSizeBuf[6];
  String floatTmpStr = dtostrf(floatValue, 6, 2, floatSizeBuf);
  floatTmpStr.trim();

  int floatNumLen = floatTmpStr.length();
  char floatBuffer[floatNumLen];

  if (fplusminus) snprintf(floatBuffer, sizeof(floatBuffer), "%+f", floatValue);
  else snprintf(floatBuffer, sizeof(floatBuffer), "%f", floatValue);

  if (fplusminus) snprintf(floatBuffer, sizeof(floatBuffer), "%+f", floatValue);
  else snprintf(floatBuffer, sizeof(floatBuffer), "%f", floatValue);

  printText(floatBuffer, ffont, falign, fmargin, fy_start, fletterSpacing, femptySpaceWidth, fblank_color);
}

// Integer printing
void printInteger(int intValue, const fontPointer* ifont, byte ialign, byte imargin, int iy_start, int iletterSpacing, int iemptySpaceWidth, byte iplusminus, uint16_t iblank_color)
{
  char intSizeBuf[6];
  String intTmpStr = dtostrf(intValue, 6, 1, intSizeBuf);
  intTmpStr.trim();

  int intNumLen = intTmpStr.length();
  char intBuffer[intNumLen];

  if (iplusminus) sprintf(intBuffer, "%+5d", intValue);
  else sprintf(intBuffer, "%5d", intValue);

  printText(intBuffer, ifont, ialign, imargin, iy_start, iletterSpacing, iemptySpaceWidth, iblank_color);
}

#endif FONT_LIBRARY_H

Right now, these text functions turn floats and ints into chars which are then printed out via the main printText() function. But to make sure that my struct can handle either type, maybe it's best to just turn both floats and ints into char arrays, and then let my menu composing function handle the rest. There will essentially be a function that will put all the visual elements on the screen, and that function will tap into whatever struct array I specify for the contents of a particular menu screen.