Widget Basics

What is a Widget?

A Widget is the basic building block of the LVGL user interface.

Examples of Widgets: Base Widget (and Screen), Button, Label, Image, List, Chart and Text Area.

See Widgets to see all Widget types.

All Widgets are referenced using an lv_obj_t pointer as a handle. This pointer can later be used to read or change the Widget's attributes.

Base Widget

The most fundamental of all Widgets is the Base Widget, on which all other widgets are based. From an Object-Oriented perspective, think of the Base Widget as the Widget class from which all other Widgets inherit.

The functions and functionalities of the Base Widget can be used with other widgets as well. For example lv_obj_set_width(slider, 100).

The Base Widget can be used directly as a simple widget. While it is a simple rectangle, it has a large number of features shared with all Widgets, detailed below and in subsequent pages. In HTML terms, think of it as a <div>.

Attributes

Basic attributes

All Widget types share some basic attributes:

  • Position

  • Size

  • Parent

  • Styles

  • Events it emits

  • Flags like Clickable, Scollable, etc.

  • Etc.

You can set/get these attributes with lv_obj_set_... and lv_obj_get_... functions. For example:

/* Set basic Widget attributes */
lv_obj_set_size(btn1, 100, 50);   /* Set a button's size */
lv_obj_set_pos(btn1, 20,30);      /* Set a button's position */

For complete details on position, size, coordinates and layouts, see Positions, Sizes and Layouts.

Widget-specific attributes

The Widget types have special attributes as well. For example, a slider has

  • Minimum and maximum values

  • Current value

For these special attributes, every Widget type may have unique API functions. For example for a slider:

/* Set slider specific attributes */
lv_slider_set_range(slider1, 0, 100);               /* Set the min. and max. values */
lv_slider_set_value(slider1, 40, LV_ANIM_ON);       /* Set the current value (position) */

The API of the widgets is described in their Documentation but you can also check the respective header files (e.g. widgets/lv_slider.h)

Parents and children

A Widget's parent is set when the widget is created — the parent is passed to the creation function.

To get a Widget's current parent, use lv_obj_get_parent(widget).

You can move the Widget to a new parent with lv_obj_set_parent(widget, new_parent).

To get a specific child of a parent use lv_obj_get_child(parent, idx). Some examples for idx:

  • 0 get the child created first

  • 1 get the child created second

  • -1 get the child created last

You can iterate through a parent Widget's children like this:

uint32_t i;
for(i = 0; i < lv_obj_get_child_count(parent); i++) {
    lv_obj_t * child = lv_obj_get_child(parent, i);
    /* Do something with child. */
}

lv_obj_get_index(widget) returns the index of the Widget in its parent. It is equivalent to the number of older children in the parent.

You can bring a Widget to the foreground or send it to the background with lv_obj_move_foreground(widget) and lv_obj_move_background(widget).

You can change the index of a Widget in its parent using lv_obj_move_to_index(widget, index).

You can swap the position of two Widgets with lv_obj_swap(widget1, widget2).

To get a Widget's Screen (highest-level parent) use lv_obj_get_screen(widget).

Working Mechanisms

Parent-child structure

A parent Widget can be considered as the container of its children. Every Widget has exactly one parent Widget (except Screens), but a parent Widget can have any number of children. There is no limitation for the type of the parent but there are Widgets which are typically a parent (e.g. button) or a child (e.g. label).

Moving together

If the position of a parent changes, the children will move along with it. Therefore, all positions are relative to the parent.

../../_images/par_child1.png
lv_obj_t * parent = lv_obj_create(lv_screen_active());  /* Create a parent Widget on current screen */
lv_obj_set_size(parent, 100, 80);                       /* Set size of parent */

lv_obj_t * obj1 = lv_obj_create(parent);                /* Create a Widget on previously created parent Widget */
lv_obj_set_pos(widget1, 10, 10);                        /* Set position of new Widget */

Modify the position of the parent:

