diff --git a/Makefile.common b/Makefile.common index 69b585701c9..d4bca2e6a86 100644 --- a/Makefile.common +++ b/Makefile.common @@ -829,6 +829,7 @@ ifeq ($(HAVE_MENU_COMMON), 1) menu/drivers/menu_generic.o \ menu/drivers/null.o \ menu/menu_thumbnail_path.o \ + menu/menu_thumbnail.o \ menu/drivers_display/menu_display_null.o ifeq ($(HAVE_MENU_WIDGETS), 1) diff --git a/config.def.h b/config.def.h index 0f8cd0d2ff0..e7a746db0cc 100644 --- a/config.def.h +++ b/config.def.h @@ -141,6 +141,11 @@ * of screen space when using landscape layouts */ #define DEFAULT_MATERIALUI_AUTO_ROTATE_NAV_BAR true +/* Default portrait/landscape playlist view modes + * (when thumbnails are enabled) */ +#define DEFAULT_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED +#define DEFAULT_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED + #define DEFAULT_CRT_SWITCH_RESOLUTION CRT_SWITCH_NONE #define DEFAULT_CRT_SWITCH_RESOLUTION_SUPER 2560 diff --git a/configuration.c b/configuration.c index 37885e57a09..5b50960faac 100644 --- a/configuration.c +++ b/configuration.c @@ -1812,6 +1812,8 @@ static struct config_uint_setting *populate_settings_uint(settings_t *settings, #endif SETTING_UINT("materialui_menu_color_theme", &settings->uints.menu_materialui_color_theme, true, DEFAULT_MATERIALUI_THEME, false); SETTING_UINT("materialui_menu_transition_animation", &settings->uints.menu_materialui_transition_animation, true, DEFAULT_MATERIALUI_TRANSITION_ANIM, false); + SETTING_UINT("materialui_thumbnail_view_portrait", &settings->uints.menu_materialui_thumbnail_view_portrait, true, DEFAULT_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT, false); + SETTING_UINT("materialui_thumbnail_view_landscape", &settings->uints.menu_materialui_thumbnail_view_landscape, true, DEFAULT_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE, false); SETTING_UINT("menu_shader_pipeline", &settings->uints.menu_xmb_shader_pipeline, true, DEFAULT_MENU_SHADER_PIPELINE, false); #ifdef HAVE_OZONE SETTING_UINT("ozone_menu_color_theme", &settings->uints.menu_ozone_color_theme, true, 1, false); diff --git a/configuration.h b/configuration.h index 0654b4487d9..0c99cf32877 100644 --- a/configuration.h +++ b/configuration.h @@ -535,6 +535,8 @@ typedef struct settings unsigned menu_xmb_thumbnail_scale_factor; unsigned menu_materialui_color_theme; unsigned menu_materialui_transition_animation; + unsigned menu_materialui_thumbnail_view_portrait; + unsigned menu_materialui_thumbnail_view_landscape; unsigned menu_ozone_color_theme; unsigned menu_font_color_red; unsigned menu_font_color_green; diff --git a/griffin/griffin.c b/griffin/griffin.c index fd8edca884d..6f00aa08acd 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1271,6 +1271,7 @@ MENU #include "../menu/menu_displaylist.c" #include "../menu/menu_animation.c" #include "../menu/menu_thumbnail_path.c" +#include "../menu/menu_thumbnail.c" #include "../menu/drivers/null.c" #include "../menu/drivers/menu_generic.c" diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 9c0e2c060c5..c017e04def8 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -729,6 +729,10 @@ MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_COLOR_THEME, "materialui_menu_color_theme") MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_TRANSITION_ANIMATION, "materialui_menu_transition_animation") +MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, + "materialui_thumbnail_view_portrait") +MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, + "materialui_thumbnail_view_landscape") MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_FOOTER_OPACITY, "materialui_menu_footer_opacity") MSG_HASH(MENU_ENUM_LABEL_MATERIALUI_MENU_HEADER_OPACITY, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 3303381a8c5..787f678e768 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1651,6 +1651,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_LOAD_CONTENT_HISTORY, "Load Recent" ) +MSG_HASH( + MENU_ENUM_SUBLABEL_LOAD_CONTENT_HISTORY, + "Select content from recent history playlist." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_LOAD_CONTENT_LIST, "Load Content" @@ -1819,6 +1823,50 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_TRANSITION_ANIM_NONE, "OFF" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, + "Portrait Thumbnail View" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, + "Specify playlist thumbnail view mode when using portrait display orientations." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, + "Landscape Thumbnail View" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, + "Specify playlist thumbnail view mode when using landscape display orientations." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED, + "OFF" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL, + "List (Small)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM, + "List (Medium)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED, + "OFF" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL, + "List (Small)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM, + "List (Medium)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE, + "List (Large)" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_FOOTER_OPACITY, "Footer Opacity" diff --git a/menu/cbs/menu_cbs_scan.c b/menu/cbs/menu_cbs_scan.c index 1c401fd56e4..6ee008f23d5 100644 --- a/menu/cbs/menu_cbs_scan.c +++ b/menu/cbs/menu_cbs_scan.c @@ -108,9 +108,13 @@ int action_switch_thumbnail(const char *path, if (settings->uints.menu_thumbnails == 0) { /* RGUI is a special case where thumbnail 'switch' corresponds to - * toggling thumbnail view on/off. For other menu drivers, we - * cycle through available thumbnail types. */ - if(!string_is_equal(settings->arrays.menu_driver, "rgui")) + * toggling thumbnail view on/off. + * GLUI is a special case where thumbnail 'switch' corresponds to + * changing thumbnail view mode. + * For other menu drivers, we cycle through available thumbnail + * types. */ + if(!string_is_equal(settings->arrays.menu_driver, "rgui") && + !string_is_equal(settings->arrays.menu_driver, "glui")) { settings->uints.menu_left_thumbnails++; if (settings->uints.menu_left_thumbnails > 3) @@ -122,9 +126,13 @@ int action_switch_thumbnail(const char *path, else { /* RGUI is a special case where thumbnail 'switch' corresponds to - * toggling thumbnail view on/off. For other menu drivers, we - * cycle through available thumbnail types. */ - if(!string_is_equal(settings->arrays.menu_driver, "rgui")) + * toggling thumbnail view on/off. + * GLUI is a special case where thumbnail 'switch' corresponds to + * changing thumbnail view mode. + * For other menu drivers, we cycle through available thumbnail + * types. */ + if(!string_is_equal(settings->arrays.menu_driver, "rgui") && + !string_is_equal(settings->arrays.menu_driver, "glui")) { settings->uints.menu_thumbnails++; if (settings->uints.menu_thumbnails > 3) diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 73630c9ffe2..aff6c9ff537 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -278,6 +278,7 @@ default_sublabel_macro(action_bind_sublabel_load_disc, MENU_ENU default_sublabel_macro(action_bind_sublabel_dump_disc, MENU_ENUM_SUBLABEL_DUMP_DISC) default_sublabel_macro(action_bind_sublabel_content_list, MENU_ENUM_SUBLABEL_LOAD_CONTENT_LIST) default_sublabel_macro(action_bind_sublabel_content_special, MENU_ENUM_SUBLABEL_LOAD_CONTENT_SPECIAL) +default_sublabel_macro(action_bind_sublabel_load_content_history, MENU_ENUM_SUBLABEL_LOAD_CONTENT_HISTORY) default_sublabel_macro(action_bind_sublabel_network_information, MENU_ENUM_SUBLABEL_NETWORK_INFORMATION) default_sublabel_macro(action_bind_sublabel_system_information, MENU_ENUM_SUBLABEL_SYSTEM_INFORMATION) default_sublabel_macro(action_bind_sublabel_quit_retroarch, MENU_ENUM_SUBLABEL_QUIT_RETROARCH) @@ -489,6 +490,8 @@ default_sublabel_macro(action_bind_sublabel_xmb_vertical_thumbnails, default_sublabel_macro(action_bind_sublabel_menu_xmb_thumbnail_scale_factor, MENU_ENUM_SUBLABEL_MENU_XMB_THUMBNAIL_SCALE_FACTOR) default_sublabel_macro(action_bind_sublabel_menu_color_theme, MENU_ENUM_SUBLABEL_MATERIALUI_MENU_COLOR_THEME) default_sublabel_macro(action_bind_sublabel_materialui_menu_transition_animation, MENU_ENUM_SUBLABEL_MATERIALUI_MENU_TRANSITION_ANIMATION) +default_sublabel_macro(action_bind_sublabel_materialui_menu_thumbnail_view_portrait, MENU_ENUM_SUBLABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT) +default_sublabel_macro(action_bind_sublabel_materialui_menu_thumbnail_view_landscape, MENU_ENUM_SUBLABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE) default_sublabel_macro(action_bind_sublabel_ozone_menu_color_theme, MENU_ENUM_SUBLABEL_OZONE_MENU_COLOR_THEME) default_sublabel_macro(action_bind_sublabel_ozone_collapse_sidebar, MENU_ENUM_SUBLABEL_OZONE_COLLAPSE_SIDEBAR) default_sublabel_macro(action_bind_sublabel_ozone_truncate_playlist_name, MENU_ENUM_SUBLABEL_OZONE_TRUNCATE_PLAYLIST_NAME) @@ -1721,6 +1724,12 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MATERIALUI_MENU_TRANSITION_ANIMATION: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_materialui_menu_transition_animation); break; + case MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_materialui_menu_thumbnail_view_portrait); + break; + case MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_materialui_menu_thumbnail_view_landscape); + break; case MENU_ENUM_LABEL_XMB_SHADOWS_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_xmb_shadows_enable); break; @@ -2352,6 +2361,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_LOAD_CONTENT_SPECIAL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_content_special); break; + case MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_load_content_history); + break; case MENU_ENUM_LABEL_START_CORE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_start_core); break; diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 9003ae53448..918cc4125f8 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -43,6 +43,8 @@ #include "../menu_driver.h" #include "../menu_animation.h" #include "../menu_input.h" +#include "../menu_thumbnail_path.h" +#include "../menu_thumbnail.h" #include "../widgets/menu_osk.h" #include "../widgets/menu_filebrowser.h" @@ -916,16 +918,41 @@ typedef struct * Colour Themes END * ============================== */ -/* This struct holds the y position and the line height for each menu entry */ +/* Set a baseline aspect ratio of 4:3 for thumbnail + * images */ +#define MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO 1.3333333f + +/* Defines the various types of supported menu + * list views + * - MUI_LIST_VIEW_DEFAULT is the standard for + * all non-playlist views + * - MUI_LIST_VIEW_PLAYLIST is for playlists + * without thumbnails + * - Everything else is for playlists with fancy + * thumbnail-based layouts */ +enum materialui_list_view_type +{ + MUI_LIST_VIEW_DEFAULT = 0, + MUI_LIST_VIEW_PLAYLIST, + MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL, + MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM, + MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE +}; + +/* This structure holds auxiliary information for + * each menu entry (physical on-screen size/position, + * icon data, thumbnail data, etc.) */ typedef struct { bool has_icon; unsigned icon_texture_index; - float line_height; + float entry_height; + float text_height; float y; + menu_thumbnail_t thumbnail; } materialui_node_t; -/* Textures used for the tabs and the switches */ +/* Defines all standard menu textures */ enum { MUI_TEXTURE_POINTER = 0, @@ -948,7 +975,6 @@ enum MUI_TEXTURE_MUSIC, MUI_TEXTURE_QUIT, MUI_TEXTURE_HELP, - MUI_TEXTURE_UPDATE, MUI_TEXTURE_HISTORY, MUI_TEXTURE_INFO, MUI_TEXTURE_ADD, @@ -995,6 +1021,7 @@ enum MUI_TEXTURE_BATTERY_90, MUI_TEXTURE_BATTERY_100, MUI_TEXTURE_BATTERY_CHARGING, + MUI_TEXTURE_SWITCH_VIEW, MUI_TEXTURE_LAST }; @@ -1132,6 +1159,8 @@ typedef struct materialui_handle bool last_auto_rotate_nav_bar; bool menu_stack_flushed; + playlist_t *playlist; + unsigned last_width; unsigned last_height; float last_scale_factor; @@ -1218,6 +1247,13 @@ typedef struct materialui_handle float transition_x_offset; size_t last_stack_size; + /* Thumbnail helpers */ + menu_thumbnail_path_data_t *thumbnail_path_data; + unsigned thumbnail_width_max; + unsigned thumbnail_height_max; + + enum materialui_list_view_type list_view_type; + } materialui_handle_t; static const materialui_theme_t *materialui_get_theme(enum materialui_color_theme color_theme) @@ -1511,6 +1547,8 @@ static const char *materialui_texture_path(unsigned id) return "battery_100.png"; case MUI_TEXTURE_BATTERY_CHARGING: return "battery_charging.png"; + case MUI_TEXTURE_SWITCH_VIEW: + return "switch_view.png"; } return NULL; @@ -1518,18 +1556,33 @@ static const char *materialui_texture_path(unsigned id) static void materialui_context_reset_textures(materialui_handle_t *mui) { + bool has_all_assets = true; + char icon_path[PATH_MAX_LENGTH]; unsigned i; - char *iconpath = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); - iconpath[0] = '\0'; + icon_path[0] = '\0'; - fill_pathname_application_special(iconpath, - PATH_MAX_LENGTH * sizeof(char), + fill_pathname_application_special( + icon_path, sizeof(icon_path), APPLICATION_SPECIAL_DIRECTORY_ASSETS_MATERIALUI_ICONS); + /* Loop through all textures */ for (i = 0; i < MUI_TEXTURE_LAST; i++) - menu_display_reset_textures_list(materialui_texture_path(i), iconpath, &mui->textures.list[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL); - free(iconpath); + { + if (!menu_display_reset_textures_list( + materialui_texture_path(i), icon_path, &mui->textures.list[i], + TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL)) + { + RARCH_WARN("[GLUI] Asset missing: %s%s%s\n", icon_path, path_default_slash(), materialui_texture_path(i)); + has_all_assets = false; + } + } + + /* Warn user if assets are missing */ + if (!has_all_assets) + runloop_msg_queue_push( + msg_hash_to_str(MSG_MISSING_ASSETS), 1, 256, false, NULL, + MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } static void materialui_draw_icon( @@ -1579,6 +1632,137 @@ static void materialui_draw_icon( menu_display_blend_end(video_info); } +static void materialui_draw_thumbnail( + materialui_handle_t *mui, + video_frame_info_t *video_info, + menu_thumbnail_t *thumbnail, + float x, float y, + unsigned width, unsigned height, + float rotation, float scale_factor) +{ + /* If thumbnail is missing, draw fallback image */ + if (thumbnail->status == MENU_THUMBNAIL_STATUS_MISSING) + { + float bg_x; + float bg_y; + float bg_width; + float bg_height; + float icon_size; + + /* Account for scale factor */ + bg_width = (float)mui->thumbnail_width_max * scale_factor; + bg_height = (float)mui->thumbnail_height_max * scale_factor; + + bg_x = x - (bg_width - (float)mui->thumbnail_width_max) / 2.0f; + bg_y = y - (bg_height - (float)mui->thumbnail_height_max) / 2.0f; + + if (scale_factor >= 1.0f) + icon_size = (float)mui->icon_size; + else + icon_size = (float)mui->icon_size * scale_factor; + + /* Background */ + menu_display_draw_quad( + video_info, + bg_x, + bg_y, + (unsigned)bg_width, + (unsigned)bg_height, + width, + height, + mui->colors.surface_background); + + /* Icon */ + materialui_draw_icon(video_info, + (unsigned)icon_size, + mui->textures.list[MUI_TEXTURE_IMAGE], + bg_x + (bg_width - icon_size) / 2.0f, + bg_y + (bg_height - icon_size) / 2.0f, + width, + height, + 0.0f, + 1.0f, + mui->colors.list_icon); + } + /* If thumbnail is available, draw it */ + else if (thumbnail->status == MENU_THUMBNAIL_STATUS_AVAILABLE) + { + menu_display_ctx_rotate_draw_t rotate_draw; + menu_display_ctx_draw_t draw; + struct video_coords coords; + math_matrix_4x4 mymat; + float draw_width; + float draw_height; + float thumbnail_aspect; + float thumbnail_alpha = + thumbnail->alpha * mui->transition_alpha; + float thumbnail_color[16] = { + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f + }; + + /* Set thumbnail opacity */ + if (thumbnail_alpha < 1.0f) + menu_display_set_alpha(thumbnail_color, thumbnail_alpha); + + /* Get thumbnail dimensions */ + thumbnail_aspect = (float)thumbnail->width / (float)thumbnail->height; + + if (thumbnail_aspect > MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO) + { + draw_width = (float)mui->thumbnail_width_max; + draw_height = (float)thumbnail->height * (draw_width / (float)thumbnail->width); + } + else + { + draw_height = (float)mui->thumbnail_height_max; + draw_width = (float)thumbnail->width * (draw_height / (float)thumbnail->height); + } + + /* Account for scale factor */ + draw_width *= scale_factor; + draw_height *= scale_factor; + + menu_display_blend_begin(video_info); + + rotate_draw.matrix = &mymat; + rotate_draw.rotation = rotation; + rotate_draw.scale_x = 1.0f; + rotate_draw.scale_y = 1.0f; + rotate_draw.scale_z = 1.0f; + rotate_draw.scale_enable = true; + + menu_display_rotate_z(&rotate_draw, video_info); + + coords.vertices = 4; + coords.vertex = NULL; + coords.tex_coord = NULL; + coords.lut_tex_coord = NULL; + coords.color = (const float*)thumbnail_color; + + draw.x = x + ((float)mui->thumbnail_width_max - draw_width) / 2.0f; + draw.y = height - y - draw_height - ((float)mui->thumbnail_height_max - draw_height) / 2.0f; + draw.width = (unsigned)draw_width; + draw.height = (unsigned)draw_height; + draw.scale_factor = 1.0f; + draw.rotation = rotation; + draw.coords = &coords; + draw.matrix_data = &mymat; + draw.texture = thumbnail->texture; + draw.prim_type = MENU_DISPLAY_PRIM_TRIANGLESTRIP; + draw.pipeline.id = 0; + + menu_display_draw(&draw, video_info); + menu_display_blend_end(video_info); + } + + /* All other thumbnail statuses are ignored + * (i.e. we draw nothing if thumbnail status is unknown, + * or we are waiting for a thumbnail to load) */ +} + static void materialui_get_message(void *data, const char *message) { materialui_handle_t *mui = (materialui_handle_t*)data; @@ -1694,9 +1878,9 @@ static void materialui_compute_entries_box(materialui_handle_t* mui, int width, { menu_entry_t entry; char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH]; - const char *sublabel_str = NULL; - unsigned sublabel_lines = 0; - materialui_node_t *node = (materialui_node_t*) + const char *sublabel_str = NULL; + unsigned num_sublabel_lines = 0; + materialui_node_t *node = (materialui_node_t*) file_list_get_userdata_at_offset(list, i); wrapped_sublabel_str[0] = '\0'; @@ -1721,15 +1905,26 @@ static void materialui_compute_entries_box(materialui_handle_t* mui, int width, word_wrap(wrapped_sublabel_str, sublabel_str, (int)((usable_width - icon_margin) / mui->font_data.hint.glyph_width), false, 0); - sublabel_lines = materialui_count_lines(wrapped_sublabel_str); + num_sublabel_lines = materialui_count_lines(wrapped_sublabel_str); } - node->line_height = - (mui->dip_base_unit_size / 5) + - mui->font_data.list.font_height + - (sublabel_lines * mui->font_data.hint.font_height); - node->y = sum; - sum += node->line_height; + node->text_height = mui->font_data.list.font_height + + (num_sublabel_lines * mui->font_data.hint.font_height); + + node->entry_height = node->text_height + + mui->dip_base_unit_size / 10; + + /* If this is a playlist and thumbnails are enabled, + * must ensure that line_height is greater than + * maximum thumbnail height */ + if ((mui->list_view_type != MUI_LIST_VIEW_DEFAULT) && + (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST)) + node->entry_height = (node->entry_height < mui->thumbnail_height_max) ? + mui->thumbnail_height_max : node->entry_height; + + node->entry_height += mui->dip_base_unit_size / 10; + node->y = sum; + sum += node->entry_height; } mui->content_height = sum; @@ -1756,7 +1951,7 @@ static float materialui_get_scroll(materialui_handle_t *mui) file_list_get_userdata_at_offset(list, i); if (node) - sum += node->line_height; + sum += node->entry_height; } if (sum < half) @@ -1765,13 +1960,14 @@ static float materialui_get_scroll(materialui_handle_t *mui) return sum - half; } -static void materialui_context_reset_internal( - materialui_handle_t *mui, bool is_threaded); +static void materialui_layout( + materialui_handle_t *mui, bool video_is_threaded); /* Called on each frame. We use this callback to: * - Determine current scroll postion * - Determine index of first/last onscreen entries - * - Handle dynamic pointer input */ + * - Handle dynamic pointer input + * - Handle streaming thumbnails */ static void materialui_render(void *data, unsigned width, unsigned height, bool is_idle) @@ -1782,6 +1978,7 @@ static void materialui_render(void *data, size_t entries_end = menu_entries_get_size(); file_list_t *list = menu_entries_get_selection_buf_ptr(0); bool first_entry_found = false; + bool last_entry_found = false; size_t i; int bottom; float scale_factor; @@ -1805,7 +2002,11 @@ static void materialui_render(void *data, mui->last_height = height; mui->last_optimize_landscape_layout = settings->bools.menu_materialui_optimize_landscape_layout; mui->last_auto_rotate_nav_bar = settings->bools.menu_materialui_auto_rotate_nav_bar; - materialui_context_reset_internal(mui, video_driver_is_threaded()); + + /* Note: We don't need a full context reset here + * > Just rescale layout, and reset frame time counter */ + materialui_layout(mui, video_driver_is_threaded()); + video_driver_monitor_reset(); } if (mui->need_compute) @@ -1877,15 +2078,31 @@ static void materialui_render(void *data, /* Check whether this is the first onscreen entry */ if (!first_entry_found) { - if ((entry_y + (int)node->line_height) > header_height) + if ((entry_y + (int)node->entry_height) > header_height) { mui->first_onscreen_entry = i; first_entry_found = true; } } + /* Check whether this is the last onscreen entry */ + else if (!last_entry_found) + { + if (entry_y > ((int)height - (int)mui->nav_bar_layout_height)) + { + /* Current entry is off screen - get index + * of previous entry */ + if (i > 0) + { + mui->last_onscreen_entry = i - 1; + last_entry_found = true; + } + } + } /* Track pointer input, if required */ - if (first_entry_found && (mui->pointer.type != MENU_POINTER_DISABLED)) + if (first_entry_found && + !last_entry_found && + (mui->pointer.type != MENU_POINTER_DISABLED)) { int16_t pointer_x = mui->pointer.x; int16_t pointer_y = mui->pointer.y; @@ -1896,7 +2113,7 @@ static void materialui_render(void *data, (pointer_y <= height - mui->nav_bar_layout_height)) { if ((pointer_y > entry_y) && - (pointer_y < (entry_y + node->line_height))) + (pointer_y < (entry_y + node->entry_height))) { menu_input_set_pointer_selection(i); @@ -1921,15 +2138,20 @@ static void materialui_render(void *data, } } - /* Check whether this is the last onscreen entry */ - if (entry_y > ((int)height - (int)mui->nav_bar_layout_height)) + /* If this is a playlist and thumbnails are enabled, + * have to load thumbnails for all on-screen entries + * and free thumbnails for all off-screen entries */ + if ((mui->list_view_type != MUI_LIST_VIEW_DEFAULT) && + (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST)) { - /* Current entry is off screen - get index - * of previous entry */ - if (i > 0) - mui->last_onscreen_entry = i - 1; - break; + bool on_screen = first_entry_found && !last_entry_found; + + menu_thumbnail_process_stream( + mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT, + mui->playlist, i, &node->thumbnail, on_screen); } + else if (last_entry_found) + break; } menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &mui->first_onscreen_entry); @@ -2042,8 +2264,10 @@ static void materialui_render_switch_icon( switch_color); } -/* Draws specified menu entry */ -static void materialui_render_menu_entry( +/* Draws specified menu entry + * > Used for standard, non-playlist entries + * >> MUI_LIST_VIEW_DEFAULT */ +static void materialui_render_menu_entry_default( materialui_handle_t *mui, video_frame_info_t *video_info, materialui_node_t *node, @@ -2124,7 +2348,7 @@ static void materialui_render_menu_entry( mui->icon_size, (uintptr_t)icon_texture, x_offset + (int)mui->landscape_entry_margin, - entry_y + (node->line_height / 2.0f) - (mui->icon_size / 2.0f), + entry_y + (node->entry_height / 2.0f) - (mui->icon_size / 2.0f), width, height, 0, @@ -2179,8 +2403,8 @@ static void materialui_render_menu_entry( * so we can't do this accurately - but as a general * rule of thumb, the descender of a font is at least * 20% of it's height - so we just add (font_height / 5) */ - label_y = entry_y + (int)((node->line_height / 2.0f) + (mui->font_data.list.font_height / 5.0f)); - value_icon_y = entry_y + (int)((node->line_height / 2.0f) - (mui->icon_size / 2.0f)); + label_y = entry_y + (int)((node->entry_height / 2.0f) + (mui->font_data.list.font_height / 5.0f)); + value_icon_y = entry_y + (int)((node->entry_height / 2.0f) - (mui->icon_size / 2.0f)); } /* Draw entry value */ @@ -2343,6 +2567,159 @@ static void materialui_render_menu_entry( } } +/* Draws specified menu entry + * > Used for playlist 'list view' (with and without + * thumbnails) entries + * >> MUI_LIST_VIEW_PLAYLIST + * >> MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL + * >> MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM + * >> MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE */ +static void materialui_render_menu_entry_playlist_list( + materialui_handle_t *mui, + video_frame_info_t *video_info, + materialui_node_t *node, + menu_entry_t *entry, + bool entry_selected, + bool touch_feedback_active, + unsigned header_height, + unsigned width, unsigned height, + int x_offset) +{ + const char *entry_label = NULL; + const char *entry_sublabel = NULL; + int entry_y = header_height - mui->scroll_y + node->y; + int entry_margin = (int)mui->margin + (int)mui->landscape_entry_margin; + int usable_width = + (int)width - (int)(mui->margin * 2) - (int)(mui->landscape_entry_margin * 2) - (int)mui->nav_bar_layout_width; + /* The label + sublabel text block is always drawn at + * the vertical centre of the current node + * Note: Text is drawn relative to the baseline, + * so we can't do this accurately - but tests + * indicate that the ascender of Material UI's + * font accounts for 7/20 of its height, so we + * just add (13/20) * font_height */ + int label_y = + entry_y + ((float)(node->entry_height - node->text_height) / 2.0f) + + (13.0f * (float)mui->font_data.list.font_height / 20.0f); + bool draw_text_outside = (x_offset != 0); + + /* Initial ticker configuration + * > Note: ticker is only used for labels, + * not sublabel text */ + if (mui->use_smooth_ticker) + { + mui->ticker_smooth.font = mui->font_data.list.font; + mui->ticker_smooth.selected = entry_selected; + } + else + mui->ticker.selected = entry_selected; + + /* Read entry parameters */ + menu_entry_get_rich_label(entry, &entry_label); + menu_entry_get_sublabel(entry, &entry_sublabel); + + /* Draw entry thumbnail + * > Has to be done first, since it affects the left + * hand margin size for label + sublabel text */ + if (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST) + { + int thumbnail_margin = 0; + + /* When using landscape display orientations, we + * have enough screen space to improve thumbnail + * appearance by adding a left hand margin - but + * we only need to do this if landscape optimisations + * are disabled (or landscape_entry_margin is less + * than mui->margin) */ + if (!mui->is_portrait) + if (mui->landscape_entry_margin < mui->margin) + thumbnail_margin = (int)(mui->margin - mui->landscape_entry_margin); + + materialui_draw_thumbnail( + mui, + video_info, + &node->thumbnail, + (float)(x_offset + thumbnail_margin + (int)mui->landscape_entry_margin), + (float)entry_y + ((float)node->entry_height / 2.0f) - ((float)mui->thumbnail_height_max / 2.0f), + width, + height, + 0.0f, + 1.0f); + + entry_margin += mui->thumbnail_width_max + thumbnail_margin; + usable_width -= mui->thumbnail_width_max + thumbnail_margin; + } + + /* Draw entry label */ + if (!string_is_empty(entry_label)) + { + char label_buf[255]; + + label_buf[0] = '\0'; + + if (usable_width > 0) + { + /* Apply ticker */ + if (mui->use_smooth_ticker) + { + /* Label */ + mui->ticker_smooth.field_width = (unsigned)usable_width; + mui->ticker_smooth.src_str = entry_label; + mui->ticker_smooth.dst_str = label_buf; + mui->ticker_smooth.dst_str_len = sizeof(label_buf); + + menu_animation_ticker_smooth(&mui->ticker_smooth); + } + else + { + /* Label */ + mui->ticker.s = label_buf; + mui->ticker.len = (size_t)(usable_width / mui->font_data.list.glyph_width); + mui->ticker.str = entry_label; + + menu_animation_ticker(&mui->ticker); + } + + /* Draw label string */ + menu_display_draw_text(mui->font_data.list.font, label_buf, + x_offset + (int)mui->ticker_x_offset + entry_margin, + label_y, + width, height, + (entry_selected || touch_feedback_active) ? + mui->colors.list_text_highlighted : mui->colors.list_text, + TEXT_ALIGN_LEFT, 1.0f, false, 0, draw_text_outside); + } + } + + /* Draw entry sublabel */ + if (!string_is_empty(entry_sublabel)) + { + int sublabel_y = label_y + (int)mui->font_data.list.font_height; + char wrapped_sublabel[MENU_SUBLABEL_MAX_LENGTH]; + + wrapped_sublabel[0] = '\0'; + + /* Wrap sublabel string */ + word_wrap(wrapped_sublabel, entry_sublabel, + (int)(usable_width / mui->font_data.hint.glyph_width), + true, 0); + + /* Draw sublabel string + * > Note: We must allow text to be drawn off-screen + * if the current y position is negative, otherwise topmost + * entries with very long sublabels may get 'clipped' too + * early as they are scrolled upwards beyond the top edge + * of the screen */ + menu_display_draw_text(mui->font_data.hint.font, wrapped_sublabel, + x_offset + entry_margin, + sublabel_y, + width, height, + (entry_selected || touch_feedback_active) ? + mui->colors.list_hint_text_highlighted : mui->colors.list_hint_text, + TEXT_ALIGN_LEFT, 1.0f, false, 0, draw_text_outside || (sublabel_y < 0)); + } +} + static void materialui_render_scrollbar( materialui_handle_t *mui, video_frame_info_t *video_info, @@ -2426,18 +2803,50 @@ static void materialui_render_menu_list( entry.path_enabled = false; menu_entry_get(&entry, 0, i, NULL, true); - /* Render label, value, and associated icons */ - materialui_render_menu_entry( - mui, - video_info, - node, - &entry, - entry_selected, - touch_feedback_active, - header_height, - width, - height, - x_offset); + /* Render label, value, and associated icons + * TODO/FIXME: Yes, I know this is ugly... + * Once we refactor the code to enable alternative + * non-list-type view modes (e.g. grid, coverflow), + * this sort of thing will be handled via function + * pointers (we'll need these in several places: + * handling pointer input, loading thumbnails, + * menu drawing, selection highlight drawing, + * etc.). Until then, a simple switch (and a bunch + * of duplicated code in the two render_menu_entry + * functions) will suffice... */ + switch (mui->list_view_type) + { + case MUI_LIST_VIEW_PLAYLIST: + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL: + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM: + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE: + materialui_render_menu_entry_playlist_list( + mui, + video_info, + node, + &entry, + entry_selected, + touch_feedback_active, + header_height, + width, + height, + x_offset); + break; + case MUI_LIST_VIEW_DEFAULT: + default: + materialui_render_menu_entry_default( + mui, + video_info, + node, + &entry, + entry_selected, + touch_feedback_active, + header_height, + width, + height, + x_offset); + break; + } } /* Draw scrollbar */ @@ -2601,7 +3010,7 @@ static void materialui_render_selection_highlight( highlight_x_offset, header_height - mui->scroll_y + node->y, (unsigned)highlight_width, - node->line_height, + node->entry_height, width, height, color); @@ -2688,6 +3097,9 @@ static void materialui_render_header( int title_x = 0; bool show_back_icon = menu_entries_ctl(MENU_ENTRIES_CTL_SHOW_BACK, NULL); bool show_search_icon = mui->is_playlist || mui->is_file_list; + bool show_switch_view_icon = mui->is_playlist && + menu_thumbnail_is_enabled(mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT); + bool use_landscape_layout = !mui->is_portrait && settings->bools.menu_materialui_optimize_landscape_layout; char menu_title_buf[255]; @@ -2949,14 +3361,35 @@ static void materialui_render_header( mui->colors.header_icon); usable_title_bar_width -= mui->icon_size; + + /* > Draw 'switch view' icon, if required + * Note: We can take a shortcut here because + * 'switch view' can only be shown if + * 'search' is also shown... */ + if (show_switch_view_icon) + { + materialui_draw_icon(video_info, + mui->icon_size, + mui->textures.list[MUI_TEXTURE_SWITCH_VIEW], + (int)width - (2 * (int)mui->icon_size) - (int)mui->nav_bar_layout_width, + (int)mui->sys_bar_height, + width, + height, + 0, + 1, + mui->colors.header_icon); + + usable_title_bar_width -= mui->icon_size; + } } else usable_title_bar_width -= mui->margin; /* If landscape optimisation is enabled and we are - * drawing a back icon but no search icon, title - * maximum width must be reduced (otherwise cannot - * centre properly...) */ + * drawing a back icon but no search icon (and by + * extension, no switch view icon), title maximum + * width must be reduced (otherwise cannot centre + * properly...) */ if (use_landscape_layout) if (show_back_icon && !show_search_icon) usable_title_bar_width -= (mui->icon_size - mui->margin); @@ -2976,8 +3409,19 @@ static void materialui_render_header( /* If ticker is not active and landscape * optimisation is enabled, centre the title text */ if (!menu_animation_ticker_smooth(&mui->ticker_smooth)) + { if (use_landscape_layout) - title_x = (usable_title_bar_width - mui->ticker_str_width) >> 1; + { + title_x = (int)(usable_title_bar_width - mui->ticker_str_width) >> 1; + + /* Even more trickery required for proper centring + * if both search and switch view icons are shown... */ + if (show_search_icon && show_switch_view_icon) + if (mui->ticker_str_width + mui->ticker_x_offset < + usable_title_bar_width - mui->icon_size) + title_x += (int)(mui->icon_size >> 1); + } + } } else { @@ -2989,11 +3433,21 @@ static void materialui_render_header( /* If ticker is not active and landscape * optimisation is enabled, centre the title text */ if (!menu_animation_ticker(&mui->ticker)) + { if (use_landscape_layout) - title_x = - (usable_title_bar_width - - ((int)utf8len(menu_title_buf) * - mui->font_data.title.glyph_width)) >> 1; + { + int str_width = (int)(utf8len(menu_title_buf) * + mui->font_data.title.glyph_width); + + title_x = (int)(usable_title_bar_width - str_width) >> 1; + + /* Even more trickery required for proper centring + * if both search and switch view icons are shown... */ + if (show_search_icon && show_switch_view_icon) + if (str_width < usable_title_bar_width - mui->icon_size) + title_x += (int)(mui->icon_size >> 1); + } + } } title_x += (int)(mui->ticker_x_offset + menu_title_margin); @@ -3054,7 +3508,7 @@ static void materialui_render_nav_bar_bottom( materialui_draw_icon(video_info, mui->icon_size, mui->textures.list[mui->nav_bar.back_tab.texture_index], - (0.5f * tab_width) - ((float)mui->icon_size / 2.0f), + (int)((0.5f * tab_width) - ((float)mui->icon_size / 2.0f)), nav_bar_y, width, height, @@ -3067,7 +3521,7 @@ static void materialui_render_nav_bar_bottom( materialui_draw_icon(video_info, mui->icon_size, mui->textures.list[mui->nav_bar.resume_tab.texture_index], - (((float)num_tabs - 0.5f) * tab_width) - ((float)mui->icon_size / 2.0f), + (int)((((float)num_tabs - 0.5f) * tab_width) - ((float)mui->icon_size / 2.0f)), nav_bar_y, width, height, @@ -3155,7 +3609,7 @@ static void materialui_render_nav_bar_right( mui->icon_size, mui->textures.list[mui->nav_bar.back_tab.texture_index], nav_bar_x, - (((float)num_tabs - 0.5f) * tab_height) - ((float)mui->icon_size / 2.0f), + (int)((((float)num_tabs - 0.5f) * tab_height) - ((float)mui->icon_size / 2.0f)), width, height, 0, @@ -3168,7 +3622,7 @@ static void materialui_render_nav_bar_right( mui->icon_size, mui->textures.list[mui->nav_bar.resume_tab.texture_index], nav_bar_x, - (0.5f * tab_height) - ((float)mui->icon_size / 2.0f), + (int)((0.5f * tab_height) - ((float)mui->icon_size / 2.0f)), width, height, 0, @@ -3243,10 +3697,11 @@ static void materialui_colors_set_transition_alpha(materialui_handle_t *mui) menu_display_set_alpha(mui->colors.list_switch_off, alpha); menu_display_set_alpha(mui->colors.list_switch_off_background, alpha); menu_display_set_alpha(mui->colors.scrollbar, alpha); + menu_display_set_alpha(mui->colors.surface_background, alpha); /* Landscape border shadow only fades if: * - Landscape border is shown - * - We are currently performaing a slide animation */ + * - We are currently performing a slide animation */ if ((mui->landscape_entry_margin != 0) && (mui->transition_x_offset != 0.0f)) { @@ -3281,10 +3736,11 @@ static void materialui_colors_reset_transition_alpha(materialui_handle_t *mui) menu_display_set_alpha(mui->colors.list_switch_off, 1.0f); menu_display_set_alpha(mui->colors.list_switch_off_background, 1.0f); menu_display_set_alpha(mui->colors.scrollbar, 1.0f); + menu_display_set_alpha(mui->colors.surface_background, 1.0f); /* Landscape border shadow only fades if: * - Landscape border is shown - * - We are currently performaing a slide animation */ + * - We are currently performing a slide animation */ if ((mui->landscape_entry_margin != 0) && (mui->transition_x_offset != 0.0f)) { @@ -3478,6 +3934,141 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) menu_display_unset_viewport(width, height); } +/* Determines current list view mode, based upon + * display orientation and user config, then + * calculates appropriate thumbnail dimensions. + * Must be called when updating menu layout and + * populating menu lists. */ +static void materialui_update_list_view(materialui_handle_t *mui) +{ + settings_t *settings = config_get_ptr(); + + if (!settings) + { + /* This will never happen, but might as well set the + * appropriate fallback... */ + mui->list_view_type = mui->is_playlist ? + MUI_LIST_VIEW_PLAYLIST : MUI_LIST_VIEW_DEFAULT; + return; + } + + if (!mui->is_playlist) + mui->list_view_type = MUI_LIST_VIEW_DEFAULT; + else + { + /* This is a playlist - set non-thumbnail view + * by default (saves checks later) */ + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST; + + /* Check whether thumbnails are enabled */ + if (menu_thumbnail_is_enabled(mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT)) + { + /* Get thumbnail view mode based on current + * display orientation */ + if (mui->is_portrait) + { + switch (settings->uints.menu_materialui_thumbnail_view_portrait) + { + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL; + break; + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM; + break; + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED: + default: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST; + break; + } + } + else + { + switch (settings->uints.menu_materialui_thumbnail_view_landscape) + { + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL; + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM; + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE; + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED: + default: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST; + break; + } + } + + /* Set thumbnail dimensions */ + switch (mui->list_view_type) + { + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL: + + /* Maximum height is just standard icon size */ + mui->thumbnail_height_max = mui->icon_size; + + /* Set thumbnail width based on max height */ + mui->thumbnail_width_max = + (unsigned)(((float)mui->thumbnail_height_max * + MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO) + 0.5f); + + break; + + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM: + + /* Maximum height corresponds to text height when + * showing full playlist sublabel metadata + * (core association + runtime info) + * > One line of list text + three lines of + * hint text + padding */ + mui->thumbnail_height_max = + mui->font_data.list.font_height + + (3 * mui->font_data.hint.font_height) + + (mui->dip_base_unit_size / 10); + + /* Set thumbnail width based on max height + * Note: We're duplicating this calculation each time + * for consistency - some view modes will require + * something different, and we want each case to + * be self-contained */ + mui->thumbnail_width_max = + (unsigned)(((float)mui->thumbnail_height_max * + MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO) + 0.5f); + + break; + + case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE: + + /* Maximum height corresponds to twice the + * text height when showing full playlist sublabel + * metadata (core association + runtime info) + * > Two lines of list text + three lines of + * hint text (no padding) */ + mui->thumbnail_height_max = + (mui->font_data.list.font_height + + (3 * mui->font_data.hint.font_height)) * 2; + + /* Set thumbnail width based on max height */ + mui->thumbnail_width_max = + (unsigned)(((float)mui->thumbnail_height_max * + MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO) + 0.5f); + + break; + + case MUI_LIST_VIEW_PLAYLIST: + default: + /* Not required, but might as well zero + * out thumbnail dimensions... */ + mui->thumbnail_height_max = 0; + mui->thumbnail_width_max = 0; + break; + } + } + } +} + /* Compute the positions of the widgets */ static void materialui_layout(materialui_handle_t *mui, bool video_is_threaded) { @@ -3633,6 +4224,8 @@ static void materialui_layout(materialui_handle_t *mui, bool video_is_threaded) mui->sys_bar_cache.timedate_str[0] = '\0'; mui->sys_bar_cache.timedate_width = 0; + materialui_update_list_view(mui); + mui->need_compute = true; } @@ -3689,6 +4282,11 @@ static void *materialui_init(void **userdata, bool video_is_threaded) *userdata = mui; + /* Initialise thumbnail path data */ + mui->thumbnail_path_data = menu_thumbnail_path_init(); + if (!mui->thumbnail_path_data) + goto error; + /* Get DPI/screen-size-aware base unit size for * UI elements */ video_driver_get_size(&width, &height); @@ -3755,6 +4353,9 @@ static void materialui_free(void *data) video_coord_array_free(&mui->font_data.hint.raster_block.carr); font_driver_bind_block(NULL, NULL); + + if (mui->thumbnail_path_data) + free(mui->thumbnail_path_data); } static void materialui_context_bg_destroy(materialui_handle_t *mui) @@ -3768,15 +4369,18 @@ static void materialui_context_bg_destroy(materialui_handle_t *mui) static void materialui_context_destroy(void *data) { + materialui_handle_t *mui = (materialui_handle_t*)data; + file_list_t *list = menu_entries_get_selection_buf_ptr(0); unsigned i; - materialui_handle_t *mui = (materialui_handle_t*)data; if (!mui) return; + /* Free standard menu textures */ for (i = 0; i < MUI_TEXTURE_LAST; i++) video_driver_texture_unload(&mui->textures.list[i]); + /* Free fonts */ if (mui->font_data.title.font) menu_display_font_free(mui->font_data.title.font); mui->font_data.title.font = NULL; @@ -3789,29 +4393,38 @@ static void materialui_context_destroy(void *data) menu_display_font_free(mui->font_data.hint.font); mui->font_data.hint.font = NULL; + /* Free node thumbnails */ + if (list) + { + for (i = 0; i < list->size; i++) + { + materialui_node_t *node = (materialui_node_t*) + file_list_get_userdata_at_offset(list, i); + + if (!node) + continue; + + menu_thumbnail_reset(&node->thumbnail); + } + } + + /* Free background/wallpaper textures */ materialui_context_bg_destroy(mui); } -/* Upload textures to the gpu */ +/* Note: This is only used for loading wallpaper + * images. Thumbnails are 'streamed' and must be + * handled differently */ static bool materialui_load_image(void *userdata, void *data, enum menu_image_type type) { materialui_handle_t *mui = (materialui_handle_t*)userdata; - switch (type) + if (type == MENU_IMAGE_WALLPAPER) { - case MENU_IMAGE_NONE: - break; - case MENU_IMAGE_WALLPAPER: - materialui_context_bg_destroy(mui); - video_driver_texture_unload(&mui->textures.bg); - video_driver_texture_load(data, - TEXTURE_FILTER_MIPMAP_LINEAR, &mui->textures.bg); - menu_display_allocate_white_texture(); - break; - case MENU_IMAGE_THUMBNAIL: - case MENU_IMAGE_LEFT_THUMBNAIL: - case MENU_IMAGE_SAVESTATE_THUMBNAIL: - break; + materialui_context_bg_destroy(mui); + video_driver_texture_load(data, + TEXTURE_FILTER_MIPMAP_LINEAR, &mui->textures.bg); + menu_display_allocate_white_texture(); } return true; @@ -3871,8 +4484,8 @@ static void materialui_list_set_selection(void *data, file_list_t *list) /* The navigation pointer is set back to zero */ static void materialui_navigation_clear(void *data, bool pending_push) { - size_t i = 0; - materialui_handle_t *mui = (materialui_handle_t*)data; + size_t i = 0; + materialui_handle_t *mui = (materialui_handle_t*)data; if (!mui) return; @@ -4127,9 +4740,18 @@ static void materialui_populate_entries( string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_PLAYLIST_LEFT_THUMBNAIL_MODE)); } + /* If this is a playlist, then cache it */ + if (mui->is_playlist) + mui->playlist = playlist_get_cached(); + else + mui->playlist = NULL; + /* Update navigation bar tabs */ materialui_populate_nav_bar(mui, label, settings); + /* Update list view/thumbnail parameters */ + materialui_update_list_view(mui); + /* Reset touch feedback parameters * (i.e. there should be no leftover highlight * animations when switching to a new list) */ @@ -4150,12 +4772,13 @@ static void materialui_populate_entries( mui->need_compute = true; } -static void materialui_context_reset_internal( - materialui_handle_t *mui, bool is_threaded) +/* Context reset is called on launch or when a core is launched */ +static void materialui_context_reset(void *data, bool is_threaded) { - settings_t *settings = config_get_ptr(); + materialui_handle_t *mui = (materialui_handle_t*)data; + settings_t *settings = config_get_ptr(); - if (!settings) + if (!mui || !settings) return; materialui_layout(mui, is_threaded); @@ -4167,17 +4790,7 @@ static void materialui_context_reset_internal( task_push_image_load(settings->paths.path_menu_wallpaper, video_driver_supports_rgba(), 0, menu_display_handle_wallpaper_upload, NULL); -} - -/* Context reset is called on launch or when a core is launched */ -static void materialui_context_reset(void *data, bool is_threaded) -{ - materialui_handle_t *mui = (materialui_handle_t*)data; - - if (!mui) - return; - materialui_context_reset_internal(mui, is_threaded); video_driver_monitor_reset(); } @@ -4765,6 +5378,56 @@ static int materialui_pointer_up_nav_bar( return 0; } +static void materialui_switch_list_view(materialui_handle_t *mui) +{ + settings_t *settings = config_get_ptr(); + size_t selection = menu_navigation_get_selection(); + + if (!settings) + return; + + /* Only enable view switching if we are currently viewing + * a playlist with thumbnails enabled */ + if ((mui->list_view_type == MUI_LIST_VIEW_DEFAULT) || + !menu_thumbnail_is_enabled(mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT)) + return; + + /* If currently selected item is off screen, then + * changing the view mode will throw the user to + * an unexpected off screen location... + * To prevent this, must immediately select the + * 'middle' on screen entry */ + if ((selection < mui->first_onscreen_entry) || + (selection > mui->last_onscreen_entry)) + menu_navigation_set_selection( + (mui->first_onscreen_entry >> 1) + (mui->last_onscreen_entry >> 1)); + + /* Update setting based upon current display orientation */ + if (mui->is_portrait) + { + settings->uints.menu_materialui_thumbnail_view_portrait++; + + if (settings->uints.menu_materialui_thumbnail_view_portrait >= + MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LAST) + settings->uints.menu_materialui_thumbnail_view_portrait = 0; + } + else + { + settings->uints.menu_materialui_thumbnail_view_landscape++; + + if (settings->uints.menu_materialui_thumbnail_view_landscape >= + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LAST) + settings->uints.menu_materialui_thumbnail_view_landscape = 0; + } + + /* Update list view parameters, and trigger + * transition animation */ + materialui_update_list_view(mui); + materialui_init_transition_animation(mui, settings); + + mui->need_compute = true; +} + /* Pointer up event */ static int materialui_pointer_up(void *userdata, unsigned x, unsigned y, unsigned ptr, @@ -4794,19 +5457,35 @@ static int materialui_pointer_up(void *userdata, (x > width - mui->nav_bar_layout_width)) return materialui_pointer_up_nav_bar( mui, x, y, width, height, (unsigned)selection, cbs, entry, action); - /* Tap/press header: Menu back/cancel, or search */ + /* Tap/press header: Menu back/cancel, or search/switch view */ else if (y < header_height) { /* If this is a playlist or file list, enable * search functionality */ if (mui->is_playlist || mui->is_file_list) { + bool switch_view_enabled = + mui->is_playlist && + menu_thumbnail_is_enabled(mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT); + /* Note: We add a little extra padding to minimise + * the risk of accidentally triggering a cancel */ + unsigned back_x_threshold = + width - + ((switch_view_enabled ? 3 : 2) * mui->icon_size) - + mui->nav_bar_layout_width; + /* Check if user has touched search icon */ if (x > width - mui->icon_size - mui->nav_bar_layout_width) return menu_input_dialog_start_search() ? 0 : -1; - /* If not, add a little extra padding to minimise - * the risk of accidentally triggering a cancel */ - else if (x <= width - (2 * mui->icon_size) - mui->nav_bar_layout_width) + /* Check if user has touched switch view icon */ + else if (switch_view_enabled && + x > width - (2 * mui->icon_size) - mui->nav_bar_layout_width) + { + materialui_switch_list_view(mui); + return 0; + } + /* Fall back to normal cancel action */ + else if (x <= back_x_threshold) return menu_entry_action(entry, (unsigned)selection, MENU_ACTION_CANCEL); } /* If this is not a playlist or file list, a tap/press @@ -4912,8 +5591,9 @@ static int materialui_pointer_up(void *userdata, * the netplay lobby, etc. * * This function allocates the materialui_node_t - *for the new entry. */ -static void materialui_list_insert(void *userdata, + * for the new entry. */ +static void materialui_list_insert( + void *userdata, file_list_t *list, const char *path, const char *fullpath, @@ -4921,10 +5601,11 @@ static void materialui_list_insert(void *userdata, size_t list_size, unsigned type) { - int i = (int)list_size; - materialui_node_t *node = NULL; - settings_t *settings = config_get_ptr(); - materialui_handle_t *mui = (materialui_handle_t*)userdata; + int i = (int)list_size; + materialui_node_t *node = NULL; + settings_t *settings = config_get_ptr(); + materialui_handle_t *mui = (materialui_handle_t*)userdata; + bool thumbnail_reset = false; if (!mui || !list) return; @@ -4934,6 +5615,13 @@ static void materialui_list_insert(void *userdata, if (!node) node = (materialui_node_t*)calloc(1, sizeof(materialui_node_t)); + else + { + /* If node already exists, must free any + * existing thumbnail */ + menu_thumbnail_reset(&node->thumbnail); + thumbnail_reset = true; + } if (!node) { @@ -4941,10 +5629,14 @@ static void materialui_list_insert(void *userdata, return; } - node->line_height = mui->dip_base_unit_size / 3; - node->y = 0; node->has_icon = false; node->icon_texture_index = 0; + node->entry_height = 0.0f; + node->text_height = 0.0f; + node->y = 0.0f; + + if (!thumbnail_reset) + menu_thumbnail_reset(&node->thumbnail); if (settings->bools.menu_materialui_icons_enable) { @@ -5343,24 +6035,99 @@ static void materialui_list_insert(void *userdata, file_list_set_userdata(list, i, node); } -/* Clearing the current menu list */ +/* Clears the current menu list */ static void materialui_list_clear(file_list_t *list) { size_t i; size_t size = list ? list->size : 0; - for (i = 0; i < size; ++i) + /* Must cancel pending thumbnail requests before + * freeing node->thumbnail objects */ + menu_thumbnail_cancel_pending_requests(); + + for (i = 0; i < size; i++) { materialui_node_t *node = (materialui_node_t*) - file_list_get_userdata_at_offset(list, i); + file_list_get_userdata_at_offset(list, i); if (!node) continue; + menu_thumbnail_reset(&node->thumbnail); file_list_free_userdata(list, i); } } +static void materialui_set_thumbnail_system(void *userdata, char *s, size_t len) +{ + materialui_handle_t *mui = (materialui_handle_t*)userdata; + if (!mui) + return; + menu_thumbnail_set_system( + mui->thumbnail_path_data, s, playlist_get_cached()); +} + +static void materialui_get_thumbnail_system(void *userdata, char *s, size_t len) +{ + materialui_handle_t *mui = (materialui_handle_t*)userdata; + const char *system = NULL; + if (!mui) + return; + if (menu_thumbnail_get_system(mui->thumbnail_path_data, &system)) + strlcpy(s, system, len); +} + +/* Used to cycle current thumbnail view mode */ +static void materialui_update_thumbnail_image(void *userdata) +{ + materialui_handle_t *mui = (materialui_handle_t*)userdata; + if (!mui) + return; + + materialui_switch_list_view(mui); +} + +static void materialui_refresh_thumbnail_image(void *userdata, unsigned i) +{ + materialui_handle_t *mui = (materialui_handle_t*)userdata; + + if (!mui) + return; + + /* Only refresh thumbnails if we are currently viewing + * a playlist with thumbnails enabled */ + if ((mui->list_view_type == MUI_LIST_VIEW_DEFAULT) || + (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST)) + return; + + /* Only refresh thumbnails if the current entry is + * on-screen */ + if ((i >= mui->first_onscreen_entry) && + (i <= mui->last_onscreen_entry)) + { + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + materialui_node_t *node = NULL; + + if (!list) + return; + + node = (materialui_node_t*)file_list_get_userdata_at_offset(list, (size_t)i); + + if (!node) + return; + + /* Reset existing thumbnail */ + menu_thumbnail_reset(&node->thumbnail); + + /* Request new thumbnail */ + if (menu_thumbnail_set_content_playlist( + mui->thumbnail_path_data, mui->playlist, (size_t)i)) + menu_thumbnail_request( + mui->thumbnail_path_data, MENU_THUMBNAIL_RIGHT, + mui->playlist, (size_t)i, &node->thumbnail); + } +} + menu_ctx_driver_t menu_ctx_mui = { NULL, materialui_get_message, @@ -5396,10 +6163,10 @@ menu_ctx_driver_t menu_ctx_mui = { "glui", materialui_environ, NULL, - NULL, - NULL, - NULL, - NULL, + materialui_update_thumbnail_image, + materialui_refresh_thumbnail_image, + materialui_set_thumbnail_system, + materialui_get_thumbnail_system, NULL, menu_display_osk_ptr_at_pos, NULL, /* update_savestate_thumbnail_path */ diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index 268b5c484fa..fbbd7445791 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -458,7 +458,7 @@ static void ozone_update_thumbnail_image(void *data) #endif } -static void ozone_refresh_thumbnail_image(void *data) +static void ozone_refresh_thumbnail_image(void *data, unsigned i) { ozone_handle_t *ozone = (ozone_handle_t*)data; diff --git a/menu/drivers/rgui.c b/menu/drivers/rgui.c index 6ac1c01e896..a822dae2fc8 100644 --- a/menu/drivers/rgui.c +++ b/menu/drivers/rgui.c @@ -4635,7 +4635,7 @@ static void rgui_update_thumbnail_image(void *userdata) rgui_scan_selected_entry_thumbnail(rgui, true); } -static void rgui_refresh_thumbnail_image(void *userdata) +static void rgui_refresh_thumbnail_image(void *userdata, unsigned i) { rgui_t *rgui = (rgui_t*)userdata; settings_t *settings = config_get_ptr(); diff --git a/menu/drivers/stripes.c b/menu/drivers/stripes.c index ea774cd18ab..c00cef74703 100644 --- a/menu/drivers/stripes.c +++ b/menu/drivers/stripes.c @@ -1086,7 +1086,7 @@ static void stripes_update_thumbnail_image(void *data) } } -static void stripes_refresh_thumbnail_image(void *data) +static void stripes_refresh_thumbnail_image(void *data, unsigned i) { stripes_update_thumbnail_image(data); } diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 8aa21401640..36bf57cb645 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -1082,7 +1082,7 @@ static unsigned xmb_get_system_tab(xmb_handle_t *xmb, unsigned i) return UINT_MAX; } -static void xmb_refresh_thumbnail_image(void *data) +static void xmb_refresh_thumbnail_image(void *data, unsigned i) { xmb_handle_t *xmb = (xmb_handle_t*)data; diff --git a/menu/menu_defines.h b/menu/menu_defines.h index 71beaaf258a..199e443d3a1 100644 --- a/menu/menu_defines.h +++ b/menu/menu_defines.h @@ -177,6 +177,23 @@ enum materialui_transition_animation MATERIALUI_TRANSITION_ANIM_LAST }; +enum materialui_thumbnail_view_portrait +{ + MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED = 0, + MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL, + MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM, + MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LAST +}; + +enum materialui_thumbnail_view_landscape +{ + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED = 0, + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL, + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM, + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE, + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LAST +}; + enum xmb_color_theme { XMB_THEME_LEGACY_RED = 0, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index fd560a7780c..4f9cb0f0bb2 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -1129,9 +1129,11 @@ static int menu_displaylist_parse_database_entry(menu_handle_t *menu, * since menu drivers cannot handle multiple successive * calls of menu_driver_set_thumbnail_content()... * Note that thumbnail updates must be disabled when using - * RGUI, since this functionality is handled elsewhere + * RGUI and GLUI, since this functionality is handled elsewhere * (and doing it here creates harmful conflicts) */ - if ((i == 0) && !string_is_equal(settings->arrays.menu_driver, "rgui")) + if ((i == 0) && + !string_is_equal(settings->arrays.menu_driver, "rgui") && + !string_is_equal(settings->arrays.menu_driver, "glui")) { if (!string_is_empty(db_info_entry->name)) strlcpy(thumbnail_content, db_info_entry->name, @@ -2831,11 +2833,12 @@ static bool menu_displaylist_parse_playlist_manager_settings( MENU_SETTING_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE, 0, 0); /* > Left thumbnail mode */ - menu_entries_append_enum(info->list, - msg_hash_to_str(left_thumbnail_label_value), - msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE), - MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, - MENU_SETTING_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, 0, 0); + if (!string_is_equal(settings->arrays.menu_driver, "glui")) + menu_entries_append_enum(info->list, + msg_hash_to_str(left_thumbnail_label_value), + msg_hash_to_str(MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE), + MENU_ENUM_LABEL_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, + MENU_SETTING_PLAYLIST_MANAGER_LEFT_THUMBNAIL_MODE, 0, 0); /* TODO - Add: * - Remove invalid entries */ @@ -5454,6 +5457,8 @@ unsigned menu_displaylist_build_list(file_list_t *list, enum menu_displaylist_ct {MENU_ENUM_LABEL_MATERIALUI_MENU_FOOTER_OPACITY, PARSE_ONLY_FLOAT}, {MENU_ENUM_LABEL_MENU_USE_PREFERRED_SYSTEM_COLOR_THEME, PARSE_ONLY_BOOL }, {MENU_ENUM_LABEL_MENU_RGUI_INLINE_THUMBNAILS, PARSE_ONLY_BOOL }, + {MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, PARSE_ONLY_UINT }, + {MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, PARSE_ONLY_UINT }, {MENU_ENUM_LABEL_THUMBNAILS, PARSE_ONLY_UINT }, {MENU_ENUM_LABEL_LEFT_THUMBNAILS, PARSE_ONLY_UINT }, {MENU_ENUM_LABEL_XMB_VERTICAL_THUMBNAILS, PARSE_ONLY_BOOL }, diff --git a/menu/menu_driver.c b/menu/menu_driver.c index a90f9da314c..1cbfc1b5933 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -3845,9 +3845,11 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) break; case RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE: { - if (!menu_driver_ctx || !menu_driver_ctx->refresh_thumbnail_image) + unsigned *i = (unsigned*)data; + + if (!i || !menu_driver_ctx || !menu_driver_ctx->refresh_thumbnail_image) return false; - menu_driver_ctx->refresh_thumbnail_image(menu_userdata); + menu_driver_ctx->refresh_thumbnail_image(menu_userdata, *i); } break; case RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_PATH: diff --git a/menu/menu_driver.h b/menu/menu_driver.h index c0a5685e353..1a2a4546d22 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -309,7 +309,7 @@ typedef struct menu_ctx_driver int (*environ_cb)(enum menu_environ_cb type, void *data, void *userdata); void (*update_thumbnail_path)(void *data, unsigned i, char pos); void (*update_thumbnail_image)(void *data); - void (*refresh_thumbnail_image)(void *data); + void (*refresh_thumbnail_image)(void *data, unsigned i); void (*set_thumbnail_system)(void *data, char* s, size_t len); void (*get_thumbnail_system)(void *data, char* s, size_t len); void (*set_thumbnail_content)(void *data, const char *s); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index cc2ab7b3287..27dd25227f6 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -3961,6 +3961,69 @@ static void setting_get_string_representation_uint_materialui_menu_transition_an break; } } + +static void setting_get_string_representation_uint_materialui_menu_thumbnail_view_portrait( + rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + switch (*setting->value.target.unsigned_integer) + { + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED), len); + break; + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL), len); + break; + case MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM), len); + break; + default: + break; + } +} + +static void setting_get_string_representation_uint_materialui_menu_thumbnail_view_landscape( + rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + switch (*setting->value.target.unsigned_integer) + { + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED), len); + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL), len); + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM), len); + break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE), len); + break; + default: + break; + } +} #endif #ifdef HAVE_XMB @@ -13012,6 +13075,40 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); + CONFIG_UINT( + list, list_info, + &settings->uints.menu_materialui_thumbnail_view_portrait, + MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, + MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT, + DEFAULT_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT, + &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_materialui_menu_thumbnail_view_portrait; + menu_settings_list_current_add_range(list, list_info, 0, MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LAST-1, 1, true, true); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; + + CONFIG_UINT( + list, list_info, + &settings->uints.menu_materialui_thumbnail_view_landscape, + MENU_ENUM_LABEL_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, + MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE, + DEFAULT_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE, + &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_materialui_menu_thumbnail_view_landscape; + menu_settings_list_current_add_range(list, list_info, 0, MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LAST-1, 1, true, true); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; + /* TODO: These should be removed entirely, but just * comment out for now in case users complain... CONFIG_FLOAT( @@ -13149,7 +13246,10 @@ static bool setting_append_list( SD_FLAG_NONE); } - if (string_is_equal(settings->arrays.menu_driver, "xmb") || string_is_equal(settings->arrays.menu_driver, "ozone") || string_is_equal(settings->arrays.menu_driver, "rgui")) + if (string_is_equal(settings->arrays.menu_driver, "xmb") || + string_is_equal(settings->arrays.menu_driver, "ozone") || + string_is_equal(settings->arrays.menu_driver, "rgui") || + string_is_equal(settings->arrays.menu_driver, "glui")) { enum msg_hash_enums thumbnails_label_value; enum msg_hash_enums left_thumbnails_label_value; @@ -13187,22 +13287,26 @@ static bool setting_append_list( menu_settings_list_current_add_range(list, list_info, 0, 3, 1, true, true); (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_RADIO_BUTTONS; - CONFIG_UINT( - list, list_info, - &settings->uints.menu_left_thumbnails, - MENU_ENUM_LABEL_LEFT_THUMBNAILS, - left_thumbnails_label_value, - menu_left_thumbnails_default, - &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_left_thumbnails; - menu_settings_list_current_add_range(list, list_info, 0, 3, 1, true, true); - (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_RADIO_BUTTONS; + /* Material UI does not use left thumbnails (yet...) */ + if (!string_is_equal(settings->arrays.menu_driver, "glui")) + { + CONFIG_UINT( + list, list_info, + &settings->uints.menu_left_thumbnails, + MENU_ENUM_LABEL_LEFT_THUMBNAILS, + left_thumbnails_label_value, + menu_left_thumbnails_default, + &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_left_thumbnails; + menu_settings_list_current_add_range(list, list_info, 0, 3, 1, true, true); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_RADIO_BUTTONS; + } } if (string_is_equal(settings->arrays.menu_driver, "xmb")) @@ -13238,7 +13342,9 @@ static bool setting_append_list( menu_settings_list_current_add_range(list, list_info, (*list)[list_info->index - 1].offset_by, 100, 1, true, true); } - if (string_is_equal(settings->arrays.menu_driver, "xmb") || string_is_equal(settings->arrays.menu_driver, "ozone")) + if (string_is_equal(settings->arrays.menu_driver, "xmb") || + string_is_equal(settings->arrays.menu_driver, "ozone") || + string_is_equal(settings->arrays.menu_driver, "glui")) { CONFIG_UINT( list, list_info, diff --git a/menu/menu_thumbnail.c b/menu/menu_thumbnail.c new file mode 100644 index 00000000000..61110527950 --- /dev/null +++ b/menu/menu_thumbnail.c @@ -0,0 +1,355 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (menu_thumbnail.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include + +#include "../retroarch.h" +#include "../configuration.h" +#include "../tasks/tasks_internal.h" +#include "menu_animation.h" + +#include "menu_thumbnail.h" + +/* When streaming thumbnails, to minimise the processing + * of unnecessary images (i.e. when scrolling rapidly through + * playlists), we delay loading until an entry has been on screen + * for at least menu_thumbnail_delay ms */ +#define DEFAULT_MENU_THUMBNAIL_STREAM_DELAY 83.333333f +static float menu_thumbnail_stream_delay = DEFAULT_MENU_THUMBNAIL_STREAM_DELAY; + +/* Duration in ms of the thumbnail 'fade in' animation */ +#define DEFAULT_MENU_THUMBNAIL_FADE_DURATION 166.66667f +static float menu_thumbnail_fade_duration = DEFAULT_MENU_THUMBNAIL_FADE_DURATION; + +/* Due to the asynchronous nature of thumbnail + * loading, it is quite possible to trigger a load + * then navigate to a different menu list before + * the load is complete/handled. As an additional + * safety check, we therefore tag the current menu + * list with counter value that is incremented whenever + * a list is cleared/set. This is sent as userdata when + * requesting a thumbnail, and the upload is only + * handled if the tag matches the most recent value + * at the time when the load completes */ +static uint64_t menu_thumbnail_list_id = 0; + +/* Utility structure, sent as userdata when pushing + * an image load */ +typedef struct +{ + menu_thumbnail_t *thumbnail; + retro_time_t list_id; +} menu_thumbnail_tag_t; + +/* Setters */ + +/* When streaming thumbnails, sets time in ms that an + * entry must be on screen before an image load is + * requested */ +void menu_thumbnail_set_stream_delay(float delay) +{ + menu_thumbnail_stream_delay = (delay >= 0.0f) ? + delay : DEFAULT_MENU_THUMBNAIL_STREAM_DELAY; +} + +/* Sets duration in ms of the thumbnail 'fade in' + * animation */ +void menu_thumbnail_set_fade_duration(float duration) +{ + menu_thumbnail_fade_duration = (duration >= 0.0f) ? + duration : DEFAULT_MENU_THUMBNAIL_FADE_DURATION; +} + +/* Getters */ + +/* Fetches current streaming thumbnails request delay */ +float menu_thumbnail_get_stream_delay(void) +{ + return menu_thumbnail_stream_delay; +} + +/* Fetches current 'fade in' animation duration */ +float menu_thumbnail_get_fade_duration(void) +{ + return menu_thumbnail_fade_duration; +} + +/* Callbacks */ + +/* Used to process thumbnail data following completion + * of image load task */ +static void menu_thumbnail_handle_upload( + retro_task_t *task, void *task_data, void *user_data, const char *err) +{ + struct texture_image *img = (struct texture_image*)task_data; + menu_thumbnail_tag_t *thumbnail_tag = (menu_thumbnail_tag_t*)user_data; + menu_animation_ctx_entry_t animation_entry; + + /* Sanity check */ + if (!thumbnail_tag) + goto end; + + /* Ensure that we are operating on the correct + * thumbnail... */ + if (thumbnail_tag->list_id != menu_thumbnail_list_id) + goto end; + + /* Only process image if we are waiting for it */ + if (thumbnail_tag->thumbnail->status != MENU_THUMBNAIL_STATUS_PENDING) + goto end; + + /* Sanity check: if thumbnail already has a texture, + * we're in some kind of weird error state - in this + * case, the best course of action is to just reset + * the thumbnail... */ + if (thumbnail_tag->thumbnail->texture) + menu_thumbnail_reset(thumbnail_tag->thumbnail); + + /* Set thumbnail 'missing' status by default + * (saves a number of checks later) */ + thumbnail_tag->thumbnail->status = MENU_THUMBNAIL_STATUS_MISSING; + + /* Check we have a valid image */ + if (!img) + goto end; + + if (img->width < 1 || img->height < 1) + goto end; + + /* Upload texture to GPU */ + video_driver_texture_load( + img, TEXTURE_FILTER_MIPMAP_LINEAR, &thumbnail_tag->thumbnail->texture); + + /* Cache dimensions */ + thumbnail_tag->thumbnail->width = img->width; + thumbnail_tag->thumbnail->height = img->height; + + /* Update thumbnail status */ + thumbnail_tag->thumbnail->status = MENU_THUMBNAIL_STATUS_AVAILABLE; + + /* Trigger 'fade in' animation */ + thumbnail_tag->thumbnail->alpha = 0.0f; + + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = (uintptr_t)&thumbnail_tag->thumbnail->alpha; + animation_entry.duration = menu_thumbnail_fade_duration; + animation_entry.target_value = 1.0f; + animation_entry.subject = &thumbnail_tag->thumbnail->alpha; + animation_entry.cb = NULL; + animation_entry.userdata = NULL; + + menu_animation_push(&animation_entry); + +end: + /* Clean up */ + if (img) + { + image_texture_free(img); + free(img); + } + + if (thumbnail_tag) + free(thumbnail_tag); +} + +/* Core interface */ + +/* When called, prevents the handling of any pending + * thumbnail load requests + * >> **MUST** be called before deleting any menu_thumbnail_t + * objects passed to menu_thumbnail_request() or + * menu_thumbnail_process_stream(), otherwise + * heap-use-after-free errors *will* occur */ +void menu_thumbnail_cancel_pending_requests(void) +{ + menu_thumbnail_list_id++; +} + +/* Requests loading of the specified thumbnail + * - If operation fails, 'thumbnail->status' will be set to + * MENU_THUMBNAIL_STATUS_MISSING + * - If operation is successful, 'thumbnail->status' will be + * set to MENU_THUMBNAIL_STATUS_PENDING + * 'thumbnail' will be populated with texture info/metadata + * once the image load is complete + * NOTE 1: Must be called *after* menu_thumbnail_set_system() + * and menu_thumbnail_set_content*() + * NOTE 2: 'playlist' and 'idx' are only required here for + * on-demand thumbnail download support + * (an annoyance...) */ +void menu_thumbnail_request( + menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id, + playlist_t *playlist, size_t idx, menu_thumbnail_t *thumbnail) +{ + settings_t *settings = config_get_ptr(); + const char *thumbnail_path = NULL; + bool has_thumbnail = false; + + if (!path_data || !thumbnail || !settings) + return; + + /* Reset thumbnail, then set 'missing' status by default + * (saves a number of checks later) */ + menu_thumbnail_reset(thumbnail); + thumbnail->status = MENU_THUMBNAIL_STATUS_MISSING; + + /* Update/extract thumbnail path */ + if (menu_thumbnail_is_enabled(path_data, thumbnail_id)) + if (menu_thumbnail_update_path(path_data, thumbnail_id)) + has_thumbnail = menu_thumbnail_get_path(path_data, thumbnail_id, &thumbnail_path); + + /* Load thumbnail, if required */ + if (has_thumbnail) + { + if (path_is_valid(thumbnail_path)) + { + menu_thumbnail_tag_t *thumbnail_tag = + (menu_thumbnail_tag_t*)calloc(1, sizeof(menu_thumbnail_tag_t)); + + if (!thumbnail_tag) + return; + + /* Configure user data */ + thumbnail_tag->thumbnail = thumbnail; + thumbnail_tag->list_id = menu_thumbnail_list_id; + + /* Would like to cancel any existing image load tasks + * here, but can't see how to do it... */ + if(task_push_image_load( + thumbnail_path, video_driver_supports_rgba(), + settings->uints.menu_thumbnail_upscale_threshold, + menu_thumbnail_handle_upload, thumbnail_tag)) + thumbnail->status = MENU_THUMBNAIL_STATUS_PENDING; + } +#ifdef HAVE_NETWORKING + /* Handle on demand thumbnail downloads */ + else if (settings->bools.network_on_demand_thumbnails) + { + const char *system = NULL; + + if (menu_thumbnail_get_system(path_data, &system)) + task_push_pl_entry_thumbnail_download( + system, playlist_get_cached(), (unsigned)idx, + false, true); + } +#endif + } +} + +/* Resets (and free()s the current texture of) the + * specified thumbnail */ +void menu_thumbnail_reset(menu_thumbnail_t *thumbnail) +{ + if (!thumbnail) + return; + + if (thumbnail->texture) + { + menu_animation_ctx_tag tag = (uintptr_t)&thumbnail->alpha; + + /* Unload texture */ + video_driver_texture_unload(&thumbnail->texture); + + /* Ensure any 'fade in' animation is killed */ + menu_animation_kill_by_tag(&tag); + } + + /* Reset all parameters */ + thumbnail->status = MENU_THUMBNAIL_STATUS_UNKNOWN; + thumbnail->texture = 0; + thumbnail->width = 0; + thumbnail->height = 0; + thumbnail->alpha = 0.0f; + thumbnail->delay_timer = 0.0f; +} + +/* Stream processing */ + +/* Handles streaming of the specified thumbnail as it moves + * on/off screen + * - Must be called each frame for every on-screen entry + * - Must be called once for each entry as it moves off-screen + * NOTE 1: Must be called *after* menu_thumbnail_set_system() + * NOTE 2: This function calls menu_thumbnail_set_content*() + * > It is therefore intended for use in situations + * where each entry has a *single* thumbnail + * > Since I can't think of any view mode that needs + * two thumbnails, this should be fine (i.e. we might + * want one additional image to go with the currently + * selected item, but this is not a streaming thing - + * the auxiliary image can just be loaded via a normal + * menu_thumbnail_request() */ +void menu_thumbnail_process_stream( + menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id, + playlist_t *playlist, size_t idx, menu_thumbnail_t *thumbnail, bool on_screen) +{ + if (!thumbnail) + return; + + if (on_screen) + { + /* Entry is on-screen + * > Only process if current status is + * MENU_THUMBNAIL_STATUS_UNKNOWN */ + if (thumbnail->status == MENU_THUMBNAIL_STATUS_UNKNOWN) + { + /* Check if stream delay timer has elapsed */ + thumbnail->delay_timer += menu_animation_get_delta_time(); + + if (thumbnail->delay_timer > menu_thumbnail_stream_delay) + { + /* Sanity check */ + if (!path_data || !playlist) + return; + + /* Update thumbnail content */ + if (!menu_thumbnail_set_content_playlist(path_data, playlist, idx)) + { + /* Content is invalid + * > Reset thumbnail and set missing status */ + menu_thumbnail_reset(thumbnail); + thumbnail->status = MENU_THUMBNAIL_STATUS_MISSING; + return; + } + + /* Request image load */ + menu_thumbnail_request( + path_data, thumbnail_id, playlist, idx, thumbnail); + } + } + } + else + { + /* Entry is off-screen + * > If status is MENU_THUMBNAIL_STATUS_UNKNOWN, + * thumbnail is already in a blank state - do nothing + * In all other cases, reset thumbnail */ + if (thumbnail->status != MENU_THUMBNAIL_STATUS_UNKNOWN) + menu_thumbnail_reset(thumbnail); + } +} diff --git a/menu/menu_thumbnail.h b/menu/menu_thumbnail.h new file mode 100644 index 00000000000..ef2dd96d78f --- /dev/null +++ b/menu/menu_thumbnail.h @@ -0,0 +1,128 @@ +/* Copyright (C) 2010-2019 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (menu_thumbnail.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef __MENU_THUMBNAIL_H +#define __MENU_THUMBNAIL_H + +#include +#include + +#include + +#include "menu_thumbnail_path.h" + +RETRO_BEGIN_DECLS + +/* Defines the current status of an entry + * thumbnail texture */ +enum menu_thumbnail_status +{ + MENU_THUMBNAIL_STATUS_UNKNOWN = 0, + MENU_THUMBNAIL_STATUS_PENDING, + MENU_THUMBNAIL_STATUS_AVAILABLE, + MENU_THUMBNAIL_STATUS_MISSING +}; + +/* Holds all runtime parameters associated with + * an entry thumbnail */ +typedef struct +{ + enum menu_thumbnail_status status; + uintptr_t texture; + unsigned width; + unsigned height; + float alpha; + float delay_timer; +} menu_thumbnail_t; + +/* Setters */ + +/* When streaming thumbnails, sets time in ms that an + * entry must be on screen before an image load is + * requested */ +void menu_thumbnail_set_stream_delay(float delay); + +/* Sets duration in ms of the thumbnail 'fade in' + * animation */ +void menu_thumbnail_set_fade_duration(float duration); + +/* Getters */ + +/* Fetches current streaming thumbnails request delay */ +float menu_thumbnail_get_stream_delay(void); + +/* Fetches current 'fade in' animation duration */ +float menu_thumbnail_get_fade_duration(void); + +/* Core interface */ + +/* When called, prevents the handling of any pending + * thumbnail load requests + * >> **MUST** be called before deleting any menu_thumbnail_t + * objects passed to menu_thumbnail_request() or + * menu_thumbnail_process_stream(), otherwise + * heap-use-after-free errors *will* occur */ +void menu_thumbnail_cancel_pending_requests(void); + +/* Requests loading of the specified thumbnail + * - If operation fails, 'thumbnail->status' will be set to + * MUI_THUMBNAIL_STATUS_MISSING + * - If operation is successful, 'thumbnail->status' will be + * set to MUI_THUMBNAIL_STATUS_PENDING + * 'thumbnail' will be populated with texture info/metadata + * once the image load is complete + * NOTE 1: Must be called *after* menu_thumbnail_set_system() + * and menu_thumbnail_set_content*() + * NOTE 2: 'playlist' and 'idx' are only required here for + * on-demand thumbnail download support + * (an annoyance...) */ +void menu_thumbnail_request( + menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id, + playlist_t *playlist, size_t idx, menu_thumbnail_t *thumbnail); + +/* Resets (and free()s the current texture of) the + * specified thumbnail */ +void menu_thumbnail_reset(menu_thumbnail_t *thumbnail); + +/* Stream processing */ + +/* Handles streaming of the specified thumbnail as it moves + * on/off screen + * - Must be called each frame for every on-screen entry + * - Must be called once for each entry as it moves off-screen + * NOTE 1: Must be called *after* menu_thumbnail_set_system() + * NOTE 2: This function calls menu_thumbnail_set_content*() + * > It is therefore intended for use in situations + * where each entry has a *single* thumbnail + * > Since I can't think of any view mode that needs + * two thumbnails, this should be fine (i.e. we might + * want one additional image to go with the currently + * selected item, but this is not a streaming thing - + * the auxiliary image can just be loaded via a normal + * menu_thumbnail_request() */ +void menu_thumbnail_process_stream( + menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id, + playlist_t *playlist, size_t idx, menu_thumbnail_t *thumbnail, bool on_screen); + +RETRO_END_DECLS + +#endif diff --git a/menu/menu_thumbnail_path.c b/menu/menu_thumbnail_path.c index e5002aff3d1..a6c137f04b6 100644 --- a/menu/menu_thumbnail_path.c +++ b/menu/menu_thumbnail_path.c @@ -1,7 +1,7 @@ /* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- - * The following license statement only applies to this file (runtime_file.c). + * The following license statement only applies to this file (menu_thumbnail_path.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, diff --git a/menu/menu_thumbnail_path.h b/menu/menu_thumbnail_path.h index b1f22794e0e..cbfb385d096 100644 --- a/menu/menu_thumbnail_path.h +++ b/menu/menu_thumbnail_path.h @@ -1,7 +1,7 @@ /* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- - * The following license statement only applies to this file (runtime_file.c). + * The following license statement only applies to this file (menu_thumbnail_path.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, diff --git a/msg_hash.h b/msg_hash.h index 8e41859f76b..28e58483919 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -622,6 +622,18 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_TRANSITION_ANIM_SLIDE, MENU_ENUM_LABEL_VALUE_MATERIALUI_MENU_TRANSITION_ANIM_NONE, + MENU_LABEL(MATERIALUI_MENU_THUMBNAIL_VIEW_PORTRAIT), + MENU_LABEL(MATERIALUI_MENU_THUMBNAIL_VIEW_LANDSCAPE), + + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_DISABLED, + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_SMALL, + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_PORTRAIT_LIST_MEDIUM, + + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED, + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL, + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM, + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE, + MENU_ENUM_LABEL_VALUE_INPUT_POLL_TYPE_BEHAVIOR_LATE, MENU_ENUM_LABEL_VALUE_INPUT_POLL_TYPE_BEHAVIOR_NORMAL, MENU_ENUM_LABEL_VALUE_INPUT_POLL_TYPE_BEHAVIOR_EARLY, diff --git a/tasks/task_pl_thumbnail_download.c b/tasks/task_pl_thumbnail_download.c index 6702f4bcecc..faaa2fa9f64 100644 --- a/tasks/task_pl_thumbnail_download.c +++ b/tasks/task_pl_thumbnail_download.c @@ -474,8 +474,9 @@ static void cb_task_pl_entry_thumbnail_refresh_menu( bool do_refresh = false; playlist_t *current_playlist = playlist_get_cached(); menu_handle_t *menu = menu_driver_get_ptr(); + settings_t *settings = config_get_ptr(); - if (!task) + if (!task || !settings) return; pl_thumb = (pl_thumb_handle_t*)task->state; @@ -487,7 +488,9 @@ static void cb_task_pl_entry_thumbnail_refresh_menu( * (Note: this is crude, but it's sufficient to prevent * 'refresh' from getting spammed when switching * playlists or scrolling through one playlist at - * maximum speed with on demand downloads enabled) */ + * maximum speed with on demand downloads enabled) + * NOTE: GLUI requires special treatment, since + * it displays multiple thumbnails at a time... */ if (!current_playlist) return; @@ -496,11 +499,20 @@ static void cb_task_pl_entry_thumbnail_refresh_menu( if (string_is_empty(playlist_get_conf_path(current_playlist))) return; - if (((pl_thumb->list_index != menu_navigation_get_selection()) && - (pl_thumb->list_index != menu->rpl_entry_selection_ptr)) || - !string_is_equal(pl_thumb->playlist_path, + if (string_is_equal(settings->arrays.menu_driver, "glui")) + { + if (!string_is_equal(pl_thumb->playlist_path, playlist_get_conf_path(current_playlist))) - return; + return; + } + else + { + if (((pl_thumb->list_index != menu_navigation_get_selection()) && + (pl_thumb->list_index != menu->rpl_entry_selection_ptr)) || + !string_is_equal(pl_thumb->playlist_path, + playlist_get_conf_path(current_playlist))) + return; + } /* Only refresh if left/right thumbnails did not exist * when the task began, but do exist now @@ -519,7 +531,10 @@ static void cb_task_pl_entry_thumbnail_refresh_menu( do_refresh = path_is_valid(left_thumbnail_path); if (do_refresh) - menu_driver_ctl(RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE, NULL); + { + unsigned i = (unsigned)pl_thumb->list_index; + menu_driver_ctl(RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE, &i); + } #endif }