Auto scroll, screen refresh behavior for list widget

Hello

I am new to LVGL and doing experiments with respect to scrolling in list widget environment. The aim is to get focus on a specific list entry if the list contents are bigger than active screen area. There is no physical input device that will be present in my application, so I will setup the focus programmatically always by using appropriate calls and states.

I have set up a list with buttons. Each button further has one label as its child.

I created a group and added each label as a member of the group with initialization like below.

    grp = lv_group_create();

currentButton = lv_obj_get_child(list1, 0);
    lab = lv_obj_get_child(currentButton, 0);   // get the label corresponding to the current button
    
    lv_indev_drv_init(&indev_drv);      /*Basic initialization*/
    indev_drv.type = LV_INDEV_TYPE_KEYPAD;                 /*See below.*/
    indev_drv.read_cb = NULL;              /*See below.*/
    /*Register the driver in LVGL and save the created input device object*/
    my_indev = lv_indev_drv_register(&indev_drv);
    lv_indev_set_group(my_indev, grp);

The keyboard is provided as input above, even though I do not have actual physical input device, and I think I could keep that entry null if needed.

The group is populated with each label as below along with setting LV_OBJ_FLAG_SCROLL_ON_FOCUS on each label.


  lab = lv_label_create(btn);
        lv_label_set_text_fmt(lab, "%s", bookNames[i]);     
        lv_style_set_text_color(&style_label[i], lv_color_hex3(0x07E0)); // green color
        lv_obj_add_style(lab, &style_label[i], 0);      
        lv_obj_add_flag(lab, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
        lv_group_add_obj(grp, lab);

Then I am setting up the focus manually with the call


    currentButton = lv_obj_get_child(list1, 1);
    lab = lv_obj_get_child(currentButton, 0);   // get the label corresponding to current button
    lv_refr_now(NULL);
    lv_group_focus_obj(lab);

In the above setup earlier I had lv_refr_now(NULL) because I was also changing the color of the label text on focus to indicate its in focus.

I have created an array of 10 text names that are set as text of each of the 10 labels on each of the 10 buttons in a root list object. I am displaying these text names sequentially one below another where I see that in an active screen only 6 fit, so 4 are out of view. So the scroll bars appear vertically on side. So the aim is to move the focus between different labels to see it getting selected and auto scrolled with focus. (see picture for a reference)
20250417_164808_copy_1330x997_copy_438x329

The above setup is working to some extent but in a flawed manner, where what I am observing is

  1. if I comment out the lv_refr_now(NULL) and only keep lv_group_focus_obj(lab); then the auto scroll stops.

  2. If I change the sequence of the two calls to swap their positions with each other like below (compared to the code shown earlier)

    lv_group_focus_obj(lab);
    lv_refr_now(NULL);

Then no focus selection and scrolling happens. So basically nothing happens.

  1. If I try to introduce delay(time) in between two focus related sequence calls (as above code) to try to do some movement in scroll selection to see how it looks on screen, then the delay is introduced first and then the focus selection takes place, for example
call function where object is focused to do scrolling such that last object in the list is selected
introduce delay
call function where object is focused to do scrolling such that the first object in the list is selected

what is observed is there is no scrolling for the duration of delay, then scrolling happens to last object, then quickly the scrolling happens to the first object. So delay is not introduced in between two calls but delay is introduced first before scrolling takes place.

  1. From the original position below.
   currentButton = lv_obj_get_child(list1, 1);
  lab = lv_obj_get_child(currentButton, 0);   // get the label corresponding to the current button
  lv_refr_now(NULL);
  lv_group_focus_obj(lab);

If I change the sequence to this, a weird behavior is observed between two calls where the first focus related scroll is ignored and only the second one is successful.

   currentButton = lv_obj_get_child(list1, 1);
  lab = lv_obj_get_child(currentButton, 0);   // get the label corresponding to the current button
  lv_group_focus_obj(lab);
  lv_refr_now(NULL);

So these are some of the observations with the context. My expectation overall was if I am using lv_group_focus_obj(lab); to focus on an object the scrolling should happen automatically without the need to use lv_refr_now(NULL); but it seems that’s not happening.

So can you please let me know if I am using these api’s correctly in the correct seuquence with the right way? As I have managed to get this info from lots of different forum posts to do experiments to see whats working etc and not exactly sure if any thing is being broken in terms of usage here.

Any insights / feedback / help will be highly appreciated.

I am using LVGL 8.3 in ESP32-S3 environment in Arduino IDE. I do not want to disturb the setup by upgrading to a different version of LVGL.

I hope to have given sufficient information. If not please ask if you need any further info.

Thanks for your time and kindly excuse any mistakes as this is my first post here.

Regards
Mg

Seems your loop code isnt riht. LVGL is event system , cant use delays for timing. In normal situation you never require call lv_refr_now

1 Like

Thanks a lot Marian for point out the event based system and issues with delays in this as an execution. I tried without the delay with only a single call to test if autoscroll works without lv_refr_now and with only lv_group_focus_obj(currentButton);
and it worked like a charm.

I am doing some more experiments, but is there any recommendation in how I can send more events in sequence in software without using delay because I did try setting up an event handler that is called in a loop and sent manual event from outside using
lv_event_send(currentButton, LV_EVENT_SCROLL, &EVENT_scroll_down);

and here also if I used the lv_group_focus_obj(currentButton); in event handler to auto scroll, it worked perfectly only on a single invocation of lv_event_send. The moment I tried sending lv_event_send in a loop with a delay to mimic scroll up / down movement in software, thinking now I am sending individual events separated by delay, the lv_group_focus_obj(currentButton) failed to autoscroll as nothing happened. To see what all events are supported during manual event send, I also tried LV_EVENT_VALUE_CHANGED as it also worked to pass events, but LV_EVENT_FOCUSED resulted in constant exception generation)