../../_images/par_child2.png
lv_obj_set_pos(parent, 50, 50); /* Move the parent. The child will move with it. */

(For simplicity the adjusting of colors of the Widgets is not shown in the example.)

Visibility only on the parent

If a child is partially or fully outside its parent then the parts outside will not be visible.

../../_images/par_child3.png
lv_obj_set_x(widget1, -30);    /* Move the child a little bit off the parent */

This behavior can be overwritten with lv_obj_add_flag(widget, LV_OBJ_FLAG_OVERFLOW_VISIBLE) which allow the children to be drawn out of the parent. In addition to this, you must register the following event callback (this was not required in previous versions).

Note: ext_width should be the maximum absolute width the children will be drawn within.

static void ext_draw_size_event_cb(lv_event_t * e)
{
    lv_event_set_ext_draw_size(e, 30); /*Set 30px extra draw area around the widget*/
}

Create and delete Widgets

In LVGL, Widgets can be created and deleted dynamically at run time. It means only the currently created (existing) Widgets consume RAM.

This allows for the creation of a screen just when a button is clicked to open it, and for deletion of screens when a new screen is loaded.

UIs can be created based on the current environment of the device. For example one can create meters, charts, bars and sliders based on the currently attached sensors.

Every widget has its own create function with a prototype like this:

lv_obj_t * lv_<widget>_create(lv_obj_t * parent, <other parameters if any>);

Typically, the create functions only have a parent parameter telling them on which Widget to create the new Widget.

The return value is a pointer to the created Widget with lv_obj_t * type.

There is a common delete function for all Widget types. It deletes the Widget and all of its children.

void lv_obj_delete(lv_obj_t * widget);

lv_obj_delete() will delete the Widget immediately. If for any reason you can't delete the Widget immediately you can use lv_obj_delete_async(widget) which will perform the deletion on the next call of lv_timer_handler(). This is useful e.g. if you want to delete the parent of a Widget in the child's LV_EVENT_DELETE handler.

You can remove all the children of a Widget (but not the Widget itself) using lv_obj_clean(widget).

You can use lv_obj_delete_delayed(widget, 1000) to delete a Widget after some time. The delay is expressed in milliseconds.

Sometimes you're not sure whether a Widget was deleted and you need some way to check if it's still "alive". Anytime before the Widget is deleted, you can use cpp:expr:lv_obj_null_on_delete(&widget) to cause your Widget pointer to be set to NULL when the Widget is deleted.

Make sure the pointer variable itself stays valid until the Widget is deleted. Here is an example:

void some_timer_callback(lv_timer_t * t)
{
   static lv_obj_t * my_label;
   if(my_label == NULL) {
      my_label = lv_label_create(lv_screen_active());
      lv_obj_delete_delayed(my_label, 1000);
      lv_obj_null_on_delete(&my_label);
   }
   else {
      lv_obj_set_x(my_label, lv_obj_get_x(my_label) + 1);
   }
}

Screens

What are Screens?

Not to be confused with a Display (lv_display), Screens are simply any Widget created without a parent (i.e. passing NULL for the parent argument during creation). As such, they form the "root" of a Widget Tree.

Normally the Base Widget is used for this purpose since it has all the features most Screens need. But an Image (lv_image) Widget can also be used to create a wallpaper background for the Widget Tree.

All Screens:

  • are automatically attached to the Default Display current when the Screen was created;

  • automatically occupy the full area of the associated display;

  • cannot be moved, i.e. functions such as lv_obj_set_pos() and lv_obj_set_size() cannot be used on screens.

Each Display (lv_display) object can have multiple screens associated with it, but not vice versa. Thus the relationship:

   Display
      |
     --- (one or more)
     /|\
Screen Widgets  (root of a Widget Tree)
      |
      O  (zero or more)
     /|\
Child Widgets

Creating Screens

Screens are created like this:

lv_obj_t * scr1 = lv_obj_create(NULL);

Screens can be deleted with lv_obj_delete(scr), but be sure you do not delete the Active Screen.

