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
584591typedef 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 */
33953496static 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