LVGL Basics

LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library providing everything you need to create an embedded GUI with easy-to-use graphical elements, beautiful visual effects, and a low memory footprint.

How does it do this?

Overview of LVGL's Data Flow

LVGL Data Flow

Overview of LVGL Data Flow

You create one Display (lv_display) for each physical display panel, create Screen Widgets on them, add Widgets onto those Screens. To handle touch, mouse, keypad, etc., you create an Input Device for each. The Tick Interface tells LVGL what time is it. Timer Handler drives LVGL's timers which, in turn, perform all of LVGL's time-related tasks:

  • periodically refreshes displays,

  • reads input devices,

  • fires events,

  • runs any animations, and

  • runs user-created timers.

Application's Job

After initialization, the application's job is merely to create Widget Trees when they are needed, manage events those Widgets generate (by way of user interaction and other things), and delete them when they are no longer needed. LVGL takes care of the rest.

Major Concepts

Display vs Screen

Before we get into any details about Widgets, let us first clarify the difference between two terms that you will hereafter see frequently:

  • A Display or Display Panel is the physical hardware displaying the pixels.

  • A Display (lv_display) object is an object in RAM that represents a Display meant to be used by LVGL.

  • A Screen is the "root" Widget in the Widget Trees mentioned above, and are "attached to" a particular Display (lv_display).

Default Display

When the first Display (lv_display) object is created, it becomes the Default Display. Many functions related to Screen Widgets use the default display. See Default Display for more information.

Screen Widgets

In this documentation, the term "Screen Widget" is frequently shortened to just "Screen". But it is important to understand that a "Screen" is simply any Widget created without a parent — the "root" of each Widget Tree.

See Screens for more details.

Active Screen

The Active Screen is the screen (and its child Widgets) currently being displayed. See Active Screen for more information.

Widgets

After LVGL is initialized (see Initializing LVGL), to create an interactive user interface, an application next creates a tree of Widgets that LVGL can render to the associated display, and with which the user can interact.

Widgets are "intelligent" LVGL graphical elements such as Base Widgets (simple rectangles and Screens), Buttons, Labels, Checkboxes, Switches, Sliders, Charts, etc. Go to Widgets to see the full list.

To build this Widget Tree, the application first acquires a pointer to a Screen Widget. A system designer is free to use the default Screen created with the Display (lv_display) and/or create his own. To create a new Screen Widget, simply create a Widget passing NULL as the parent argument. Technically, this can be any type of Widget, but in most cases it is a Base Widget. (An example of another type of Widget being used as a Screen is an Image (lv_image) Widget to supply an image for the background.)

The application then adds Widgets to this Screen as children in the tree. Widgets are automatically added as children to their parent Widgets at time of creation — the Widget's parent is passed as the first argument to the function that creates the Widget. After being so added, we say that the parent Widget "contains" the child Widget.

Any Widget can contain other Widgets. For example, if you want a Button to have text, create a Label Widget and add it to the Button as a child.

Each child Widget becomes "part of" its parent Widget. Because of this relationship:

  • when the parent Widget moves, its children move with it;

  • when the parent Widget is deleted, its children are deleted with it;

  • a child Widget is only visible within its parent's boundaries; any part of a child outside its parent's boundaries is clipped (i.e. not rendered).

Screens (and their child Widgets) can be created and deleted at any time except when the Screen is the Active Screen. If you want to delete the current Screen as you load a new one, call lv_screen_load_anim() and pass true for the auto_del argument. If you want to keep the current Screen in RAM when you load a new Screen, pass false for the auto_del argument, or call lv_screen_active() to load the new screen.

A system designer is free to keep any number of Screens (and their child Widgets) in RAM (e.g. for quick re-display again later). Doing so:

  • requires more RAM, but

  • can save the time of repeatedly creating the Screen and its child Widgets;

  • can be handy when a Screen is complex and/or can be made the Active Screen frequently.

If multiple Screens are maintained in RAM simultaneously, it is up to the system designer as to how they are managed.

Creating Widgets

Widgets are created by calling functions that look like this:

lv_<type>_create(parent)

The call will return an lv_obj_t * pointer that can be used later to reference the Widget to set its attributes.

For example:

