diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index c3fa7db7f2..350f58c905 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2850,6 +2850,7 @@ static int xmb_draw_item( uintptr_t texture_switch = 0; bool do_draw_text = false; unsigned ticker_limit = 35 * scale_mod[0]; + unsigned line_ticker_width = 45 * scale_mod[3]; xmb_node_t * node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); settings_t *settings = config_get_ptr(); @@ -2951,16 +2952,27 @@ static int xmb_draw_item( && settings->bools.menu_xmb_vertical_thumbnails) ) { - ticker_limit = 40 * scale_mod[1]; + ticker_limit = 40 * scale_mod[1]; + line_ticker_width = 50 * scale_mod[3]; /* Can increase text length if thumbnail is downscaled */ if (settings->uints.menu_xmb_thumbnail_scale_factor < 100) + { + float ticker_scale_factor = + 1.0f - ((float)settings->uints.menu_xmb_thumbnail_scale_factor / 100.0f); + ticker_limit += - (unsigned)((1.0f - ((float)settings->uints.menu_xmb_thumbnail_scale_factor / 100.0f)) * - 15.0f * scale_mod[1]); + (unsigned)(ticker_scale_factor * 15.0f * scale_mod[1]); + + line_ticker_width += + (unsigned)(ticker_scale_factor * 10.0f * scale_mod[3]); + } } else - ticker_limit = 70 * scale_mod[2]; + { + ticker_limit = 70 * scale_mod[2]; + line_ticker_width = 60 * scale_mod[3]; + } } menu_entry_get_rich_label(entry, &ticker_str); @@ -2981,11 +2993,26 @@ static int xmb_draw_item( if (i == current && width > 320 && height > 240 && !string_is_empty(entry->sublabel)) { + menu_animation_ctx_line_ticker_t line_ticker; char entry_sublabel[512] = {0}; - label_offset = - xmb->margins_label_top; + line_ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + line_ticker.idx = menu_animation_get_ticker_idx(); - word_wrap(entry_sublabel, entry->sublabel, 50 * scale_mod[3], true, 0); + line_ticker.line_width = (size_t)(line_ticker_width); + /* Note: max_lines should be calculated at runtime, + * but this is a nuisance. There is room for 4 lines + * to be displayed when using all existing XMB themes, + * so leave this value hard coded for now. */ + line_ticker.max_lines = 4; + + line_ticker.s = entry_sublabel; + line_ticker.len = sizeof(entry_sublabel); + line_ticker.str = entry->sublabel; + + menu_animation_line_ticker(&line_ticker); + + label_offset = - xmb->margins_label_top; xmb_draw_text(video_info, xmb, entry_sublabel, node->x + xmb->margins_screen_left + diff --git a/menu/menu_animation.c b/menu/menu_animation.c index b1cec123aa..59c7d2d8a9 100644 --- a/menu/menu_animation.c +++ b/menu/menu_animation.c @@ -23,6 +23,7 @@ #include #include #include +#include #define DG_DYNARR_IMPLEMENTATION #include @@ -386,6 +387,60 @@ static void menu_animation_ticker_loop(uint64_t idx, *width3 = width; } +static size_t get_line_display_ticks(size_t line_width) +{ + /* Mean human reading speed for all western languages, + * characters per minute */ + float cpm = 1000.0f; + /* Base time for which a line should be shown, in ms */ + float line_duration = (line_width * 60.0f * 1000.0f) / cpm; + /* Ticker updates (nominally) once every TICKER_SPEED ms + * > Return base number of ticks for which line should be shown */ + return (size_t)(line_duration / (float)TICKER_SPEED); +} + +static void menu_animation_line_ticker_generic(uint64_t idx, + size_t line_width, size_t max_lines, size_t num_lines, + size_t *line_offset) +{ + size_t line_ticks = get_line_display_ticks(line_width); + /* Note: This function is only called if num_lines > max_lines */ + size_t excess_lines = num_lines - max_lines; + /* Ticker will pause for one line duration when the first + * or last line is reached (this is mostly required for the + * case where num_lines == (max_lines + 1), since otherwise + * the text flicks rapidly up and down in disconcerting + * fashion...) */ + size_t ticker_period = (excess_lines * 2) + 2; + size_t phase = (idx / line_ticks) % ticker_period; + + /* Pause on first line */ + if (phase > 0) + phase--; + /* Pause on last line */ + if (phase > excess_lines) + phase--; + + /* Lines scrolling upwards */ + if (phase <= excess_lines) + *line_offset = phase; + /* Lines scrolling downwards */ + else + *line_offset = (excess_lines * 2) - phase; +} + +static void menu_animation_line_ticker_loop(uint64_t idx, + size_t line_width, size_t num_lines, + size_t *line_offset) +{ + size_t line_ticks = get_line_display_ticks(line_width); + size_t ticker_period = num_lines + 1; + size_t phase = (idx / line_ticks) % ticker_period; + + /* In this case, line_offset is simply equal to the phase */ + *line_offset = phase; +} + static void menu_delayed_animation_cb(void *userdata) { menu_delayed_animation_t *delayed_animation = (menu_delayed_animation_t*) userdata; @@ -771,6 +826,125 @@ bool menu_animation_ticker(menu_animation_ctx_ticker_t *ticker) return true; } +bool menu_animation_line_ticker(menu_animation_ctx_line_ticker_t *line_ticker) +{ + size_t i; + char *wrapped_str = NULL; + struct string_list *lines = NULL; + size_t line_offset = 0; + bool success = false; + bool is_active = false; + + /* Sanity check */ + if (!line_ticker) + return false; + + if (string_is_empty(line_ticker->str) || + (line_ticker->line_width < 1) || + (line_ticker->max_lines < 1)) + goto end; + + /* Line wrap input string */ + wrapped_str = (char*)malloc((strlen(line_ticker->str) + 1) * sizeof(char)); + if (!wrapped_str) + goto end; + + word_wrap( + wrapped_str, + line_ticker->str, + (int)line_ticker->line_width, + true, 0); + + if (string_is_empty(wrapped_str)) + goto end; + + /* Split into component lines */ + lines = string_split(wrapped_str, "\n"); + if (!lines) + goto end; + + /* Check whether total number of lines fits within + * the set limit */ + if (lines->size <= line_ticker->max_lines) + { + strlcpy(line_ticker->s, wrapped_str, line_ticker->len); + success = true; + goto end; + } + + /* Determine offset of first line in wrapped string */ + switch (line_ticker->type_enum) + { + case TICKER_TYPE_LOOP: + { + menu_animation_line_ticker_loop( + line_ticker->idx, + line_ticker->line_width, + lines->size, + &line_offset); + + break; + } + case TICKER_TYPE_BOUNCE: + default: + { + menu_animation_line_ticker_generic( + line_ticker->idx, + line_ticker->line_width, + line_ticker->max_lines, + lines->size, + &line_offset); + + break; + } + } + + /* Build output string from required lines */ + for (i = 0; i < line_ticker->max_lines; i++) + { + size_t offset = i + line_offset; + size_t line_index = 0; + bool line_valid = true; + + if (offset < lines->size) + line_index = offset; + else if (offset > lines->size) + line_index = (offset - 1) - lines->size; + else + line_valid = false; + + if (line_valid) + strlcat(line_ticker->s, lines->elems[line_index].data, line_ticker->len); + + if (i < line_ticker->max_lines - 1) + strlcat(line_ticker->s, "\n", line_ticker->len); + } + + success = true; + is_active = true; + ticker_is_active = true; + +end: + + if (wrapped_str) + { + free(wrapped_str); + wrapped_str = NULL; + } + + if (lines) + { + string_list_free(lines); + lines = NULL; + } + + if (!success) + if (line_ticker->len > 0) + line_ticker->s[0] = '\0'; + + return is_active; +} + bool menu_animation_is_active(void) { return animation_is_active || ticker_is_active; diff --git a/menu/menu_animation.h b/menu/menu_animation.h index a41e122346..b35987b661 100644 --- a/menu/menu_animation.h +++ b/menu/menu_animation.h @@ -126,6 +126,17 @@ typedef struct menu_animation_ctx_ticker const char *spacer; } menu_animation_ctx_ticker_t; +typedef struct menu_animation_ctx_line_ticker +{ + size_t line_width; + size_t max_lines; + uint64_t idx; + enum menu_animation_ticker_type type_enum; + char *s; + size_t len; + const char *str; +} menu_animation_ctx_line_ticker_t; + typedef float menu_timer_t; typedef struct menu_timer_ctx_entry @@ -149,6 +160,8 @@ bool menu_animation_update(void); bool menu_animation_ticker(menu_animation_ctx_ticker_t *ticker); +bool menu_animation_line_ticker(menu_animation_ctx_line_ticker_t *line_ticker); + float menu_animation_get_delta_time(void); bool menu_animation_is_active(void);