Active Screen

While each Display (lv_display) object can have any number of Screens Widgets associated with it, only one of those Screens is considered "Active" at any given time. That Screen is referred to as the Display's "Active Screen". For this reason, only one Screen and its child Widgets will ever be shown on a display at one time.

When each Display (lv_display) object was created, a default screen was created with it and set as its "Active Screen".

To get a pointer to the "Active Screen", call lv_screen_active().

To set a Screen to be the "Active Screen", call lv_screen_load() or lv_screen_load_anim().

Loading Screens

To load a new screen, use lv_screen_load(scr1). This sets scr1 as the Active Screen.

Load Screen with Animation

A new screen can be loaded with animation by using lv_screen_load_anim(scr, transition_type, time, delay, auto_del). The following transition types exist:

Setting auto_del to true will automatically delete the old screen when the animation is finished.

The new screen will become active (returned by lv_screen_active()) when the animation starts after delay time. All inputs are disabled during the screen animation.

Layers

When an lv_display_t object is created, 4 Screens (layers) are created and attached to it.

  1. Bottom Layer

  2. Active Screen

  3. Top Layer

  4. System Layer

1, 3 and 4 are independent of the Active Screen and they will be shown (if they contain anything that is visible) regardless of which screen is the Active Screen. See Screen Layers for more information.

Transparent Screens

Usually, the opacity of the Screen is LV_OPA_COVER to provide a solid background for its children. If this is not the case (opacity < 100%) the display's bottom_layer will be visible. If the bottom layer's opacity is also not LV_OPA_COVER LVGL will have no solid background to draw.

This configuration (transparent Screen) could be useful to create, for example, on-screen display (OSD) menus where a video is played on a different hardware layer of the display panel, and a menu is overlaid on a higher layer.

To properly render a UI on a transparent Screen the Display's color format needs to be set to one with an alpha channel (for example LV_COLOR_FORMAT_ARGB8888).

In summary, to enable transparent screens and displays for OSD menu-like UIs:

Parts

The widgets are built from multiple parts. For example a Base Widget uses the main and scrollbar parts but a Slider uses the main, indicator and knob parts. Parts are similar to pseudo-elements in CSS.

The following predefined parts exist in LVGL:

The main purpose of parts is to allow styling the "components" of the widgets. They are described in more detail in the Style overview section.

States

The Widget can be in a combination of the following states:

The states are usually automatically changed by the library as the user interacts with a Widget (presses, releases, focuses, etc.). However, the states can be changed manually as well. To set or clear given state (but leave the other states untouched) use lv_obj_add_state(widget, LV_STATE_...) and lv_obj_remove_state(widget, LV_STATE_...). In both cases OR-ed state values can be used as well. E.g. lv_obj_add_state(widget, part, LV_STATE_PRESSED | LV_PRESSED_CHECKED).

To learn more about the states read the related section of the Style overview.

Flags

There are some Widget attributes which can be enabled/disabled by lv_obj_add_flag(widget, LV_OBJ_FLAG_...) and lv_obj_remove_flag(widget, LV_OBJ_FLAG_...).

Some examples:

/* Hide on Widget */
lv_obj_add_flag(widget, LV_OBJ_FLAG_HIDDEN);

/* Make a Widget non-clickable */
lv_obj_remove_flag(widget, LV_OBJ_FLAG_CLICKABLE);

Base-Widget Events

Events from Input Devices

Special Events

Drawing Events

Other Events

Further Reading

Learn more about Events.

Keys

If LV_OBJ_FLAG_CHECKABLE is enabled, LV_KEY_RIGHT and LV_KEY_UP make the Widget checked, and LV_KEY_LEFT and LV_KEY_DOWN make it unchecked.

If LV_OBJ_FLAG_SCROLLABLE is enabled, but the Widget is not editable (as declared by the widget class), the arrow keys (LV_KEY_UP, LV_KEY_DOWN, LV_KEY_LEFT, LV_KEY_RIGHT) scroll the Widget. If the Widget can only scroll vertically, LV_KEY_LEFT and LV_KEY_RIGHT will scroll up/down instead, making it compatible with an encoder input device. See Input devices overview for more on encoder behaviors and the edit mode.

