Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LVGL] Support groups #229

Closed
3 tasks done
fietser28 opened this issue Dec 26, 2022 · 27 comments
Closed
3 tasks done

[LVGL] Support groups #229

fietser28 opened this issue Dec 26, 2022 · 27 comments

Comments

@fietser28
Copy link
Collaborator

fietser28 commented Dec 26, 2022

Please add support for groups (needed for keys and encoders):

  • Have a tab with the list of groups (like Global vars). The list of objects assigned to a group should be orderable (like a struct or an enum), maybe per screen?
  • Add a group property to objects: Select the groups the object belongs to (multiple selections possible?)
  • On screen load event assigns objects to the selected groups in the right order.
@mvladic
Copy link
Contributor

mvladic commented Sep 23, 2024

I'm finally ready to implement this feature. In the meantime a new feature request, similar to this one, has been created: #487.

I'm taking into consideration what you proposed in this issue and what is proposed in #487. I want this to be as simple as possible.

Have a tab with the list of groups (like Global vars). The list of objects assigned to a group should be orderable (like a struct or an enum), maybe per screen?

Do we need another ordering? We already have an ordered list of widgets inside the page. So, we can use this also for the groups.

And can be added a input device (maybe also in the settings of the created group) like so lv_indev_set_group(encoder_indev, group).

I'm thinking how this should be implemented. To call lv_indev_set_group we need input_device parameter. We can have an optional property called "Target input devices" for the group definition in the project. With this property we can specify one or more input device names. Input device name is arbitrary name chosen by the user. If specified then a new variable in the screens.h/cpp will be added. For example if for some screen we defined group named group1 and for the "Target input devices" we specified encoder_indev keyboard_indev then following code will be generated:

In screens.h:

lv_group_t *group1;

lv_indev_t *encoder_indev;
lv_indev_t *keyboard_indev;

Note: User should assign to encoder_indev and keyboard_indev variables before ui_init is called. If not assigned then this variables will be NULL and no lv_indev_set_group will be called.

In screens.cpp, I will generate this code:

    // ...
    group1 = lv_group_create();
    lv_group_add_obj(group1, objects.obj1);
    lv_group_add_obj(group1, objects.obj2);
    lv_group_add_obj(group1, objects.obj3);
    // ...

And in the screen load event handler I will generate this code:

if (encoder_indev) {
    lv_indev_set_group(encoder_indev, group1);
}

if (keyboard_indev) {
    lv_indev_set_group(keyboard_indev, group1);
}

@mvladic
Copy link
Contributor

mvladic commented Sep 23, 2024

Some clarifications:

  • we can have multiple groups per single page/screen
  • each widget can be member of multiple groups
  • so, for example, we can have one group for encoder and another for the keyboard and some widgets can be in both groups, only in encoder group, only in keyboard group or in no group

@mvladic
Copy link
Contributor

mvladic commented Sep 23, 2024

Another way to implement this is to leave to the user to call lv_indev_set_group for each input device. In that case, screen load event will look like this:

lv_group_remove_all_objs(encoder_group);
lv_group_add_obj(encoder_group, objects.obj1);
lv_group_add_obj(encoder_group, objects.obj2);
lv_group_add_obj(encoder_group, objects.obj3);

So, we first remove all the objects from the group from previous screen and add a new set of object for the current screen.

This means that groups are global not per page. I think this is simpler model and maybe what you originally proposed.

@fietser28
Copy link
Collaborator Author

Do we need another ordering? We already have an ordered list of widgets inside the page. So, we can use this also for the groups.

I do think we need a different ordering: The order in the group dictates the order in which widgets are selected when moving between them with keys. For example: pressing the button of an encoder moves to the next widget (slider for example). It also determines the widget that is selected when the page is entered.

The order in the page determines also the 'Z axis' on the screen. Combining this into a single ordering might cause impossible situations?

The generated code should probably also call lv_group_focus_obj() with the first object when entering a page.

@mvladic
Copy link
Contributor

mvladic commented Sep 23, 2024

Yes, probably there are cases when you want different order.

The generated code should probably also call lv_group_focus_obj() with the first object when entering a page.

This is not default behaviour for LVGL. If encoder is not used then why should any widget be in focus?

@fietser28
Copy link
Collaborator Author

