diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index fcf3612c39..f393d6ad86 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -3038,42 +3038,57 @@ static int xmb_draw_item( && !string_is_empty(entry->sublabel)) { char entry_sublabel[MENU_SUBLABEL_MAX_LENGTH]; + char entry_sublabel_top_fade[MENU_SUBLABEL_MAX_LENGTH >> 2]; + char entry_sublabel_bottom_fade[MENU_SUBLABEL_MAX_LENGTH >> 2]; menu_animation_ctx_line_ticker_t line_ticker; - menu_animation_ctx_line_ticker_smooth_t line_ticker_smooth = {0}; - unsigned ticker_line_height = 0; - unsigned ticker_num_lines = 0; - float ticker_y_offset = 0.0f; - bool do_scissor = false; - float sublabel_x = 0.0f; - float sublabel_y = 0.0f; + menu_animation_ctx_line_ticker_smooth_t line_ticker_smooth; + float ticker_y_offset = 0.0f; + float ticker_top_fade_y_offset = 0.0f; + float ticker_bottom_fade_y_offset = 0.0f; + float ticker_top_fade_alpha = 0.0f; + float ticker_bottom_fade_alpha = 0.0f; + float sublabel_x = node->x + xmb->margins_screen_left + + xmb->icon_spacing_horizontal + xmb->margins_label_left; + float sublabel_y = xmb->margins_screen_top + + node->y + (xmb->margins_label_top * 3.5f); - entry_sublabel[0] = '\0'; + entry_sublabel[0] = '\0'; + entry_sublabel_top_fade[0] = '\0'; + entry_sublabel_bottom_fade[0] = '\0'; if (use_smooth_ticker) { - line_ticker_smooth.scissor_enabled = true; - line_ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - line_ticker_smooth.idx = menu_animation_get_ticker_pixel_idx(); + line_ticker_smooth.fade_enabled = true; + line_ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + line_ticker_smooth.idx = menu_animation_get_ticker_pixel_idx(); - line_ticker_smooth.font = xmb->font2; - line_ticker_smooth.font_scale = 1.0f; + line_ticker_smooth.font = xmb->font2; + line_ticker_smooth.font_scale = 1.0f; - line_ticker_smooth.field_width = (unsigned)(xmb->font2_size * 0.6f * line_ticker_width); + line_ticker_smooth.field_width = (unsigned)(xmb->font2_size * 0.6f * line_ticker_width); /* The calculation here is incredibly obtuse. I think * this is correct... (c.f. xmb_item_y()) */ - line_ticker_smooth.field_height = (unsigned)( + line_ticker_smooth.field_height = (unsigned)( (xmb->icon_spacing_vertical * ((1 + xmb->under_item_offset) - xmb->active_item_factor)) - - (xmb->margins_label_top * 4.0f)); /* Should be 3.5f, but prefer the extra padding */ + (xmb->margins_label_top * 3.5f) - + xmb->under_item_offset); /* This last one is just a little extra padding (seems to help) */ - line_ticker_smooth.src_str = entry->sublabel; - line_ticker_smooth.dst_str = entry_sublabel; - line_ticker_smooth.dst_str_len = sizeof(entry_sublabel); + line_ticker_smooth.src_str = entry->sublabel; + line_ticker_smooth.dst_str = entry_sublabel; + line_ticker_smooth.dst_str_len = sizeof(entry_sublabel); + line_ticker_smooth.y_offset = &ticker_y_offset; - line_ticker_smooth.line_height = &ticker_line_height; - line_ticker_smooth.num_lines = &ticker_num_lines; - line_ticker_smooth.y_offset = &ticker_y_offset; + line_ticker_smooth.top_fade_str = entry_sublabel_top_fade; + line_ticker_smooth.top_fade_str_len = sizeof(entry_sublabel_top_fade); + line_ticker_smooth.top_fade_y_offset = &ticker_top_fade_y_offset; + line_ticker_smooth.top_fade_alpha = &ticker_top_fade_alpha; - do_scissor = menu_animation_line_ticker_smooth(&line_ticker_smooth); + line_ticker_smooth.bottom_fade_str = entry_sublabel_bottom_fade; + line_ticker_smooth.bottom_fade_str_len = sizeof(entry_sublabel_bottom_fade); + line_ticker_smooth.bottom_fade_y_offset = &ticker_bottom_fade_y_offset; + line_ticker_smooth.bottom_fade_alpha = &ticker_bottom_fade_alpha; + + menu_animation_line_ticker_smooth(&line_ticker_smooth); } else { @@ -3096,67 +3111,28 @@ static int xmb_draw_item( label_offset = - xmb->margins_label_top; - /* Base draw position */ - sublabel_x = node->x + xmb->margins_screen_left + - xmb->icon_spacing_horizontal + xmb->margins_label_left; - sublabel_y = xmb->margins_screen_top + - node->y + (xmb->margins_label_top * 3.5f); - - if (do_scissor) - { - /* We are currently 'blending', so stop */ - menu_display_blend_end(video_info); - /* These font shenanigans seem to be requied before - * calling menu_display_scissor_begin() */ - font_driver_flush(video_info->width, video_info->height, xmb->font2, video_info); - xmb->raster_block2.carr.coords.vertices = 0; - /* TODO/FIXME - * Okay, font handling in RetroArch sucks... - * - It seems that text is drawn relative to the baseline, - * which kinda-sorta makes sense... - * - But there's no way to extract any useful font metrics - * such as descender/ascender height and baseline position - * So basically, when drawing text you pick a y postion and - * hope for the best - the text will appear somewhere near the - * place you want it to, but not quite, and trying to clip text - * to a specified draw area is basically impossible. - * The *correct* way to implement a font library/handler is to - * deal with all font metrics internally such that the y draw - * position is the vertical centre of the line. Since you know - * the line height, this makes all layout operations trivial. - * This should be done at some point, but I don't have time - * to rewrite all of the font handling code for the sake of a - * single line ticker, so for now we'll just make do... - * So here it is: we want to clip the scrolling text such that - * it fills a vertical region eqivalent to the maximum number - * of static sublabel lines that can be shown. Since we don't - * know any font metrics, we have to use a fudge factor for - * the scissor start position (the 0.75 comes from the fact that - * for a typical font, the descender is ~20-30% of the line - * height). *This is not robust*, but it works well enough for - * all existing XMB themes */ - menu_display_scissor_begin( - video_info, (int)sublabel_x, (int)((float)sublabel_y - ((float)ticker_line_height * 0.75f)), - (unsigned)((float)video_info->width - sublabel_x), - ticker_num_lines * ticker_line_height); - } - - /* Only apply ticker y offset when actually - * drawing the text */ + /* Draw sublabel */ xmb_draw_text(video_info, xmb, entry_sublabel, sublabel_x, ticker_y_offset + sublabel_y, 1, node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font2); - if (do_scissor) + /* Draw top/bottom line fade effect, if required */ + if (use_smooth_ticker) { - /* These font shenanigans seem to be requied before - * calling menu_display_scissor_end() */ - font_driver_flush(video_info->width, video_info->height, xmb->font2, video_info); - xmb->raster_block2.carr.coords.vertices = 0; - menu_display_scissor_end(video_info); - /* Resume 'blending' */ - menu_display_blend_begin(video_info); + if (!string_is_empty(entry_sublabel_top_fade) && + ticker_top_fade_alpha > 0.0f) + xmb_draw_text(video_info, xmb, entry_sublabel_top_fade, + sublabel_x, ticker_top_fade_y_offset + sublabel_y, + 1, ticker_top_fade_alpha * node->label_alpha, TEXT_ALIGN_LEFT, + width, height, xmb->font2); + + if (!string_is_empty(entry_sublabel_bottom_fade) && + ticker_bottom_fade_alpha > 0.0f) + xmb_draw_text(video_info, xmb, entry_sublabel_bottom_fade, + sublabel_x, ticker_bottom_fade_y_offset + sublabel_y, + 1, ticker_bottom_fade_alpha * node->label_alpha, TEXT_ALIGN_LEFT, + width, height, xmb->font2); } } } diff --git a/menu/menu_animation.c b/menu/menu_animation.c index 9ff22f3dcd..bae0825d0b 100644 --- a/menu/menu_animation.c +++ b/menu/menu_animation.c @@ -846,10 +846,53 @@ static size_t get_line_smooth_scroll_ticks(size_t line_len) return (size_t)(line_duration / ticker_pixel_period); } +static void set_line_smooth_fade_parameters( + bool scroll_up, size_t scroll_ticks, size_t line_phase, size_t line_height, + size_t num_lines, size_t num_display_lines, size_t line_offset, float y_offset, + size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha, + size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha) +{ + float fade_out_alpha = 0.0f; + float fade_in_alpha = 0.0f; + + /* When a line fades out, alpha transitions from + * 1 to 0 over the course of one half of the + * scrolling line height. When a line fades in, + * it's the other way around */ + fade_out_alpha = ((float)scroll_ticks - ((float)line_phase * 2.0f)) / (float)scroll_ticks; + fade_in_alpha = -1.0f * fade_out_alpha; + fade_out_alpha = (fade_out_alpha < 0.0f) ? 0.0f : fade_out_alpha; + fade_in_alpha = (fade_in_alpha < 0.0f) ? 0.0f : fade_in_alpha; + + *top_fade_line_offset = (line_offset > 0) ? line_offset - 1 : num_lines; + *top_fade_y_offset = y_offset - (float)line_height; + *top_fade_alpha = scroll_up ? fade_out_alpha : fade_in_alpha; + + *bottom_fade_line_offset = line_offset + num_display_lines; + *bottom_fade_y_offset = y_offset + (float)(line_height * num_display_lines); + *bottom_fade_alpha = scroll_up ? fade_in_alpha : fade_out_alpha; +} + +static void set_line_smooth_fade_parameters_default( + size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha, + size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha) +{ + *top_fade_line_offset = 0; + *top_fade_y_offset = 0.0f; + *top_fade_alpha = 0.0f; + + *bottom_fade_line_offset = 0; + *bottom_fade_y_offset = 0.0f; + *bottom_fade_alpha = 0.0f; +} + static void menu_animation_line_ticker_smooth_generic(uint64_t idx, - bool scissor_enabled, size_t line_len, size_t line_height, + bool fade_enabled, size_t line_len, size_t line_height, size_t max_display_lines, size_t num_lines, - size_t *num_display_lines, size_t *line_offset, float *y_offset) + size_t *num_display_lines, size_t *line_offset, float *y_offset, + bool *fade_active, + size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha, + size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha) { size_t scroll_ticks = get_line_smooth_scroll_ticks(line_len); /* Note: This function is only called if num_lines > max_display_lines */ @@ -880,72 +923,96 @@ static void menu_animation_line_ticker_smooth_generic(uint64_t idx, phase -= (excess_lines + 1) * scroll_ticks; } - /* If we are currently paused, can use static offsets */ - if (pause) + line_phase = phase % scroll_ticks; + + if (pause || (line_phase == 0)) { + /* Static display of max_display_lines + * (no animation) */ *num_display_lines = max_display_lines; - *line_offset = scroll_up ? 0 : excess_lines; - *y_offset = 0.0f; + *y_offset = 0.0f; + *fade_active = false; + + if (pause) + *line_offset = scroll_up ? 0 : excess_lines; + else + *line_offset = scroll_up ? (phase / scroll_ticks) : (excess_lines - (phase / scroll_ticks)); } else { - line_phase = phase % scroll_ticks; + /* Scroll animation is active */ + *num_display_lines = max_display_lines - 1; + *fade_active = fade_enabled; - if (scissor_enabled) + if (scroll_up) { - *num_display_lines = max_display_lines + 1; - - if (scroll_up) - { - *line_offset = phase / scroll_ticks; - *y_offset = (float)line_height * (((float)(scroll_ticks - line_phase) / (float)scroll_ticks) - 1.0f); - } - else - { - *line_offset = (excess_lines - 1) - (phase / scroll_ticks); - *y_offset = (float)line_height * ((1.0f - (float)(scroll_ticks - line_phase) / (float)scroll_ticks) - 1.0f); - } + *line_offset = (phase / scroll_ticks) + 1; + *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks; } else { - *num_display_lines = max_display_lines - 1; - - if (scroll_up) - { - *line_offset = (phase / scroll_ticks) + 1; - *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks; - } - else - { - *line_offset = excess_lines - (phase / scroll_ticks); - *y_offset = (float)line_height * (1.0f - (float)(scroll_ticks - line_phase) / (float)scroll_ticks); - } + *line_offset = excess_lines - (phase / scroll_ticks); + *y_offset = (float)line_height * (1.0f - (float)(scroll_ticks - line_phase) / (float)scroll_ticks); } + + /* Set fade parameters if fade animation is active */ + if (*fade_active) + set_line_smooth_fade_parameters( + scroll_up, scroll_ticks, line_phase, line_height, + num_lines, *num_display_lines, *line_offset, *y_offset, + top_fade_line_offset, top_fade_y_offset, top_fade_alpha, + bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha); } + + /* Set 'default' fade parameters if fade animation + * is inactive */ + if (!*fade_active) + set_line_smooth_fade_parameters_default( + top_fade_line_offset, top_fade_y_offset, top_fade_alpha, + bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha); } static void menu_animation_line_ticker_smooth_loop(uint64_t idx, - bool scissor_enabled, size_t line_len, size_t line_height, + bool fade_enabled, size_t line_len, size_t line_height, size_t max_display_lines, size_t num_lines, - size_t *num_display_lines, size_t *line_offset, float *y_offset) + size_t *num_display_lines, size_t *line_offset, float *y_offset, + bool *fade_active, + size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha, + size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha) { size_t scroll_ticks = get_line_smooth_scroll_ticks(line_len); size_t ticker_period = (num_lines + 1) * scroll_ticks; size_t phase = idx % ticker_period; size_t line_phase = phase % scroll_ticks; - *line_offset = phase / scroll_ticks; + *line_offset = phase / scroll_ticks; - if (scissor_enabled) + if (line_phase == (scroll_ticks - 1)) { - *num_display_lines = max_display_lines + 1; - *y_offset = (float)line_height * (((float)(scroll_ticks - line_phase) / (float)scroll_ticks) - 1.0f); + /* Static display of max_display_lines + * (no animation) */ + *num_display_lines = max_display_lines; + *fade_active = false; } else { *num_display_lines = max_display_lines - 1; - *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks; + *fade_active = fade_enabled; } + + *y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks; + + /* Set fade parameters */ + if (*fade_active) + set_line_smooth_fade_parameters( + true, scroll_ticks, line_phase, line_height, + num_lines, *num_display_lines, *line_offset, *y_offset, + top_fade_line_offset, top_fade_y_offset, top_fade_alpha, + bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha); + else + set_line_smooth_fade_parameters_default( + top_fade_line_offset, top_fade_y_offset, top_fade_alpha, + bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha); } static void menu_delayed_animation_cb(void *userdata) @@ -1898,16 +1965,19 @@ end: bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t *line_ticker) { - char *wrapped_str = NULL; - struct string_list *lines = NULL; - int glyph_width = 0; - int glyph_height = 0; - size_t line_len = 0; - size_t max_display_lines = 0; - size_t num_display_lines = 0; - size_t line_offset = 0; - bool success = false; - bool is_active = false; + char *wrapped_str = NULL; + struct string_list *lines = NULL; + int glyph_width = 0; + int glyph_height = 0; + size_t line_len = 0; + size_t max_display_lines = 0; + size_t num_display_lines = 0; + size_t line_offset = 0; + size_t top_fade_line_offset = 0; + size_t bottom_fade_line_offset = 0; + bool fade_active = false; + bool success = false; + bool is_active = false; /* Sanity check */ if (!line_ticker) @@ -1941,8 +2011,6 @@ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t * if (glyph_height < 0) goto end; - *line_ticker->line_height = (unsigned)glyph_height; - /* Determine line wrap parameters */ line_len = (size_t)(line_ticker->field_width / glyph_width); max_display_lines = (size_t)(line_ticker->field_height / glyph_height); @@ -1974,14 +2042,28 @@ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t * if (lines->size <= max_display_lines) { strlcpy(line_ticker->dst_str, wrapped_str, line_ticker->dst_str_len); - *line_ticker->num_lines = (unsigned)lines->size; *line_ticker->y_offset = 0.0f; + + /* No fade animation is required */ + if (line_ticker->fade_enabled) + { + if (line_ticker->top_fade_str_len > 0) + line_ticker->top_fade_str[0] = '\0'; + + if (line_ticker->bottom_fade_str_len > 0) + line_ticker->bottom_fade_str[0] = '\0'; + + *line_ticker->top_fade_y_offset = 0.0f; + *line_ticker->bottom_fade_y_offset = 0.0f; + + *line_ticker->top_fade_alpha = 0.0f; + *line_ticker->bottom_fade_alpha = 0.0f; + } + success = true; goto end; } - *line_ticker->num_lines = (unsigned)max_display_lines; - /* Determine which lines should be shown, along with * y axis draw offset */ switch (line_ticker->type_enum) @@ -1990,14 +2072,13 @@ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t * { menu_animation_line_ticker_smooth_loop( line_ticker->idx, - line_ticker->scissor_enabled, - line_len, - (size_t)glyph_height, - max_display_lines, - lines->size, - &num_display_lines, - &line_offset, - line_ticker->y_offset); + line_ticker->fade_enabled, + line_len, (size_t)glyph_height, + max_display_lines, lines->size, + &num_display_lines, &line_offset, line_ticker->y_offset, + &fade_active, + &top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha, + &bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha); break; } @@ -2006,14 +2087,13 @@ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t * { menu_animation_line_ticker_smooth_generic( line_ticker->idx, - line_ticker->scissor_enabled, - line_len, - (size_t)glyph_height, - max_display_lines, - lines->size, - &num_display_lines, - &line_offset, - line_ticker->y_offset); + line_ticker->fade_enabled, + line_len, (size_t)glyph_height, + max_display_lines, lines->size, + &num_display_lines, &line_offset, line_ticker->y_offset, + &fade_active, + &top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha, + &bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha); break; } @@ -2021,8 +2101,23 @@ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t * /* Build output string from required lines */ build_line_ticker_string( - num_display_lines, line_offset, lines, - line_ticker->dst_str, line_ticker->dst_str_len); + num_display_lines, line_offset, lines, + line_ticker->dst_str, line_ticker->dst_str_len); + + /* Extract top/bottom fade strings, if required */ + if (fade_active) + { + /* We waste a handful of clock cycles by using + * build_line_ticker_string() here, but it saves + * rewriting a heap of code... */ + build_line_ticker_string( + 1, top_fade_line_offset, lines, + line_ticker->top_fade_str, line_ticker->top_fade_str_len); + + build_line_ticker_string( + 1, bottom_fade_line_offset, lines, + line_ticker->bottom_fade_str, line_ticker->bottom_fade_str_len); + } success = true; is_active = true; @@ -2043,9 +2138,23 @@ end: } if (!success) + { if (line_ticker->dst_str_len > 0) line_ticker->dst_str[0] = '\0'; + if (line_ticker->fade_enabled) + { + if (line_ticker->top_fade_str_len > 0) + line_ticker->top_fade_str[0] = '\0'; + + if (line_ticker->bottom_fade_str_len > 0) + line_ticker->bottom_fade_str[0] = '\0'; + + *line_ticker->top_fade_alpha = 0.0f; + *line_ticker->bottom_fade_alpha = 0.0f; + } + } + return is_active; } diff --git a/menu/menu_animation.h b/menu/menu_animation.h index f3213962b9..da32191a43 100644 --- a/menu/menu_animation.h +++ b/menu/menu_animation.h @@ -158,7 +158,7 @@ typedef struct menu_animation_ctx_line_ticker typedef struct menu_animation_ctx_line_ticker_smooth { - bool scissor_enabled; + bool fade_enabled; font_data_t *font; float font_scale; unsigned field_width; @@ -168,9 +168,15 @@ typedef struct menu_animation_ctx_line_ticker_smooth const char *src_str; char *dst_str; size_t dst_str_len; - unsigned *line_height; - unsigned *num_lines; float *y_offset; + char *top_fade_str; + size_t top_fade_str_len; + float *top_fade_y_offset; + float *top_fade_alpha; + char *bottom_fade_str; + size_t bottom_fade_str_len; + float *bottom_fade_y_offset; + float *bottom_fade_alpha; } menu_animation_ctx_line_ticker_smooth_t; typedef float menu_timer_t; @@ -200,16 +206,6 @@ bool menu_animation_ticker_smooth(menu_animation_ctx_ticker_smooth_t *ticker); bool menu_animation_line_ticker(menu_animation_ctx_line_ticker_t *line_ticker); -/* Note: When line_ticker->scissor_enabled is true, - * resultant string must be drawn in conjunction with - * menu_display_scissor_*() - * i.e. draw area must be scissored vertically by - * (line_ticker->line_height * line_ticker->num_lines), - * with a scissor y start position of text y postion - * (ignoring line_ticker->y_offset) *minus* - * (line_ticker->line_height - font_descender_size). - * font_descender_size is typically 20-30% of the line - * height... */ bool menu_animation_line_ticker_smooth(menu_animation_ctx_line_ticker_smooth_t *line_ticker); float menu_animation_get_delta_time(void);