lv_obj_t * slider1 = lv_slider_create(lv_screen_active());

Modifying Widgets

Attributes common to all Widgets are set by functions that look like this:

lv_obj_set_<attribute_name>(widget, <value>)

For example:

lv_obj_set_x(slider1, 30);
lv_obj_set_y(slider1, 10);
lv_obj_set_size(slider1, 200, 50);

Along with these attributes, widgets can have type-specific attributes which are set by functions that look like this:

lv_<type>_set_<attribute_name>(widget, <value>)

For example:

lv_slider_set_value(slider1, 70, LV_ANIM_ON);

To see the full API visit the documentation of the Widget in question under Widgets or study its related header file in the source code, e.g.

  • lvgl/src/widgets/slider/lv_slider.h

or view it on GitHub, e.g.

Deleting Widgets

To delete any widget and its children:

lv_obj_delete(lv_obj_t * widget)

Events

Events are used to inform the application that something has happened with a Widget. You can assign one or more callbacks to a Widget which will be called when the Widget is clicked, released, dragged, being deleted, etc.

A callback is assigned like this:

lv_obj_add_event_cb(btn, my_btn_event_cb, LV_EVENT_CLICKED, NULL);

...

void my_btn_event_cb(lv_event_t * e)
{
    printf("Clicked\n");
}

LV_EVENT_ALL can be used instead of LV_EVENT_CLICKED to invoke the callback for all events. (Beware: there are a LOT of events! This can be handy for debugging or learning what events occur for a given Widget, or indeed if the application needs to process all events for some reason.)

Event callbacks receive the argument lv_event_t * e containing the current event code and other event-related information. The current event code can be retrieved with:

lv_event_code_t code = lv_event_get_code(e);

The Widget that triggered the event can be retrieved with:

lv_obj_t * obj = lv_event_get_target(e);

To learn all features of the events go to the Events section.

Parts

Widgets are built from one or more parts. For example, a button has only one part called LV_PART_MAIN. However, a Slider (lv_slider) has LV_PART_MAIN, LV_PART_INDICATOR and LV_PART_KNOB.

By using parts you can apply different styles to sub-elements of a widget. (See below.)

Read the Widget's documentation to learn which parts it uses.

States

Widgets can be in a combination of the following states:

For example, if you press a Widget it will automatically go to the LV_STATE_FOCUSED and LV_STATE_PRESSED states and when you release it the LV_STATE_PRESSED state will be removed while the LV_STATE_FOCUSED state remains active.

To check if a Widget is in a given state use lv_obj_has_state(widget, LV_STATE_...). It will return true if the Widget is currently in that state.

To manually add or remove states use:

lv_obj_add_state(widget, LV_STATE_...);
lv_obj_remove_state(widget, LV_STATE_...);

Styles

A style instance contains properties such as background color, border width, font, etc. that describe the appearance of Widgets.

Styles are carried in lv_style_t objects. Only their pointer is saved in the Widgets so they need to be defined as static or global variables. Before using a style it needs to be initialized with lv_style_init(&style1). After that, properties can be added to configure the style. For example:

static lv_style_t style1;
lv_style_init(&style1);
lv_style_set_bg_color(&style1, lv_color_hex(0xa03080))
lv_style_set_border_width(&style1, 2))

See Style Properties Overview for more details.

See Style Properties to see the full list.

Styles are assigned using the OR-ed combination of a Widget's part and state. For example to use this style on the slider's indicator when the slider is pressed:

lv_obj_add_style(slider1, &style1, LV_PART_INDICATOR | LV_STATE_PRESSED);

If the part is LV_PART_MAIN it can be omitted:

lv_obj_add_style(btn1, &style1, LV_STATE_PRESSED); /* Equal to LV_PART_MAIN | LV_STATE_PRESSED */

Similarly, LV_STATE_DEFAULT can be omitted:

lv_obj_add_style(slider1, &style1, LV_PART_INDICATOR); /* Equal to LV_PART_INDICATOR | LV_STATE_DEFAULT */

For LV_STATE_DEFAULT | LV_PART_MAIN simply pass 0:

lv_obj_add_style(btn1, &style1, 0); /* Equal to LV_PART_MAIN | LV_STATE_DEFAULT */