You are correct. It deviates from LVGL default so it should not be default setting.
I'm using it, but it should be a separate action.

This means that groups are global not per page. I think this is simpler model and maybe what you originally proposed.

Looks fine to me: Groups are global and for each group there is a ordered list for each screen: On a screen load only the objects in the ordered list of this specific screen of this specific group are loaded into the group. This is done for every group.
Eh, I'm still making sense?

@mvladic
Copy link
Contributor

mvladic commented Sep 24, 2024

I just found that specific object can be only inside one group at the time. Here is the relevant code from the LVGL:

image

This is surprise to me, I didn't expect that. Is that makes sense to you?

@mvladic
Copy link
Contributor

mvladic commented Sep 24, 2024

And there is an API call lv_obj_get_group(obj) to see which group an object belongs to. From this it can also be concluded that the same object can only be in one group.

@mvladic
Copy link
Contributor

mvladic commented Sep 24, 2024

I was already implemented this feature with the premise that the same object can be in multiple groups:

image

Now, I need to change that.

BTW this is the code that is auto generated and the code that I needed to add manually:

image

@fietser28
Copy link
Collaborator Author

I also didn’t realize this. It might be logical from a UI perspective: how can you visualize which group (and which inputs) are active for a widget.

BTW I might have missed the ‘default group’ setting for a screen?

@mvladic
Copy link
Contributor

mvladic commented Sep 24, 2024

BTW I might have missed the ‘default group’ setting for a screen?

I think it is not very useful feature because it doesn't take care which screen is active.

@mvladic
Copy link
Contributor

mvladic commented Sep 24, 2024

I mean, the object will remain in the default group even if its parent screen is not active anymore.

@fietser28
Copy link
Collaborator Author

Clear, default is not very useful for us.

Your code example looks fine.

I compared it to my manual implementation and noticed groups has a configurable property that might be useful:

lv_group_set_wrap

Other properties don’t seem relevant for studio use cases I can think of.

Some functions might be relevant as events you can trigger via the LVGL action. This would allow use cases where pressing a specific button (or other event) selects a certain widget for your encoder or changes the group state:

lv_group_focus_obj
lv_group_focus_next
lv_group_focus_prev
lv_group_focus_freeze
lv_group_set_editing

The _obj function requires an object (widget).
The _ next and _prev skip hidden widgets

A good complex example is a device with a row of button next to the screen and 1 generic encoder with ‘<‘ and ‘>’ keys:

  • Pressing one of the buttons next to the screen will select a specific widget for the encoder group. This highly depends on the screen state you are in.
  • Pressing the ‘<‘ key (or a button on screen) will call _prev, same for >
  • Freeze/set edit can be used when you create some kind of pop-up/overlay to lock you into this input.
  • I think LVGL doesn’t focus on any object if you load a screen and add objects to a group. The above functions allow a user to determine the status when a screen is entered.
    For example: In my device on entering the main screen I want the encoder focus to be on the spinbox in edit mode. I now handle this in code.

@mvladic mvladic changed the title LVGL flow: support groups [LVGL] Support groups Sep 25, 2024
mvladic added a commit that referenced this issue Sep 25, 2024
@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

Group ordering can be defined with the "Group index" property of the widget:

image

This is similar to tabindex in HTML:

  • if "Group index" is 0 then group order is the same as in Widgets Structure
  • if "Group index" is > 0 then widget is added to the group before any widget with "Group index" 0 and before any widget with the greater "Group index" value. That is, "Group index"=4 is added before "Group index"=5 and "Group index"=0, but after "Group index"=3. If multiple widgets share the same "Group index" value, their order relative to each other follows their position in the Widgets Structure.

@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

We can select one group to be used for the encoder input in simulator:

image

and one group for the keyboard:

image

The same group can be used for both.

mvladic added a commit that referenced this issue Sep 25, 2024
@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

Here you can see which widgets are in the group for the selected group and page:

image

mvladic added a commit that referenced this issue Sep 25, 2024
@fietser28
Copy link
Collaborator Author

Here you can see which widgets are in the group for the selected group and page:

Looks great! Can’t wait to use this and remove some code.
Are the widgets in the widget group panel ordered by the given index number? In that way you can see the expected order of focus with next/previous.

@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

Are the widgets in the widget group panel ordered by the given index number? In that way you can see the expected order of focus with next/previous.

