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

Custom scroll path support #2434

Closed
XuNeo opened this issue Aug 5, 2021 · 23 comments
Closed

Custom scroll path support #2434

XuNeo opened this issue Aug 5, 2021 · 23 comments
Labels

Comments

@XuNeo
Copy link
Collaborator

XuNeo commented Aug 5, 2021

Introduce the problem

The default scroll event will map input device coordinates linearly to object coordinates. It's processed by lvgl by default.

Examples and cases

However, bellow example requires a custom scroll path and an object scale model.

demo

The only solution I can come out is using pressing event, disable scrollable event and manually set the coordinates. But this won't work when children object exists that parent object won't get this event, thus won't work.

Suggested solution

Maybe a custom scroll process handler by user to override default behavior in _lv_indev_scroll_handler, _lv_indev_scroll_throw_handler etc.?

cc @FASTSHIFT

Regards,

@kisvegabor
Copy link
Member

Maybe a custom scroll process handler by user to override default behavior in _lv_indev_scroll_handler, _lv_indev_scroll_throw_handler etc.?

IMO these functions are too complicated to make the users reimplement them. I'm rather thinking about a callback for the indev driver like:

void my_diff_modifier(lv_indev_drv_t * drv, lv_point_t * pressed_point, lv_coord_t * d)
{
  //Dummy example slower scroll on the top sections
   if(pressed_point->y < 200)  *d = (*d) / 2; 
}

It makes LVGL believe you have scrolled less or more depending on various conditions. What do you think?

In a project I also need a feature like this but it was much simpler. I just wanted to scroll slower at "half steps".

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 18, 2021

Hi @kisvegabor, I agree those functions are too complicated to expose them to user.
A simple diff_modifier callback would not be enough for my situation.
I think the info it needs would be:

  • The original position of object pressed down.
  • The distance indev has moved since the begining.
  • A customized callback function to move children.

The new position of object can be calculated with a function which defines object movement path.
User can do simple linear map between pointer with object position, or complicated calculation including slowing down etc.

I'm not sure if there are better methods. I will investigate more these days.
Appreciate if any ideas. Thanks!

Edit: typo fix.

@kisvegabor
Copy link
Member

Isn't the task is to change the scroll "sensitivity" based on the position on the screen? E.g. scroll less when the object is zoomed out?

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 18, 2021

I may have mixed too issues here: zoom while scroll.

The title "scroll path support" means that the object is limited to a path when scrolling. Please check below example. The object coordinates is actually following the path of this round screen.

object scroll with zoom

The scale of object(image in this case) is a function of its x coordinates. Basically it makes sure object always stays inside of the round screen.

@kisvegabor
Copy link
Member

The title "scroll path support" means that the object is limited to a path when scrolling. Please check below example. The object coordinates is actually following the path of this round screen.

And is it managed on the "scroll handler's" level? So as the y coordinate changes you adjust the x coordinate too?

If so I made something similar using only LV_EVENT_SCROLL:
https://docs.lvgl.io/master/examples.html#translate-on-scroll

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 18, 2021

That basically what I needed. Thank you! I never thought to use style-translate.
I will give it a try and let you know if all good.

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 19, 2021

Hi @kisvegabor ,
I'm afraid my problem still exists. The core issue here is scroll handler directly set the children coordinates with same diff_x(y).

The situation here is that object will resize when scrolling, thus not all children should move same distance. That's the reason I looked low level _lv_indev_scroll_handler.

Using the tranlate style, it simply adds offset to coordinates that scroll handler calculates. When object size changes, it can't take object new size into consideration. Here is a screenshot to explain the issue: children moves faster than mouse and snap to center won't work because of wrong distance calculated.

Do you have any suggestions?

Peek 2021-08-19 19-47

@kisvegabor
Copy link
Member

kisvegabor commented Aug 19, 2021

The situation here is that object will resize when scrolling, thus not all children should move same distance. That's the reason I looked low level _lv_indev_scroll_handler.

