From 8569e65c18db7f8fd309e8e77ca16eae921eecc7 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Wed, 21 Aug 2019 16:32:39 +0100 Subject: [PATCH] Add optional smooth scrolling menu ticker text --- config.def.h | 1 + configuration.c | 1 + configuration.h | 1 + intl/msg_hash_lbl.h | 2 + intl/msg_hash_us.h | 8 + menu/cbs/menu_cbs_sublabel.c | 4 + menu/drivers/materialui.c | 151 +++++--- menu/drivers/ozone/ozone.c | 47 ++- menu/drivers/ozone/ozone_entries.c | 86 ++++- menu/drivers/ozone/ozone_sidebar.c | 48 ++- menu/drivers/rgui.c | 227 +++++++++--- menu/drivers/xmb.c | 80 ++-- menu/menu_animation.c | 564 ++++++++++++++++++++++++++++- menu/menu_animation.h | 23 ++ menu/menu_displaylist.c | 1 + menu/menu_setting.c | 15 + msg_hash.h | 1 + 17 files changed, 1096 insertions(+), 164 deletions(-) diff --git a/config.def.h b/config.def.h index a993203002..21e131e7cf 100644 --- a/config.def.h +++ b/config.def.h @@ -392,6 +392,7 @@ static bool menu_show_sublabels = true; static unsigned menu_ticker_type = TICKER_TYPE_BOUNCE; static float menu_ticker_speed = 1.0f; +static bool menu_ticker_smooth = false; #if defined(HAVE_THREADS) static bool menu_savestate_resume = true; diff --git a/configuration.c b/configuration.c index c5952fb73c..2c18f0a584 100644 --- a/configuration.c +++ b/configuration.c @@ -1453,6 +1453,7 @@ static struct config_bool_setting *populate_settings_bool(settings_t *settings, SETTING_BOOL("menu_core_enable", &settings->bools.menu_core_enable, true, true, false); SETTING_BOOL("menu_show_sublabels", &settings->bools.menu_show_sublabels, true, menu_show_sublabels, false); SETTING_BOOL("menu_dynamic_wallpaper_enable", &settings->bools.menu_dynamic_wallpaper_enable, true, false, false); + SETTING_BOOL("menu_ticker_smooth", &settings->bools.menu_ticker_smooth, true, menu_ticker_smooth, false); SETTING_BOOL("settings_show_input", &settings->bools.settings_show_input, true, DEFAULT_SETTINGS_SHOW_INPUT, false); SETTING_BOOL("quick_menu_show_resume_content", &settings->bools.quick_menu_show_resume_content, true, DEFAULT_QUICK_MENU_SHOW_RESUME_CONTENT, false); SETTING_BOOL("quick_menu_show_restart_content", &settings->bools.quick_menu_show_restart_content, true, DEFAULT_QUICK_MENU_SHOW_RESTART_CONTENT, false); diff --git a/configuration.h b/configuration.h index 51850ef14c..73350f076d 100644 --- a/configuration.h +++ b/configuration.h @@ -203,6 +203,7 @@ typedef struct settings bool menu_use_preferred_system_color_theme; bool menu_preferred_system_color_theme_set; bool menu_unified_controls; + bool menu_ticker_smooth; bool settings_show_input; bool quick_menu_show_resume_content; bool quick_menu_show_restart_content; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index dd3c9ba583..590ef0c915 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -1247,6 +1247,8 @@ MSG_HASH(MENU_ENUM_LABEL_MENU_TICKER_TYPE, "menu_ticker_type") MSG_HASH(MENU_ENUM_LABEL_MENU_TICKER_SPEED, "menu_ticker_speed") +MSG_HASH(MENU_ENUM_LABEL_MENU_TICKER_SMOOTH, + "menu_ticker_smooth") 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 27f0b89d1d..98c41da519 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -3402,6 +3402,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_MENU_TICKER_SPEED, "Animation speed when scrolling long menu text strings." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MENU_TICKER_SMOOTH, + "Smooth Ticker Text" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MENU_TICKER_SMOOTH, + "Use smooth scrolling animation when displaying long menu text strings. Has a small performance impact." + ) 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 3b12191c38..90c6bc3e16 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -639,6 +639,7 @@ default_sublabel_macro(action_bind_sublabel_menu_rgui_internal_upscale_level, default_sublabel_macro(action_bind_sublabel_menu_rgui_aspect_ratio, MENU_ENUM_SUBLABEL_MENU_RGUI_ASPECT_RATIO) default_sublabel_macro(action_bind_sublabel_menu_ticker_type, MENU_ENUM_SUBLABEL_MENU_TICKER_TYPE) default_sublabel_macro(action_bind_sublabel_menu_ticker_speed, MENU_ENUM_SUBLABEL_MENU_TICKER_SPEED) +default_sublabel_macro(action_bind_sublabel_menu_ticker_smooth, MENU_ENUM_SUBLABEL_MENU_TICKER_SMOOTH) default_sublabel_macro(action_bind_sublabel_playlist_show_inline_core_name, MENU_ENUM_SUBLABEL_PLAYLIST_SHOW_INLINE_CORE_NAME) default_sublabel_macro(action_bind_sublabel_playlist_sort_alphabetical, MENU_ENUM_SUBLABEL_PLAYLIST_SORT_ALPHABETICAL) default_sublabel_macro(action_bind_sublabel_playlist_fuzzy_archive_match, MENU_ENUM_SUBLABEL_PLAYLIST_FUZZY_ARCHIVE_MATCH) @@ -2764,6 +2765,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MENU_TICKER_SPEED: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_ticker_speed); break; + case MENU_ENUM_LABEL_MENU_TICKER_SMOOTH: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_ticker_smooth); + break; case MENU_ENUM_LABEL_PLAYLIST_SHOW_INLINE_CORE_NAME: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_playlist_show_inline_core_name); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 249d13715f..386607439a 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -743,9 +743,14 @@ static void materialui_render_label_value( { menu_entry_t entry; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; char label_str[255]; char value_str[255]; char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH]; + unsigned ticker_label_x_offset = 0; + unsigned ticker_value_x_offset = 0; + unsigned ticker_str_width = 0; + int value_x_offset = 0; unsigned entry_type = 0; const char *sublabel_str = NULL; bool switch_is_on = true; @@ -760,10 +765,23 @@ static void materialui_render_label_value( float scale_factor = menu_display_get_dpi(video_info->width, video_info->height); settings_t *settings = config_get_ptr(); + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = NULL; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = mui->font; + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = NULL; + ticker_smooth.dst_str_width = &ticker_str_width; + } + else + { + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = NULL; + } label_str[0] = value_str[0] = wrapped_sublabel_str[0] = '\0'; @@ -778,21 +796,48 @@ static void materialui_render_label_value( if (value_len * mui->glyph_width > usable_width / 2) value_len = (int)((usable_width/2) / mui->glyph_width); - ticker_limit = (int)((usable_width / mui->glyph_width) - (value_len + 2)); + ticker_limit = (int)((usable_width / mui->glyph_width) - (value_len + 3)); - ticker.s = label_str; - ticker.len = ticker_limit; - ticker.idx = index; - ticker.str = label; - ticker.selected = selected; + if (use_smooth_ticker) + { + /* Label */ + ticker_smooth.selected = selected; + ticker_smooth.field_width = ticker_limit * mui->glyph_width; + ticker_smooth.src_str = label; + ticker_smooth.dst_str = label_str; + ticker_smooth.dst_str_len = sizeof(label_str); + ticker_smooth.x_offset = &ticker_label_x_offset; - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); - ticker.s = value_str; - ticker.len = value_len; - ticker.str = value; + /* Value */ + ticker_smooth.field_width = (value_len + 1) * mui->glyph_width; + ticker_smooth.src_str = value; + ticker_smooth.dst_str = value_str; + ticker_smooth.dst_str_len = sizeof(value_str); + ticker_smooth.x_offset = &ticker_value_x_offset; - menu_animation_ticker(&ticker); + /* Value text is right aligned, so have to offset x + * by the 'padding' width at the end of the ticker string... */ + if (menu_animation_ticker_smooth(&ticker_smooth)) + value_x_offset = (ticker_value_x_offset + ticker_str_width) - ticker_smooth.field_width; + } + else + { + ticker.s = label_str; + ticker.len = ticker_limit; + ticker.idx = index; + ticker.str = label; + ticker.selected = selected; + + menu_animation_ticker(&ticker); + + ticker.s = value_str; + ticker.len = value_len; + ticker.str = value; + + menu_animation_ticker(&ticker); + } /* set switch_is_on */ /* set texture_switch */ @@ -889,13 +934,13 @@ static void materialui_render_label_value( } menu_display_draw_text(mui->font, label_str, - mui->margin + icon_margin, + ticker_label_x_offset + mui->margin + icon_margin, y + (scale_factor / 5), width, height, color, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); if (do_draw_text) menu_display_draw_text(mui->font, value_str, - width - mui->margin, + value_x_offset + width - mui->margin, y + (scale_factor / 5), width, height, color, TEXT_ALIGN_RIGHT, 1.0f, false, 0, false); @@ -1080,9 +1125,12 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) menu_display_ctx_clearcolor_t clearcolor; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; + unsigned ticker_x_offset = 0; menu_display_ctx_draw_t draw; char msg[255]; - char title_buf[255]; + char menu_title[640]; + char title_buf[640]; char title_msg[255]; float black_bg[16] = { @@ -1179,17 +1227,30 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) settings_t *settings = config_get_ptr(); materialui_handle_t *mui = (materialui_handle_t*)data; + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; if (!mui) return; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = NULL; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = mui->font; + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = NULL; + ticker_smooth.x_offset = &ticker_x_offset; + ticker_smooth.dst_str_width = NULL; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = NULL; + } - usable_width = width - (mui->margin * 2); - - msg[0] = title_buf[0] = title_msg[0] = '\0'; + msg[0] = menu_title[0] = title_buf[0] = title_msg[0] = '\0'; switch (video_info->materialui_color_theme) { @@ -1536,44 +1597,40 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) ); } - ticker_limit = (unsigned)(usable_width / mui->glyph_width); - - ticker.s = title_buf; - ticker.len = ticker_limit; - ticker.idx = menu_animation_get_ticker_slow_idx(); - ticker.str = mui->menu_title; - ticker.selected = true; - - menu_animation_ticker(&ticker); - /* Title */ + usable_width = width - (mui->margin * 2) - title_margin; + + strlcpy(menu_title, mui->menu_title, sizeof(menu_title)); if (materialui_get_core_title(title_msg, sizeof(title_msg)) == 0) { - int ticker_limit, value_len; - char title_buf_msg_tmp[255]; - char title_buf_msg[640]; + strlcat(menu_title, " (", sizeof(menu_title)); + strlcat(menu_title, title_msg, sizeof(menu_title)); + strlcat(menu_title, ")", sizeof(menu_title)); + } - title_buf_msg_tmp[0] = title_buf_msg[0] = '\0'; + if (use_smooth_ticker) + { + ticker_smooth.selected = true; + ticker_smooth.field_width = (unsigned)usable_width; + ticker_smooth.src_str = menu_title; + ticker_smooth.dst_str = title_buf; + ticker_smooth.dst_str_len = sizeof(title_buf); - snprintf(title_buf_msg, sizeof(title_buf_msg), "%s (%s)", - title_buf, title_msg); - value_len = (int)utf8len(title_buf); - ticker_limit = (int)((usable_width / mui->glyph_width) - (value_len + 2)); - - ticker.s = title_buf_msg_tmp; - ticker.len = ticker_limit; - ticker.idx = menu_animation_get_ticker_idx(); - ticker.str = title_buf_msg; + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = title_buf; + ticker.len = (unsigned)(usable_width / mui->glyph_width); + ticker.str = menu_title; ticker.selected = true; menu_animation_ticker(&ticker); - - strlcpy(title_buf, title_buf_msg_tmp, sizeof(title_buf)); } if (mui->font) menu_display_draw_text(mui->font, title_buf, - title_margin, + ticker_x_offset + title_margin, header_height / 2 + mui->font->size / 3, width, height, font_header_color, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index 45f60bb493..6f18b01619 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -1027,27 +1027,56 @@ static void ozone_draw_header(ozone_handle_t *ozone, video_frame_info_t *video_i { char title[255]; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; static const char* const ticker_spacer = OZONE_TICKER_SPACER; + unsigned ticker_x_offset = 0; settings_t *settings = config_get_ptr(); unsigned timedate_offset = 0; + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = ticker_spacer; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = ticker_spacer; + ticker_smooth.x_offset = &ticker_x_offset; + ticker_smooth.dst_str_width = NULL; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)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); /* Title */ - ticker.s = title; - ticker.len = (video_info->width - 128 - 47 - 130) / ozone->title_font_glyph_width; - ticker.idx = menu_animation_get_ticker_idx(); - ticker.str = ozone->title; - ticker.selected = true; + if (use_smooth_ticker) + { + ticker_smooth.font = ozone->fonts.title; + ticker_smooth.selected = true; + ticker_smooth.field_width = (video_info->width - 128 - 47 - 180); + ticker_smooth.src_str = ozone->title; + ticker_smooth.dst_str = title; + ticker_smooth.dst_str_len = sizeof(title); - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = title; + ticker.len = (video_info->width - 128 - 47 - 180) / ozone->title_font_glyph_width; + ticker.str = ozone->title; + ticker.selected = true; - ozone_draw_text(video_info, ozone, title, 128, ozone->dimensions.header_height / 2 + FONT_SIZE_TITLE * 3/8, TEXT_ALIGN_LEFT, video_info->width, video_info->height, ozone->fonts.title, ozone->theme->text_rgba, false); + menu_animation_ticker(&ticker); + } + + ozone_draw_text(video_info, ozone, title, ticker_x_offset + 128, ozone->dimensions.header_height / 2 + FONT_SIZE_TITLE * 3/8, TEXT_ALIGN_LEFT, video_info->width, video_info->height, ozone->fonts.title, ozone->theme->text_rgba, false); /* Icon */ menu_display_blend_begin(video_info); diff --git a/menu/drivers/ozone/ozone_entries.c b/menu/drivers/ozone/ozone_entries.c index eefead3bf9..3de8296faa 100644 --- a/menu/drivers/ozone/ozone_entries.c +++ b/menu/drivers/ozone/ozone_entries.c @@ -474,7 +474,11 @@ border_iterate: menu_texture_item tex; menu_entry_t entry; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; static const char* const ticker_spacer = OZONE_TICKER_SPACER; + unsigned ticker_x_offset = 0; + unsigned ticker_str_width = 0; + int value_x_offset = 0; char rich_label[255]; char entry_value_ticker[255]; char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH]; @@ -485,10 +489,25 @@ border_iterate: bool entry_selected = false; int text_offset = -ozone->dimensions.entry_icon_padding - ozone->dimensions.entry_icon_size; float *icon_color = NULL; + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = ticker_spacer; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = ozone->fonts.entries_label; + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = ticker_spacer; + ticker_smooth.x_offset = &ticker_x_offset; + ticker_smooth.dst_str_width = &ticker_str_width; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + } entry_selected = selection == i; node = (ozone_node_t*) file_list_get_userdata_at_offset(selection_buf, i); @@ -510,16 +529,30 @@ border_iterate: /* Prepare text */ menu_entry_get_rich_label(&entry, &entry_rich_label); - ticker.idx = menu_animation_get_ticker_idx(); - ticker.s = rich_label; - ticker.str = entry_rich_label; - ticker.selected = entry_selected && !ozone->cursor_in_sidebar; - ticker.len = (entry_width - entry_padding) / ozone->entry_font_glyph_width; + if (use_smooth_ticker) + { + ticker_smooth.selected = entry_selected && !ozone->cursor_in_sidebar; + ticker_smooth.field_width = entry_width - entry_padding - 10 - ozone->dimensions.entry_icon_padding; + ticker_smooth.src_str = entry_rich_label; + ticker_smooth.dst_str = rich_label; + ticker_smooth.dst_str_len = sizeof(rich_label); - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = rich_label; + ticker.str = entry_rich_label; + ticker.selected = entry_selected && !ozone->cursor_in_sidebar; + ticker.len = (entry_width - entry_padding - 10 - ozone->dimensions.entry_icon_padding) / ozone->entry_font_glyph_width; + + menu_animation_ticker(&ticker); + } if (ozone->empty_playlist) { + /* Note: This entry can never be selected, so ticker_x_offset + * is irrelevant here (i.e. this text will never scroll) */ unsigned text_width = font_driver_get_message_width(ozone->fonts.entries_label, rich_label, (unsigned)strlen(rich_label), 1); x_offset = (video_info_width - (unsigned) ozone->dimensions.sidebar_width - entry_padding * 2) / 2 - text_width / 2 - 60; y = video_info_height / 2 - 60; @@ -588,24 +621,41 @@ border_iterate: } /* Draw text */ - ozone_draw_text(video_info, ozone, rich_label, text_offset + (unsigned) ozone->dimensions.sidebar_width + x_offset + entry_padding + ozone->dimensions.entry_icon_size + ozone->dimensions.entry_icon_padding * 2, + ozone_draw_text(video_info, ozone, rich_label, ticker_x_offset + text_offset + (unsigned) ozone->dimensions.sidebar_width + x_offset + entry_padding + ozone->dimensions.entry_icon_size + ozone->dimensions.entry_icon_padding * 2, y + ozone->dimensions.entry_height / 2 + FONT_SIZE_ENTRIES_LABEL * 3/8 + scroll_y, TEXT_ALIGN_LEFT, video_info->width, video_info->height, ozone->fonts.entries_label, COLOR_TEXT_ALPHA(ozone->theme->text_rgba, alpha_uint32), false); if (!string_is_empty(sublabel_str)) ozone_draw_text(video_info, ozone, sublabel_str, (unsigned) ozone->dimensions.sidebar_width + x_offset + entry_padding + ozone->dimensions.entry_icon_padding, y + ozone->dimensions.entry_height + 1 + 5 + FONT_SIZE_ENTRIES_SUBLABEL + scroll_y, TEXT_ALIGN_LEFT, video_info->width, video_info->height, ozone->fonts.entries_sublabel, COLOR_TEXT_ALPHA(ozone->theme->text_sublabel_rgba, alpha_uint32), false); /* Value */ - ticker.idx = menu_animation_get_ticker_idx(); - ticker.s = entry_value_ticker; - ticker.str = entry_value; - ticker.selected = entry_selected && !ozone->cursor_in_sidebar; - ticker.len = (entry_width - ozone->dimensions.entry_icon_size - ozone->dimensions.entry_icon_padding * 2 - - ((int)utf8len(entry_rich_label) * ozone->entry_font_glyph_width)) / ozone->entry_font_glyph_width; + if (use_smooth_ticker) + { + ticker_smooth.selected = entry_selected && !ozone->cursor_in_sidebar; + ticker_smooth.field_width = (entry_width - ozone->dimensions.entry_icon_size - ozone->dimensions.entry_icon_padding * 2 - + ((unsigned)utf8len(entry_rich_label) * ozone->entry_font_glyph_width)); + ticker_smooth.src_str = entry_value; + ticker_smooth.dst_str = entry_value_ticker; + ticker_smooth.dst_str_len = sizeof(entry_value_ticker); - menu_animation_ticker(&ticker); + /* Value text is right aligned, so have to offset x + * by the 'padding' width at the end of the ticker string... */ + if (menu_animation_ticker_smooth(&ticker_smooth)) + value_x_offset = (ticker_x_offset + ticker_str_width) - ticker_smooth.field_width; + } + else + { + ticker.s = entry_value_ticker; + ticker.str = entry_value; + ticker.selected = entry_selected && !ozone->cursor_in_sidebar; + ticker.len = (entry_width - ozone->dimensions.entry_icon_size - ozone->dimensions.entry_icon_padding * 2 - + ((unsigned)utf8len(entry_rich_label) * ozone->entry_font_glyph_width)) / ozone->entry_font_glyph_width; - ozone_draw_entry_value(ozone, video_info, entry_value_ticker, (unsigned) ozone->dimensions.sidebar_width + entry_padding + x_offset + entry_width - ozone->dimensions.entry_icon_padding, - y + ozone->dimensions.entry_height / 2 + FONT_SIZE_ENTRIES_LABEL * 3/8 + scroll_y, alpha_uint32, &entry); + menu_animation_ticker(&ticker); + } + + ozone_draw_entry_value(ozone, video_info, entry_value_ticker, + value_x_offset + (unsigned) ozone->dimensions.sidebar_width + entry_padding + x_offset + entry_width - ozone->dimensions.entry_icon_padding, + y + ozone->dimensions.entry_height / 2 + FONT_SIZE_ENTRIES_LABEL * 3/8 + scroll_y, alpha_uint32, &entry); icons_iterate: y += node->height; diff --git a/menu/drivers/ozone/ozone_sidebar.c b/menu/drivers/ozone/ozone_sidebar.c index 7711205ee5..86f2d5e6b1 100644 --- a/menu/drivers/ozone/ozone_sidebar.c +++ b/menu/drivers/ozone/ozone_sidebar.c @@ -110,14 +110,30 @@ void ozone_draw_sidebar(ozone_handle_t *ozone, video_frame_info_t *video_info) unsigned i, sidebar_height, selection_y, selection_old_y, horizontal_list_size; char console_title[255]; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; static const char* const ticker_spacer = OZONE_TICKER_SPACER; + unsigned ticker_x_offset = 0; settings_t *settings = config_get_ptr(); - uint32_t text_alpha = ozone->animations.sidebar_text_alpha * 255.0f; + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = ticker_spacer; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = ozone->fonts.sidebar; + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = ticker_spacer; + ticker_smooth.x_offset = &ticker_x_offset; + ticker_smooth.dst_str_width = NULL; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + } selection_y = 0; selection_old_y = 0; @@ -234,15 +250,27 @@ void ozone_draw_sidebar(ozone_handle_t *ozone, video_frame_info_t *video_info) if (ozone->sidebar_collapsed) goto console_iterate; - ticker.idx = menu_animation_get_ticker_idx(); - ticker.len = (entry_width - ozone->dimensions.sidebar_entry_icon_size - 35) / ozone->sidebar_font_glyph_width; - ticker.s = console_title; - ticker.selected = selected; - ticker.str = node->console_name; + if (use_smooth_ticker) + { + ticker_smooth.selected = selected; + ticker_smooth.field_width = (entry_width - ozone->dimensions.sidebar_entry_icon_size - 40); + ticker_smooth.src_str = node->console_name; + ticker_smooth.dst_str = console_title; + ticker_smooth.dst_str_len = sizeof(console_title); - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.len = (entry_width - ozone->dimensions.sidebar_entry_icon_size - 40) / ozone->sidebar_font_glyph_width; + ticker.s = console_title; + ticker.selected = selected; + ticker.str = node->console_name; - ozone_draw_text(video_info, ozone, console_title, ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.sidebar_entry_icon_padding * 2 + ozone->dimensions.sidebar_entry_icon_size, + menu_animation_ticker(&ticker); + } + + ozone_draw_text(video_info, ozone, console_title, ticker_x_offset + ozone->sidebar_offset + ozone->dimensions.sidebar_padding_horizontal + ozone->dimensions.sidebar_entry_icon_padding * 2 + ozone->dimensions.sidebar_entry_icon_size, y + ozone->dimensions.sidebar_entry_height / 2 + FONT_SIZE_SIDEBAR * 3/8 + ozone->animations.scroll_y_sidebar, TEXT_ALIGN_LEFT, video_info->width, video_info->height, ozone->fonts.sidebar, text_color, true); diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index fb2101455b..231846bf03 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -2820,7 +2820,10 @@ static void rgui_blit_cursor(void) } } -static void rgui_render_osk(rgui_t *rgui, menu_animation_ctx_ticker_t *ticker) +static void rgui_render_osk( + rgui_t *rgui, + menu_animation_ctx_ticker_t *ticker, menu_animation_ctx_ticker_smooth_t *ticker_smooth, + bool use_smooth_ticker) { size_t fb_pitch; unsigned fb_width, fb_height; @@ -2945,17 +2948,33 @@ static void rgui_render_osk(rgui_t *rgui, menu_animation_ctx_ticker_t *ticker) char input_label_buf[255]; unsigned input_label_length; int input_label_x, input_label_y; + unsigned ticker_x_offset = 0; input_label_buf[0] = '\0'; - ticker->s = input_label_buf; - ticker->len = input_label_max_length; - ticker->str = input_label; - ticker->selected = true; - menu_animation_ticker(ticker); + if (use_smooth_ticker) + { + ticker_smooth->selected = true; + ticker_smooth->field_width = input_label_max_length * FONT_WIDTH_STRIDE; + ticker_smooth->src_str = input_label; + ticker_smooth->dst_str = input_label_buf; + ticker_smooth->dst_str_len = sizeof(input_label_buf); + ticker_smooth->x_offset = &ticker_x_offset; + + menu_animation_ticker_smooth(ticker_smooth); + } + else + { + ticker->s = input_label_buf; + ticker->len = input_label_max_length; + ticker->str = input_label; + ticker->selected = true; + + menu_animation_ticker(ticker); + } input_label_length = (unsigned)(utf8len(input_label_buf) * FONT_WIDTH_STRIDE); - input_label_x = osk_x + input_offset_x + ((input_label_max_length * FONT_WIDTH_STRIDE) - input_label_length) / 2; + input_label_x = ticker_x_offset + osk_x + input_offset_x + ((input_label_max_length * FONT_WIDTH_STRIDE) - input_label_length) / 2; input_label_y = osk_y + input_offset_y; blit_line(fb_width, input_label_x, input_label_y, input_label_buf, @@ -3087,11 +3106,14 @@ static void rgui_render(void *data, bool is_idle) { menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; static const char* const ticker_spacer = RGUI_TICKER_SPACER; + bool use_smooth_ticker; unsigned x, y; size_t i, end, fb_pitch, old_start, new_start; unsigned fb_width, fb_height; int bottom; + unsigned ticker_x_offset = 0; size_t entries_end = 0; bool msg_force = false; bool fb_size_changed = false; @@ -3231,14 +3253,27 @@ static void rgui_render(void *data, /* We use a single ticker for all text animations, * with the following configuration: */ - ticker.idx = menu_animation_get_ticker_idx(); - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = ticker_spacer; + use_smooth_ticker = settings->bools.menu_ticker_smooth; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = NULL; + ticker_smooth.glyph_width = FONT_WIDTH_STRIDE; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = ticker_spacer; + ticker_smooth.dst_str_width = NULL; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = ticker_spacer; + } /* Note: On-screen keyboard takes precedence over * normal menu thumbnail/text list display modes */ if (current_display_cb) - rgui_render_osk(rgui, &ticker); + rgui_render_osk(rgui, &ticker, &ticker_smooth, use_smooth_ticker); else if (rgui->show_fs_thumbnail && rgui->entry_has_thumbnail && (fs_thumbnail.is_valid || (rgui->thumbnail_queue_size > 0))) { /* If fullscreen thumbnails are enabled and we are viewing a playlist, @@ -3260,14 +3295,34 @@ static void rgui_render(void *data, if (menu_thumbnail_get_label(rgui->thumbnail_path_data, &thumbnail_title)) { /* Format thumbnail title */ - ticker.s = thumbnail_title_buf; - ticker.len = rgui_term_layout.width - 10; - ticker.str = thumbnail_title; - ticker.selected = true; - menu_animation_ticker(&ticker); + if (use_smooth_ticker) + { + ticker_smooth.selected = true; + ticker_smooth.field_width = (rgui_term_layout.width - 10) * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = thumbnail_title; + ticker_smooth.dst_str = thumbnail_title_buf; + ticker_smooth.dst_str_len = sizeof(thumbnail_title_buf); + ticker_smooth.x_offset = &ticker_x_offset; - title_width = (unsigned)(utf8len(thumbnail_title_buf) * FONT_WIDTH_STRIDE); - title_x = rgui_term_layout.start_x + ((rgui_term_layout.width * FONT_WIDTH_STRIDE) - title_width) / 2; + /* If title is scrolling, then width == field_width */ + if (menu_animation_ticker_smooth(&ticker_smooth)) + title_width = ticker_smooth.field_width; + else + title_width = (unsigned)(utf8len(thumbnail_title_buf) * FONT_WIDTH_STRIDE); + } + else + { + ticker.s = thumbnail_title_buf; + ticker.len = rgui_term_layout.width - 10; + ticker.str = thumbnail_title; + ticker.selected = true; + + menu_animation_ticker(&ticker); + + title_width = (unsigned)(utf8len(thumbnail_title_buf) * FONT_WIDTH_STRIDE); + } + + title_x = rgui_term_layout.start_x + ((rgui_term_layout.width * FONT_WIDTH_STRIDE) - title_width) / 2; /* Draw thumbnail title background */ rgui_fill_rect(rgui_frame_buf.data, fb_width, fb_height, @@ -3275,7 +3330,7 @@ static void rgui_render(void *data, rgui->colors.bg_dark_color, rgui->colors.bg_light_color, rgui->bg_thickness); /* Draw thumbnail title */ - blit_line(fb_width, (int)title_x, 0, thumbnail_title_buf, + blit_line(fb_width, ticker_x_offset + title_x, 0, thumbnail_title_buf, rgui->colors.hover_color, rgui->colors.shadow_color); } } @@ -3390,17 +3445,36 @@ static void rgui_render(void *data, title_max_len = rgui_term_layout.width - 5 - (powerstate_len > 5 ? powerstate_len : 5); title_buf[0] = '\0'; - ticker.s = title_buf; - ticker.len = title_max_len; - ticker.str = rgui->menu_title; - ticker.selected = true; + if (use_smooth_ticker) + { + ticker_smooth.selected = true; + ticker_smooth.field_width = title_max_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = rgui->menu_title; + ticker_smooth.dst_str = title_buf; + ticker_smooth.dst_str_len = sizeof(title_buf); + ticker_smooth.x_offset = &ticker_x_offset; - menu_animation_ticker(&ticker); + /* If title is scrolling, then title_len == title_max_len */ + if (menu_animation_ticker_smooth(&ticker_smooth)) + title_len = title_max_len; + else + title_len = utf8len(title_buf); + } + else + { + ticker.s = title_buf; + ticker.len = title_max_len; + ticker.str = rgui->menu_title; + ticker.selected = true; + + menu_animation_ticker(&ticker); + + title_len = utf8len(title_buf); + } string_to_upper(title_buf); - title_len = utf8len(title_buf); - title_x = rgui_term_layout.start_x + + title_x = ticker_x_offset + rgui_term_layout.start_x + (rgui_term_layout.width - title_len) * FONT_WIDTH_STRIDE / 2; /* Title is always centred, unless it is long enough @@ -3509,15 +3583,29 @@ static void rgui_render(void *data, } /* Format entry title string */ - ticker.s = entry_title_buf; - ticker.len = entry_title_max_len; - ticker.str = entry_label; - ticker.selected = entry_selected; + if (use_smooth_ticker) + { + ticker_smooth.selected = entry_selected; + ticker_smooth.field_width = entry_title_max_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = entry_label; + ticker_smooth.dst_str = entry_title_buf; + ticker_smooth.dst_str_len = sizeof(entry_title_buf); + ticker_smooth.x_offset = &ticker_x_offset; - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = entry_title_buf; + ticker.len = entry_title_max_len; + ticker.str = entry_label; + ticker.selected = entry_selected; + + menu_animation_ticker(&ticker); + } /* Print entry title */ - blit_line(fb_width, x + (2 * FONT_WIDTH_STRIDE), y, + blit_line(fb_width, ticker_x_offset + x + (2 * FONT_WIDTH_STRIDE), y, entry_title_buf, entry_color, rgui->colors.shadow_color); @@ -3525,14 +3613,27 @@ static void rgui_render(void *data, if (entry_value_len > 0) { /* Format entry value string */ - ticker.s = type_str_buf; - ticker.len = entry_value_len; - ticker.str = entry_value; + if (use_smooth_ticker) + { + ticker_smooth.field_width = entry_value_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = entry_value; + ticker_smooth.dst_str = type_str_buf; + ticker_smooth.dst_str_len = sizeof(type_str_buf); + ticker_smooth.x_offset = &ticker_x_offset; - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = type_str_buf; + ticker.len = entry_value_len; + ticker.str = entry_value; + + menu_animation_ticker(&ticker); + } /* Print entry value */ - blit_line(fb_width, term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), y, + blit_line(fb_width, ticker_x_offset + term_end_x - ((entry_value_len + 1) * FONT_WIDTH_STRIDE), y, type_str_buf, entry_color, rgui->colors.shadow_color); } @@ -3564,16 +3665,30 @@ static void rgui_render(void *data, char sublabel_buf[MENU_SUBLABEL_MAX_LENGTH]; sublabel_buf[0] = '\0'; - ticker.s = sublabel_buf; - ticker.len = core_name_len; - ticker.str = rgui->menu_sublabel; - ticker.selected = true; + if (use_smooth_ticker) + { + ticker_smooth.selected = true; + ticker_smooth.field_width = core_name_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = rgui->menu_sublabel; + ticker_smooth.dst_str = sublabel_buf; + ticker_smooth.dst_str_len = sizeof(sublabel_buf); + ticker_smooth.x_offset = &ticker_x_offset; - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = sublabel_buf; + ticker.len = core_name_len; + ticker.str = rgui->menu_sublabel; + ticker.selected = true; + + menu_animation_ticker(&ticker); + } blit_line( fb_width, - rgui_term_layout.start_x + FONT_WIDTH_STRIDE, + ticker_x_offset + rgui_term_layout.start_x + FONT_WIDTH_STRIDE, (rgui_term_layout.height * FONT_HEIGHT_STRIDE) + rgui_term_layout.start_y + 2, sublabel_buf, rgui->colors.hover_color, rgui->colors.shadow_color); @@ -3586,16 +3701,30 @@ static void rgui_render(void *data, menu_entries_get_core_title(core_title, sizeof(core_title)); - ticker.s = core_title_buf; - ticker.len = core_name_len; - ticker.str = core_title; - ticker.selected = true; + if (use_smooth_ticker) + { + ticker_smooth.selected = true; + ticker_smooth.field_width = core_name_len * FONT_WIDTH_STRIDE; + ticker_smooth.src_str = core_title; + ticker_smooth.dst_str = core_title_buf; + ticker_smooth.dst_str_len = sizeof(core_title_buf); + ticker_smooth.x_offset = &ticker_x_offset; - menu_animation_ticker(&ticker); + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = core_title_buf; + ticker.len = core_name_len; + ticker.str = core_title; + ticker.selected = true; + + menu_animation_ticker(&ticker); + } blit_line( fb_width, - rgui_term_layout.start_x + FONT_WIDTH_STRIDE, + ticker_x_offset + rgui_term_layout.start_x + FONT_WIDTH_STRIDE, (rgui_term_layout.height * FONT_HEIGHT_STRIDE) + rgui_term_layout.start_y + 2, core_title_buf, rgui->colors.hover_color, rgui->colors.shadow_color); diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index f280c6881d..17e3669a6f 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -2845,7 +2845,9 @@ static int xmb_draw_item( { float icon_x, icon_y, label_offset; menu_animation_ctx_ticker_t ticker; + menu_animation_ctx_ticker_smooth_t ticker_smooth; char tmp[255]; + unsigned ticker_x_offset = 0; const char *ticker_str = NULL; unsigned entry_type = 0; const float half_size = xmb->icon_size / 2.0f; @@ -2856,10 +2858,25 @@ static int xmb_draw_item( xmb_node_t * node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); settings_t *settings = config_get_ptr(); + bool use_smooth_ticker = settings->bools.menu_ticker_smooth; /* Initial ticker configuration */ - ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; - ticker.spacer = NULL; + if (use_smooth_ticker) + { + ticker_smooth.idx = menu_animation_get_ticker_fast_idx(); + ticker_smooth.font = xmb->font; + ticker_smooth.font_scale = 1.0f; + ticker_smooth.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker_smooth.spacer = NULL; + ticker_smooth.x_offset = &ticker_x_offset; + ticker_smooth.dst_str_width = NULL; + } + else + { + ticker.idx = menu_animation_get_ticker_idx(); + ticker.type_enum = (enum menu_animation_ticker_type)settings->uints.menu_ticker_type; + ticker.spacer = NULL; + } if (!node) return 0; @@ -2979,14 +2996,27 @@ static int xmb_draw_item( menu_entry_get_rich_label(entry, &ticker_str); - ticker.s = tmp; - ticker.len = ticker_limit; - ticker.idx = menu_animation_get_ticker_idx(); - ticker.str = ticker_str; - ticker.selected = (i == current); + if (use_smooth_ticker) + { + ticker_smooth.selected = (i == current); + ticker_smooth.field_width = xmb->font_size * 0.5f * ticker_limit; + ticker_smooth.src_str = ticker_str; + ticker_smooth.dst_str = tmp; + ticker_smooth.dst_str_len = sizeof(tmp); - if (ticker.str) - menu_animation_ticker(&ticker); + if (ticker_smooth.src_str) + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = tmp; + ticker.len = ticker_limit; + ticker.str = ticker_str; + ticker.selected = (i == current); + + if (ticker.str) + menu_animation_ticker(&ticker); + } label_offset = xmb->margins_label_top; @@ -3028,7 +3058,7 @@ static int xmb_draw_item( } xmb_draw_text(video_info, xmb, tmp, - node->x + xmb->margins_screen_left + + (float)ticker_x_offset + node->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->margins_label_left, xmb->margins_screen_top + node->y + label_offset, 1, node->label_alpha, TEXT_ALIGN_LEFT, @@ -3036,23 +3066,31 @@ static int xmb_draw_item( tmp[0] = '\0'; - ticker.s = tmp; - ticker.len = 35 * scale_mod[7]; - ticker.idx = menu_animation_get_ticker_idx(); - ticker.selected = (i == current); - - if (!string_is_empty(entry->value)) + if (use_smooth_ticker) { - const char *entry_value = NULL; - menu_entry_get_value(entry, &entry_value); - ticker.str = entry_value; + ticker_smooth.selected = (i == current); + ticker_smooth.field_width = xmb->font_size * 0.5f * 35 * scale_mod[7]; + ticker_smooth.src_str = entry->value; + ticker_smooth.dst_str = tmp; + ticker_smooth.dst_str_len = sizeof(tmp); - menu_animation_ticker(&ticker); + if (!string_is_empty(entry->value)) + menu_animation_ticker_smooth(&ticker_smooth); + } + else + { + ticker.s = tmp; + ticker.len = 35 * scale_mod[7]; + ticker.selected = (i == current); + ticker.str = entry->value; + + if (!string_is_empty(entry->value)) + menu_animation_ticker(&ticker); } if (do_draw_text) xmb_draw_text(video_info, xmb, tmp, - node->x + + (float)ticker_x_offset + node->x + + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->margins_label_left diff --git a/menu/menu_animation.c b/menu/menu_animation.c index 50ebeb6453..5a47d369c4 100644 --- a/menu/menu_animation.c +++ b/menu/menu_animation.c @@ -65,6 +65,7 @@ typedef struct menu_animation menu_animation_t; #define TICKER_SPEED 333 #define TICKER_SLOW_SPEED 1600 +#define TICKER_FAST_SPEED 16 static const char ticker_spacer_default[] = TICKER_SPACER_DEFAULT; @@ -73,6 +74,7 @@ static retro_time_t cur_time = 0; static retro_time_t old_time = 0; static uint64_t ticker_idx = 0; /* updated every TICKER_SPEED ms */ static uint64_t ticker_slow_idx = 0; /* updated every TICKER_SLOW_SPEED ms */ +static uint64_t ticker_fast_idx = 0; /* updated every TICKER_FAST_SPEED ms */ static float delta_time = 0.0f; static bool animation_is_active = false; static bool ticker_is_active = false; @@ -387,6 +389,235 @@ static void menu_animation_ticker_loop(uint64_t idx, *width3 = width; } +static void ticker_smooth_scan_characters( + const unsigned *char_widths, size_t num_chars, unsigned field_width, unsigned scroll_offset, + unsigned *char_offset, unsigned *num_chars_to_copy, unsigned *x_offset, + unsigned *str_width, unsigned *display_width) +{ + unsigned text_width = 0; + unsigned scroll_pos = scroll_offset; + bool deferred_str_width = true; + unsigned i; + + /* Initialise output variables to 'sane' values */ + *char_offset = 0; + *num_chars_to_copy = 0; + *x_offset = 0; + if (str_width) + *str_width = 0; + if (display_width) + *display_width = 0; + + /* Determine index of first character to copy */ + if (scroll_pos == 0) + { + *char_offset = 0; + *x_offset = 0; + } + else + { + for (i = 0; i < num_chars; i++) + { + if (scroll_pos > char_widths[i]) + scroll_pos -= char_widths[i]; + else + { + /* Note: It's okay for char_offset to go out + * of range here (num_chars_to_copy will be zero + * in this case) */ + *char_offset = i + 1; + *x_offset = char_widths[i] - scroll_pos; + break; + } + } + } + + /* Determine number of characters to copy */ + for (i = *char_offset; i < num_chars; i++) + { + text_width += char_widths[i]; + + if (*x_offset + text_width <= field_width) + (*num_chars_to_copy)++; + else + { + /* Get actual width of resultant string + * (excluding x offset + end padding) + * Note that this is only set if we exceed the + * field width - if all characters up to the end + * of the string are copied... */ + if (str_width) + { + deferred_str_width = false; + *str_width = text_width - char_widths[i]; + } + break; + } + } + + /* ...then we have to update str_width here instead */ + if (str_width) + if (deferred_str_width) + *str_width = text_width; + + /* Get total display width of resultant string + * (x offset + text width + end padding) */ + if (display_width) + { + *display_width = *x_offset + text_width; + *display_width = (*display_width > field_width) ? field_width : *display_width; + } +} + +static void menu_animation_ticker_smooth_generic(uint64_t idx, + const unsigned *char_widths, size_t num_chars, unsigned str_width, unsigned field_width, + unsigned *char_offset, unsigned *num_chars_to_copy, unsigned *x_offset, unsigned *dst_str_width) +{ + unsigned scroll_width = str_width - field_width; + unsigned scroll_offset = 0; + + unsigned pause_duration = 32; + unsigned ticker_period = 2 * (scroll_width + pause_duration); + unsigned phase = idx % ticker_period; + + /* Initialise output variables to 'sane' values */ + *char_offset = 0; + *num_chars_to_copy = 0; + *x_offset = 0; + if (dst_str_width) + *dst_str_width = 0; + + /* Sanity check */ + if (num_chars < 1) + return; + + /* Determine scroll offset */ + if (phase < pause_duration) + scroll_offset = 0; + else if (phase < ticker_period >> 1) + scroll_offset = phase - pause_duration; + else if (phase < (ticker_period >> 1) + pause_duration) + scroll_offset = (ticker_period - (2 * pause_duration)) >> 1; + else + scroll_offset = ticker_period - phase; + + ticker_smooth_scan_characters( + char_widths, num_chars, field_width, scroll_offset, + char_offset, num_chars_to_copy, x_offset, dst_str_width, NULL); +} + +static void menu_animation_ticker_smooth_loop(uint64_t idx, + const unsigned *char_widths, size_t num_chars, + const unsigned *spacer_widths, size_t num_spacer_chars, + unsigned str_width, unsigned spacer_width, unsigned field_width, + unsigned *char_offset1, unsigned *num_chars_to_copy1, + unsigned *char_offset2, unsigned *num_chars_to_copy2, + unsigned *char_offset3, unsigned *num_chars_to_copy3, + unsigned *x_offset, unsigned *dst_str_width) + +{ + unsigned ticker_period = str_width + spacer_width; + unsigned phase = idx % ticker_period; + + unsigned remaining_width = field_width; + + /* Initialise output variables to 'sane' values */ + *char_offset1 = 0; + *num_chars_to_copy1 = 0; + *char_offset2 = 0; + *num_chars_to_copy2 = 0; + *char_offset3 = 0; + *num_chars_to_copy3 = 0; + *x_offset = 0; + if (dst_str_width) + *dst_str_width = 0; + + /* 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: + * + * |----field_width----| + * [string 1][string 2][string 3] + */ + + /* String 1 */ + if (phase < str_width) + { + unsigned scroll_offset = phase; + unsigned display_width = 0; + unsigned str1_width = 0; + + ticker_smooth_scan_characters( + char_widths, num_chars, remaining_width, scroll_offset, + char_offset1, num_chars_to_copy1, x_offset, &str1_width, &display_width); + + /* Update remaining width */ + remaining_width -= display_width; + + /* Update dst_str_width */ + if (dst_str_width) + *dst_str_width += str1_width; + } + + /* String 2 */ + if (remaining_width > 0) + { + unsigned scroll_offset = 0; + unsigned display_width = 0; + unsigned str2_width = 0; + unsigned x_offset2 = 0; + + /* Check whether we've passed the end of string 1 */ + if (phase > str_width) + scroll_offset = phase - str_width; + else + scroll_offset = 0; + + ticker_smooth_scan_characters( + spacer_widths, num_spacer_chars, remaining_width, scroll_offset, + char_offset2, num_chars_to_copy2, &x_offset2, &str2_width, &display_width); + + /* > Update remaining width */ + remaining_width -= display_width; + + /* Update dst_str_width */ + if (dst_str_width) + *dst_str_width += str2_width; + + /* If scroll_offset is greater than zero, it means + * string 2 is the first string to be displayed + * > ticker x offset is therefore string 2's offset */ + if (scroll_offset > 0) + *x_offset = x_offset2; + } + + /* String 3 */ + if (remaining_width > 0) + { + /* String 3 is only shown when string 2 is shown, + * so we can take some shortcuts... */ + unsigned i; + unsigned text_width = 0; + *char_offset3 = 0; + + /* Determine number of characters to copy */ + for (i = 0; i < num_chars; i++) + { + text_width += char_widths[i]; + + if (text_width <= remaining_width) + (*num_chars_to_copy3)++; + else + { + /* Update dst_str_width */ + if (dst_str_width) + *dst_str_width += text_width - char_widths[i]; + break; + } + } + } +} + static size_t get_line_display_ticks(size_t line_width) { /* Mean human reading speed for all western languages, @@ -619,6 +850,11 @@ static void menu_animation_update_time(bool timedate_enable) last_ticker_update = 0; static retro_time_t last_ticker_slow_update = 0; + static retro_time_t + last_ticker_fast_update = 0; + + static float ticker_fast_accumulator = 0.0L; + unsigned ticker_fast_accumulator_uint = 0; /* Adjust ticker speed */ settings_t *settings = config_get_ptr(); @@ -638,18 +874,35 @@ static void menu_animation_update_time(bool timedate_enable) last_clock_update = cur_time; } - if (ticker_is_active - && cur_time - last_ticker_update >= ticker_speed) + if (ticker_is_active) { - ticker_idx++; - last_ticker_update = cur_time; - } + if (cur_time - last_ticker_update >= ticker_speed) + { + ticker_idx++; + last_ticker_update = cur_time; + } - if (ticker_is_active - && cur_time - last_ticker_slow_update >= ticker_slow_speed) - { - ticker_slow_idx++; - last_ticker_slow_update = cur_time; + if (cur_time - last_ticker_slow_update >= ticker_slow_speed) + { + ticker_slow_idx++; + last_ticker_slow_update = cur_time; + } + + if (cur_time - last_ticker_fast_update >= TICKER_FAST_SPEED) + { + /* ticker fast updates approximately every frame, so + * have to handle the speed setting differently... */ + ticker_fast_accumulator += speed_factor * 0.5f; + ticker_fast_accumulator_uint = (unsigned)ticker_fast_accumulator; + + if (ticker_fast_accumulator_uint > 0) + { + ticker_fast_idx += ticker_fast_accumulator_uint; + ticker_fast_accumulator -= (float)ticker_fast_accumulator_uint; + } + + last_ticker_fast_update = cur_time; + } } } @@ -826,6 +1079,292 @@ bool menu_animation_ticker(menu_animation_ctx_ticker_t *ticker) return true; } +bool menu_animation_ticker_smooth(menu_animation_ctx_ticker_smooth_t *ticker) +{ + size_t i; + size_t src_str_len = 0; + size_t spacer_len = 0; + unsigned src_str_width = 0; + unsigned spacer_width = 0; + unsigned *src_char_widths = NULL; + unsigned *spacer_char_widths = NULL; + bool success = false; + bool is_active = false; + + /* Sanity check */ + if (string_is_empty(ticker->src_str) || + (ticker->dst_str_len < 1) || + (ticker->field_width < 1) || + (!ticker->font && (ticker->glyph_width < 1))) + goto end; + + /* Find the display width of each character in + * the src string + total width */ + src_str_len = utf8len(ticker->src_str); + if (src_str_len < 1) + goto end; + + src_char_widths = (unsigned*)calloc(src_str_len, sizeof(unsigned)); + if (!src_char_widths) + goto end; + + /* > If a font is provided, have to do this + * 'the hard way' + * (Note: we branch externally rather than inside + * for loop - this is ugly, but improves performance) */ + if (ticker->font) + { + const char *str_ptr = ticker->src_str; + + for (i = 0; i < src_str_len; i++) + { + int glyph_width = font_driver_get_message_width( + ticker->font, str_ptr, 1, ticker->font_scale); + + if (glyph_width < 0) + goto end; + + src_char_widths[i] = (unsigned)glyph_width; + src_str_width += (unsigned)glyph_width; + + str_ptr = utf8skip(str_ptr, 1); + } + } + /* > If font is not provided, just use fallback width */ + else + { + unsigned glyph_width = ticker->glyph_width; + + for (i = 0; i < src_str_len; i++) + { + src_char_widths[i] = glyph_width; + src_str_width += glyph_width; + } + } + + /* If total src string width is <= text field width, we + * can just copy the entire string */ + if (src_str_width <= ticker->field_width) + { + utf8cpy(ticker->dst_str, ticker->dst_str_len, ticker->src_str, src_str_len); + + if (ticker->dst_str_width) + *ticker->dst_str_width = src_str_width; + *ticker->x_offset = 0; + success = true; + goto end; + } + + /* If entry is not selected, just clip input string + * and add '...' suffix */ + if (!ticker->selected) + { + unsigned text_width; + unsigned current_width = 0; + unsigned num_chars = 0; + int period_width = 0; + + if (ticker->font) + period_width = font_driver_get_message_width(ticker->font, ".", 1, ticker->font_scale); + else + period_width = ticker->glyph_width; + + if (period_width < 0) + goto end; + + /* Sanity check */ + if (ticker->field_width < (3 * period_width)) + goto end; + + /* Determine number of characters to copy */ + text_width = ticker->field_width - (3 * period_width); + while (true) + { + current_width += src_char_widths[num_chars]; + + if (current_width > text_width) + { + /* Have to go back one in order to get 'actual' + * value for dst_str_width */ + current_width -= src_char_widths[num_chars]; + break; + } + + num_chars++; + } + + /* Copy string segment + add suffix */ + utf8cpy(ticker->dst_str, ticker->dst_str_len, ticker->src_str, num_chars); + strlcat(ticker->dst_str, "...", ticker->dst_str_len); + + if (ticker->dst_str_width) + *ticker->dst_str_width = current_width + (3 * period_width); + *ticker->x_offset = 0; + success = true; + goto end; + } + + /* If we get this far, then a scrolling animation + * is required... */ + + /* Use default spacer, if none is provided */ + if (!ticker->spacer) + ticker->spacer = ticker_spacer_default; + + /* Find the display width of each character in + * the spacer */ + spacer_len = utf8len(ticker->spacer); + if (spacer_len < 1) + goto end; + + spacer_char_widths = (unsigned*)calloc(spacer_len, sizeof(unsigned)); + if (!spacer_char_widths) + goto end; + + /* > If a font is provided, have to do this + * 'the hard way' + * (Note: we branch externally rather than inside + * for loop - this is ugly, but improves performance) */ + if (ticker->font) + { + const char *str_ptr = ticker->spacer; + + for (i = 0; i < spacer_len; i++) + { + int glyph_width = font_driver_get_message_width( + ticker->font, str_ptr, 1, ticker->font_scale); + + if (glyph_width < 0) + goto end; + + spacer_char_widths[i] = (unsigned)glyph_width; + spacer_width += (unsigned)glyph_width; + + str_ptr = utf8skip(str_ptr, 1); + } + } + else + { + unsigned glyph_width = ticker->glyph_width; + + for (i = 0; i < spacer_len; i++) + { + spacer_char_widths[i] = glyph_width; + spacer_width += glyph_width; + } + } + + /* Determine animation type */ + switch (ticker->type_enum) + { + case TICKER_TYPE_LOOP: + { + unsigned char_offset1 = 0; + unsigned num_chars1 = 0; + unsigned char_offset2 = 0; + unsigned num_chars2 = 0; + unsigned char_offset3 = 0; + unsigned num_chars3 = 0; + + char tmp[PATH_MAX_LENGTH]; + + tmp[0] = '\0'; + ticker->dst_str[0] = '\0'; + + menu_animation_ticker_smooth_loop( + ticker->idx, + src_char_widths, src_str_len, + spacer_char_widths, spacer_len, + src_str_width, spacer_width, ticker->field_width, + &char_offset1, &num_chars1, + &char_offset2, &num_chars2, + &char_offset3, &num_chars3, + ticker->x_offset, ticker->dst_str_width); + + /* Copy 'trailing' chunk of source string, if required */ + if (num_chars1 > 0) + { + utf8cpy( + ticker->dst_str, ticker->dst_str_len, + utf8skip(ticker->src_str, char_offset1), num_chars1); + } + + /* Copy chunk of spacer string, if required */ + if (num_chars2 > 0) + { + utf8cpy( + tmp, sizeof(tmp), + utf8skip(ticker->spacer, char_offset2), num_chars2); + + strlcat(ticker->dst_str, tmp, ticker->dst_str_len); + } + + /* Copy 'leading' chunk of source string, if required */ + if (num_chars3 > 0) + { + utf8cpy( + tmp, sizeof(tmp), + utf8skip(ticker->src_str, char_offset3), num_chars3); + + strlcat(ticker->dst_str, tmp, ticker->dst_str_len); + } + + break; + } + case TICKER_TYPE_BOUNCE: + default: + { + unsigned char_offset = 0; + unsigned num_chars = 0; + + ticker->dst_str[0] = '\0'; + + menu_animation_ticker_smooth_generic( + ticker->idx, + src_char_widths, src_str_len, src_str_width, ticker->field_width, + &char_offset, &num_chars, ticker->x_offset, ticker->dst_str_width); + + /* Copy required substring */ + if (num_chars > 0) + { + utf8cpy( + ticker->dst_str, ticker->dst_str_len, + utf8skip(ticker->src_str, char_offset), num_chars); + } + + break; + } + } + + success = true; + is_active = true; + ticker_is_active = true; + +end: + + if (src_char_widths) + { + free(src_char_widths); + src_char_widths = NULL; + } + + if (spacer_char_widths) + { + free(spacer_char_widths); + spacer_char_widths = NULL; + } + + if (!success) + { + *ticker->x_offset = 0; + + if (ticker->dst_str_len > 0) + ticker->dst_str[0] = '\0'; + } + + return is_active; +} + bool menu_animation_line_ticker(menu_animation_ctx_line_ticker_t *line_ticker) { size_t i; @@ -1094,3 +1633,8 @@ uint64_t menu_animation_get_ticker_slow_idx(void) { return ticker_slow_idx; } + +uint64_t menu_animation_get_ticker_fast_idx(void) +{ + return ticker_fast_idx; +} diff --git a/menu/menu_animation.h b/menu/menu_animation.h index b35987b661..8ce8173f90 100644 --- a/menu/menu_animation.h +++ b/menu/menu_animation.h @@ -23,6 +23,8 @@ #include #include +#include "../gfx/font_driver.h" + RETRO_BEGIN_DECLS #define TICKER_SPACER_DEFAULT " | " @@ -126,6 +128,23 @@ typedef struct menu_animation_ctx_ticker const char *spacer; } menu_animation_ctx_ticker_t; +typedef struct menu_animation_ctx_ticker_smooth +{ + bool selected; + font_data_t *font; + float font_scale; + unsigned glyph_width; /* Fallback if font == NULL */ + unsigned field_width; + enum menu_animation_ticker_type type_enum; + uint64_t idx; + const char *src_str; + const char *spacer; + char *dst_str; + size_t dst_str_len; + unsigned *dst_str_width; /* May be set to NULL (RGUI + XMB do not require this info) */ + unsigned *x_offset; +} menu_animation_ctx_ticker_smooth_t; + typedef struct menu_animation_ctx_line_ticker { size_t line_width; @@ -160,6 +179,8 @@ bool menu_animation_update(void); bool menu_animation_ticker(menu_animation_ctx_ticker_t *ticker); +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); float menu_animation_get_delta_time(void); @@ -180,6 +201,8 @@ uint64_t menu_animation_get_ticker_idx(void); uint64_t menu_animation_get_ticker_slow_idx(void); +uint64_t menu_animation_get_ticker_fast_idx(void); + RETRO_END_DECLS #endif diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index bd6ec9a274..5e501c3b34 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -5212,6 +5212,7 @@ unsigned menu_displaylist_build_list(file_list_t *list, enum menu_displaylist_ct {MENU_ENUM_LABEL_MENU_RGUI_THUMBNAIL_DELAY, PARSE_ONLY_UINT }, {MENU_ENUM_LABEL_MENU_TICKER_TYPE, PARSE_ONLY_UINT }, {MENU_ENUM_LABEL_MENU_TICKER_SPEED, PARSE_ONLY_FLOAT}, + {MENU_ENUM_LABEL_MENU_TICKER_SMOOTH, PARSE_ONLY_BOOL }, {MENU_ENUM_LABEL_MENU_RGUI_EXTENDED_ASCII, PARSE_ONLY_BOOL }, }; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index daf6924266..f620ebeafd 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -11567,6 +11567,21 @@ static bool setting_append_list( (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; menu_settings_list_current_add_range(list, list_info, 0.1, 10.0, 0.1, true, true); + CONFIG_BOOL( + list, list_info, + &settings->bools.menu_ticker_smooth, + MENU_ENUM_LABEL_MENU_TICKER_SMOOTH, + MENU_ENUM_LABEL_VALUE_MENU_TICKER_SMOOTH, + menu_ticker_smooth, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + 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 453530746d..d4988db870 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -986,6 +986,7 @@ enum msg_hash_enums MENU_LABEL(QUICK_MENU_SHOW_DOWNLOAD_THUMBNAILS), MENU_LABEL(MENU_TICKER_TYPE), MENU_LABEL(MENU_TICKER_SPEED), + MENU_LABEL(MENU_TICKER_SMOOTH), MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_BOUNCE, MENU_ENUM_LABEL_VALUE_MENU_TICKER_TYPE_LOOP,