Styles can be cascaded (similarly to CSS). This means you can add more styles to a part of a Widget. For example style_btn can set a default button appearance, and style_btn_red can overwrite the background color to make the button red:

lv_obj_add_style(btn1, &style_btn, 0);
lv_obj_add_style(btn1, &style1_btn_red, 0);

If a property is not set for the current state, the style with LV_STATE_DEFAULT will be used. A default value is used if the property is not defined in the default state.

Some properties (particularly the text-related ones) can be inherited. This means if a property is not set in a Widget it will be searched for in its parents. For example, you can set the font once in the screen's style and all text on that screen will inherit it by default.

Local style properties also can be added to Widgets. This creates a style which resides inside the Widget and is used only by that Widget:

lv_obj_set_style_bg_color(slider1, lv_color_hex(0x2080bb), LV_PART_INDICATOR | LV_STATE_PRESSED);

To learn all the features of styles see Styles.

Themes

Themes are the default styles for Widgets. Styles from a theme are applied automatically when Widgets are created.

The theme for your application is a compile time configuration set in lv_conf.h.

MicroPython

LVGL can even be used with MicroPython.

# Initialize
import display_driver
import lvgl as lv

# Create a button with a label
scr = lv.obj()
btn = lv.button(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text('Hello World!')
lv.screen_load(scr)

Going Deeper

There are several good ways ways to gain deeper knowledge of LVGL. Here is one recommended order of documents to read and things to play with while you are advancing your knowledge:

  1. If not already read, start with Introduction page of the documentation. (5 minutes)

  2. Check out the Online Demos to see LVGL in action. (3 minutes)

  3. If not already done, read the LVGL Basics (above). (15 minutes)

  4. Set up an LVGL Simulator on PC. (10 minutes)

  5. Have a look at some Examples and their code.

  6. Add LVGL to your project. See Add LVGL to Your Project or check out the ready-to-use Projects.

  7. Read the Main Components pages to get a better understanding of the library. (2-3 hours)

  8. Skim the documentation of Widgets to see what is available.

  9. If you have questions go to the Forum.

  10. Read the Contributing guide to see how you can help to improve LVGL. (15 minutes)

Basic Examples

Below are several basic examples. They include the application code that produces the Widget Tree needed to make LVGL render the examples shown. Each example assumes a LVGL has undergone normal initialization, meaning that a lv_display_t object was created and therefore has an Active Screen.

A very simple hello world label

#include "../lv_examples.h"
#if LV_BUILD_EXAMPLES && LV_USE_LABEL

/**
 * Basic example to create a "Hello world" label
 */
void lv_example_get_started_1(void)
{
    /*Change the active screen's background color*/
    lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x003a57), LV_PART_MAIN);

    /*Create a white label, set its text and align it to the center*/
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello world");
    lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

#endif

A button with a label and react on click event

#include "../lv_examples.h"
#if LV_BUILD_EXAMPLES && LV_USE_BUTTON

static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);
    if(code == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;

        /*Get the first child of the button which is the label and change its text*/
        lv_obj_t * label = lv_obj_get_child(btn, 0);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}

/**
 * Create a button with a label and react on click event.
 */
void lv_example_get_started_2(void)
{
    lv_obj_t * btn = lv_button_create(lv_screen_active());     /*Add a button the current screen*/
    lv_obj_set_pos(btn, 10, 10);                            /*Set its position*/
    lv_obj_set_size(btn, 120, 50);                          /*Set its size*/
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);           /*Assign a callback to the button*/

    lv_obj_t * label = lv_label_create(btn);          /*Add a label to the button*/
    lv_label_set_text(label, "Button");                     /*Set the labels text*/
    lv_obj_center(label);
}

#endif

Create styles from scratch for buttons

#include "../lv_examples.h"
#if LV_USE_BUTTON && LV_BUILD_EXAMPLES

static lv_style_t style_btn;
static lv_style_t style_button_pressed;
static lv_style_t style_button_red;

static lv_color_t darken(const lv_color_filter_dsc_t * dsc, lv_color_t color, lv_opa_t opa)
{
    LV_UNUSED(dsc);
    return lv_color_darken(color, opa);
}