Correct me if I'm wrong but it seems to me that if you know the

  • pressed object
  • its position
  • the indented scroll distance

you can change the scroll distance accordingly. That is a simple callback can work.

The logic is to scroll less as you get closer to the top/bottom edge of the screen, isn't it?

BTW, the rendering issue with opacity is already fixed 🙂

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 19, 2021

Yes. But where the callback should be made? And this callback should move the children accordingly, right?
Then this callback should also do the elastc and throw effect?

The logic is actuall make sure object being pressed follow mouse and others move accordingly. It may seem like scroll less but may need precise method to calculate distance.

BTW, the rendering issue with opacity is already fixed 🙂

Yes, we are currently using the release version.

@kisvegabor
Copy link
Member

Yes. But where the callback should be made? And this callback should move the children accordingly, right?

I imagine it like this:

  1. LVGL is about to scroll 20 px but it'll call a callback to let you modify that 20 px.
  2. In the callback you see that the press is close to the top of the screen and with some math magic you make 12px from 20px.
  3. LVGL will scroll 12 px.

This way you keep all the complex scrolling logic in LVGL (e.g. throw and elastic scroll)

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 19, 2021

Yes, I agree. The callback should include all info you concluded to do the math. What about the position of children? Will there be a callback for every children?

@kisvegabor
Copy link
Member

My idea is to add it to the indev driver. So you can modify only the scroll difference and let LVGL draw everything.

Can you share to code of this UI. I can create something experimental to demonstrate what I mean.

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 25, 2021

Thank you Gabor. The code is based on lv_example_scroll_6. Below is the full code I used.
In the mean time, we are trying to implement code based on pressing event. The top container's all children have been set to bubble the events. Thank you for spending time investigating it.

modified lv_example_scroll_6.c

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

static void scroll_event_cb(lv_event_t * e)
{
    lv_obj_t * cont = lv_event_get_target(e);

    lv_area_t cont_a;
    lv_obj_get_coords(cont, &cont_a);
    lv_coord_t cont_y_center = cont_a.y1 + lv_area_get_height(&cont_a) / 2;

    lv_coord_t r = lv_obj_get_height(cont) * 3 / 10;
    uint32_t i;
    uint32_t child_cnt = lv_obj_get_child_cnt(cont);
    for(i = 0; i < child_cnt; i++) {
        lv_obj_t * child = lv_obj_get_child(cont, i);
        lv_area_t child_a;
        lv_obj_get_coords(child, &child_a);

        lv_coord_t child_y_center = child_a.y1 + lv_area_get_height(&child_a) / 2;

        lv_coord_t diff_y = child_y_center - cont_y_center;
        diff_y = LV_ABS(diff_y);

        /*Get the x of diff_y on a circle.*/
        lv_coord_t x;
        /*If diff_y is out of the circle use the last point of the circle (the radius)*/
        if(diff_y >= r) {
            x = r;
        } else {
            /*Use Pythagoras theorem to get x from radius and y*/
            lv_coord_t x_sqr = r * r - diff_y * diff_y;
            lv_sqrt_res_t res;
            lv_sqrt(x_sqr, &res, 0x8000);   /*Use lvgl's built in sqrt root function*/
            x = r - res.i;
            x = LV_MAX(0, x);
        }

        /*Translate the item by the calculated X coordinate*/
        lv_obj_set_style_translate_x(child, x, 0);

        /*Use some opacity with larger translations*/
        lv_opa_t opa = lv_map(x, 0, r, LV_OPA_TRANSP, LV_OPA_COVER);
        lv_obj_set_style_opa(child, LV_OPA_COVER - opa, 0);

        lv_coord_t w = lv_map(r - x, 0, r, 0, 330);
        lv_coord_t h = lv_map(r - x, 0, r, 0, 80);

        w = LV_MAX(10, w);
        h = LV_MAX(10, h);
        LV_LOG_WARN("x: %d, w:%d, h: %d\n", x, w, h);
        lv_obj_set_style_width(child, w, 0);
        lv_obj_set_style_height(child, h, 0);
    }
}

