diff --git a/config.def.h b/config.def.h index 6c0d3eb8b9..e5d8741bb6 100644 --- a/config.def.h +++ b/config.def.h @@ -76,6 +76,8 @@ #ifdef HAVE_MATERIALUI /* Show icons to the left of each menu entry */ #define DEFAULT_MATERIALUI_ICONS_ENABLE true +/* Show system-specific icons in the playlists tab */ +#define DEFAULT_MATERIALUI_PLAYLIST_ICONS_ENABLE true #endif /* Material UI colour theme */ diff --git a/configuration.c b/configuration.c index bcfc2be52a..8457c586ed 100644 --- a/configuration.c +++ b/configuration.c @@ -1624,6 +1624,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("menu_show_advanced_settings", &settings->bools.menu_show_advanced_settings, true, DEFAULT_SHOW_ADVANCED_SETTINGS, false); #ifdef HAVE_MATERIALUI SETTING_BOOL("materialui_icons_enable", &settings->bools.menu_materialui_icons_enable, true, DEFAULT_MATERIALUI_ICONS_ENABLE, false); + SETTING_BOOL("materialui_playlist_icons_enable", &settings->bools.menu_materialui_playlist_icons_enable, true, DEFAULT_MATERIALUI_PLAYLIST_ICONS_ENABLE, false); SETTING_BOOL("materialui_show_nav_bar", &settings->bools.menu_materialui_show_nav_bar, true, DEFAULT_MATERIALUI_SHOW_NAV_BAR, false); SETTING_BOOL("materialui_auto_rotate_nav_bar", &settings->bools.menu_materialui_auto_rotate_nav_bar, true, DEFAULT_MATERIALUI_AUTO_ROTATE_NAV_BAR, false); SETTING_BOOL("materialui_dual_thumbnail_list_view_enable", &settings->bools.menu_materialui_dual_thumbnail_list_view_enable, true, DEFAULT_MATERIALUI_DUAL_THUMBNAIL_LIST_VIEW_ENABLE, false); diff --git a/configuration.h b/configuration.h index 821f72472f..d9972b3308 100644 --- a/configuration.h +++ b/configuration.h @@ -207,6 +207,7 @@ typedef struct settings bool menu_show_video_layout; #endif bool menu_materialui_icons_enable; + bool menu_materialui_playlist_icons_enable; bool menu_materialui_show_nav_bar; bool menu_materialui_auto_rotate_nav_bar; bool menu_materialui_dual_thumbnail_list_view_enable; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index b3c0fa98e5..d3ba039514 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3646,6 +3646,10 @@ MSG_HASH( MENU_ENUM_LABEL_MATERIALUI_ICONS_ENABLE, "materialui_icons_enable" ) +MSG_HASH( + MENU_ENUM_LABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE, + "materialui_playlist_icons_enable" + ) MSG_HASH( MENU_ENUM_LABEL_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION, "materialui_landscape_layout_optimization" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index ed834656d6..0bd42f24b7 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -8387,7 +8387,15 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_MATERIALUI_ICONS_ENABLE, - "Show icons at the left of the menu entries." + "Show icons to the left of the menu entries." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_PLAYLIST_ICONS_ENABLE, + "Playlist Icons" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE, + "Show system-specific icons in the playlists tab. (Restart Required)" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index cd1be7ef75..ea433d43c5 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -272,6 +272,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_menu_toggle, ME DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_hotkey_block_delay, MENU_ENUM_SUBLABEL_INPUT_HOTKEY_BLOCK_DELAY) #ifdef HAVE_MATERIALUI DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_icons_enable, MENU_ENUM_SUBLABEL_MATERIALUI_ICONS_ENABLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_playlist_icons_enable, MENU_ENUM_SUBLABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_landscape_layout_optimization, MENU_ENUM_SUBLABEL_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_show_nav_bar, MENU_ENUM_SUBLABEL_MATERIALUI_SHOW_NAV_BAR) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_auto_rotate_nav_bar, MENU_ENUM_SUBLABEL_MATERIALUI_AUTO_ROTATE_NAV_BAR) @@ -1719,6 +1720,11 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_MATERIALUI_ICONS_ENABLE: #ifdef HAVE_MATERIALUI BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_materialui_icons_enable); +#endif + break; + case MENU_ENUM_LABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE: +#ifdef HAVE_MATERIALUI + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_materialui_playlist_icons_enable); #endif break; case MENU_ENUM_LABEL_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION: diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 2d42850979..48463ec229 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -51,6 +51,7 @@ #include "../../verbosity.h" #include "../../tasks/tasks_internal.h" #include "../../runtime_file.h" +#include "../../list_special.h" /* Defines the 'device independent pixel' base * unit reference size for all UI elements. @@ -1159,7 +1160,8 @@ enum materialui_node_icon_type { MUI_ICON_TYPE_NONE = 0, MUI_ICON_TYPE_INTERNAL, - MUI_ICON_TYPE_MENU_EXPLORE + MUI_ICON_TYPE_MENU_EXPLORE, + MUI_ICON_TYPE_PLAYLIST }; /* This structure holds auxiliary information for @@ -1424,6 +1426,23 @@ enum materialui_onscreen_entry_position_type MUI_ONSCREEN_ENTRY_CENTRE }; +/* Contains the file path(s) and texture pointer + * of a single playlist icon */ +typedef struct +{ + char *playlist_file; + char *image_file; + uintptr_t image; +} materialui_playlist_icon_t; + +/* Contains icon data for all installed + * playlists */ +typedef struct +{ + size_t size; + materialui_playlist_icon_t *icons; +} materialui_playlist_icons_t; + typedef struct materialui_handle { bool is_portrait; @@ -1499,6 +1518,7 @@ typedef struct materialui_handle { uintptr_t bg; uintptr_t list[MUI_TEXTURE_LAST]; + materialui_playlist_icons_t playlist; } textures; /* Font data */ @@ -1924,6 +1944,239 @@ void materialui_font_flush( font_data->raster_block.carr.coords.vertices = 0; } +/* ============================== + * Playlist icons START + * ============================== */ + +static void materialui_context_destroy_playlist_icons(materialui_handle_t *mui) +{ + size_t i; + + for (i = 0; i < mui->textures.playlist.size; i++) + video_driver_texture_unload(&mui->textures.playlist.icons[i].image); +} + +static void materialui_context_reset_playlist_icons(materialui_handle_t *mui) +{ + size_t i; + char icon_path[PATH_MAX_LENGTH]; + + icon_path[0] = '\0'; + + if (mui->textures.playlist.size < 1) + return; + + /* Get icon directory */ + fill_pathname_application_special( + icon_path, sizeof(icon_path), + APPLICATION_SPECIAL_DIRECTORY_ASSETS_SYSICONS); + + if (string_is_empty(icon_path)) + return; + + /* Load icons + * > Note that missing icons are ignored */ + for (i = 0; i < mui->textures.playlist.size; i++) + { + const char *image_file = + mui->textures.playlist.icons[i].image_file; + + if (string_is_empty(image_file)) + continue; + + gfx_display_reset_textures_list( + image_file, icon_path, + &mui->textures.playlist.icons[i].image, + TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL); + } +} + +static void materialui_free_playlist_icon_list(materialui_handle_t *mui) +{ + size_t i; + + for (i = 0; i < mui->textures.playlist.size; i++) + { + /* Ensure that any textures are unloaded + * > Note: This should never be required. + * Loaded icons will always be 'freed' by + * materialui_context_destroy_playlist_icons() */ + if (mui->textures.playlist.icons[i].image) + video_driver_texture_unload(&mui->textures.playlist.icons[i].image); + + /* Free file names */ + if (mui->textures.playlist.icons[i].playlist_file) + free(mui->textures.playlist.icons[i].playlist_file); + + if (mui->textures.playlist.icons[i].image_file) + free(mui->textures.playlist.icons[i].image_file); + } + + /* Free icons array and set list size to zero */ + if (mui->textures.playlist.icons) + free(mui->textures.playlist.icons); + + mui->textures.playlist.icons = NULL; + mui->textures.playlist.size = 0; +} + +static void materialui_refresh_playlist_icon_list(materialui_handle_t *mui) +{ + settings_t *settings = config_get_ptr(); + const char *dir_playlist = settings ? + settings->paths.directory_playlist : NULL; + bool icons_enabled = settings ? + settings->bools.menu_materialui_icons_enable : false; + bool playlist_icons_enabled = settings ? + settings->bools.menu_materialui_playlist_icons_enable : false; + struct string_list *file_list = NULL; + size_t i; + + /* Free existing icon list */ + materialui_free_playlist_icon_list(mui); + + /* If playlist icons are disabled, no further + * action is required */ + if (!icons_enabled || !playlist_icons_enabled) + goto end; + + /* Get list of .lpl files in playlists directory */ + if (string_is_empty(dir_playlist)) + goto end; + + file_list = dir_list_new_special(dir_playlist, + DIR_LIST_COLLECTIONS, NULL, false); + + if (!file_list || (file_list->size < 1)) + goto end; + + /* Allocate icons array + * > We may end up making this larger than + * necessary (if 'invalid' playlist files + * are included in the list), but this + * reduces code complexity */ + mui->textures.playlist.icons = (materialui_playlist_icon_t*) + malloc(file_list->size * sizeof(materialui_playlist_icon_t)); + + if (!mui->textures.playlist.icons) + goto end; + + mui->textures.playlist.size = file_list->size; + + for (i = 0; i < file_list->size; i++) + { + const char *path = file_list->elems[i].data; + const char *playlist_file = NULL; + char image_file[PATH_MAX_LENGTH]; + + image_file[0] = '\0'; + + /* We used malloc() to create the icons + * array - ensure struct members are + * correctly initialised */ + mui->textures.playlist.icons[i].playlist_file = NULL; + mui->textures.playlist.icons[i].image_file = NULL; + mui->textures.playlist.icons[i].image = 0; + + /* dir_list_new_special() is 'well behaved'. + * - It will only return file paths, not + * directories + * - It will only return .lpl files + * Only basic sanity checks are therefore + * required */ + if (string_is_empty(path)) + continue; + + playlist_file = path_basename(path); + + if (string_is_empty(playlist_file)) + continue; + + /* > Ignore history/favourites playlists */ + if (string_ends_with_size(playlist_file, "_history.lpl", + strlen(playlist_file), STRLEN_CONST("_history.lpl")) || + string_is_equal(playlist_file, + file_path_str(FILE_PATH_CONTENT_FAVORITES))) + continue; + + /* Playlist is valid - generate image file name */ + strlcpy(image_file, playlist_file, sizeof(image_file)); + path_remove_extension(image_file); + strlcat(image_file, ".png", sizeof(image_file)); + + if (string_is_empty(image_file)) + continue; + + /* All good - cache paths */ + mui->textures.playlist.icons[i].playlist_file = strdup(playlist_file); + mui->textures.playlist.icons[i].image_file = strdup(image_file); + } + +end: + if (file_list) + string_list_free(file_list); +} + +static void materialui_set_node_playlist_icon( + materialui_handle_t *mui, materialui_node_t* node, + const char *playlist_path) +{ + const char *playlist_file = NULL; + size_t i; + + /* Set defaults */ + node->icon_texture_index = MUI_TEXTURE_PLAYLIST; + node->icon_type = MUI_ICON_TYPE_INTERNAL; + + if (mui->textures.playlist.size < 1) + return; + + /* Get playlist file name */ + if (string_is_empty(playlist_path)) + return; + + playlist_file = path_basename(playlist_path); + + if (string_is_empty(playlist_path)) + return; + + /* Search icon list for specified file */ + for (i = 0; i < mui->textures.playlist.size; i++) + { + const char *icon_playlist_file = + mui->textures.playlist.icons[i].playlist_file; + + if (string_is_empty(icon_playlist_file)) + continue; + + if (string_is_equal(playlist_file, icon_playlist_file)) + { + node->icon_texture_index = i; + node->icon_type = MUI_ICON_TYPE_PLAYLIST; + break; + } + } +} + +static uintptr_t materialui_get_playlist_icon( + materialui_handle_t *mui, unsigned texture_index) +{ + uintptr_t playlist_icon; + + /* Always use MUI_TEXTURE_PLAYLIST as + * a fallback */ + if (texture_index >= mui->textures.playlist.size) + return mui->textures.list[MUI_TEXTURE_PLAYLIST]; + + playlist_icon = mui->textures.playlist.icons[texture_index].image; + + return playlist_icon ? playlist_icon : mui->textures.list[MUI_TEXTURE_PLAYLIST]; +} + +/* ============================== + * Playlist icons END + * ============================== */ + static void materialui_context_reset_textures(materialui_handle_t *mui) { bool has_all_assets = true; @@ -2372,14 +2625,26 @@ static void materialui_compute_entries_box_default( unsigned num_sublabel_lines = 0; materialui_node_t *node = (materialui_node_t*) file_list_get_userdata_at_offset(list, i); - bool has_icon; + bool has_icon = false; if (!node) continue; - has_icon = ((node->icon_type == MUI_ICON_TYPE_INTERNAL) && - mui->textures.list[node->icon_texture_index]) || - (node->icon_type == MUI_ICON_TYPE_MENU_EXPLORE); + switch (node->icon_type) + { + case MUI_ICON_TYPE_INTERNAL: + has_icon = mui->textures.list[node->icon_texture_index] != 0; + break; + case MUI_ICON_TYPE_MENU_EXPLORE: + has_icon = true; + break; + case MUI_ICON_TYPE_PLAYLIST: + has_icon = materialui_get_playlist_icon( + mui, node->icon_texture_index) != 0; + break; + default: + break; + } num_sublabel_lines = materialui_count_sublabel_lines( mui, usable_width, i, has_icon); @@ -3500,6 +3765,10 @@ static void materialui_render_menu_entry_default( case MUI_ICON_TYPE_MENU_EXPLORE: icon_texture = menu_explore_get_entry_icon(entry_type); break; + case MUI_ICON_TYPE_PLAYLIST: + icon_texture = materialui_get_playlist_icon( + mui, node->icon_texture_index); + break; default: switch (entry_file_type) { @@ -7228,6 +7497,11 @@ static void *materialui_init(void **userdata, bool video_is_threaded) mui->status_bar.runtime_fallback_str[0] = '\0'; mui->status_bar.last_played_fallback_str[0] = '\0'; + /* Initialise playlist icon list */ + mui->textures.playlist.size = 0; + mui->textures.playlist.icons = NULL; + materialui_refresh_playlist_icon_list(mui); + gfx_animation_set_update_time_cb(materialui_menu_animation_update_time); return menu; @@ -7253,6 +7527,9 @@ static void materialui_free(void *data) if (mui->thumbnail_path_data) free(mui->thumbnail_path_data); + + materialui_free_playlist_icon_list(mui); + gfx_animation_unset_update_time_cb(); } @@ -7290,7 +7567,7 @@ static void materialui_reset_thumbnails(void) static void materialui_context_destroy(void *data) { materialui_handle_t *mui = (materialui_handle_t*)data; - unsigned i; + size_t i; if (!mui) return; @@ -7299,6 +7576,9 @@ static void materialui_context_destroy(void *data) for (i = 0; i < MUI_TEXTURE_LAST; i++) video_driver_texture_unload(&mui->textures.list[i]); + /* Free playlist icons */ + materialui_context_destroy_playlist_icons(mui); + /* Free fonts */ if (mui->font_data.title.font) gfx_display_font_free(mui->font_data.title.font); @@ -7766,13 +8046,14 @@ static void materialui_context_reset(void *data, bool is_threaded) settings_t *settings = config_get_ptr(); const char *path_menu_wallpaper = settings ? settings->paths.path_menu_wallpaper : NULL; - if (!mui || !settings) + if (!mui) return; materialui_layout(mui, is_threaded); materialui_context_bg_destroy(mui); gfx_display_allocate_white_texture(); materialui_context_reset_textures(mui); + materialui_context_reset_playlist_icons(mui); if (path_is_valid(path_menu_wallpaper)) task_push_image_load(path_menu_wallpaper, @@ -7785,21 +8066,37 @@ static void materialui_context_reset(void *data, bool is_threaded) static int materialui_environ(enum menu_environ_cb type, void *data, void *userdata) { - materialui_handle_t *mui = (materialui_handle_t*)userdata; + materialui_handle_t *mui = (materialui_handle_t*)userdata; + + if (!mui) + return -1; switch (type) { case MENU_ENVIRON_ENABLE_MOUSE_CURSOR: - if (!mui) - return -1; mui->mouse_show = true; break; case MENU_ENVIRON_DISABLE_MOUSE_CURSOR: - if (!mui) - return -1; mui->mouse_show = false; break; - case 0: + case MENU_ENVIRON_RESET_HORIZONTAL_LIST: + + /* Reset playlist icon list */ + materialui_context_destroy_playlist_icons(mui); + materialui_refresh_playlist_icon_list(mui); + materialui_context_reset_playlist_icons(mui); + + /* If we are currently viewing the playlists tab, + * the menu must be refreshed (since icon indices + * may have changed) */ + if (mui->is_playlist_tab) + { + bool refresh = false; + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); + } + + break; default: break; } @@ -9199,8 +9496,7 @@ static void materialui_list_insert( node->icon_type = MUI_ICON_TYPE_INTERNAL; break; case FILE_TYPE_PLAYLIST_COLLECTION: - node->icon_texture_index = MUI_TEXTURE_PLAYLIST; - node->icon_type = MUI_ICON_TYPE_INTERNAL; + materialui_set_node_playlist_icon(mui, node, path); break; case FILE_TYPE_RDB: node->icon_texture_index = MUI_TEXTURE_DATABASE; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index b9c8cc2368..a587265e6e 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8557,6 +8557,7 @@ unsigned menu_displaylist_build_list( { settings_t *settings = config_get_ptr(); bool menu_horizontal_animation = settings->bools.menu_horizontal_animation; + bool menu_materialui_icons_enable = settings->bools.menu_materialui_icons_enable; bool menu_materialui_show_nav_bar = settings->bools.menu_materialui_show_nav_bar; bool menu_use_preferred_system_color_theme = settings->bools.menu_use_preferred_system_color_theme; @@ -8597,6 +8598,7 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_OZONE_TRUNCATE_PLAYLIST_NAME, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_OZONE_SORT_AFTER_TRUNCATE_PLAYLIST_NAME, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_MATERIALUI_ICONS_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE, PARSE_ONLY_BOOL, false}, {MENU_ENUM_LABEL_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION, PARSE_ONLY_UINT, true}, {MENU_ENUM_LABEL_MATERIALUI_SHOW_NAV_BAR, PARSE_ONLY_BOOL, true}, {MENU_ENUM_LABEL_MATERIALUI_AUTO_ROTATE_NAV_BAR, PARSE_ONLY_BOOL, false}, @@ -8635,6 +8637,10 @@ unsigned menu_displaylist_build_list( if (menu_horizontal_animation) build_list[i].checked = true; break; + case MENU_ENUM_LABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE: + if (menu_materialui_icons_enable) + build_list[i].checked = true; + break; case MENU_ENUM_LABEL_MATERIALUI_AUTO_ROTATE_NAV_BAR: if (menu_materialui_show_nav_bar) build_list[i].checked = true; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index a19c04f00f..c505bf6e2b 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -14451,6 +14451,24 @@ static bool setting_append_list( general_write_handler, general_read_handler, SD_FLAG_ADVANCED); + (*list)[list_info->index - 1].action_ok = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_left = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = &setting_bool_action_right_with_refresh; + + CONFIG_BOOL( + list, list_info, + &settings->bools.menu_materialui_playlist_icons_enable, + MENU_ENUM_LABEL_MATERIALUI_PLAYLIST_ICONS_ENABLE, + MENU_ENUM_LABEL_VALUE_MATERIALUI_PLAYLIST_ICONS_ENABLE, + DEFAULT_MATERIALUI_PLAYLIST_ICONS_ENABLE, + 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); CONFIG_UINT( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index e5cd0138ce..45088b146f 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -532,6 +532,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_AUTO, MENU_LABEL(MATERIALUI_ICONS_ENABLE), + MENU_LABEL(MATERIALUI_PLAYLIST_ICONS_ENABLE), MENU_LABEL(MATERIALUI_SHOW_NAV_BAR), MENU_LABEL(MATERIALUI_AUTO_ROTATE_NAV_BAR), MENU_LABEL(MATERIALUI_DUAL_THUMBNAIL_LIST_VIEW_ENABLE),