Further Reading

Learn more about Keys.

Snapshot

A snapshot image can be generated for a Widget together with its children. Check details in Snapshot.

Example

Base objects with custom styles

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

void lv_example_obj_1(void)
{
    lv_obj_t * obj1;
    obj1 = lv_obj_create(lv_screen_active());
    lv_obj_set_size(obj1, 100, 50);
    lv_obj_align(obj1, LV_ALIGN_CENTER, -60, -30);

    static lv_style_t style_shadow;
    lv_style_init(&style_shadow);
    lv_style_set_shadow_width(&style_shadow, 10);
    lv_style_set_shadow_spread(&style_shadow, 5);
    lv_style_set_shadow_color(&style_shadow, lv_palette_main(LV_PALETTE_BLUE));

    lv_obj_t * obj2;
    obj2 = lv_obj_create(lv_screen_active());
    lv_obj_add_style(obj2, &style_shadow, 0);
    lv_obj_align(obj2, LV_ALIGN_CENTER, 60, 30);
}
#endif

Make an object draggable

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

static void drag_event_handler(lv_event_t * e)
{
    lv_obj_t * obj = lv_event_get_target(e);

    lv_indev_t * indev = lv_indev_active();
    if(indev == NULL)  return;

    lv_point_t vect;
    lv_indev_get_vect(indev, &vect);

    int32_t x = lv_obj_get_x_aligned(obj) + vect.x;
    int32_t y = lv_obj_get_y_aligned(obj) + vect.y;
    lv_obj_set_pos(obj, x, y);
}

/**
 * Make an object draggable.
 */
void lv_example_obj_2(void)
{
    lv_obj_t * obj;
    obj = lv_obj_create(lv_screen_active());
    lv_obj_set_size(obj, 150, 100);
    lv_obj_add_event_cb(obj, drag_event_handler, LV_EVENT_PRESSING, NULL);

    lv_obj_t * label = lv_label_create(obj);
    lv_label_set_text(label, "Drag me");
    lv_obj_center(label);

}
#endif

Transform object using a 3x3 matrix

#include "../../lv_examples.h"
#if LV_BUILD_EXAMPLES
#if LV_DRAW_TRANSFORM_USE_MATRIX

static void timer_cb(lv_timer_t * timer)
{
    lv_obj_t * obj = lv_timer_get_user_data(timer);

    static float value = 0.1f;
    lv_matrix_t matrix;
    lv_matrix_identity(&matrix);
    lv_matrix_scale(&matrix, value, 1);
    lv_matrix_rotate(&matrix, value * 360);
    lv_obj_set_transform(obj, &matrix);

    value += 0.01f;

    if(value > 2.0f) {
        lv_obj_reset_transform(obj);
        value = 0.1f;
    }
}

void lv_example_obj_3(void)
{
    lv_obj_t * obj = lv_obj_create(lv_screen_active());
    lv_obj_center(obj);

    lv_timer_create(timer_cb, 20, obj);
}

#else

void lv_example_obj_3(void)
{
    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text_static(label, "LV_DRAW_TRANSFORM_USE_MATRIX is not enabled");
    lv_obj_center(label);
}

#endif /*LV_DRAW_TRANSFORM_USE_MATRIX*/
#endif /*LV_BUILD_EXAMPLES*/

API

lv_api_map_v8.h

lv_types.h

lv_obj_draw.h

lv_obj_property.h

lv_obj_style_gen.h

lv_flex.h

lv_obj_style.h

lv_obj_class.h

lv_api_map_v9_1.h

lv_obj.h

lv_observer.h

lv_obj_property_names.h

lv_obj_pos.h

lv_obj_event.h

lv_obj_scroll.h

lv_refr.h

lv_grid.h

lv_obj_tree.h