/**
 * Translate the object as they scroll
 */
void lv_example_scroll_6(void)
{
    lv_obj_t * cont = lv_obj_create(lv_scr_act());
    lv_obj_set_size(cont, 400, 400);
    lv_obj_set_style_border_width(cont, 5, 0);

    // lv_obj_center(cont);
    lv_obj_align(cont, LV_ALIGN_RIGHT_MID, 0, 0);
    lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);
    lv_obj_add_event_cb(cont, scroll_event_cb, LV_EVENT_SCROLL, NULL);
    lv_obj_set_style_radius(cont, LV_RADIUS_CIRCLE, 0);
    lv_obj_set_style_clip_corner(cont, true, 0);
    lv_obj_set_scroll_dir(cont, LV_DIR_VER);
    lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER);
    lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF);

    uint32_t i;
    for(i = 0; i < 20; i++) {
        lv_obj_t * btn = lv_btn_create(cont);
        lv_obj_set_width(btn, lv_pct(100));

        lv_obj_t * label = lv_label_create(btn);
        lv_label_set_text_fmt(label, "Button %d", i);
    }

    /*Update the buttons position manually for first*/
    lv_event_send(cont, LV_EVENT_SCROLL, NULL);

    /*Be sure the fist button is in the middle*/
    lv_obj_scroll_to_view(lv_obj_get_child(cont, 0), LV_ANIM_OFF);
}

#endif

@kisvegabor
Copy link
Member

I've tested you code but found that the buttons on the top and bottom are flickering. I suppose it's because when you modify the width the X coordinate will change too. And that modified X coordinate is used in the next scroll event to calculate the width.

s

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 25, 2021

Yes, I noticed that too. That's my concern too. Using scroll function together with changing object size may have interferences.
If you move upward further, move button4 to center, then you can see that distance objects moved is much larger than mouse moved.

@kisvegabor
Copy link
Member

I could fix the flickering by modifying the event like this:

        lv_obj_set_style_width(child, w, 0);
        lv_obj_set_style_height(child, h, 0);
        lv_obj_update_layout(child);   //Add this

The other problem I found (and can't see the reason now) is that if the object is snapped incorrectly.
s

@XuNeo
Copy link
Collaborator Author

XuNeo commented Aug 25, 2021

The other problem I found (and can't see the reason now) is that if the object is snapped incorrectly.

That's all because object size changed, distance snap function calculated is too far if object is zoomed in, I think.

@kisvegabor
Copy link
Member

I agree. If I comment out the height change it works well. But the error on snapping seems too large.

@lvgl-bot
Copy link

lvgl-bot commented Sep 9, 2021

This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@XuNeo
Copy link
Collaborator Author

XuNeo commented Sep 10, 2021

Hi @kisvegabor ,
I have made some progress using raw pressing event with the help of @FASTSHIFT. See the pull request for codes and below demo.

circle-list-using-pressing-event

The scrollable flag must be cleared in order to manully layout the list. This comes at a cost of no elastic effect, no default throw handler and no snap to center function. I'm going to simply copy relevant code to widget to fill the gap.

What do you think? Should I continue to finish the functionality or seeking for other solutions like the scroll-diff-modifier?

Regards,

@lvgl-bot lvgl-bot removed the stale label Sep 11, 2021
@kisvegabor
Copy link
Member

Nice! I'm just reviewing the PR and will comment there.

@lvgl-bot
Copy link

lvgl-bot commented Oct 5, 2021

This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@lvgl-bot lvgl-bot added the stale label Oct 5, 2021
@lvgl-bot
Copy link

This issue was closed because it has been stalled for 7 days with no activity.

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

No branches or pull requests

3 participants