static void style_init(void)
{
    /*Create a simple button style*/
    lv_style_init(&style_btn);
    lv_style_set_radius(&style_btn, 10);
    lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
    lv_style_set_bg_color(&style_btn, lv_palette_lighten(LV_PALETTE_GREY, 3));
    lv_style_set_bg_grad_color(&style_btn, lv_palette_main(LV_PALETTE_GREY));
    lv_style_set_bg_grad_dir(&style_btn, LV_GRAD_DIR_VER);

    lv_style_set_border_color(&style_btn, lv_color_black());
    lv_style_set_border_opa(&style_btn, LV_OPA_20);
    lv_style_set_border_width(&style_btn, 2);

    lv_style_set_text_color(&style_btn, lv_color_black());

    /*Create a style for the pressed state.
     *Use a color filter to simply modify all colors in this state*/
    static lv_color_filter_dsc_t color_filter;
    lv_color_filter_dsc_init(&color_filter, darken);
    lv_style_init(&style_button_pressed);
    lv_style_set_color_filter_dsc(&style_button_pressed, &color_filter);
    lv_style_set_color_filter_opa(&style_button_pressed, LV_OPA_20);

    /*Create a red style. Change only some colors.*/
    lv_style_init(&style_button_red);
    lv_style_set_bg_color(&style_button_red, lv_palette_main(LV_PALETTE_RED));
    lv_style_set_bg_grad_color(&style_button_red, lv_palette_lighten(LV_PALETTE_RED, 3));
}

/**
 * Create styles from scratch for buttons.
 */
void lv_example_get_started_3(void)
{
    /*Initialize the style*/
    style_init();

    /*Create a button and use the new styles*/
    lv_obj_t * btn = lv_button_create(lv_screen_active());
    /* Remove the styles coming from the theme
     * Note that size and position are also stored as style properties
     * so lv_obj_remove_style_all will remove the set size and position too */
    lv_obj_remove_style_all(btn);
    lv_obj_set_pos(btn, 10, 10);
    lv_obj_set_size(btn, 120, 50);
    lv_obj_add_style(btn, &style_btn, 0);
    lv_obj_add_style(btn, &style_button_pressed, LV_STATE_PRESSED);

    /*Add a label to the button*/
    lv_obj_t * label = lv_label_create(btn);
    lv_label_set_text(label, "Button");
    lv_obj_center(label);

    /*Create another button and use the red style too*/
    lv_obj_t * btn2 = lv_button_create(lv_screen_active());
    lv_obj_remove_style_all(btn2);                      /*Remove the styles coming from the theme*/
    lv_obj_set_pos(btn2, 10, 80);
    lv_obj_set_size(btn2, 120, 50);
    lv_obj_add_style(btn2, &style_btn, 0);
    lv_obj_add_style(btn2, &style_button_red, 0);
    lv_obj_add_style(btn2, &style_button_pressed, LV_STATE_PRESSED);
    lv_obj_set_style_radius(btn2, LV_RADIUS_CIRCLE, 0); /*Add a local style too*/

    label = lv_label_create(btn2);
    lv_label_set_text(label, "Button 2");
    lv_obj_center(label);
}

#endif

Create a slider and write its value on a label

#include "../lv_examples.h"
#if LV_BUILD_EXAMPLES && LV_USE_SLIDER

static lv_obj_t * label;

static void slider_event_cb(lv_event_t * e)
{
    lv_obj_t * slider = lv_event_get_target(e);

    /*Refresh the text*/
    lv_label_set_text_fmt(label, "%"LV_PRId32, lv_slider_get_value(slider));
    lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);    /*Align top of the slider*/
}

/**
 * Create a slider and write its value on a label.
 */
void lv_example_get_started_4(void)
{
    /*Create a slider in the center of the display*/
    lv_obj_t * slider = lv_slider_create(lv_screen_active());
    lv_obj_set_width(slider, 200);                          /*Set the width*/
    lv_obj_center(slider);                                  /*Align to the center of the parent (screen)*/
    lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);     /*Assign an event function*/

    /*Create a label above the slider*/
    label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "0");
    lv_obj_align_to(label, slider, LV_ALIGN_OUT_TOP_MID, 0, -15);    /*Align top of the slider*/
}

#endif