Skip to content

Commit fde5a9d

Browse files
committed
Overscroll with spring
1 parent 7857c36 commit fde5a9d

1 file changed

Lines changed: 158 additions & 11 deletions

File tree

menu/drivers/materialui.c

Lines changed: 158 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
/* Animation defines */
100100
#define MUI_ANIM_DURATION_SCROLL (166.66667f)
101101
#define MUI_ANIM_DURATION_SCROLL_RESET (83.333333f)
102+
#define MUI_OVERSCROLL_RESISTANCE 0.35f
103+
#define MUI_OVERSCROLL_MAX_FRACTION 0.25f
104+
#define MUI_OVERSCROLL_SPRING_STIFFNESS 70.0f
105+
#define MUI_OVERSCROLL_SPRING_DAMPING 14.0f
106+
#define MUI_OVERSCROLL_STOP_DISTANCE 0.5f
107+
#define MUI_OVERSCROLL_STOP_VELOCITY 5.0f
102108
/* According to Material UI specifications, animations
103109
* that affect a large portion of the screen should
104110
* have a duration of between 250ms and 300ms. This
@@ -578,7 +584,8 @@ enum materialui_handle_flags
578584
MUI_FLAG_SCROLLBAR_ACTIVE = (1 << 25),
579585
MUI_FLAG_SCROLLBAR_DRAGGED = (1 << 26),
580586
MUI_FLAG_NAVBAR_MENU_NAVIGATION_WRAPPED = (1 << 27),
581-
MUI_FLAG_COL_DIVIDER_IS_LIST_BG = (1 << 28)
587+
MUI_FLAG_COL_DIVIDER_IS_LIST_BG = (1 << 28),
588+
MUI_FLAG_OVERSCROLL_ACTIVE = (1 << 29)
582589
};
583590

584591
typedef struct materialui_handle
@@ -697,6 +704,8 @@ typedef struct materialui_handle
697704
float thumbnail_stream_delay;
698705
float fullscreen_thumbnail_alpha;
699706
float touch_feedback_alpha;
707+
float overscroll_velocity;
708+
float overscroll_target;
700709
int16_t pointer_start_x;
701710
int16_t pointer_start_y;
702711
bool transition_alpha_lock;
@@ -3390,6 +3399,98 @@ static float materialui_get_scroll(materialui_handle_t *mui,
33903399
return selection_centre - view_centre;
33913400
}
33923401

3402+
static INLINE float materialui_get_scroll_y_max(
3403+
materialui_handle_t *mui, unsigned height,
3404+
unsigned header_height)
3405+
{
3406+
float scroll_y_max = mui->content_height - (float)height +
3407+
(float)header_height + (float)mui->nav_bar_layout_height +
3408+
(float)mui->status_bar.height;
3409+
3410+
return (scroll_y_max > 0.0f) ? scroll_y_max : 0.0f;
3411+
}
3412+
3413+
static INLINE float materialui_get_overscroll_max(
3414+
materialui_handle_t *mui, unsigned height,
3415+
unsigned header_height)
3416+
{
3417+
float view_height = (float)height - (float)header_height -
3418+
(float)mui->nav_bar_layout_height - (float)mui->status_bar.height;
3419+
3420+
return (view_height > 0.0f) ?
3421+
view_height * MUI_OVERSCROLL_MAX_FRACTION : 0.0f;
3422+
}
3423+
3424+
static INLINE float materialui_apply_overscroll(
3425+
float scroll_y, float scroll_y_max, float overscroll_max)
3426+
{
3427+
if (scroll_y < 0.0f)
3428+
{
3429+
scroll_y *= MUI_OVERSCROLL_RESISTANCE;
3430+
3431+
if (scroll_y < -overscroll_max)
3432+
scroll_y = -overscroll_max;
3433+
}
3434+
else if (scroll_y > scroll_y_max)
3435+
{
3436+
scroll_y = scroll_y_max +
3437+
((scroll_y - scroll_y_max) * MUI_OVERSCROLL_RESISTANCE);
3438+
3439+
if (scroll_y > scroll_y_max + overscroll_max)
3440+
scroll_y = scroll_y_max + overscroll_max;
3441+
}
3442+
3443+
return scroll_y;
3444+
}
3445+
3446+
static INLINE float materialui_clamp_scroll(
3447+
float scroll_y, float scroll_y_max)
3448+
{
3449+
if (scroll_y < 0.0f)
3450+
return 0.0f;
3451+
3452+
if (scroll_y > scroll_y_max)
3453+
return scroll_y_max;
3454+
3455+
return scroll_y;
3456+
}
3457+
3458+
static INLINE float materialui_abs_float(float value)
3459+
{
3460+
return (value < 0.0f) ? -value : value;
3461+
}
3462+
3463+
static void materialui_update_overscroll_spring(
3464+
materialui_handle_t *mui, float target, float delta_time)
3465+
{
3466+
float delta_time_sec;
3467+
float displacement;
3468+
float acceleration;
3469+
3470+
if (delta_time <= 0.0f)
3471+
return;
3472+
3473+
delta_time_sec = delta_time / 1000.0f;
3474+
3475+
/* Prevent a long frame from injecting excessive energy. */
3476+
if (delta_time_sec > 0.033333f)
3477+
delta_time_sec = 0.033333f;
3478+
3479+
displacement = mui->scroll_y - target;
3480+
acceleration = (-MUI_OVERSCROLL_SPRING_STIFFNESS * displacement) -
3481+
(MUI_OVERSCROLL_SPRING_DAMPING * mui->overscroll_velocity);
3482+
mui->overscroll_velocity += acceleration * delta_time_sec;
3483+
mui->scroll_y += mui->overscroll_velocity * delta_time_sec;
3484+
3485+
if ( (materialui_abs_float(mui->scroll_y - target) < MUI_OVERSCROLL_STOP_DISTANCE)
3486+
&& (materialui_abs_float(mui->overscroll_velocity) < MUI_OVERSCROLL_STOP_VELOCITY))
3487+
{
3488+
mui->scroll_y = target;
3489+
mui->overscroll_velocity = 0.0f;
3490+
mui->flags &= ~MUI_FLAG_OVERSCROLL_ACTIVE;
3491+
}
3492+
}
3493+
33933494
/* Returns true if specified entry is currently
33943495
* displayed on screen */
33953496
static INLINE bool materialui_entry_onscreen(
@@ -3458,7 +3559,10 @@ static INLINE void materialui_kill_scroll_animation(
34583559
menu_input->pointer.y_accel = 0.0f;
34593560

34603561
mui->flags &= ~MUI_FLAG_SCROLL_ANIMATION_ACTIVE;
3562+
mui->flags &= ~MUI_FLAG_OVERSCROLL_ACTIVE;
34613563
mui->scroll_animation_selection = 0;
3564+
mui->overscroll_velocity = 0.0f;
3565+
mui->overscroll_target = 0.0f;
34623566
}
34633567

34643568
/* ==============================
@@ -3837,7 +3941,9 @@ static void materialui_render(void *data,
38373941
bool is_idle)
38383942
{
38393943
size_t i;
3840-
float bottom;
3944+
float scroll_y_max;
3945+
float overscroll_max;
3946+
bool list_drag_active;
38413947
/* c.f. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
38423948
* On some platforms (e.g. 32-bit x86 without SSE),
38433949
* gcc can produce inconsistent floating point results
@@ -3850,6 +3956,7 @@ static void materialui_render(void *data,
38503956
settings_t *settings = config_get_ptr();
38513957
materialui_handle_t *mui = (materialui_handle_t*)data;
38523958
gfx_display_t *p_disp = disp_get_ptr();
3959+
gfx_animation_t *p_anim = anim_get_ptr();
38533960
struct menu_state *menu_st = menu_state_get_ptr();
38543961
menu_list_t *menu_list = menu_st->entries.list;
38553962
menu_input_t *menu_input = &menu_st->input_state;
@@ -3965,6 +4072,15 @@ static void materialui_render(void *data,
39654072
/* Need to adjust/range-check scroll position first,
39664073
* otherwise cannot determine correct entry index for
39674074
* MENU_ENTRIES_CTL_SET_START */
4075+
scroll_y_max = materialui_get_scroll_y_max(mui, height, header_height);
4076+
overscroll_max = materialui_get_overscroll_max(mui, height, header_height);
4077+
list_drag_active =
4078+
(mui->pointer.type != MENU_POINTER_DISABLED)
4079+
&& (!(mui->flags & MUI_FLAG_SCROLLBAR_DRAGGED))
4080+
&& (!(mui->flags & MUI_FLAG_SHOW_FULLSCREEN_THUMBNAILS))
4081+
&& (mui->pointer.flags & MENU_INP_PTR_FLG_PRESSED)
4082+
&& (mui->pointer.flags & MENU_INP_PTR_FLG_DRAGGED);
4083+
39684084
if (mui->pointer.type != MENU_POINTER_DISABLED)
39694085
{
39704086
/* If user is dragging the scrollbar, scroll
@@ -3998,22 +4114,32 @@ static void materialui_render(void *data,
39984114
{
39994115
if ( (mui->pointer.flags & MENU_INP_PTR_FLG_PRESSED)
40004116
&& (mui->pointer.flags & MENU_INP_PTR_FLG_DRAGGED))
4001-
mui->scroll_y = mui->pointer_start_scroll_y -
4002-
(float)(mui->pointer.y - mui->pointer_start_y);
4117+
{
4118+
mui->flags &= ~MUI_FLAG_OVERSCROLL_ACTIVE;
4119+
mui->overscroll_velocity = 0.0f;
4120+
mui->scroll_y = materialui_apply_overscroll(
4121+
mui->pointer_start_scroll_y -
4122+
(float)(mui->pointer.y - mui->pointer_start_y),
4123+
scroll_y_max, overscroll_max);
4124+
}
40034125
else
40044126
mui->scroll_y -= mui->pointer.y_accel;
40054127
}
40064128
}
40074129

4008-
if (mui->scroll_y < 0.0f)
4009-
mui->scroll_y = 0.0f;
4130+
if ( !list_drag_active
4131+
&& (mui->flags & MUI_FLAG_OVERSCROLL_ACTIVE))
4132+
materialui_update_overscroll_spring(
4133+
mui, materialui_clamp_scroll(mui->overscroll_target, scroll_y_max),
4134+
p_anim->delta_time);
40104135

4011-
bottom = mui->content_height - (float)height + (float)header_height +
4012-
(float)mui->nav_bar_layout_height + (float)mui->status_bar.height;
4013-
if (mui->scroll_y > bottom)
4014-
mui->scroll_y = bottom;
4136+
if ( !list_drag_active
4137+
&& (!(mui->flags & MUI_FLAG_OVERSCROLL_ACTIVE)))
4138+
mui->scroll_y = materialui_clamp_scroll(mui->scroll_y, scroll_y_max);
40154139

4016-
if (mui->content_height < (height - header_height - mui->nav_bar_layout_height - mui->status_bar.height))
4140+
if ( !list_drag_active
4141+
&& (!(mui->flags & MUI_FLAG_OVERSCROLL_ACTIVE))
4142+
&& (mui->content_height < (height - header_height - mui->nav_bar_layout_height - mui->status_bar.height)))
40174143
mui->scroll_y = 0.0f;
40184144

40194145
/* Loop over all entries */
@@ -7834,6 +7960,11 @@ static void materialui_update_scrollbar(materialui_handle_t *mui,
78347960
/* > Apply vertical padding to improve visual appearance */
78357961
mui->scrollbar.y += (int)mui->scrollbar.width;
78367962

7963+
/* > Ensure overscroll does not move the scrollbar
7964+
* outside the list region */
7965+
if (mui->scrollbar.y < (int)header_height + (int)mui->scrollbar.width)
7966+
mui->scrollbar.y = (int)header_height + (int)mui->scrollbar.width;
7967+
78377968
/* > Ensure we don't fall off the bottom of the screen... */
78387969
if (mui->scrollbar.y > y_max)
78397970
mui->scrollbar.y = y_max;
@@ -9365,6 +9496,8 @@ static void materialui_animate_scroll(materialui_handle_t *mui,
93659496

93669497
/* Kill any existing scroll animation */
93679498
gfx_animation_kill_by_tag(&animation_tag);
9499+
mui->flags &= ~MUI_FLAG_OVERSCROLL_ACTIVE;
9500+
mui->overscroll_velocity = 0.0f;
93689501

93699502
/* mui->scroll_y will be modified by the animation
93709503
* > Set scroll acceleration to zero to minimise
@@ -10974,6 +11107,8 @@ static int materialui_pointer_up(void *userdata,
1097411107
menu_list_t *menu_list = menu_st->entries.list;
1097511108
size_t entries_end = menu_list ? MENU_LIST_GET_SELECTION(menu_list, 0)->size : 0;
1097611109
materialui_handle_t *mui = (materialui_handle_t*)userdata;
11110+
float scroll_y_max;
11111+
float scroll_y_target;
1097711112

1097811113
if (!mui)
1097911114
return -1;
@@ -11010,6 +11145,18 @@ static int materialui_pointer_up(void *userdata,
1101011145

1101111146
video_driver_get_size(&width, &height);
1101211147

11148+
scroll_y_max = materialui_get_scroll_y_max(mui, height, header_height);
11149+
scroll_y_target = materialui_clamp_scroll(mui->scroll_y, scroll_y_max);
11150+
11151+
if (mui->scroll_y != scroll_y_target)
11152+
{
11153+
mui->overscroll_velocity = -mui->pointer.y_accel * 60.0f;
11154+
mui->overscroll_target = scroll_y_target;
11155+
menu_input->pointer.y_accel = 0.0f;
11156+
mui->flags |= MUI_FLAG_OVERSCROLL_ACTIVE;
11157+
return 0;
11158+
}
11159+
1101311160
switch (gesture)
1101411161
{
1101511162
case MENU_INPUT_GESTURE_TAP:

0 commit comments

Comments
 (0)