So is there some way to simulate sending of multiple events to do scrolling using the lv_group_focus_obj(currentButton) function, in the list from top to down etc that I can do as a general practice ? What is the recommendation for the looping here to send events in sequence in general in software, if there is any.

In the final system, we will actually have a physical button sending events, so thats not an issue, but this is for testing simulation etc where I am trying to see what I can do further in software first.

Any hints / suggestions will be highly appreciated. Thanks much for your time and help. Highly appreciated.

Seems yuo beginner then create next task for this is advanced for you.
More simple is use in loop some global timing variable and call based on it.

nextevent = millis() + 1000;  // require set only once in prev event
if( nextevent < millis() ) ...
1 Like

Thanks. Please suggest using task how to do it if thats possible. I m just beginneer in lvgl not in programming. I have played with tasks to try and understand it. So please also suggest task based approach as well so i can check.

Operating system and interrupts — LVGL documentation

Thanks. Is there some small sample code that can be used as a reference to get some hints here for the requirement in current context?

Hi Marian.

I tried using the approach of introducing a delay with the millis() method as you referenced earlier. Below for example is a sample code. Here I am using two separate event handlers on same button, to identify two different events being called. In each event handler I am using lv_group_focus_obj(currentButton); only once, so in each event handler I can focus on two separate positions in the list, so between two manual event send calls auto scroll happens between two positions. In the first manual event send I am focusing on 9th element in a list 10 elements (so bottom area), so it scrolls down, while in the 2nd event handler I am focusing on 1st element so it scrolls back up.

So basically I am trying to simulate two manual event calls that would make the auto scroll happen so the movement can be seen visually, first to 9th and then to the 1st element in the display. There is no loop involved here but two sequential event calls separated by delay introduced using millis()

  Serial.printf("\n ######## Before the 1st millis");
      nextevent = millis() + 7000;  // require set only once in prev event
      while(1)
      {
          if( nextevent < millis() ) 
              break;
      }

  Serial.printf("\n ######## Before the 1st scroll down event");  
  lv_event_send(currentButton, LV_EVENT_VALUE_CHANGED, &EVENT_scroll_down);

  Serial.printf("\n ######## Before the 2nd millis");
  nextevent = millis() + 7000;  // require set only once in prev event
      while(1)
      {
          if( nextevent < millis() ) 
              break;
      }

  Serial.printf("\n %%%%%%%%%%%%%%%%%  Before the 1st scroll up event");  
  lv_event_send(currentButton, LV_EVENT_SCROLL, &EVENT_scroll_up);

However, what is observed in the above code execution is the millis introduces delay correctly, but the first focus scroll in the first event to scroll down is missed completely (no visible change in UI to focus but I do see the printfs from event handler), and only the 2nd event is picked up so the focus only takes places for the 2nd event, so on the 2nd button in the UI.

If I remove the 2nd event call for scroll up, so only the first event is sent then I do see the scroll down event correctly taking place with focus happening correctly with scroll, where 9th element in the list gets focused.

So what I am observing from this above and many similar experiments the millis is introducing delay correctly, but the expected behavior of focus scroll happens only for the last event always (the events get entered correctly in the handler as I can see printfs in sequence of execution, but focus scroll goes missing), whereas the focus autoscroll takes place on the element in the last event call ignoring all other calls before. Situation in loop based execution is similar as well. So I tried executing first the simple case of a sequential event firing one after another to make it simple.

