Skip to content

Adding New Screens

Relevant source files

This guide provides a step-by-step process for adding new application screens to the PiBot CNC Pendant firmware. It covers the technical requirements, code structure, and integration points needed to create a functional screen that integrates with the existing UI framework.

For information about screen lifecycle management and the base classes, see 7.2. Screen Lifecycle Management For details on reusable components that can be integrated into screens, see 8. Reusable UI Components


The screen system is built on a hierarchical architecture where each screen:

  1. Is identified by an ESP3DScreenType enum value main/display/cnc/fluidnc/screens/esp3d_screen_type.h#26-61
  2. Implements a create() function within a dedicated namespace main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#25-115
  3. Is routed through a centralized createScreen() dispatcher main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#122-232
  4. Extends GenericScreen for common functionality like orientation and styling main/display/screens/generic_screen.h#26-54
  5. Registers itself with UIManager for lifecycle management main/display/screens/generic_screen.cpp#160-165

Screen Registration Layer Architecture

Screen Registration Layer

Screen Dispatcher

Screen Implementation

UIManager Integration

ui_manager.registerComponent()

UIManager component registry

LV_EVENT_DELETE callback

yourScreen namespace

yourScreen::create() function

GenericScreen instance

lv_obj_t* container

UI elements (labels, buttons, etc)

createScreen() (esp3d_screen_type.cpp:122-232)

switch(screen_type) case statements

ESP3DScreenType enum (esp3d_screen_type.h:26-61)

screen_type_names array (esp3d_screen_type.h:81-103)

Sources:main/display/cnc/fluidnc/screens/esp3d_screen_type.h#26-103main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#122-232main/display/screens/generic_screen.h#26-54


The following diagram shows the complete flow from screen creation request to active screen, using actual function and variable names from the codebase:

Screen Initialization Sequence

"LVGL Library""ui_manager""GenericScreen::GenericScreen()""yourScreen::create()""createScreen(type)"Caller"LVGL Library""ui_manager""GenericScreen::GenericScreen()""yourScreen::create()""createScreen(type)"CallerDefine button_configs[]Create UI elements on containerSubscribe to value updatescreateScreen(ESP3DScreenType::your_screen)case routes to yourScreen::create()new GenericScreen(parent | screen_type | configs | user_data)Check transition_in_progress_applyScreenBackgroundStyle(screen_)lv_obj_create(parent)lv_screen_load(screen_)Create VirtualButtonsComponentregisterComponent(host_screen | type | this)Returns instance pointergetContainer()Returns container_lv_label_create(container)esp3dTftValues.subscribe(index | callback)lv_obj_add_event_cb(screen | onScreenDestroy | LV_EVENT_DELETE)

Sources:main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#122-232main/display/screens/generic_screen.cpp#35-177main/display/screens/generic_screen.h#52-54


Add your screen type to the ESP3DScreenType enum in main/display/cnc/fluidnc/screens/esp3d_screen_type.h#26-61 If ESP3D_LOG is enabled, also add the string name to the screen_type_names array at main/display/cnc/fluidnc/screens/esp3d_screen_type.h#81-103

2. Add Forward Declaration and Dispatcher Case

Section titled “2. Add Forward Declaration and Dispatcher Case”

In main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#25-115 add a forward declaration for your screen’s create() function within its namespace. Then, add a case statement to the createScreen() function main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#125-231

The create() function is the entry point for screen instantiation. It typically follows this pattern:

  1. Check for existing instance: Prevent duplicate creation using a static instance pointer (e.g., files_screen_obj_instance) main/display/cnc/fluidnc/screens/files_screen.cpp#105
  2. Define Button Configuration: Use the virtual_button_conf_t structure to define the bottom virtual buttons.
  3. Instantiate GenericScreen: This handles base LVGL screen creation, background styling, and component registration main/display/screens/generic_screen.cpp#35-177
  4. Get Container: Access the rotatable container via getContainer()main/display/screens/generic_screen.h#63 to place your UI elements.
  5. Create UI Elements: Use standard LVGL functions (lv_label_create, etc.) on the container.
  6. Subscribe to Values: If the screen needs real-time data (like position), subscribe to esp3dTftValues using onFileEntryUpdate style callbacks main/display/cnc/fluidnc/screens/files_screen.cpp#149-150
  7. Setup Cleanup: Add an LV_EVENT_DELETE callback to the screen object to handle destruction. Use ESP3D_TRANSITION_START or ESP3D_CLEANUP_TIMER_BODY macros if using the standard transition pattern main/display/esp3d_screen_transition.h#73-130

Sources:main/display/screens/generic_screen.cpp#35-177main/display/cnc/fluidnc/screens/esp3d_screen_type.cpp#122-232main/display/esp3d_screen_transition.h#73-130main/display/cnc/fluidnc/screens/files_screen.cpp#105


Screens often utilize specialized components. The GenericScreen constructor automatically registers itself as a ComponentType::generic_screenmain/display/screens/generic_screen.cpp#160-165

ComponentClass / TypePurpose
Virtual ButtonsVirtualButtonsComponentManages the 3 bottom navigation/action buttons main/display/screens/generic_screen.h#31
List MenusListMenuScreenBase class for scrollable lists like file browsing main/display/cnc/fluidnc/screens/files_screen.cpp#42
Status IndicatorsConnectionStatusComponentShows network/communication status main/display/cnc/fluidnc/screens/esp3d_screen_type.h#72

Buttons are configured during GenericScreen initialization main/display/screens/generic_screen.cpp#147-157

Sources:main/display/screens/generic_screen.cpp#147-157main/display/screens/generic_screen.h#31-73


Every screen must implement a cleanup mechanism, typically triggered by LV_EVENT_DELETE.

Screen Cleanup Process

LV_EVENT_DELETE triggered

Unsubscribe from esp3dTftValues

ui_manager.unregisterComponent()

screen_instance->prepareForDestruction()

delete screen_instance

Set static pointers to nullptr

The prepareForDestruction() method in GenericScreen is responsible for unregistering the component from the UIManager and cleaning up virtual buttons main/display/screens/generic_screen.cpp#252-272 Specific screens like files_screen extend this to stop timers (e.g., watchdog_timer) and clear buffers main/display/cnc/fluidnc/screens/files_screen.cpp#139-142

Sources:main/display/screens/generic_screen.cpp#252-272main/display/screens/generic_screen.h#58main/display/cnc/fluidnc/screens/files_screen.cpp#139-142


Text should never be hardcoded. Use the ESP3DTranslationService and the ESP3DLabel enum.

  1. Define Label: Add a label definition in the .inc definition files (e.g., esp3d_translations_defs.inc).
  2. Generate Enum: The ESP3D_TR_DEF macro generates the enum value in ESP3DLabelmain/display/esp3d_translations_list.h#37-47
  3. Implement Fallback: Ensure the string is added to the English fallback table kEnglishTextsmain/display/esp3d_translations_init.cpp#33-37
  4. Retrieve Text: In your screen, retrieve the text using esp3d_translations_english(ESP3DLabel::label_name)main/display/esp3d_translations_init.cpp#45-51

Sources:main/display/esp3d_translations_init.cpp#33-51main/display/esp3d_translations_list.h#32-47tools/language_packs/template.lng#11-47