Nest a scrollable Page within another Scrollable page


What MCU/Processor/Board and compiler are you using?

ESP32, VSCode/PlatformIO

What do you want to achieve?

I have a Window with multiple screen filling pages aligned vertically that scroll from top to bottom as the focus gets shifted with Up/Down/Enter buttons. No Touchscreen.

On one page I need to have a container that contains a list of containers that is longer than fits the screen. I am planning to just create a page sized appropriately for the available screen and have the list scroll inside that subpage. However, this page never scrolls, it is always the main page that scrolls. I’d like to only have the main page scroll once focus jumps from the last element of the list to the next page. As long as the focus is on a list element, i’d like to have the nested page scroll.

What have you tried so far?

Really all kinds of configuarations and settings. Reading through documentation of virtually every function that exists. Maybe Noteworthy, calling lv_cont_set_fit2(lvContainer, LV_FIT_NONE, LV_FIT_FILL); on the parent container crashes the Esp32 when it creates another container inside the parent container.

Code to reproduce

void AltiGuiGPS_Satellite::create(lv_obj_t* parent, lv_group_t *_focusGroup, uint8_t id) {
    Serial.println("creating satellite");
    this->parent = parent;
    this->focusGroup = _focusGroup;

    lvContainer = lv_cont_create(this->parent, NULL);
    lv_obj_set_width(lvContainer, LV_HOR_RES_MAX-20);
    lv_obj_set_height(lvContainer, SAT_CONT_HEIGHT);
    lv_group_add_obj(focusGroup, lvContainer);

    lbl_id = lv_label_create(lvContainer, NULL);
    lv_label_set_text(lbl_id, String(id).c_str());
    lv_obj_align(lbl_id, lvContainer, LV_ALIGN_IN_LEFT_MID, 10, 0);
    lv_obj_set_style(lbl_id, &lv_style_plain);

    lbl_elev = lv_label_create(lvContainer, NULL);
    lv_label_set_text(lbl_elev, "ele");
    lv_obj_align(lbl_elev, lvContainer, LV_ALIGN_IN_LEFT_MID, 30, 0);
    lv_obj_set_style(lbl_elev, &lv_style_plain);

    lbl_azimuth = lv_label_create(lvContainer, NULL);
    lv_label_set_text(lbl_azimuth, "azi");
    lv_obj_align(lbl_azimuth, lvContainer, LV_ALIGN_IN_LEFT_MID, 70, 0);
    lv_obj_set_style(lbl_azimuth, &lv_style_plain);

    lbl_snr = lv_label_create(lvContainer, NULL);
    lv_label_set_text(lbl_snr, "snr");
    lv_obj_align(lbl_snr, lvContainer, LV_ALIGN_IN_LEFT_MID, 110, 0);
    lv_obj_set_style(lbl_snr, &lv_style_plain);

    snrBar = lv_bar_create(lvContainer, NULL);
    lv_obj_set_size(snrBar, 120, 16);
    lv_bar_set_range(snrBar, 0, 60);
    lv_obj_align(snrBar, lvContainer, LV_ALIGN_IN_RIGHT_MID, -10, 0);
    lv_bar_set_anim_time(snrBar, 1000);
    lv_bar_set_value(snrBar, 30, LV_ANIM_ON);

void AltiGuiGPS::create(lv_obj_t *parent, lv_group_t *_focusGroup) {
    this->parent = parent;
    this->focusGroup = _focusGroup;

    lvContainer = lv_cont_create(this->parent, NULL);
    lv_obj_set_width(lvContainer, LV_HOR_RES_MAX);
    lv_obj_set_height(lvContainer, 220);
    lv_group_add_obj(focusGroup, lvContainer);

    // On/Off Checkbox
    onOffCheckbox = lv_cb_create(this->lvContainer, NULL);     // First check box
    lv_cb_set_text(onOffCheckbox, "GPS On");
    lv_obj_align(onOffCheckbox, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 0);
    lv_group_add_obj(this->focusGroup, this->onOffCheckbox);
    lv_obj_set_event_cb(onOffCheckbox, lvcb_GPSonOffCheckboxHandler);
    lv_cb_set_checked(onOffCheckbox, Data.gpsEnabled);

    static lv_style_t cbStyle;
    lv_style_copy(&cbStyle, lv_obj_get_style(this->onOffCheckbox));
    cbStyle.text.font = &lv_font_roboto_28;
    lv_obj_set_style(this->onOffCheckbox, &cbStyle);

    // status label
    lv_obj_t *cont_Status = lv_cont_create(lvContainer, NULL);
    lv_obj_set_style(cont_Status, &lv_style_plain);
    lv_obj_set_height(cont_Status, 30);
    lv_obj_set_width(cont_Status, LV_HOR_RES_MAX-20);
    lv_obj_align(cont_Status, onOffCheckbox, LV_ALIGN_OUT_BOTTOM_LEFT, 10, 0);
    lbl_Status = lv_label_create(cont_Status, NULL);
    lv_label_set_text(lbl_Status, "Status");
    lv_obj_align(lbl_Status, cont_Status, LV_ALIGN_IN_LEFT_MID, 0, 0);

    // satellites page
    lv_obj_t *cont_Satellites = lv_page_create(lvContainer, NULL);

    lv_obj_set_height(cont_Satellites, 120);
    lv_obj_set_width(cont_Satellites, LV_HOR_RES_MAX);
    lv_obj_align(cont_Satellites, cont_Status, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
    lv_page_set_scrl_fit2(cont_Satellites, LV_FIT_NONE, LV_FIT_FILL);

    for (int i = 0; i < 20;i++) {
        satellites[i].create(cont_Satellites, focusGroup, i);
        lv_obj_align(satellites[i].lvContainer, cont_Satellites, LV_ALIGN_IN_TOP_LEFT, 0, i * SAT_CONT_HEIGHT);

Screenshot and/or video

Video scrolling down, illustrating the problem:

Have you tried lv_page_set_scroll_propagation? Scroll propagation was improved significantly in v7 but the v6 implementation might be good enough for your use case.

yes I have, both true and false, exactly the same result :frowning:
Also the problem is actually the opposite of what the doc says scroll propagation is for. The nested page does not scroll at all, it always scrolls the parent.

Could you put together a very simple example in C to run it in the simulator?
Do you use LV_INDEV_TYPE_KEYPAD for navigation, right?

I have not yet got around setting up a simulator yet :frowning:
Any pointers to get one up and running quick on mac os using vscode/platformio?
Edit, nvm, got it up real quick.

it is not pretty, but it almost replicates the behavior, despite not scrolling at all:
it’s a modified platformio project containing only the necessary gui code to replicate the behavior (no callbacks) and modified the driver.c and driver.h to use an Encoder - to answer your previous question, I am using LV_INDEV_TYPE_ENCODER.
I am clueless on why it wouldn’t scroll at all, but maybe this is even a pointer to the solution of the actual problem?

Just minor thing first: there was a bug in your code: lv_style_t cbStyle;.

So you can hale this a focus callback like this:

static lv_obj_t *cont_Satellites;

void focus_cb(lv_group_t * g)
    if(cont_Satellites == NULL) return;
    lv_obj_t * obj = lv_group_get_focused(g);
    if(lv_obj_get_parent(obj) == lv_page_get_scrl(cont_Satellites)) {
        lv_page_focus(cont_Satellites, obj, LV_ANIM_ON);

Thank you gabor!
I am not sure how to read your reply correctly. What is the bug in cbStyle? On my device this only sets the font, which i commented out for the simulator version as i did not bother transferring the fonts to the sim, thinking it would not make a difference to the outcome …

Ah indeed there is no focus callback in the demo. I have added it above void demo_create(void) and called lv_group_set_focus_cb(focusGroup, focus_cb); as the first line in demo_create(), but the behavior of the sim is exactly the same. It marks the focused object but no scrolling taking place … Neither scrolls the list nor scrolls to the next page.

But it triggered something, I do have that focus callback on my devices code and never really understood what it does. i’ll have a look at that section now.

Sorry, the styles should be static if declared as local variable. I just forgot to write the gist in my comment :sweat_smile:

Can you debug if your code reaches lv_page_focus?

thank you gabor! Now that you say it, the static declaration makes total sense, if not static the variable will be gone but still referenced, right? It is not static because on the micro i use classes for the GUI, so I guess in that case the bug is even worse, I’ll make sure the variable is declared in the class and not locally.

I got the scrolling working now by adapting the my focus callback to this:

void group_focus_cb(lv_group_t * group)
    lv_obj_t * f = lv_group_get_focused(group);
    if(f != Gui.guiWindow) {
        if(Gui.gpsGui.cont_Satellites != NULL && lv_obj_get_parent(f) == lv_page_get_scrl(Gui.gpsGui.cont_Satellites)) {
            lv_page_focus(Gui.gpsGui.cont_Satellites, f, LV_ANIM_ON);
            lv_win_focus(Gui.guiWindow, Gui.gpsGui.lvContainer, LV_ANIM_ON);
        } else {
            lv_win_focus(Gui.guiWindow, f, LV_ANIM_ON);

I think I finally understand lv_page_focus and lv_win_focus, based on the behavior of this function. There is only one minor problem left. Whenever the satellites start scrolling, they scroll by two items. I’d like to have them scroll only one item at a time (this way pressing a button will have the same bevavior on every press, not on every second, which I find is better flow). Do you have any hint on how to achieve this?

I’ll check with the debugger why the simulator is not doing what I expect it to do. I should really use it for faster prototyping of UI elements …

thank you!

It should happen because lv_page_foncus keeps pad_bottom space when the object was not visible. Just to be sure it causes the problem, can you set pad_bottom=0 for the page’s scrollable part?

Ahh, pad_bottom = style.body.padding.bottom!
It actually is 0. Or in other words, all paddings are manually set to 0 for every container…
but then again, another oddity, when I scroll back up, the scrolling actually overshoots/the scrolling part is lower on y than it should even be able to with all paddings to 0 …

Sorry for the late answer, I’m fully occupied with the near release of v7.0.

Please send a simple example with the current state of yo code. Please include only the required widgets for simplicity.