In a nutshell event handler routines are entered correctly (verified by printfs) in the sequence of event calls, however, the focus scroll does not happen correctly for lv_group_focus_obj(currentButton); in sequence as expected.

Do you have any suggestions in this context because I think when you gave me feedback to use millis() to introduce delay to invoke multiple events in sequence you meant to do some thing similar earlier. Please let me know if this sounds odd or correct behavior in sequential event handling that are fired manually with delay introduced using mills() in between two event firing, and how can I get two events fired in the above case to see visual movement in UI from scroll down to scroll up for example.

Hmm you missunderstand and replace delay with othor blocking delay.
Learn how write event based code…

void loop()
{
static long nextevent = millis() + 1000;
static uint8_t evnum=1;
lv_timer_handler();
delay(5);

if(nextevent < millis() )
{
// do first event
switch (evnum) {
case 1; ...
}
evnum++;
//set next event time
nextevent = millis() + 7000;
}

}
1 Like

Thanks for this input. Highly appreciated. I am not familiar with the timer and related system in LVGL and related event based programming aspects, so I am trying to get a hang of it now from various sources, as it seems there is no direct material that talks directly about the relation between these various concepts, as if you look at solely events, it only talks about the event handling the way I have done, without going in details about its linkage to timers directly etc.

When you read docu , lvgl isnt thread safe and as any other gui system must be handled with not blocking code. This result if use one thread never use delay more as one screen refresh setup in conf. (default is 30ms normal is 16ms) All screen and indev refresh lvgl do automatic inside call lv_timer_handler.

1 Like

Thanks. Yes, I think I am kind of getting there to understand these basics after lot of related context reading.

I just did one simple experiment to understand the effect of lv_timer_handler on serial monitor prints. So instead of putting lv_timer_handler in regular loop() for a simple sketch, I put it in the sketch towards the end of the setup () with only one lv_timer_handler(); delay(5); and I noticed that the GUI showed up as white blank screen as I am not printing anything on GUI but just initialized it with LVGL in setup(), and then in a timer callback function created in the setup() that I have set, calling a serial.printf by creating that timer in the setup() as well.

In this setup I observed that a single printf to indicate Setup is finished (Setup is finished) is getting printed on serial.monitor, however, the printf from timer callback function my_timer is not getting printed.

I had thought since I am calling lv_timer_handler(); delay(5); towards end of setup after the timer is created, the timer callback should have got called at least once, to atleast do one printf from timer callback function, as afterwards since I think as my lv_timer_handler is not in a loop the timer handler function callback to print the serial.monitor will not get called repeatedly.

Did experiment with putting different times instead of 2 in the timer creation that are bigger than 5, such as 300, 500 etc. to see if that affects anything due to delays etc. but never got the printf from my_timer on serial monitor printed. So that kind of left me puzzling what is exactly happening here, as I thought lv_timer_handler is responsible to call the timer functions etc if its in a loop, but I tried checking in this experiment if its not in a loop does it still call the timer function callback at least once if setup before, but it looks like it does not. The GUI in this entire setup once it becomes white initially stays white throughout, so I believe once the pixels are drawn the screen just stays on without getting refreshed ever, since lv_timer_handler is called only once.

// there is timer call back my_timer() here with a serial.printf statement 

// There is a setup lvgl code here in setup()
  lv_timer_t * timer = lv_timer_create(my_timer, 2,  &user_data);
  Serial.printf("Setup is now finished\n");
  lv_timer_handler();
  delay(5);
}

void loop()
{
}

Just trying to get a feel of the things here with simple experiments to understand how these things work together etc.

Hi Marian

So I was able to get the scrolling working using the event based switch code and further modifying to actually what I wanted to do now. So I think I understand this part pretty good now. Thanks much for all your help in this process especially the intro to the event based and lv_timer_handler and its importance as it was not clear to me for what I wanted to do. I am still not sure though I understand what’s happening in the experimental case I posted in the reply earlier for timer creation and printf experiment.

simply this name need understand as lv_all_handler and inside is many ifs for required LV tasks. One of ifs check is some planed timer ontime ? if no handler simply returns and do nothink. And your setup test have one error delay 5 placed after task check = do nothink.

Thanks. I looked at the source of lv_timer_handler and did some more experiments and now i understand what is going on in my code exactly. Appreciate the pointers. Thanks.