From 27694b6b3152179b1bf9def15108d9b8dbf19fc8 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Thu, 14 Feb 2019 15:10:07 +0000 Subject: [PATCH] Add optional 'looping' menu text ticker --- config.def.h | 3 + configuration.c | 1 + configuration.h | 1 + intl/msg_hash_lbl.h | 2 + intl/msg_hash_us.h | 16 ++++ menu/cbs/menu_cbs_sublabel.c | 4 + menu/drivers/materialui.c | 12 +++ menu/drivers/ozone/ozone.c | 5 + menu/drivers/ozone/ozone_entries.c | 5 + menu/drivers/ozone/ozone_sidebar.c | 6 ++ menu/drivers/rgui.c | 8 +- menu/drivers/stripes.c | 5 + menu/drivers/xmb.c | 5 + menu/drivers/xui.cpp | 6 ++ menu/menu_animation.c | 149 ++++++++++++++++++++++++++--- menu/menu_animation.h | 12 +++ menu/menu_displaylist.c | 4 + menu/menu_setting.c | 40 ++++++++ msg_hash.h | 4 + 19 files changed, 272 insertions(+), 16 deletions(-) diff --git a/config.def.h b/config.def.h index 708a55be1c..8a92c9a92b 100644 --- a/config.def.h +++ b/config.def.h @@ -272,6 +272,7 @@ static const float default_input_overlay_opacity = 0.7f; #ifdef HAVE_MENU #include "menu/menu_driver.h" +#include "menu/menu_animation.h" static bool default_block_config_read = true; @@ -323,6 +324,8 @@ static bool menu_show_core_updater = true; #endif static bool menu_show_sublabels = false; +static unsigned menu_ticker_type = TICKER_TYPE_BOUNCE; + static bool content_show_settings = true; static bool content_show_favorites = true; #ifdef HAVE_IMAGEVIEWER diff --git a/configuration.c b/configuration.c index 0f413de803..968a1a39fe 100644 --- a/configuration.c +++ b/configuration.c @@ -1666,6 +1666,7 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings, SETTING_UINT("dpi_override_value", &settings->uints.menu_dpi_override_value, true, menu_dpi_override_value, false); SETTING_UINT("menu_thumbnails", &settings->uints.menu_thumbnails, true, menu_thumbnails_default, false); SETTING_UINT("menu_timedate_style", &settings->uints.menu_timedate_style, true, menu_timedate_style, false); + SETTING_UINT("menu_ticker_type", &settings->uints.menu_ticker_type, true, menu_ticker_type, false); #ifdef HAVE_RGUI SETTING_UINT("rgui_menu_color_theme", &settings->uints.menu_rgui_color_theme, true, rgui_color_theme, false); SETTING_UINT("rgui_thumbnail_downscaler", &settings->uints.menu_rgui_thumbnail_downscaler, true, rgui_thumbnail_downscaler, false); diff --git a/configuration.h b/configuration.h index 30b9e17a57..83115c8fd8 100644 --- a/configuration.h +++ b/configuration.h @@ -427,6 +427,7 @@ typedef struct settings unsigned menu_font_color_green; unsigned menu_font_color_blue; unsigned menu_rgui_internal_upscale_level; + unsigned menu_ticker_type; unsigned camera_width; unsigned camera_height; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 85ee44e7c4..8ee17e55c7 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1129,6 +1129,8 @@ MSG_HASH(MENU_ENUM_LABEL_TIMEDATE_ENABLE, "menu_timedate_enable") MSG_HASH(MENU_ENUM_LABEL_TIMEDATE_STYLE, "menu_timedate_style") +MSG_HASH(MENU_ENUM_LABEL_MENU_TICKER_TYPE, + "menu_ticker_type") MSG_HASH(MENU_ENUM_LABEL_UI_COMPANION_ENABLE, "ui_companion_enable") MSG_HASH(MENU_ENUM_LABEL_UI_COMPANION_START_ON_BOOT, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 80cf097929..a2340b3fd8 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -3074,6 +3074,22 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_TIMEDATE_STYLE_AM_PM, "HH:MM:SS (AM/PM)" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE, + "Ticker Text Animation" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MENU_TICKER_TYPE, + "Select horizontal scrolling method used to display long menu text strings." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_BOUNCE, + "Bounce Left/Right" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_LOOP, + "Scroll Left" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_RGUI_MENU_COLOR_THEME, "Menu Color Theme" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 4d54455e53..f2ce9fd78b 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -518,6 +518,7 @@ default_sublabel_macro(action_bind_sublabel_rgui_menu_theme_preset, default_sublabel_macro(action_bind_sublabel_menu_rgui_thumbnail_downscaler, MENU_ENUM_SUBLABEL_MENU_RGUI_THUMBNAIL_DOWNSCALER) default_sublabel_macro(action_bind_sublabel_content_runtime_log, MENU_ENUM_SUBLABEL_CONTENT_RUNTIME_LOG) default_sublabel_macro(action_bind_sublabel_menu_rgui_internal_upscale_level, MENU_ENUM_SUBLABEL_MENU_RGUI_INTERNAL_UPSCALE_LEVEL) +default_sublabel_macro(action_bind_sublabel_menu_ticker_type, MENU_ENUM_SUBLABEL_MENU_TICKER_TYPE) static int action_bind_sublabel_systeminfo_controller_entry( file_list_t *list, @@ -2256,6 +2257,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MENU_RGUI_INTERNAL_UPSCALE_LEVEL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_rgui_internal_upscale_level); break; + case MENU_ENUM_LABEL_MENU_TICKER_TYPE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_ticker_type); + break; default: case MSG_UNKNOWN: return -1; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 84204e3056..ad45b50efd 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -739,6 +739,7 @@ static void materialui_render_label_value( { menu_entry_t entry; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; char label_str[255]; char value_str[255]; char *sublabel_str = NULL; @@ -752,6 +753,11 @@ static void materialui_render_label_value( int icon_margin = 0; enum msg_file_type hash_type = msg_hash_to_file_type(msg_hash_calculate(value)); float scale_factor = menu_display_get_dpi(); + settings_t *settings = config_get_ptr(); + + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; label_str[0] = value_str[0] = '\0'; @@ -1063,6 +1069,7 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) menu_display_ctx_clearcolor_t clearcolor; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; menu_display_ctx_draw_t draw; char msg[255]; char title_buf[255]; @@ -1160,11 +1167,16 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) bool background_rendered = false; bool libretro_running = video_info->libretro_running; + settings_t *settings = config_get_ptr(); materialui_handle_t *mui = (materialui_handle_t*)data; if (!mui) return; + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + usable_width = width - (mui->margin * 2); mui->frame_count++; diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index c5a9060cf2..4b100c5bbc 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -848,9 +848,14 @@ static void ozone_draw_header(ozone_handle_t *ozone, video_frame_info_t *video_i { char title[255]; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; settings_t *settings = config_get_ptr(); unsigned timedate_offset = 0; + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + /* Separator */ menu_display_draw_quad(video_info, 30, ozone->dimensions.header_height, video_info->width - 60, 1, video_info->width, video_info->height, ozone->theme->header_footer_separator); diff --git a/menu/drivers/ozone/ozone_entries.c b/menu/drivers/ozone/ozone_entries.c index 69c4725c0f..07d450ae33 100644 --- a/menu/drivers/ozone/ozone_entries.c +++ b/menu/drivers/ozone/ozone_entries.c @@ -408,6 +408,7 @@ border_iterate: menu_texture_item tex; menu_entry_t entry; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; char entry_value[255]; char rich_label[255]; char entry_value_ticker[255]; @@ -418,6 +419,10 @@ border_iterate: int text_offset = -ozone->dimensions.entry_icon_padding - ozone->dimensions.entry_icon_size; float *icon_color = NULL; + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + entry_value[0] = '\0'; entry_selected = selection == i; node = (ozone_node_t*) file_list_get_userdata_at_offset(selection_buf, i); diff --git a/menu/drivers/ozone/ozone_sidebar.c b/menu/drivers/ozone/ozone_sidebar.c index 22f48e6b4e..8ebb852bd5 100644 --- a/menu/drivers/ozone/ozone_sidebar.c +++ b/menu/drivers/ozone/ozone_sidebar.c @@ -109,6 +109,12 @@ void ozone_draw_sidebar(ozone_handle_t *ozone, video_frame_info_t *video_info) unsigned i, sidebar_height; char console_title[255]; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; + settings_t *settings = config_get_ptr(); + + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; unsigned selection_y = 0; unsigned selection_old_y = 0; diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 1538d8812c..1e4b7a8453 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -1464,6 +1464,7 @@ static void rgui_frame(void *data, video_frame_info_t *video_info) static void rgui_render(void *data, bool is_idle) { menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; unsigned x, y; size_t i, end, fb_pitch, old_start; unsigned fb_width, fb_height; @@ -1586,10 +1587,11 @@ static void rgui_render(void *data, bool is_idle) rgui_render_background(rgui); } - /* We use a single ticker for all text animations. - * The same 'idx' is used in all cases, so set it - * once at the beginning. */ + /* We use a single ticker for all text animations, + * with the following configuration: */ ticker.idx = frame_count / RGUI_TERM_START_X(fb_width); + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; /* If thumbnails are enabled and we are viewing a playlist, * switch to thumbnail view mode if either current thumbnail diff --git a/menu/drivers/stripes.c b/menu/drivers/stripes.c index 134abbd611..58ebffb1f8 100644 --- a/menu/drivers/stripes.c +++ b/menu/drivers/stripes.c @@ -2332,6 +2332,7 @@ static int stripes_draw_item( { float icon_x, icon_y, label_offset; menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; char tmp[255]; char *ticker_str = NULL; unsigned entry_type = 0; @@ -2343,6 +2344,10 @@ static int stripes_draw_item( file_list_get_userdata_at_offset(list, i); settings_t *settings = config_get_ptr(); + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + if (!node) goto iterate; diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index b1ebdc3ca8..eeec1645e6 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2806,6 +2806,7 @@ static int xmb_draw_item( float icon_x, icon_y, label_offset; menu_animation_ctx_ticker_t ticker; char tmp[255]; + static const char ticker_spacer[] = " | "; char *ticker_str = NULL; unsigned entry_type = 0; const float half_size = xmb->icon_size / 2.0f; @@ -2816,6 +2817,10 @@ static int xmb_draw_item( file_list_get_userdata_at_offset(list, i); settings_t *settings = config_get_ptr(); + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + if (!node) goto iterate; diff --git a/menu/drivers/xui.cpp b/menu/drivers/xui.cpp index f691198861..7c80af2a12 100644 --- a/menu/drivers/xui.cpp +++ b/menu/drivers/xui.cpp @@ -546,6 +546,7 @@ static void xui_render(void *data, bool is_idle) unsigned menu_type = 0; uint64_t frame_count = xui_frame_count; bool msg_force = menu_display_get_msg_force(); + settings_t *settings = config_get_ptr(); menu_display_get_fb_size(&fb_width, &fb_height, &fb_pitch); @@ -565,10 +566,15 @@ static void xui_render(void *data, bool is_idle) if (XuiHandleIsValid(m_menutitle)) { menu_animation_ctx_ticker_t ticker; + static const char ticker_spacer[] = " | "; menu_entries_get_title(title, sizeof(title)); mbstowcs(strw_buffer, title, sizeof(strw_buffer) / sizeof(wchar_t)); XuiTextElementSetText(m_menutitle, strw_buffer); + /* Initial ticker configuration */ + ticker.type_enum = settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + ticker.s = title; ticker.len = RXUI_TERM_WIDTH(fb_width) - 3; ticker.idx = (unsigned int)frame_count / 15; diff --git a/menu/menu_animation.c b/menu/menu_animation.c index 0c3aef5c87..c92eb65d5e 100644 --- a/menu/menu_animation.c +++ b/menu/menu_animation.c @@ -322,6 +322,63 @@ static void menu_animation_ticker_generic(uint64_t idx, *width = max_width; } +static void menu_animation_ticker_loop(uint64_t idx, + size_t max_width, size_t str_width, size_t spacer_width, + size_t *offset1, size_t *width1, + size_t *offset2, size_t *width2, + size_t *offset3, size_t *width3) +{ + int ticker_period = (int)(str_width + spacer_width); + int phase = idx % ticker_period; + + /* Output offsets/widths are unsigned size_t, but it's + * easier to perform the required calculations with ints, + * so create some temporary variables... */ + int offset; + int width; + + /* Looping text is composed of up to three strings, + * where string 1 and 2 are different regions of the + * source text and string 2 is a spacer: + * + * |-----max_width-----| + * [string 1][string 2][string 3] + * + * The following implementation could probably be optimised, + * but any performance gains would be trivial compared with + * all the string manipulation that has to happen afterwards... + */ + + /* String 1 */ + offset = phase < str_width ? phase : 0; + width = str_width - phase; + width = width < 0 ? 0 : width; + width = width > max_width ? max_width : width; + + *offset1 = offset; + *width1 = width; + + /* String 2 */ + offset = phase - str_width; + offset = offset < 0 ? 0 : offset; + width = max_width - *width1; + width = width > spacer_width ? spacer_width : width; + width = width - offset; + + *offset2 = offset; + *width2 = width; + + /* String 3 */ + width = max_width - (*width1 + *width2); + width = width < 0 ? 0 : width; + + /* Note: offset is always zero here so offset3 is + * unnecessary - but include it anyway to preserve + * symmetry... */ + *offset3 = 0; + *width3 = width; +} + void menu_animation_init(void) { da_init(anim.list); @@ -556,7 +613,6 @@ bool menu_animation_update(float anim_delta_time) bool menu_animation_ticker(const menu_animation_ctx_ticker_t *ticker) { size_t str_len = utf8len(ticker->str); - size_t offset = 0; if ((size_t)str_len <= ticker->len) { @@ -574,18 +630,85 @@ bool menu_animation_ticker(const menu_animation_ctx_ticker_t *ticker) return false; } - if (str_len > ticker->len) - menu_animation_ticker_generic( - ticker->idx, - ticker->len, - &offset, - &str_len); - - utf8cpy( - ticker->s, - PATH_MAX_LENGTH, - utf8skip(ticker->str, offset), - str_len); + /* Note: If we reach this point then str_len > ticker->len + * (previously had an unecessary 'if (str_len > ticker->len)' + * check here...) */ + switch (ticker->type_enum) + { + case TICKER_TYPE_LOOP: + { + size_t offset1, offset2, offset3; + size_t width1, width2, width3; + + /* Horribly oversized temporary buffer + * > utf8 support makes this whole thing incredibly + * ugly/inefficient. Not much we can do about it... */ + char tmp[PATH_MAX_LENGTH]; + + tmp[0] = '\0'; + ticker->s[0] = '\0'; + + menu_animation_ticker_loop( + ticker->idx, + ticker->len, + str_len, utf8len(ticker->spacer), + &offset1, &width1, + &offset2, &width2, + &offset3, &width3); + + if (width1 > 0) + { + utf8cpy( + ticker->s, + PATH_MAX_LENGTH, + utf8skip(ticker->str, offset1), + width1); + } + + if (width2 > 0) + { + utf8cpy( + tmp, + PATH_MAX_LENGTH, + utf8skip(ticker->spacer, offset2), + width2); + + strlcat(ticker->s, tmp, PATH_MAX_LENGTH); + } + + if (width3 > 0) + { + utf8cpy( + tmp, + PATH_MAX_LENGTH, + utf8skip(ticker->str, offset3), + width3); + + strlcat(ticker->s, tmp, PATH_MAX_LENGTH); + } + + break; + } + case TICKER_TYPE_BOUNCE: + default: + { + size_t offset = 0; + + menu_animation_ticker_generic( + ticker->idx, + ticker->len, + &offset, + &str_len); + + utf8cpy( + ticker->s, + PATH_MAX_LENGTH, + utf8skip(ticker->str, offset), + str_len); + + break; + } + } ticker_is_active = true; diff --git a/menu/menu_animation.h b/menu/menu_animation.h index 56a5171655..234cfd1bc3 100644 --- a/menu/menu_animation.h +++ b/menu/menu_animation.h @@ -84,6 +84,16 @@ enum menu_animation_easing_type EASING_LAST }; +/* TODO: + * Add a reverse loop ticker for languages + * that read right to left */ +enum menu_animation_ticker_type +{ + TICKER_TYPE_BOUNCE = 0, + TICKER_TYPE_LOOP, + TICKER_TYPE_LAST +}; + typedef struct menu_animation_ctx_delta { float current; @@ -114,8 +124,10 @@ typedef struct menu_animation_ctx_ticker bool selected; size_t len; uint64_t idx; + enum menu_animation_ticker_type type_enum; char *s; const char *str; + const char *spacer; } menu_animation_ctx_ticker_t; typedef float menu_timer_t; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index ef0fcedcd3..b72c017ad7 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -6132,6 +6132,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, menu_displaylist MENU_ENUM_LABEL_MENU_RGUI_THUMBNAIL_DOWNSCALER, PARSE_ONLY_UINT, false) == 0) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_MENU_TICKER_TYPE, + PARSE_ONLY_UINT, false) == 0) + count++; if (count == 0) menu_entries_append_enum(info->list, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index d6642dfc92..59a1f7980e 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -891,6 +891,30 @@ static void setting_get_string_representation_uint_rgui_internal_upscale_level( } } +static void setting_get_string_representation_uint_menu_ticker_type( + rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + switch (*setting->value.target.unsigned_integer) + { + case TICKER_TYPE_BOUNCE: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_BOUNCE), + len); + break; + case TICKER_TYPE_LOOP: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_LOOP), + len); + break; + } +} + #ifdef HAVE_XMB static void setting_get_string_representation_uint_xmb_icon_theme( rarch_setting_t *setting, @@ -8165,6 +8189,22 @@ static bool setting_append_list( #endif } + CONFIG_UINT( + list, list_info, + &settings->uints.menu_ticker_type, + MENU_ENUM_LABEL_MENU_TICKER_TYPE, + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE, + menu_ticker_type, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + (*list)[list_info->index - 1].get_string_representation = + &setting_get_string_representation_uint_menu_ticker_type; + menu_settings_list_current_add_range(list, list_info, 0, TICKER_TYPE_LAST-1, 1, true, true); + END_SUB_GROUP(list, list_info, parent_group); START_SUB_GROUP(list, list_info, "Navigation", &group_info, &subgroup_info, parent_group); diff --git a/msg_hash.h b/msg_hash.h index 6bf72c5a48..6863aef0ec 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -920,6 +920,10 @@ enum msg_hash_enums MENU_LABEL(QUICK_MENU_STOP_RECORDING), MENU_LABEL(QUICK_MENU_START_STREAMING), MENU_LABEL(QUICK_MENU_STOP_STREAMING), + MENU_LABEL(MENU_TICKER_TYPE), + + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_BOUNCE, + MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_LOOP, /* UI settings */ MENU_LABEL(VIDEO_DISABLE_COMPOSITION),