Yes.

@fietser28
Copy link
Collaborator Author

fietser28 commented Sep 25, 2024

I gave it a try on real hardware with an encoder and it is working great!

--delete limitation with user actions, tested it not good enough --

@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

User widgets are currently not supported. I'm adding group actions in LVGL action and after that I will see how to support user widgets.

@fietser28
Copy link
Collaborator Author

FYI: My test use case for a user action looks like this:
image
It is an overlay with 2 buttons. It might be to complicated.

@mvladic
Copy link
Contributor

mvladic commented Sep 25, 2024

Added LVGL group actions:

image

Also, I changed how group variables are declared. I needed to put it inside structure (similar to objects), so I can access it sequentially by the index, i.e. to get group object from its index (this is required in the implementation of the group actions in LVGL action):

image

mvladic added a commit that referenced this issue Sep 26, 2024
mvladic added a commit that referenced this issue Sep 26, 2024
@mvladic
Copy link
Contributor

mvladic commented Sep 26, 2024

Groups are now supported for the user widgets too.

@fietser28
Copy link
Collaborator Author

I'm having an issue with the group support in user widgets. I can add widgets from a user widget to a group but the group membership is not loaded upon screen load.

This is my user widget:
image
(Yes and No button are member of encoder group, but this is not shown in the encoder group panel here)

This is my screen (with the above widget in it):
image
(note the Yes and No button are not shown in the group panel)

The source code generated is (yes no button group adds are missing):


static void event_handler_cb_ranges_ranges(lv_event_t *e) {
    lv_event_code_t event = lv_event_get_code(e);
    void *flowState = lv_event_get_user_data(e);
    if (event == LV_EVENT_SCREEN_LOAD_START) {
        e->user_data = (void *)0;
        flowPropagateValueLVGLEvent(flowState, 9, 0, e);
    }
    if (event == LV_EVENT_SCREEN_LOADED) {
        // group: encoder_group
        lv_group_remove_all_objs(groups.encoder_group);
        lv_group_add_obj(groups.encoder_group, objects.current_range);
        lv_group_add_obj(groups.encoder_group, objects.volt_range);
        lv_group_add_obj(groups.encoder_group, objects.sense);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_back);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_ok);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_cancel);
    }
}

Fixing it manually solves it:

static void event_handler_cb_ranges_ranges(lv_event_t *e) {
    lv_event_code_t event = lv_event_get_code(e);
    void *flowState = lv_event_get_user_data(e);
    if (event == LV_EVENT_SCREEN_LOAD_START) {
        e->user_data = (void *)0;
        flowPropagateValueLVGLEvent(flowState, 9, 0, e);
    }
    if (event == LV_EVENT_SCREEN_LOADED) {
        // group: encoder_group
        lv_group_remove_all_objs(groups.encoder_group);
        lv_group_add_obj(groups.encoder_group, objects.current_range);
        lv_group_add_obj(groups.encoder_group, objects.volt_range);
        lv_group_add_obj(groups.encoder_group, objects.sense);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_back);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_ok);
        lv_group_add_obj(groups.encoder_group, objects.nlpc_cancel);
        lv_group_add_obj(groups.encoder_group, objects.obj82__sure_but_no);
        lv_group_add_obj(groups.encoder_group, objects.obj82__sure_but_yes);
    }
}

@mvladic
Copy link
Contributor

mvladic commented Sep 26, 2024

Are you sure you have the latest version?

@fietser28
Copy link
Collaborator Author

I missed a few commits. It is working now in HW and simulator!

mvladic added a commit that referenced this issue Sep 28, 2024
@mvladic
Copy link
Contributor

mvladic commented Sep 28, 2024

I've added ui_create_groups function in screens.h, so groups can be created before ui_init, like this:

#include "src/ui.h"
#include "src/screens.h"

// ...

ui_create_groups();

lv_indev_set_group(enc_indev, groups.encoder_group);
lv_indev_set_group(kb_indev, groups.keyboard_group);

ui_init();

If ui_create_groups is not called explicitly then groups will be still created in ui_init. For example, this also works:

ui_init();
lv_indev_set_group(enc_indev, groups.encoder_group);
lv_indev_set_group(kb_indev, groups.keyboard_group);

Apparently, the first way is required on LVGL 9.x otherwise focus on first group member by default will not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants