Using LVGL Library¶
LVGL gives EdgeTX Lua scripts access to the same style of UI controls used by the firmware on color-screen radios. Instead of redrawing every element manually with the lcd API, a script can build a UI from objects such as labels, boxes, pages, buttons, toggles, sliders, and dialogs.
The practical result is:
- less manual redraw logic
- better support for state-driven UI updates
- a closer match to native EdgeTX screens
For the detailed constructor pages, see Display LVGL. For LVGL-specific constants, see LVGL Constants.
Limitations¶
- Direct drawing through the
lcdAPI is not available inside scripts that use LVGL to create the UI. - LVGL is only available on radios and builds that support the LVGL UI layer.
Activating the LVGL API¶
A script must return useLvgl = true in the table it exports:
When LVGL is available, EdgeTX exposes a global lvgl object. This is the main entry point for building and updating the UI.
Scripts should still check that lvgl is not nil before using it. If it is nil, the script is usually:
- running on an earlier EdgeTX version
- running on a target without LVGL support
- missing
useLvgl = true
In that case, the script should show a clear error or fall back to a non-LVGL implementation.
Usage Patterns¶
One-Time scripts¶
For One-Time scripts, init() should still prepare the initial script state, but it should also create the LVGL UI tree. The run() function can still handle events, but most interaction should be delegated to the LVGL objects themselves.
When used this way, the UI updates automatically as state changes instead of being redrawn by hand every cycle.
Widget scripts¶
Widget scripts should usually create or rebuild their LVGL UI in update(), using the widget zone size to decide layout. The background() and refresh() functions should focus on state updates that are not already managed by the LVGL objects.
Controls and focus¶
Interactive controls such as buttons, toggles, and text editors show a focus outline when selected. Leave enough space between nearby controls so that outline remains visible and does not collide with surrounding content.
Common Call Pattern¶
Most LVGL constructors follow this shape:
The optional parent object determines where the new object is attached. If no parent is passed, the object is created in the top-level script window.
Most functions return an LVGL object, so you can keep a reference and update it later or call object methods on it.
LVGL functions can also be used with Lua OO syntax:
Shared Settings¶
Many LVGL objects share a few common settings:
| Name | Type | Purpose |
|---|---|---|
x, y | number | Position relative to the top-left of the parent |
w, h | number | Width and height |
color | color or function | Primary color for the object |
pos | function | Dynamic position callback returning x, y |
size | function | Dynamic size callback returning w, h |
visible | function | Dynamic visibility callback |
floating | boolean | Keep an object fixed inside a scrollable container |
Functions used for settings are called repeatedly by the firmware. When the function returns a new value, the object updates automatically.
Adapting To Different Screens¶
EdgeTX supports several color-screen sizes and orientations, so fixed coordinates do not always scale cleanly. A few LVGL helpers make this easier:
- flex layouts can reduce manual positioning
lvgl.PAGE_BODY_HEIGHTgives the body height of a page containerlvgl.UI_ELEMENT_HEIGHTgives the default control heightlvgl.LCD_SCALEhelps scale fixed values for the current screenlvgl.PERCENT_SIZE + Ncan be used for percentage-based sizes and positions
Use percentage sizing carefully: the parent container must already have a defined size, and not every object type supports percentage-based values equally well.
Examples¶
One-Time script¶
local exitTool = false
local function close()
lvgl.confirm({
title = "Exit",
message = "Really exit?",
confirm = function()
exitTool = true
end,
})
end
local function init()
if lvgl == nil then
return
end
lvgl.clear()
local pg = lvgl.page({
title = "Test Tool",
subtitle = "Page 1",
back = close,
})
pg:label({ x = 70, y = 16, color = BLACK, font = DBLSIZE, text = "Test Page" })
pg:button({ x = 200, y = 150, text = "CLOSE", press = close })
end
local function run()
if lvgl == nil then
lcd.drawText(0, 0, "LVGL support required", COLOR_THEME_WARNING)
end
if exitTool then
return 2
end
return 0
end
return { init = init, run = run, useLvgl = true }
Widget script¶
local options = {
{ "Color", COLOR, COLOR_THEME_SECONDARY1 },
{ "Shadow", BOOL, 0 },
}
local function create(zone, options)
return { zone = zone, options = options, counter = 0 }
end
local function update(wgt, options)
wgt.options = options
lvgl.clear()
if wgt.options.Shadow ~= 0 then
lvgl.label({
x = wgt.zone.x + 1,
y = wgt.zone.y + 1,
color = BLACK,
font = DBLSIZE,
text = function()
return wgt.counter
end,
})
end
lvgl.label({
x = wgt.zone.x,
y = wgt.zone.y,
color = wgt.options.Color,
font = DBLSIZE,
text = function()
return wgt.counter
end,
})
end
local function background(wgt)
wgt.counter = wgt.counter + 1
end
local function refresh(wgt)
wgt.counter = wgt.counter + 1
end
return {
name = "CounterLVGL",
options = options,
create = create,
update = update,
refresh = refresh,
background = background,
useLvgl = true,
}