From 63644ac76163ba7e7c4a32519c4dfbd0868f21c8 Mon Sep 17 00:00:00 2001 From: jdgleaver Date: Sat, 9 May 2020 14:30:49 +0100 Subject: [PATCH] (GLUI) Add desktop-style playlist view mode --- gfx/gfx_thumbnail.c | 111 +- gfx/gfx_thumbnail.h | 10 + intl/msg_hash_us.h | 4 + menu/drivers/materialui.c | 2246 +++++++++++++++++++++++++++--------- menu/drivers/ozone/ozone.c | 1 + menu/drivers/xmb.c | 1 + menu/menu_defines.h | 1 + menu/menu_setting.c | 5 + msg_hash.h | 1 + runtime_file.c | 17 +- 10 files changed, 1797 insertions(+), 600 deletions(-) diff --git a/gfx/gfx_thumbnail.c b/gfx/gfx_thumbnail.c index 6f63ac00bf..72a42cdae4 100644 --- a/gfx/gfx_thumbnail.c +++ b/gfx/gfx_thumbnail.c @@ -51,6 +51,10 @@ struct gfx_thumbnail_state /* Duration in ms of the thumbnail 'fade in' animation */ float fade_duration; + /* When true, 'fade in' animation will also be + * triggered for missing thumbnails */ + bool fade_missing; + /* Due to the asynchronous nature of thumbnail * loading, it is quite possible to trigger a load * then navigate to a different menu list before @@ -111,6 +115,17 @@ void gfx_thumbnail_set_fade_duration(float duration) duration : DEFAULT_GFX_THUMBNAIL_FADE_DURATION; } +/* Specifies whether 'fade in' animation should be + * triggered for missing thumbnails + * > When 'true', allows menu driver to animate + * any 'thumbnail unavailable' notifications */ +void gfx_thumbnail_set_fade_missing(bool fade_missing) +{ + gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr(); + + p_gfx_thumb->fade_missing = fade_missing; +} + /* Getters */ /* Fetches current streaming thumbnails request delay */ @@ -129,17 +144,63 @@ float gfx_thumbnail_get_fade_duration(void) return p_gfx_thumb->fade_duration; } +/* Fetches current enable state for missing + * thumbnail 'fade in' animations */ +bool gfx_thumbnail_get_fade_missing(bool fade_missing) +{ + gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr(); + + return p_gfx_thumb->fade_missing; +} + /* Callbacks */ +/* Initialises thumbnail 'fade in' animation */ +static void gfx_thumbnail_init_fade(gfx_thumbnail_t *thumbnail) +{ + gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr(); + + /* Sanity check */ + if (!thumbnail) + return; + + /* A 'fade in' animation is triggered if: + * - Thumbnail is available + * - Thumbnail is missing and 'fade_missing' is enabled */ + if ((thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE) || + (p_gfx_thumb->fade_missing && + (thumbnail->status == GFX_THUMBNAIL_STATUS_MISSING))) + { + if (p_gfx_thumb->fade_duration > 0.0f) + { + gfx_animation_ctx_entry_t animation_entry; + + thumbnail->alpha = 0.0f; + + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = (uintptr_t)&thumbnail->alpha; + animation_entry.duration = p_gfx_thumb->fade_duration; + animation_entry.target_value = 1.0f; + animation_entry.subject = &thumbnail->alpha; + animation_entry.cb = NULL; + animation_entry.userdata = NULL; + + gfx_animation_push(&animation_entry); + } + else + thumbnail->alpha = 1.0f; + } +} + /* Used to process thumbnail data following completion * of image load task */ static void gfx_thumbnail_handle_upload( retro_task_t *task, void *task_data, void *user_data, const char *err) { - gfx_animation_ctx_entry_t animation_entry; gfx_thumbnail_state_t *p_gfx_thumb = gfx_thumb_get_ptr(); struct texture_image *img = (struct texture_image*)task_data; gfx_thumbnail_tag_t *thumbnail_tag = (gfx_thumbnail_tag_t*)user_data; + bool fade_enabled = false; /* Sanity check */ if (!thumbnail_tag) @@ -165,6 +226,11 @@ static void gfx_thumbnail_handle_upload( * (saves a number of checks later) */ thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_MISSING; + /* If we reach this stage, thumbnail 'fade in' + * animations should be applied (based on current + * thumbnail status and global configuration) */ + fade_enabled = true; + /* Check we have a valid image */ if (!img) goto end; @@ -184,24 +250,6 @@ static void gfx_thumbnail_handle_upload( /* Update thumbnail status */ thumbnail_tag->thumbnail->status = GFX_THUMBNAIL_STATUS_AVAILABLE; - /* Trigger 'fade in' animation, if required */ - if (p_gfx_thumb->fade_duration > 0.0f) - { - 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 = p_gfx_thumb->fade_duration; - animation_entry.target_value = 1.0f; - animation_entry.subject = &thumbnail_tag->thumbnail->alpha; - animation_entry.cb = NULL; - animation_entry.userdata = NULL; - - gfx_animation_push(&animation_entry); - } - else - thumbnail_tag->thumbnail->alpha = 1.0f; - end: /* Clean up */ if (img) @@ -211,7 +259,13 @@ end: } if (thumbnail_tag) + { + /* Trigger 'fade in' animation, if required */ + if (fade_enabled) + gfx_thumbnail_init_fade(thumbnail_tag->thumbnail); + free(thumbnail_tag); + } } /* Core interface */ @@ -248,8 +302,8 @@ void gfx_thumbnail_request( bool network_on_demand_thumbnails ) { - const char *thumbnail_path = NULL; - bool has_thumbnail = false; + const char *thumbnail_path = NULL; + bool has_thumbnail = false; if (!path_data || !thumbnail) return; @@ -274,7 +328,7 @@ void gfx_thumbnail_request( (gfx_thumbnail_tag_t*)calloc(1, sizeof(gfx_thumbnail_tag_t)); if (!thumbnail_tag) - return; + goto end; /* Configure user data */ thumbnail_tag->thumbnail = thumbnail; @@ -297,11 +351,11 @@ void gfx_thumbnail_request( static char last_img_name[PATH_MAX_LENGTH] = {0}; if (!playlist) - return; + goto end; /* Get current image name */ if (!gfx_thumbnail_get_img_name(path_data, &img_name)) - return; + goto end; /* Only trigger a thumbnail download if image * name has changed since the last call of @@ -316,13 +370,13 @@ void gfx_thumbnail_request( * overheads. We can avoid this entirely with * a simple string comparison) */ if (string_is_equal(img_name, last_img_name)) - return; + goto end; strlcpy(last_img_name, img_name, sizeof(last_img_name)); /* Get system name */ if (!gfx_thumbnail_get_system(path_data, &system)) - return; + goto end; /* Trigger thumbnail download */ task_push_pl_entry_thumbnail_download( @@ -331,6 +385,11 @@ void gfx_thumbnail_request( } #endif } + +end: + /* Trigger 'fade in' animation, if required */ + if (thumbnail->status != GFX_THUMBNAIL_STATUS_PENDING) + gfx_thumbnail_init_fade(thumbnail); } /* Requests loading of a specific thumbnail image file diff --git a/gfx/gfx_thumbnail.h b/gfx/gfx_thumbnail.h index f1f89205b0..1c27041a75 100644 --- a/gfx/gfx_thumbnail.h +++ b/gfx/gfx_thumbnail.h @@ -104,6 +104,12 @@ void gfx_thumbnail_set_stream_delay(float delay); * > If 'duration' is negative, default value is set */ void gfx_thumbnail_set_fade_duration(float duration); +/* Specifies whether 'fade in' animation should be + * triggered for missing thumbnails + * > When 'true', allows menu driver to animate + * any 'thumbnail unavailable' notifications */ +void gfx_thumbnail_set_fade_missing(bool fade_missing); + /* Getters */ /* Fetches current streaming thumbnails request delay */ @@ -112,6 +118,10 @@ float gfx_thumbnail_get_stream_delay(void); /* Fetches current 'fade in' animation duration */ float gfx_thumbnail_get_fade_duration(void); +/* Fetches current enable state for missing + * thumbnail 'fade in' animations */ +bool gfx_thumbnail_get_fade_missing(bool fade_missing); + /* Core interface */ /* When called, prevents the handling of any pending diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2151b206b5..d5d77dd606 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -8107,6 +8107,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE, "List (Large)" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP, + "Desktop" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION_DISABLED, "OFF" diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index 39b1999711..601983d4d9 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -51,6 +51,7 @@ #include "../../configuration.h" #include "../../verbosity.h" #include "../../tasks/tasks_internal.h" +#include "../../runtime_file.h" /* Defines the 'device independent pixel' base * unit reference size for all UI elements. @@ -82,6 +83,7 @@ typedef struct uint32_t list_text_highlighted; uint32_t list_hint_text; uint32_t list_hint_text_highlighted; + uint32_t status_bar_text; /* Background colours */ uint32_t sys_bar_background; uint32_t title_bar_background; @@ -90,6 +92,8 @@ typedef struct uint32_t nav_bar_background; uint32_t surface_background; uint32_t thumbnail_background; + uint32_t side_bar_background; + uint32_t status_bar_background; /* List icon colours */ uint32_t list_icon; uint32_t list_switch_on; @@ -103,12 +107,14 @@ typedef struct /* Misc. colours */ uint32_t header_shadow; uint32_t landscape_border_shadow; + uint32_t status_bar_shadow; uint32_t scrollbar; uint32_t divider; uint32_t screen_fade; uint32_t missing_thumbnail_icon; float header_shadow_opacity; float landscape_border_shadow_opacity; + float status_bar_shadow_opacity; float screen_fade_opacity; } materialui_theme_t; @@ -120,6 +126,7 @@ static const materialui_theme_t materialui_theme_blue = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0x0069c0, /* sys_bar_background */ 0x2196f3, /* title_bar_background */ @@ -128,6 +135,8 @@ static const materialui_theme_t materialui_theme_blue = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xc1d5e0, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0x0069c0, /* list_icon */ 0x2196f3, /* list_switch_on */ @@ -141,12 +150,14 @@ static const materialui_theme_t materialui_theme_blue = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x0069c0, /* scrollbar */ 0x9ea7aa, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -158,6 +169,7 @@ static const materialui_theme_t materialui_theme_blue_grey = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0x34515e, /* sys_bar_background */ 0x607d8b, /* title_bar_background */ @@ -166,6 +178,8 @@ static const materialui_theme_t materialui_theme_blue_grey = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xe0e0e0, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0x34515e, /* list_icon */ 0x607d8b, /* list_switch_on */ @@ -179,12 +193,14 @@ static const materialui_theme_t materialui_theme_blue_grey = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x34515e, /* scrollbar */ 0xc2c2c2, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -196,6 +212,7 @@ static const materialui_theme_t materialui_theme_dark_blue = { 0xFFFFFF, /* list_text_highlighted */ 0x999999, /* list_hint_text */ 0xDEDEDE, /* list_hint_text_highlighted */ + 0x999999, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x1F1F1F, /* title_bar_background */ @@ -204,6 +221,8 @@ static const materialui_theme_t materialui_theme_dark_blue = { 0x242424, /* nav_bar_background */ 0x1D1D1D, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x1D1D1D, /* side_bar_background */ + 0x242424, /* status_bar_background */ /* List icon colours */ 0x90caf9, /* list_icon */ 0x64b5f6, /* list_switch_on */ @@ -217,12 +236,14 @@ static const materialui_theme_t materialui_theme_dark_blue = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x3B3B3B, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x90caf9, /* scrollbar */ 0x607d8b, /* divider */ 0x000000, /* screen_fade */ 0xDEDEDE, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -234,6 +255,7 @@ static const materialui_theme_t materialui_theme_green = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0x087f23, /* sys_bar_background */ 0x4caf50, /* title_bar_background */ @@ -242,6 +264,8 @@ static const materialui_theme_t materialui_theme_green = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xdcedc8, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0x087f23, /* list_icon */ 0x4caf50, /* list_switch_on */ @@ -255,12 +279,14 @@ static const materialui_theme_t materialui_theme_green = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x087f23, /* scrollbar */ 0xaabb97, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -272,6 +298,7 @@ static const materialui_theme_t materialui_theme_red = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0xba000d, /* sys_bar_background */ 0xf44336, /* title_bar_background */ @@ -280,6 +307,8 @@ static const materialui_theme_t materialui_theme_red = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xf8bbd0, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0xba000d, /* list_icon */ 0xf44336, /* list_switch_on */ @@ -293,12 +322,14 @@ static const materialui_theme_t materialui_theme_red = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0xba000d, /* scrollbar */ 0xbf5f82, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -310,6 +341,7 @@ static const materialui_theme_t materialui_theme_yellow = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0xc8b900, /* sys_bar_background */ 0xffeb3b, /* title_bar_background */ @@ -318,6 +350,8 @@ static const materialui_theme_t materialui_theme_yellow = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xffecb3, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0xc6a700, /* list_icon */ 0xffeb3b, /* list_switch_on */ @@ -331,12 +365,14 @@ static const materialui_theme_t materialui_theme_yellow = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0xc6a700, /* scrollbar */ 0xcbba83, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -348,6 +384,7 @@ static const materialui_theme_t materialui_theme_nvidia_shield = { 0xFFFFFF, /* list_text_highlighted */ 0x999999, /* list_hint_text */ 0xDEDEDE, /* list_hint_text_highlighted */ + 0x999999, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x1F1F1F, /* title_bar_background */ @@ -356,6 +393,8 @@ static const materialui_theme_t materialui_theme_nvidia_shield = { 0x242424, /* nav_bar_background */ 0x1D1D1D, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x1D1D1D, /* side_bar_background */ + 0x242424, /* status_bar_background */ /* List icon colours */ 0x7ab547, /* list_icon */ 0x85bb5c, /* list_switch_on */ @@ -369,12 +408,14 @@ static const materialui_theme_t materialui_theme_nvidia_shield = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x3B3B3B, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x7ab547, /* scrollbar */ 0x498515, /* divider */ 0x000000, /* screen_fade */ 0xDEDEDE, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -386,6 +427,7 @@ static const materialui_theme_t materialui_theme_materialui = { 0x000000, /* list_text_highlighted */ 0x666666, /* list_hint_text */ 0x212121, /* list_hint_text_highlighted */ + 0x000000, /* status_bar_text */ /* Background colours */ 0x3700B3, /* sys_bar_background */ 0x6200ee, /* title_bar_background */ @@ -394,6 +436,8 @@ static const materialui_theme_t materialui_theme_materialui = { 0xE1E2E1, /* nav_bar_background */ 0xFFFFFF, /* surface_background */ 0x242424, /* thumbnail_background */ + 0xe7b9ff, /* side_bar_background */ + 0x9F9FA0, /* status_bar_background */ /* List icon colours */ 0x3700B3, /* list_icon */ 0x03DAC6, /* list_switch_on */ @@ -407,12 +451,14 @@ static const materialui_theme_t materialui_theme_materialui = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x018786, /* scrollbar */ 0x018786, /* divider */ 0x000000, /* screen_fade */ 0xF5F5F6, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.35f, /* landscape_border_shadow_opacity */ + 0.45f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -424,6 +470,7 @@ static const materialui_theme_t materialui_theme_materialui_dark = { 0xFFFFFF, /* list_text_highlighted */ 0x999999, /* list_hint_text */ 0xDEDEDE, /* list_hint_text_highlighted */ + 0x999999, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x1F1F1F, /* title_bar_background */ @@ -432,6 +479,8 @@ static const materialui_theme_t materialui_theme_materialui_dark = { 0x242424, /* nav_bar_background */ 0x1D1D1D, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x1D1D1D, /* side_bar_background */ + 0x242424, /* status_bar_background */ /* List icon colours */ 0xbb86fc, /* list_icon */ 0x03DAC5, /* list_switch_on */ @@ -445,12 +494,14 @@ static const materialui_theme_t materialui_theme_materialui_dark = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x3B3B3B, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0xC89EFC, /* scrollbar */ 0x03DAC6, /* divider */ 0x000000, /* screen_fade */ 0xDEDEDE, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -462,6 +513,7 @@ static const materialui_theme_t materialui_theme_ozone_dark = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x373737, /* title_bar_background */ @@ -470,6 +522,8 @@ static const materialui_theme_t materialui_theme_ozone_dark = { 0x373737, /* nav_bar_background */ 0x333333, /* surface_background */ 0x0B0B0B, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x191919, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0x00FFC5, /* list_switch_on */ @@ -483,12 +537,14 @@ static const materialui_theme_t materialui_theme_ozone_dark = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x9F9F9F, /* scrollbar */ 0xFFFFFF, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -500,6 +556,7 @@ static const materialui_theme_t materialui_theme_nord = { 0xECEFF4, /* list_text_highlighted */ 0x93E5CC, /* list_hint_text */ 0x93E5CC, /* list_hint_text_highlighted */ + 0x93E5CC, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x4C566A, /* title_bar_background */ @@ -508,6 +565,8 @@ static const materialui_theme_t materialui_theme_nord = { 0x3B4252, /* nav_bar_background */ 0x3B4252, /* surface_background */ 0x0B0B0B, /* thumbnail_background */ + 0x3f444f, /* side_bar_background */ + 0x191D23, /* status_bar_background */ /* List icon colours */ 0xD8DEE9, /* list_icon */ 0xA3BE8C, /* list_switch_on */ @@ -521,12 +580,14 @@ static const materialui_theme_t materialui_theme_nord = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0xA0A5AD, /* scrollbar */ 0x81A1C1, /* divider */ 0x000000, /* screen_fade */ 0xD8DEE9, /* missing_thumbnail_icon */ 0.4f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -538,6 +599,7 @@ static const materialui_theme_t materialui_theme_gruvbox_dark = { 0xFBF1C7, /* list_text_highlighted */ 0xD79921, /* list_hint_text */ 0xFABD2F, /* list_hint_text_highlighted */ + 0xD79921, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x504945, /* title_bar_background */ @@ -546,6 +608,8 @@ static const materialui_theme_t materialui_theme_gruvbox_dark = { 0x1D2021, /* nav_bar_background */ 0x32302F, /* surface_background */ 0x0B0B0B, /* thumbnail_background */ + 0x3C3836, /* side_bar_background */ + 0x161616, /* status_bar_background */ /* List icon colours */ 0xA89984, /* list_icon */ 0xB8BB26, /* list_switch_on */ @@ -559,12 +623,14 @@ static const materialui_theme_t materialui_theme_gruvbox_dark = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x7C6F64, /* scrollbar */ 0xD5C4A1, /* divider */ 0x000000, /* screen_fade */ 0xA89984, /* missing_thumbnail_icon */ 0.4f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -576,6 +642,7 @@ static const materialui_theme_t materialui_theme_solarized_dark = { 0x93A1A1, /* list_text_highlighted */ 0x2AA198, /* list_hint_text */ 0x2AA198, /* list_hint_text_highlighted */ + 0x2AA198, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x053542, /* title_bar_background */ @@ -584,6 +651,8 @@ static const materialui_theme_t materialui_theme_solarized_dark = { 0x003541, /* nav_bar_background */ 0x073642, /* surface_background */ 0x0B0B0B, /* thumbnail_background */ + 0x073642, /* side_bar_background */ + 0x00181E, /* status_bar_background */ /* List icon colours */ 0x657B83, /* list_icon */ 0x859900, /* list_switch_on */ @@ -597,12 +666,14 @@ static const materialui_theme_t materialui_theme_solarized_dark = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x586E75, /* scrollbar */ 0x2AA198, /* divider */ 0x000000, /* screen_fade */ 0x657B83, /* missing_thumbnail_icon */ 0.4f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.8f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -614,6 +685,7 @@ static const materialui_theme_t materialui_theme_cutie_blue = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -622,6 +694,8 @@ static const materialui_theme_t materialui_theme_cutie_blue = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0x3399FF, /* list_switch_on */ @@ -635,12 +709,14 @@ static const materialui_theme_t materialui_theme_cutie_blue = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -652,6 +728,7 @@ static const materialui_theme_t materialui_theme_cutie_cyan = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -660,6 +737,8 @@ static const materialui_theme_t materialui_theme_cutie_cyan = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0x39859A, /* list_switch_on */ @@ -673,12 +752,14 @@ static const materialui_theme_t materialui_theme_cutie_cyan = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -690,6 +771,7 @@ static const materialui_theme_t materialui_theme_cutie_green = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -698,6 +780,8 @@ static const materialui_theme_t materialui_theme_cutie_green = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0x23A367, /* list_switch_on */ @@ -711,12 +795,14 @@ static const materialui_theme_t materialui_theme_cutie_green = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -728,6 +814,7 @@ static const materialui_theme_t materialui_theme_cutie_orange = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -736,6 +823,8 @@ static const materialui_theme_t materialui_theme_cutie_orange = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0xCE6E1F, /* list_switch_on */ @@ -749,12 +838,14 @@ static const materialui_theme_t materialui_theme_cutie_orange = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -766,6 +857,7 @@ static const materialui_theme_t materialui_theme_cutie_pink = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -774,6 +866,8 @@ static const materialui_theme_t materialui_theme_cutie_pink = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0xD16FD8, /* list_switch_on */ @@ -787,12 +881,14 @@ static const materialui_theme_t materialui_theme_cutie_pink = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -804,6 +900,7 @@ static const materialui_theme_t materialui_theme_cutie_purple = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -812,6 +909,8 @@ static const materialui_theme_t materialui_theme_cutie_purple = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0x814FFF, /* list_switch_on */ @@ -825,12 +924,14 @@ static const materialui_theme_t materialui_theme_cutie_purple = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -842,6 +943,7 @@ static const materialui_theme_t materialui_theme_cutie_red = { 0xFFFFFF, /* list_text_highlighted */ 0xDADADA, /* list_hint_text */ 0xEEEEEE, /* list_hint_text_highlighted */ + 0xDADADA, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x353535, /* title_bar_background */ @@ -850,6 +952,8 @@ static const materialui_theme_t materialui_theme_cutie_red = { 0x282828, /* nav_bar_background */ 0x333333, /* surface_background */ 0x000000, /* thumbnail_background */ + 0x333333, /* side_bar_background */ + 0x0E0E0E, /* status_bar_background */ /* List icon colours */ 0xFFFFFF, /* list_icon */ 0xCB1619, /* list_switch_on */ @@ -863,12 +967,14 @@ static const materialui_theme_t materialui_theme_cutie_red = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0x727272, /* scrollbar */ 0x727272, /* divider */ 0x000000, /* screen_fade */ 0xDADADA, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.9f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -880,6 +986,7 @@ static const materialui_theme_t materialui_theme_virtual_boy = { 0xF00000, /* list_text_highlighted */ 0xE60000, /* list_hint_text */ 0xF00000, /* list_hint_text_highlighted */ + 0xE60000, /* status_bar_text */ /* Background colours */ 0x000000, /* sys_bar_background */ 0x350000, /* title_bar_background */ @@ -888,6 +995,8 @@ static const materialui_theme_t materialui_theme_virtual_boy = { 0x350000, /* nav_bar_background */ 0x400000, /* surface_background */ 0x250000, /* thumbnail_background */ + 0x400000, /* side_bar_background */ + 0x000000, /* status_bar_background */ /* List icon colours */ 0xE60000, /* list_icon */ 0xE60000, /* list_switch_on */ @@ -901,12 +1010,14 @@ static const materialui_theme_t materialui_theme_virtual_boy = { /* Misc. colours */ 0x000000, /* header_shadow */ 0x000000, /* landscape_border_shadow */ + 0x000000, /* status_bar_shadow */ 0xA10000, /* scrollbar */ 0xE60000, /* divider */ 0x000000, /* screen_fade */ 0xE60000, /* missing_thumbnail_icon */ 0.3f, /* header_shadow_opacity */ 0.45f, /* landscape_border_shadow_opacity */ + 0.7f, /* status_bar_shadow_opacity */ 0.75f /* screen_fade_opacity */ }; @@ -919,6 +1030,7 @@ typedef struct uint32_t list_text_highlighted; uint32_t list_hint_text; uint32_t list_hint_text_highlighted; + uint32_t status_bar_text; /* Background colours */ float sys_bar_background[16]; float title_bar_background[16]; @@ -927,6 +1039,8 @@ typedef struct float nav_bar_background[16]; float surface_background[16]; float thumbnail_background[16]; + float side_bar_background[16]; + float status_bar_background[16]; /* System bar + header icon colours */ float sys_bar_icon[16]; float header_icon[16]; @@ -944,11 +1058,14 @@ typedef struct float header_shadow[16]; float landscape_border_shadow_left[16]; float landscape_border_shadow_right[16]; + float status_bar_shadow[16]; float scrollbar[16]; float divider[16]; + float entry_divider[16]; float screen_fade[16]; float missing_thumbnail_icon[16]; float landscape_border_shadow_opacity; + float status_bar_shadow_opacity; float screen_fade_opacity; } materialui_colors_t; @@ -995,7 +1112,8 @@ enum materialui_list_view_type MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL, MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM, MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE, - MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON + MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON, + MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP }; /* This structure holds auxiliary information for @@ -1005,8 +1123,10 @@ typedef struct { bool has_icon; unsigned icon_texture_index; + float entry_width; float entry_height; float text_height; + float x; float y; /* Thumbnail containers */ @@ -1091,6 +1211,18 @@ enum MUI_TEXTURE_LAST }; +/* This structure holds all runtime parameters + * associated with landscape optimisation + * (enable state, border width, nominal + * additional horizontal margin/padding for + * menu entries) */ +typedef struct +{ + bool enabled; + unsigned border_width; + unsigned entry_margin; +} materialui_landscape_optimization_t; + /* Maximum number of menu tabs that can be shown on * the navigation bar */ #define MUI_NAV_BAR_NUM_MENU_TABS_MAX 3 @@ -1214,6 +1346,25 @@ typedef struct int timedate_width; } materialui_sys_bar_cache_t; +/* This structure holds all runtime parameters + * for the status bar (used to display auxiliary + * information at the bottom of the current list + * view). At present, this enables metadata for + * the currently selected playlist entry to be + * shown when using the 'desktop'-layout */ +typedef struct +{ + bool enabled; + bool cached; + size_t last_selected; + float delay_timer; + float alpha; + unsigned height; + char str[MENU_SUBLABEL_MAX_LENGTH]; + char runtime_fallback_str[255]; + char last_played_fallback_str[255]; +} materialui_status_bar_t; + /* Animation defines */ #define MUI_ANIM_DURATION_SCROLL 166.66667f #define MUI_ANIM_DURATION_SCROLL_RESET 83.333333f @@ -1256,6 +1407,8 @@ typedef struct materialui_handle enum materialui_landscape_layout_optimization_type last_landscape_layout_optimization; + materialui_landscape_optimization_t + landscape_optimization; playlist_t *playlist; @@ -1273,7 +1426,6 @@ typedef struct materialui_handle unsigned sys_bar_icon_size; unsigned margin; unsigned sys_bar_margin; - unsigned landscape_entry_margin; unsigned entry_divider_width; unsigned sublabel_gap; unsigned sublabel_padding; @@ -1363,6 +1515,9 @@ typedef struct materialui_handle float fullscreen_thumbnail_alpha; char fullscreen_thumbnail_label[255]; + /* Status bar */ + materialui_status_bar_t status_bar; + enum materialui_list_view_type list_view_type; } materialui_handle_t; @@ -1442,6 +1597,7 @@ static void materialui_prepare_colors( mui->colors.list_text_highlighted = (current_theme->list_text_highlighted << 8) | 0xFF; mui->colors.list_hint_text = (current_theme->list_hint_text << 8) | 0xFF; mui->colors.list_hint_text_highlighted = (current_theme->list_hint_text_highlighted << 8) | 0xFF; + mui->colors.status_bar_text = (current_theme->status_bar_text << 8) | 0xFF; /* > Background colours */ hex32_to_rgba_normalized( @@ -1465,6 +1621,12 @@ static void materialui_prepare_colors( hex32_to_rgba_normalized( current_theme->thumbnail_background, mui->colors.thumbnail_background, 1.0f); + hex32_to_rgba_normalized( + current_theme->side_bar_background, + mui->colors.side_bar_background, 1.0f); + hex32_to_rgba_normalized( + current_theme->status_bar_background, + mui->colors.status_bar_background, 1.0f); /* > System bar + header icon colours */ hex32_to_rgba_normalized( @@ -1512,12 +1674,18 @@ static void materialui_prepare_colors( hex32_to_rgba_normalized( current_theme->landscape_border_shadow, mui->colors.landscape_border_shadow_right, 0.0f); + hex32_to_rgba_normalized( + current_theme->status_bar_shadow, + mui->colors.status_bar_shadow, 0.0f); hex32_to_rgba_normalized( current_theme->scrollbar, mui->colors.scrollbar, 1.0f); hex32_to_rgba_normalized( current_theme->divider, mui->colors.divider, 1.0f); + hex32_to_rgba_normalized( + current_theme->divider, + mui->colors.entry_divider, 1.0f); hex32_to_rgba_normalized( current_theme->missing_thumbnail_icon, mui->colors.missing_thumbnail_icon, 1.0f); @@ -1539,6 +1707,9 @@ static void materialui_prepare_colors( mui->colors.landscape_border_shadow_right[3] = current_theme->landscape_border_shadow_opacity; mui->colors.landscape_border_shadow_right[11] = current_theme->landscape_border_shadow_opacity; mui->colors.landscape_border_shadow_opacity = current_theme->landscape_border_shadow_opacity; + mui->colors.status_bar_shadow[11] = current_theme->status_bar_shadow_opacity; + mui->colors.status_bar_shadow[15] = current_theme->status_bar_shadow_opacity; + mui->colors.status_bar_shadow_opacity = current_theme->status_bar_shadow_opacity; } static const char *materialui_texture_path(unsigned id) @@ -1684,6 +1855,31 @@ static const char *materialui_texture_path(unsigned id) return NULL; } +static void INLINE materialui_font_bind(materialui_font_data_t *font_data) +{ + font_driver_bind_block(font_data->font, &font_data->raster_block); + font_data->raster_block.carr.coords.vertices = 0; +} + +static void INLINE materialui_font_unbind(materialui_font_data_t *font_data) +{ + font_driver_bind_block(font_data->font, NULL); +} + +void materialui_font_flush( + unsigned video_width, unsigned video_height, + materialui_font_data_t *font_data) +{ + /* Flushing is slow - only do it if font + * has actually been used */ + if (!font_data || + (font_data->raster_block.carr.coords.vertices == 0)) + return; + + font_driver_flush(video_width, video_height, font_data->font); + font_data->raster_block.carr.coords.vertices = 0; +} + static void materialui_context_reset_textures(materialui_handle_t *mui) { bool has_all_assets = true; @@ -1722,7 +1918,6 @@ static void materialui_draw_icon( unsigned icon_size, uintptr_t texture, float x, float y, - unsigned width, unsigned height, float rotation, float scale_factor, float *color) { @@ -1749,7 +1944,7 @@ static void materialui_draw_icon( coords.color = (const float*)color; draw.x = x; - draw.y = height - y - icon_size; + draw.y = video_height - y - icon_size; draw.width = icon_size; draw.height = icon_size; draw.scale_factor = scale_factor; @@ -1772,7 +1967,6 @@ static void materialui_draw_thumbnail( unsigned video_width, unsigned video_height, float x, float y, - unsigned width, unsigned height, float scale_factor) { float bg_x; @@ -1797,6 +1991,10 @@ static void materialui_draw_thumbnail( case GFX_THUMBNAIL_STATUS_MISSING: { float icon_size = (float)mui->icon_size; + float alpha = mui->transition_alpha * thumbnail->alpha; + + if (alpha <= 0.0f) + return; /* Adjust icon size based on scale factor */ if (scale_factor < 1.0f) @@ -1804,8 +2002,7 @@ static void materialui_draw_thumbnail( /* Background */ gfx_display_set_alpha( - mui->colors.thumbnail_background, - mui->transition_alpha); + mui->colors.thumbnail_background, alpha); gfx_display_draw_quad( userdata, @@ -1815,11 +2012,14 @@ static void materialui_draw_thumbnail( bg_y, (unsigned)bg_width, (unsigned)bg_height, - width, - height, + video_width, + video_height, mui->colors.thumbnail_background); /* Icon */ + gfx_display_set_alpha( + mui->colors.missing_thumbnail_icon, alpha); + materialui_draw_icon( userdata, video_width, @@ -1828,8 +2028,6 @@ static void materialui_draw_thumbnail( 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.missing_thumbnail_icon); @@ -1872,8 +2070,8 @@ static void materialui_draw_thumbnail( (int)bg_y, (unsigned)(bg_width + 0.5f), (unsigned)(bg_height + 1.5f), - width, - height, + video_width, + video_height, mui->colors.thumbnail_background); } @@ -1990,23 +2188,13 @@ end: string_list_free(list); } -/* Used for the sublabels */ -static unsigned materialui_count_lines(const char *str) -{ - unsigned c = 0; - unsigned lines = 1; - - for (c = 0; str[c]; c++) - lines += (str[c] == '\n'); - return lines; -} - /* Initialises scrollbar parameters (width/height) */ static void materialui_scrollbar_init( materialui_handle_t* mui, unsigned width, unsigned height, unsigned header_height) { - int view_height = (int)height - (int)header_height - (int)mui->nav_bar_layout_height; + int view_height = (int)height - (int)header_height - + (int)mui->nav_bar_layout_height - (int)mui->status_bar.height; int scrollbar_height; /* Set initial defaults */ @@ -2044,56 +2232,149 @@ static void materialui_scrollbar_init( * set elsewhere */ } +/* ============================== + * materialui_compute_entries_box() START + * ============================== */ + /* Calculate physical size of each menu entry, plus * any derived screen dimensions of menu list elements */ -static void materialui_compute_entries_box( + +/* Utility functions */ + +/* > Returns number of lines in a string */ +static unsigned materialui_count_lines(const char *str) +{ + unsigned c = 0; + unsigned lines = 1; + + for (c = 0; str[c]; c++) + lines += (str[c] == '\n'); + return lines; +} + +/* > Returns number of lines required to display + * the sublabel of entry 'entry_idx' */ +static unsigned materialui_count_sublabel_lines( + materialui_handle_t* mui, int usable_width, + size_t entry_idx, bool has_icon) +{ + menu_entry_t entry; + char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH]; + const char *sublabel_str = NULL; + int sublabel_width_max = 0; + + wrapped_sublabel_str[0] = '\0'; + + /* Get entry sublabel */ + menu_entry_init(&entry); + entry.path_enabled = false; + entry.label_enabled = false; + entry.rich_label_enabled = false; + entry.value_enabled = false; + menu_entry_get(&entry, 0, entry_idx, NULL, true); + + menu_entry_get_sublabel(&entry, &sublabel_str); + + /* If sublabel is empty, return immediately */ + if (string_is_empty(sublabel_str)) + return 0; + + /* Wrap sublabel string to fit available width */ + sublabel_width_max = usable_width - (int)mui->sublabel_padding - + (has_icon ? (int)mui->icon_size : 0); + + word_wrap( + wrapped_sublabel_str, sublabel_str, + sublabel_width_max / (int)mui->font_data.hint.glyph_width, + false, 0); + + /* Return number of lines in wrapped string */ + return materialui_count_lines(wrapped_sublabel_str); +} + +/* Used for standard, non-playlist entries + * > MUI_LIST_VIEW_DEFAULT */ +static void materialui_compute_entries_box_default( materialui_handle_t* mui, unsigned width, unsigned height, unsigned header_height) { - unsigned i; - int usable_width = - (int)width - (int)(mui->margin * 2) - (int)(mui->landscape_entry_margin * 2) - (int)mui->nav_bar_layout_width; - file_list_t *list = menu_entries_get_selection_buf_ptr(0); - float sum = 0; - size_t entries_end = menu_entries_get_size(); - - /* If this is a playlist and the 'dual icon' - * thumbnail view mode is enabled, each node has - * a fixed height - and we can skip all the - * usual calculations */ - if (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON) - { - /* One line of list text */ - float node_text_height = (float)mui->font_data.list.line_height; - /* List text + thumbnail height + padding */ - float node_entry_height = - node_text_height + (float)mui->thumbnail_height_max + - ((float)mui->dip_base_unit_size / 5.0f); - - for (i = 0; i < entries_end; i++) - { - materialui_node_t *node = (materialui_node_t*) - file_list_get_userdata_at_offset(list, i); - - node->text_height = node_text_height; - node->entry_height = node_entry_height; - node->y = sum; - sum += node_entry_height; - } - - mui->content_height = sum; - - /* Total height is now known - can initialise scrollbar */ - materialui_scrollbar_init(mui, width, height, header_height); + size_t i; + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + float node_entry_width = (float)width - + (float)(mui->landscape_optimization.border_width * 2) - + (float)mui->nav_bar_layout_width; + float node_x = (float)mui->landscape_optimization.border_width; + int usable_width = node_entry_width - + (int)(mui->margin * 2) - + (int)(mui->landscape_optimization.entry_margin * 2); + float sum = 0; + if (!list) return; + + for (i = 0; i < menu_entries_get_size(); i++) + { + unsigned num_sublabel_lines = 0; + materialui_node_t *node = (materialui_node_t*) + file_list_get_userdata_at_offset(list, i); + + if (!node) + continue; + + num_sublabel_lines = materialui_count_sublabel_lines( + mui, usable_width, i, + (node->has_icon && mui->textures.list[node->icon_texture_index])); + + node->text_height = mui->font_data.list.line_height + + (num_sublabel_lines * mui->font_data.hint.line_height); + + node->entry_height = node->text_height + + mui->dip_base_unit_size / 10; + + node->entry_height += mui->dip_base_unit_size / 10; + node->y = sum; + + node->entry_width = node_entry_width; + node->x = node_x; + + sum += node->entry_height; } - /* If this is a playlist and any other thumbnail view - * mode is enabled, must reduce usable width by - * thumbnail width */ - if ((mui->list_view_type != MUI_LIST_VIEW_DEFAULT) && - (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST)) + mui->content_height = sum; + + /* Total height is now known - can initialise scrollbar */ + materialui_scrollbar_init(mui, width, height, header_height); +} + +/* 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_compute_entries_box_playlist_list( + materialui_handle_t* mui, + unsigned width, unsigned height, unsigned header_height) +{ + size_t i; + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + float node_entry_width = (float)width - + (float)(mui->landscape_optimization.border_width * 2) - + (float)mui->nav_bar_layout_width; + float node_x = (float)mui->landscape_optimization.border_width; + int usable_width = node_entry_width - (int)(mui->margin * 2); + float sum = 0; + + if (!list) + return; + + /* If thumbnails are *not* enabled, decrease usable + * width by landscape optimisation entry margin */ + if (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST) + usable_width -= (int)(mui->landscape_optimization.entry_margin * 2); + /* If a thumbnail view mode is enabled, must reduce + * usable width by thumbnail width */ + else { int thumbnail_margin = 0; @@ -2105,60 +2386,26 @@ static void materialui_compute_entries_box( } /* Account for additional padding in landscape mode */ else - if (mui->landscape_entry_margin < mui->margin) - thumbnail_margin = (int)(mui->margin - mui->landscape_entry_margin); + thumbnail_margin = (int)mui->margin; usable_width -= mui->thumbnail_width_max + thumbnail_margin; /* Account for second thumbnail, if enabled */ if (mui->secondary_thumbnail_enabled) usable_width -= mui->thumbnail_width_max + thumbnail_margin; - /* If there is no second thumbnail and landscape - * optimisations are active, increase width to - * balance x offset of primary thumbnail */ - else if (mui->landscape_entry_margin > 0) - usable_width += (mui->margin > mui->landscape_entry_margin) ? - (int)mui->landscape_entry_margin : (int)mui->margin; } - for (i = 0; i < entries_end; i++) + for (i = 0; i < menu_entries_get_size(); i++) { - menu_entry_t entry; - char wrapped_sublabel_str[MENU_SUBLABEL_MAX_LENGTH]; - 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); + 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'; + if (!node) + continue; - menu_entry_init(&entry); - entry.path_enabled = false; - entry.label_enabled = false; - entry.rich_label_enabled = false; - entry.value_enabled = false; - menu_entry_get(&entry, 0, i, NULL, true); - - menu_entry_get_sublabel(&entry, &sublabel_str); - - if (!string_is_empty(sublabel_str)) - { - int sublabel_width_max = usable_width - (int)mui->sublabel_padding; - - /* If this is a default menu list with an icon, - * must subtract icon size from sublabel width */ - if (mui->list_view_type == MUI_LIST_VIEW_DEFAULT) - if (node->has_icon) - if (mui->textures.list[node->icon_texture_index]) - sublabel_width_max -= (int)mui->icon_size; - - word_wrap( - wrapped_sublabel_str, sublabel_str, - sublabel_width_max / (int)mui->font_data.hint.glyph_width, - false, 0); - - num_sublabel_lines = materialui_count_lines(wrapped_sublabel_str); - } + num_sublabel_lines = materialui_count_sublabel_lines( + mui, usable_width, i, false); node->text_height = mui->font_data.list.line_height + (num_sublabel_lines * mui->font_data.hint.line_height); @@ -2166,16 +2413,19 @@ static void materialui_compute_entries_box( 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)) + /* If thumbnails are enabled, must ensure + * that line_height is greater than maximum + * thumbnail height */ + if (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; + + node->entry_width = node_entry_width; + node->x = node_x; + sum += node->entry_height; } @@ -2185,6 +2435,126 @@ static void materialui_compute_entries_box( materialui_scrollbar_init(mui, width, height, header_height); } +/* Used for playlist 'dual icon' entries + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON */ +static void materialui_compute_entries_box_playlist_dual_icon( + materialui_handle_t* mui, + unsigned width, unsigned height, unsigned header_height) +{ + size_t i; + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + float node_entry_width = (float)width - + (float)(mui->landscape_optimization.border_width * 2) - + (float)mui->nav_bar_layout_width; + float node_x = (float)mui->landscape_optimization.border_width; + /* Entry height is constant: + * > One line of list text */ + float node_text_height = (float)mui->font_data.list.line_height; + /* > List text + thumbnail height + padding */ + float node_entry_height = node_text_height + + (float)mui->thumbnail_height_max + + ((float)mui->dip_base_unit_size / 5.0f); + float sum = 0; + + if (!list) + return; + + for (i = 0; i < menu_entries_get_size(); i++) + { + materialui_node_t *node = (materialui_node_t*) + file_list_get_userdata_at_offset(list, i); + + if (!node) + continue; + + node->text_height = node_text_height; + node->entry_width = node_entry_width; + node->entry_height = node_entry_height; + node->x = node_x; + node->y = sum; + sum += node_entry_height; + } + + mui->content_height = sum; + + /* Total height is now known - can initialise scrollbar */ + materialui_scrollbar_init(mui, width, height, header_height); +} + +/* Used for playlist 'desktop'-layout entries + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP */ +static void materialui_compute_entries_box_playlist_desktop( + materialui_handle_t* mui, + unsigned width, unsigned height, unsigned header_height) +{ + size_t i; + file_list_t *list = menu_entries_get_selection_buf_ptr(0); + /* Entry width is available screen width minus + * thumbnail sidebar + * > Note: If landscape optimisations are enabled, + * need to allow space for a second divider at + * the left hand edge of the sidebar */ + float node_entry_width = (float)width - + (float)(mui->landscape_optimization.border_width * 2) - + (float)mui->nav_bar_layout_width - + (float)mui->thumbnail_width_max - + (float)(mui->margin * 2) - + (float)(mui->entry_divider_width * + (mui->landscape_optimization.enabled ? + 2 : 1)); + /* Entry x position is the right hand edge of + * the thumbnail sidebar */ + float node_x = (float)mui->landscape_optimization.border_width + + (float)mui->thumbnail_width_max + (float)(mui->margin * 2) + + (float)(mui->entry_divider_width * + (mui->landscape_optimization.enabled ? + 2 : 1)); + /* Entry height: + * > One line of list text */ + float node_text_height = (float)mui->font_data.list.line_height; + /* > List text + padding + * Note: Since this is intended for the desktop, + * use less padding than normal to increase list + * density (each entry will still be large enough + * to select with a finger via touchscreen, but + * this is optimised for gamepad/keyboard) */ + float node_entry_height = node_text_height + + ((float)mui->dip_base_unit_size / 7.0f); + float sum = 0; + + if (!list) + return; + + for (i = 0; i < menu_entries_get_size(); i++) + { + materialui_node_t *node = (materialui_node_t*) + file_list_get_userdata_at_offset(list, i); + + if (!node) + continue; + + node->text_height = node_text_height; + node->entry_width = node_entry_width; + node->entry_height = node_entry_height; + node->x = node_x; + node->y = sum; + sum += node_entry_height; + } + + mui->content_height = sum; + + /* Total height is now known - can initialise scrollbar */ + materialui_scrollbar_init(mui, width, height, header_height); +} + +static void (*materialui_compute_entries_box)( + materialui_handle_t* mui, + unsigned width, unsigned height, unsigned header_height) = materialui_compute_entries_box_default; + +/* ============================== + * materialui_compute_entries_box() END + * ============================== */ + /* Compute the scroll value depending on the highlighted entry */ static float materialui_get_scroll(materialui_handle_t *mui) { @@ -2207,8 +2577,8 @@ static float materialui_get_scroll(materialui_handle_t *mui) /* Get the vertical midpoint of the actual * list view - i.e. account for header + * navigation bar */ - view_centre = - (float)(height - header_height - mui->nav_bar_layout_height) / 2.0f; + view_centre = (float)(height - header_height - mui->nav_bar_layout_height - + mui->status_bar.height) / 2.0f; /* Get the vertical midpoint of the currently * selected entry */ @@ -2313,12 +2683,284 @@ static INLINE void materialui_kill_scroll_animation( mui->scroll_animation_selection = 0; } +/* ============================== + * materialui_render_process_entry() START + * ============================== */ + +/* Handles any auxiliary entry-specific processing + * required during the per-frame 'materialui_render()' + * task. This typically involves the loading/unloading + * of playlist thumbnails. + * > Should be called within a loop over all entries + * in the current menu list + * > If return value is false, loop should break + * (i.e. indicates that last entry to be processed + * has been found) */ + +/* Used for non-playlist menus, and playlists + * without thumbnails (i.e. any list without + * thumbnails) + * > MUI_LIST_VIEW_DEFAULT + * > MUI_LIST_VIEW_PLAYLIST + * Returns false when the last on-screen entry + * is detected */ +static bool materialui_render_process_entry_default( + materialui_handle_t* mui, + materialui_node_t *node, + size_t entry_idx, size_t selection, + bool first_entry_found, bool last_entry_found, + unsigned thumbnail_upscale_threshold, + bool network_on_demand_thumbnails) +{ + /* 'Normal' menu lists require no entry-specific + * processing */ + return !last_entry_found; +} + +/* Used for 'list view' playlists *with* + * thumbnails + * > MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL + * > MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM + * > MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE + * Always returns true */ +static bool materialui_render_process_entry_playlist_thumb_list( + materialui_handle_t* mui, + materialui_node_t *node, + size_t entry_idx, size_t selection, + bool first_entry_found, bool last_entry_found, + unsigned thumbnail_upscale_threshold, + bool network_on_demand_thumbnails) +{ + bool on_screen = first_entry_found && !last_entry_found; + + /* Load thumbnails for all on-screen entries + * and free thumbnails for all off-screen entries */ + if (mui->secondary_thumbnail_enabled) + gfx_thumbnail_process_streams( + mui->thumbnail_path_data, mui->playlist, entry_idx, + &node->thumbnails.primary, &node->thumbnails.secondary, + on_screen, + thumbnail_upscale_threshold, + network_on_demand_thumbnails); + else + gfx_thumbnail_process_stream( + mui->thumbnail_path_data, GFX_THUMBNAIL_RIGHT, + mui->playlist, entry_idx, &node->thumbnails.primary, + on_screen, + thumbnail_upscale_threshold, + network_on_demand_thumbnails); + + /* Always return true - every entry must + * be processed */ + return true; +} + +/* Used for 'dual icon' playlists + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON + * Always returns true */ +static bool materialui_render_process_entry_playlist_dual_icon( + materialui_handle_t* mui, + materialui_node_t *node, + size_t entry_idx, size_t selection, + bool first_entry_found, bool last_entry_found, + unsigned thumbnail_upscale_threshold, + bool network_on_demand_thumbnails) +{ + bool on_screen = first_entry_found && !last_entry_found; + + /* Load thumbnails for all on-screen entries + * and free thumbnails for all off-screen entries + * > Note that secondary thumbnail is force + * enabled in dual icon mode */ + gfx_thumbnail_process_streams( + mui->thumbnail_path_data, mui->playlist, entry_idx, + &node->thumbnails.primary, &node->thumbnails.secondary, + on_screen, + thumbnail_upscale_threshold, + network_on_demand_thumbnails); + + /* Always return true - every entry must + * be processed */ + return true; +} + +/* Used for 'desktop'-layout playlists + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP + * Always returns true */ +static bool materialui_render_process_entry_playlist_desktop( + materialui_handle_t* mui, + materialui_node_t *node, + size_t entry_idx, size_t selection, + bool first_entry_found, bool last_entry_found, + unsigned thumbnail_upscale_threshold, + bool network_on_demand_thumbnails) +{ + bool is_selected = (entry_idx == selection); + + /* Load thumbnails for selected entry and free + * thumbnails for all other entries + * > Note that secondary thumbnail is force + * enabled */ + gfx_thumbnail_process_streams( + mui->thumbnail_path_data, mui->playlist, entry_idx, + &node->thumbnails.primary, &node->thumbnails.secondary, + is_selected, + thumbnail_upscale_threshold, + network_on_demand_thumbnails); + + /* Fetch metadata for selected entry */ + if (is_selected && mui->status_bar.enabled) + { + gfx_animation_ctx_tag alpha_tag = (uintptr_t)&mui->status_bar.alpha; + + /* Reset metadata if current selection + * has changed */ + if (selection != mui->status_bar.last_selected) + { + gfx_animation_kill_by_tag(&alpha_tag); + + mui->status_bar.cached = false; + mui->status_bar.last_selected = selection; + mui->status_bar.delay_timer = 0.0f; + mui->status_bar.alpha = 0.0f; + mui->status_bar.str[0] = '\0'; + } + + /* Check whether metadata needs to be cached */ + if (!mui->status_bar.cached) + { + /* Check if delay timer has elapsed */ + mui->status_bar.delay_timer += gfx_animation_get_delta_time(); + + if (mui->status_bar.delay_timer > gfx_thumbnail_get_stream_delay()) + { + settings_t *settings = config_get_ptr(); + bool content_runtime_log = settings->bools.content_runtime_log; + bool content_runtime_log_aggregate = settings->bools.content_runtime_log_aggregate; + const char *directory_runtime_log = settings->paths.directory_runtime_log; + const char *directory_playlist = settings->paths.directory_playlist; + unsigned runtime_type = settings->uints.playlist_sublabel_runtime_type; + enum playlist_sublabel_last_played_style_type + runtime_last_played_style = + (enum playlist_sublabel_last_played_style_type) + settings->uints.playlist_sublabel_last_played_style; + float fade_duration = gfx_thumbnail_get_fade_duration(); + const struct playlist_entry *entry = NULL; + const char *core_name = NULL; + const char *runtime_str = NULL; + const char *last_played_str = NULL; + int n; + + /* Read playlist entry */ + playlist_get_index(mui->playlist, selection, &entry); + + /* Sanity check */ + if (!entry) + { + /* If this happens, then everything is + * broken - just ensure that metadata + * string is NUL and set cached status + * to 'true' to avoid reading this + * broken playlist again... */ + mui->status_bar.str[0] = '\0'; + mui->status_bar.cached = true; + return true; + } + + /* Get core name */ + if (string_is_empty(entry->core_name) || + string_is_equal(entry->core_name, "DETECT")) + core_name = msg_hash_to_str(MSG_AUTODETECT); + else + core_name = entry->core_name; + + /* Get runtime info, if available */ + if (content_runtime_log || content_runtime_log_aggregate) + { + if (entry->runtime_status == PLAYLIST_RUNTIME_UNKNOWN) + runtime_update_playlist( + mui->playlist, selection, + directory_runtime_log, + directory_playlist, + (runtime_type == PLAYLIST_RUNTIME_PER_CORE), + runtime_last_played_style); + + if (!string_is_empty(entry->runtime_str)) + runtime_str = entry->runtime_str; + + if (!string_is_empty(entry->last_played_str)) + last_played_str = entry->last_played_str; + } + + /* Set fallback strings, if required */ + if (string_is_empty(runtime_str)) + runtime_str = mui->status_bar.runtime_fallback_str; + + if (string_is_empty(last_played_str)) + last_played_str = mui->status_bar.last_played_fallback_str; + + /* Generate metadata string */ + n = snprintf(mui->status_bar.str, sizeof(mui->status_bar.str), + "%s %s%s%s%s%s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_CORE), + core_name, + MUI_TICKER_SPACER, + runtime_str, + MUI_TICKER_SPACER, + last_played_str); + + if ((n < 0) || (n >= 255)) + n = 0; /* Silence GCC warnings... */ + + /* All metadata is cached */ + mui->status_bar.cached = true; + + /* Trigger fade in animation */ + if (fade_duration > 0.0f) + { + gfx_animation_ctx_entry_t animation_entry; + + mui->status_bar.alpha = 0.0f; + + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = alpha_tag; + animation_entry.duration = fade_duration; + animation_entry.target_value = 1.0f; + animation_entry.subject = &mui->status_bar.alpha; + animation_entry.cb = NULL; + animation_entry.userdata = NULL; + + gfx_animation_push(&animation_entry); + } + else + mui->status_bar.alpha = 1.0f; + } + } + } + + /* Always return true - every entry must + * be processed */ + return true; +} + +static bool (*materialui_render_process_entry)( + materialui_handle_t* mui, + materialui_node_t *node, + size_t entry_idx, size_t selection, + bool first_entry_found, bool last_entry_found, + unsigned thumbnail_upscale_threshold, + bool network_on_demand_thumbnails) = materialui_render_process_entry_default; + +/* ============================== + * materialui_render_process_entry() END + * ============================== */ + static void materialui_layout( materialui_handle_t *mui, bool video_is_threaded); /* Called on each frame. We use this callback to: * - Determine current scroll position - * - Determine index of first/last onscreen entries + * - Determine index of first/last on-screen entries * - Handle dynamic pointer input * - Handle streaming thumbnails */ static void materialui_render(void *data, @@ -2332,6 +2974,7 @@ static void materialui_render(void *data, materialui_handle_t *mui = (materialui_handle_t*)data; unsigned header_height = gfx_display_get_header_height(); size_t entries_end = menu_entries_get_size(); + size_t selection = menu_navigation_get_selection(); file_list_t *list = menu_entries_get_selection_buf_ptr(0); bool first_entry_found = false; bool last_entry_found = false; @@ -2424,7 +3067,8 @@ static void materialui_render(void *data, * y position */ if (mui->scrollbar.dragged) { - float view_height = (float)height - (float)header_height - (float)mui->nav_bar_layout_height; + float view_height = (float)height - (float)header_height - + (float)mui->nav_bar_layout_height - (float)mui->status_bar.height; float view_y = (float)mui->pointer.y - (float)header_height; float y_scroll_max = mui->content_height - view_height; @@ -2451,11 +3095,12 @@ static void materialui_render(void *data, if (mui->scroll_y < 0.0f) mui->scroll_y = 0.0f; - bottom = mui->content_height - (float)height + (float)header_height + (float)mui->nav_bar_layout_height; + bottom = mui->content_height - (float)height + (float)header_height + + (float)mui->nav_bar_layout_height + (float)mui->status_bar.height; if (mui->scroll_y > bottom) mui->scroll_y = bottom; - if (mui->content_height < (height - header_height - mui->nav_bar_layout_height)) + if (mui->content_height < (height - header_height - mui->nav_bar_layout_height - mui->status_bar.height)) mui->scroll_y = 0.0f; /* Loop over all entries */ @@ -2466,13 +3111,15 @@ static void materialui_render(void *data, { materialui_node_t *node = (materialui_node_t*) file_list_get_userdata_at_offset(list, i); + int entry_x; int entry_y; /* Sanity check */ if (!node) break; - /* Get current entry y position */ + /* Get current entry x/y position */ + entry_x = (int)node->x; entry_y = (int)((float)header_height - mui->scroll_y + node->y); /* Check whether this is the first on screen entry */ @@ -2487,7 +3134,7 @@ static void materialui_render(void *data, /* Check whether this is the last on screen entry */ else if (!last_entry_found) { - if (entry_y > ((int)height - (int)mui->nav_bar_layout_height)) + if (entry_y > ((int)height - (int)mui->nav_bar_layout_height - (int)mui->status_bar.height)) { /* Current entry is off screen - get index * of previous entry */ @@ -2512,14 +3159,16 @@ static void materialui_render(void *data, /* Check if pointer is within the 'list' region of * the window (i.e. exclude header, navigation bar, * landscape borders) */ - if ((pointer_x > mui->landscape_entry_margin) && - (pointer_x < width - mui->landscape_entry_margin - mui->nav_bar_layout_width) && + if ((pointer_x > mui->landscape_optimization.border_width) && + (pointer_x < width - mui->landscape_optimization.border_width - mui->nav_bar_layout_width) && (pointer_y >= header_height) && - (pointer_y <= height - mui->nav_bar_layout_height)) + (pointer_y <= height - mui->nav_bar_layout_height - mui->status_bar.height)) { /* Check if pointer is within the bounds of the * current entry */ - if ((pointer_y > entry_y) && + if ((pointer_x > entry_x) && + (pointer_x < (entry_x + node->entry_width)) && + (pointer_y > entry_y) && (pointer_y < (entry_y + node->entry_height))) { /* Pointer selection is always updated */ @@ -2541,6 +3190,7 @@ static void materialui_render(void *data, if (mui->pointer.press_duration >= MENU_INPUT_PRESS_TIME_SHORT) { menu_navigation_set_selection(i); + selection = i; /* Once an entry has been auto selected, disable * touch feedback selection updates until the next @@ -2553,36 +3203,27 @@ static void materialui_render(void *data, } } - /* 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)) - { - bool on_screen = - first_entry_found && !last_entry_found; - - if (mui->secondary_thumbnail_enabled) - gfx_thumbnail_process_streams( - mui->thumbnail_path_data, mui->playlist, i, - &node->thumbnails.primary, &node->thumbnails.secondary, - on_screen, - thumbnail_upscale_threshold, - network_on_demand_thumbnails); - else - gfx_thumbnail_process_stream( - mui->thumbnail_path_data, GFX_THUMBNAIL_RIGHT, - mui->playlist, i, &node->thumbnails.primary, on_screen, - thumbnail_upscale_threshold, - network_on_demand_thumbnails); - } - else if (last_entry_found) + /* Perform any additional processing required + * for the current entry */ + if (!materialui_render_process_entry( + mui, node, i, selection, + first_entry_found, last_entry_found, + thumbnail_upscale_threshold, + network_on_demand_thumbnails)) break; } menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &mui->first_onscreen_entry); } +/* ============================== + * materialui_render_menu_entry() START + * ============================== */ + +/* Draws specified menu entry */ + +/* Utility functions */ + enum materialui_entry_value_type materialui_get_entry_value_type( materialui_handle_t *mui, const char *entry_value, bool entry_checked, @@ -2656,11 +3297,12 @@ enum materialui_entry_value_type materialui_get_entry_value_type( static void materialui_render_switch_icon( materialui_handle_t *mui, + materialui_node_t *node, void *userdata, unsigned video_width, unsigned video_height, float y, - unsigned width, unsigned height, int x_offset, + int x_offset, bool on) { unsigned switch_texture_index = on ? @@ -2669,9 +3311,9 @@ static void materialui_render_switch_icon( mui->colors.list_switch_on_background : mui->colors.list_switch_off_background; float *switch_color = on ? mui->colors.list_switch_on : mui->colors.list_switch_off; - int x = - x_offset + (int)width - (int)mui->margin - (int)mui->landscape_entry_margin - - (int)mui->nav_bar_layout_width - (int)mui->icon_size; + int x = x_offset + node->x + node->entry_width - + (int)mui->landscape_optimization.entry_margin - + (int)mui->margin - (int)mui->icon_size; /* Draw background */ if (mui->textures.list[MUI_TEXTURE_SWITCH_BG]) @@ -2683,8 +3325,6 @@ static void materialui_render_switch_icon( mui->textures.list[MUI_TEXTURE_SWITCH_BG], x, y, - width, - height, 0, 1, bg_color); @@ -2699,16 +3339,13 @@ static void materialui_render_switch_icon( mui->textures.list[switch_texture_index], x, y, - width, - height, 0, 1, switch_color); } -/* Draws specified menu entry - * > Used for standard, non-playlist entries - * >> MUI_LIST_VIEW_DEFAULT */ +/* Used for standard, non-playlist entries + * > MUI_LIST_VIEW_DEFAULT */ static void materialui_render_menu_entry_default( materialui_handle_t *mui, void *userdata, @@ -2717,9 +3354,9 @@ static void materialui_render_menu_entry_default( materialui_node_t *node, menu_entry_t *entry, bool entry_selected, + bool entry_is_last, bool touch_feedback_active, unsigned header_height, - unsigned width, unsigned height, int x_offset) { const char *entry_value = NULL; @@ -2729,10 +3366,11 @@ static void materialui_render_menu_entry_default( enum materialui_entry_value_type entry_value_type = MUI_ENTRY_VALUE_NONE; unsigned entry_value_width = 0; enum msg_file_type entry_file_type = FILE_TYPE_NONE; + int entry_x = x_offset + node->x; 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; + int entry_margin = (int)mui->margin + (int)mui->landscape_optimization.entry_margin; + int usable_width = (int)node->entry_width - + (int)(mui->margin * 2) - (int)(mui->landscape_optimization.entry_margin * 2); int label_y = 0; int value_icon_y = 0; uintptr_t icon_texture = 0; @@ -2779,8 +3417,8 @@ static void materialui_render_menu_entry_default( /* Note that we have to perform a backup check here, * since the 'manual content scan - file extensions' * setting may have a value of 'zip' or '7z' etc, which - * means it would otherwise get incorreclty identified as - * an achive file... */ + * means it would otherwise get incorrectly identified as + * an archive file... */ if (entry_type == FILE_TYPE_CARCHIVE) icon_texture = mui->textures.list[MUI_TEXTURE_ARCHIVE]; break; @@ -2800,10 +3438,8 @@ static void materialui_render_menu_entry_default( video_height, mui->icon_size, (uintptr_t)icon_texture, - x_offset + (int)mui->landscape_entry_margin, + entry_x + (int)mui->landscape_optimization.entry_margin, entry_y + (node->entry_height / 2.0f) - (mui->icon_size / 2.0f), - width, - height, 0, 1, mui->colors.list_icon); @@ -2853,9 +3489,9 @@ static void materialui_render_menu_entry_default( * early as they are scrolled upwards beyond the top edge * of the screen */ gfx_display_draw_text(mui->font_data.hint.font, wrapped_sublabel, - x_offset + entry_margin, + entry_x + entry_margin, sublabel_y, - width, height, + video_width, video_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)); @@ -2934,9 +3570,9 @@ static void materialui_render_menu_entry_default( /* Draw value string */ gfx_display_draw_text(mui->font_data.list.font, value_buf, - x_offset + value_x_offset + (int)width - (int)mui->margin - (int)mui->landscape_entry_margin - (int)mui->nav_bar_layout_width, + entry_x + value_x_offset + node->entry_width - (int)mui->margin - (int)mui->landscape_optimization.entry_margin, label_y, - width, height, + video_width, video_height, (entry_selected || touch_feedback_active) ? mui->colors.list_text_highlighted : mui->colors.list_text, TEXT_ALIGN_RIGHT, 1.0f, false, 0, draw_text_outside); @@ -2944,17 +3580,15 @@ static void materialui_render_menu_entry_default( break; case MUI_ENTRY_VALUE_SWITCH_ON: { - materialui_render_switch_icon( - mui, userdata, video_width, - video_height, value_icon_y, width, height, x_offset, true); + materialui_render_switch_icon(mui, node, userdata, + video_width, video_height, value_icon_y, x_offset, true); entry_value_width = mui->icon_size; } break; case MUI_ENTRY_VALUE_SWITCH_OFF: { - materialui_render_switch_icon( - mui, userdata, video_width, - video_height, value_icon_y, width, height, x_offset, false); + materialui_render_switch_icon(mui, node, userdata, + video_width, video_height, value_icon_y, x_offset, false); entry_value_width = mui->icon_size; } break; @@ -2968,10 +3602,8 @@ static void materialui_render_menu_entry_default( video_height, mui->icon_size, mui->textures.list[MUI_TEXTURE_CHECKMARK], - x_offset + (int)width - (int)mui->margin - (int)mui->landscape_entry_margin - (int)mui->nav_bar_layout_width - (int)mui->icon_size, + entry_x + node->entry_width - (int)mui->margin - (int)mui->landscape_optimization.entry_margin - (int)mui->icon_size, value_icon_y, - width, - height, 0, 1, mui->colors.list_switch_on); @@ -3023,9 +3655,9 @@ static void materialui_render_menu_entry_default( /* Draw label string */ gfx_display_draw_text(mui->font_data.list.font, label_buf, - x_offset + (int)mui->ticker_x_offset + entry_margin, + (int)mui->ticker_x_offset + entry_x + entry_margin, label_y, - width, height, + video_width, video_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); @@ -3033,13 +3665,12 @@ static void materialui_render_menu_entry_default( } } -/* Draws specified menu entry - * > Used for playlist 'list view' (with and without +/* 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 */ + * > 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, void *userdata, @@ -3048,17 +3679,17 @@ static void materialui_render_menu_entry_playlist_list( materialui_node_t *node, menu_entry_t *entry, bool entry_selected, + bool entry_is_last, 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_x = x_offset + node->x; 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; + int entry_margin = (int)mui->margin; + int usable_width = (int)node->entry_width - (int)(mui->margin * 2); int label_y = 0; bool draw_text_outside = (x_offset != 0); @@ -3077,15 +3708,24 @@ static void materialui_render_menu_entry_playlist_list( menu_entry_get_rich_label(entry, &entry_label); menu_entry_get_sublabel(entry, &entry_sublabel); + /* If thumbnails are *not* enabled, increase entry + * margin and decrease usable width by landscape + * optimisation margin */ + if (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST) + { + entry_margin += (int)mui->landscape_optimization.entry_margin; + usable_width -= (int)(mui->landscape_optimization.entry_margin * 2); + } /* Draw entry thumbnail(s) * > Has to be done first, since it affects the left * hand margin size and total width for label + * sublabel text */ - if (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST) + else { int thumbnail_margin = 0; - float thumbnail_y = - (float)entry_y + ((float)node->entry_height / 2.0f) - ((float)mui->thumbnail_height_max / 2.0f); + float thumbnail_y = (float)entry_y + + ((float)node->entry_height / 2.0f) - + ((float)mui->thumbnail_height_max / 2.0f); /* When using portrait display orientations with * secondary thumbnails enabled, have to add a @@ -3099,13 +3739,9 @@ static void materialui_render_menu_entry_playlist_list( } /* When using landscape display orientations, we * have enough screen space to improve thumbnail - * appearance by adding left/right margins - but - * we only need to do this if landscape optimisations - * are disabled (or landscape_entry_margin is less - * than mui->margin) */ + * appearance by adding left/right margins */ else - if (mui->landscape_entry_margin < mui->margin) - thumbnail_margin = (int)(mui->margin - mui->landscape_entry_margin); + thumbnail_margin = (int)mui->margin; /* Draw primary thumbnail */ materialui_draw_thumbnail( @@ -3114,10 +3750,8 @@ static void materialui_render_menu_entry_playlist_list( userdata, video_width, video_height, - (float)(x_offset + thumbnail_margin + (int)mui->landscape_entry_margin), + (float)(entry_x + thumbnail_margin), thumbnail_y, - width, - height, 1.0f); entry_margin += mui->thumbnail_width_max + thumbnail_margin; @@ -3132,23 +3766,12 @@ static void materialui_render_menu_entry_playlist_list( userdata, video_width, video_height, - (float)(x_offset + (int)width - thumbnail_margin - (int)mui->landscape_entry_margin - - (int)mui->nav_bar_layout_width - (int)mui->thumbnail_width_max), + (float)(entry_x + node->entry_width - thumbnail_margin - (int)mui->thumbnail_width_max), thumbnail_y, - width, - height, 1.0f); usable_width -= mui->thumbnail_width_max + thumbnail_margin; } - /* If there is no secondary thumbnail and landscape - * optimisations are active, must increase usable width - * to balance out the margin of the primary thumbnail - * (since this is effectively left shifted when landscape - * optimisations are active...) */ - else if (mui->landscape_entry_margin > 0) - usable_width += (mui->margin > mui->landscape_entry_margin) ? - (int)mui->landscape_entry_margin : (int)mui->margin; } /* Draw entry sublabel @@ -3186,9 +3809,9 @@ static void materialui_render_menu_entry_playlist_list( * early as they are scrolled upwards beyond the top edge * of the screen */ gfx_display_draw_text(mui->font_data.hint.font, wrapped_sublabel, - x_offset + entry_margin, + entry_x + entry_margin, sublabel_y, - width, height, + video_width, video_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)); @@ -3230,9 +3853,9 @@ static void materialui_render_menu_entry_playlist_list( /* Draw label string */ gfx_display_draw_text(mui->font_data.list.font, label_buf, - x_offset + (int)mui->ticker_x_offset + entry_margin, + (int)mui->ticker_x_offset + entry_x + entry_margin, label_y, - width, height, + video_width, video_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); @@ -3246,28 +3869,24 @@ static void materialui_render_menu_entry_playlist_list( * by drawing a divider between entries. This is particularly * beneficial when dual thumbnails are enabled, since it * 'ties' the left/right thumbnails together */ - if ((mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_SMALL) || - (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM) || - (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE)) - { - if (usable_width > 0) - gfx_display_draw_quad( - userdata, - video_width, - video_height, - (float)(x_offset + entry_margin), - entry_y + (float)node->entry_height, - (unsigned)usable_width, - mui->entry_divider_width, - width, - height, - mui->colors.divider); - } + if (!entry_is_last && + (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST) && + (usable_width > 0)) + gfx_display_draw_quad( + userdata, + video_width, + video_height, + (float)(entry_x + entry_margin), + entry_y + (float)node->entry_height, + (unsigned)usable_width, + mui->entry_divider_width, + video_width, + video_height, + mui->colors.entry_divider); } -/* Draws specified menu entry - * > Used for playlist 'dual icon' entries - * >> MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON */ +/* Used for playlist 'dual icon' entries + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON */ static void materialui_render_menu_entry_playlist_dual_icon( materialui_handle_t *mui, void *userdata, @@ -3276,16 +3895,15 @@ static void materialui_render_menu_entry_playlist_dual_icon( materialui_node_t *node, menu_entry_t *entry, bool entry_selected, + bool entry_is_last, bool touch_feedback_active, unsigned header_height, - unsigned width, unsigned height, int x_offset) { const char *entry_label = NULL; - int usable_width = - (int)width - (int)(mui->margin * 2) - - (int)(mui->landscape_entry_margin * 2) - (int)mui->nav_bar_layout_width; + float entry_x = (float)x_offset + node->x; float entry_y = (float)header_height - mui->scroll_y + node->y; + int usable_width = (int)node->entry_width - (int)(mui->margin * 2); float thumbnail_y; /* Initial ticker configuration @@ -3304,8 +3922,7 @@ static void materialui_render_menu_entry_playlist_dual_icon( /* Draw thumbnails * > These go at the top of the entry, with a * small vertical margin */ - thumbnail_y = - entry_y + ((float)mui->dip_base_unit_size / 10.0f); + thumbnail_y = entry_y + ((float)mui->dip_base_unit_size / 10.0f); /* > Primary thumbnail */ materialui_draw_thumbnail( @@ -3314,10 +3931,8 @@ static void materialui_render_menu_entry_playlist_dual_icon( userdata, video_width, video_height, - (float)(x_offset + (int)mui->margin + (int)mui->landscape_entry_margin), + entry_x + (float)mui->margin, thumbnail_y, - width, - height, 1.0f); /* > Secondary thumbnail */ @@ -3327,11 +3942,8 @@ static void materialui_render_menu_entry_playlist_dual_icon( userdata, video_width, video_height, - (float)(x_offset + (int)width - (int)mui->margin - (int)mui->landscape_entry_margin - - (int)mui->nav_bar_layout_width - (int)mui->thumbnail_width_max), + entry_x + node->entry_width - (float)mui->margin - (float)mui->thumbnail_width_max, thumbnail_y, - width, - height, 1.0f); /* Draw entry label */ @@ -3383,14 +3995,13 @@ static void materialui_render_menu_entry_playlist_dual_icon( } } - label_x += (float)(x_offset + (int)mui->ticker_x_offset + - (int)mui->margin + (int)mui->landscape_entry_margin); + label_x += (float)mui->ticker_x_offset + entry_x + (float)mui->margin; /* Draw label string */ gfx_display_draw_text(mui->font_data.list.font, label_buf, label_x, label_y, - width, height, + video_width, video_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); @@ -3398,28 +4009,362 @@ static void materialui_render_menu_entry_playlist_dual_icon( } /* Draw divider */ - if (usable_width > 0) + if (!entry_is_last && (usable_width > 0)) gfx_display_draw_quad( userdata, video_width, video_height, - (float)(x_offset + (int)mui->margin + (int)mui->landscape_entry_margin), + entry_x + (float)mui->margin, thumbnail_y + (float)mui->thumbnail_height_max + ((float)mui->dip_base_unit_size / 10.0f) + (float)mui->font_data.list.line_height, (unsigned)usable_width, mui->entry_divider_width, - width, - height, - mui->colors.divider); + video_width, + video_height, + mui->colors.entry_divider); } -static void materialui_render_scrollbar( +/* Used for playlist 'desktop'-layout entries + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP */ +static void materialui_render_menu_entry_playlist_desktop( materialui_handle_t *mui, void *userdata, unsigned video_width, unsigned video_height, - unsigned width, unsigned height) + materialui_node_t *node, + menu_entry_t *entry, + bool entry_selected, + bool entry_is_last, + bool touch_feedback_active, + unsigned header_height, + int x_offset) +{ + const char *entry_label = NULL; + int entry_x = x_offset + node->x; + int entry_y = header_height - mui->scroll_y + node->y; + int divider_y = entry_y + (float)node->entry_height; + int entry_margin = (int)mui->margin; + int usable_width = node->entry_width - (int)(mui->margin * 2); + /* Entry label is drawn at the vertical centre + * of the current node */ + int label_y = entry_y + (node->entry_height / 2.0f) + + mui->font_data.list.line_centre_offset; + bool draw_text_outside = (x_offset != 0); + bool draw_divider = !entry_is_last; + + /* Read entry parameters */ + menu_entry_get_rich_label(entry, &entry_label); + + /* 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) + { + mui->ticker_smooth.font = mui->font_data.list.font; + mui->ticker_smooth.selected = entry_selected; + 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); + + gfx_animation_ticker_smooth(&mui->ticker_smooth); + } + else + { + mui->ticker.selected = entry_selected; + mui->ticker.s = label_buf; + mui->ticker.len = (size_t)(usable_width / mui->font_data.list.glyph_width); + mui->ticker.str = entry_label; + + gfx_animation_ticker(&mui->ticker); + } + + /* Draw text */ + gfx_display_draw_text(mui->font_data.list.font, label_buf, + (int)mui->ticker_x_offset + entry_x + entry_margin, + label_y, + video_width, video_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 divider + * > To prevent any ugly alignment issues, we + * only draw a divider if its bottom edge is + * more than two times the divider thickness from + * the top edge of the status bar... */ + draw_divider = draw_divider && (usable_width > 0) && + (mui->status_bar.enabled ? + ((divider_y + (mui->entry_divider_width * 2)) < + (video_height - mui->nav_bar_layout_height - mui->status_bar.height)) : + true); + + if (draw_divider) + gfx_display_draw_quad( + userdata, + video_width, + video_height, + (float)entry_x, + (float)divider_y, + (unsigned)node->entry_width, + mui->entry_divider_width, + video_width, + video_height, + mui->colors.entry_divider); +} + +static void (*materialui_render_menu_entry)( + materialui_handle_t *mui, + void *userdata, + unsigned video_width, + unsigned video_height, + materialui_node_t *node, + menu_entry_t *entry, + bool entry_selected, + bool entry_is_last, + bool touch_feedback_active, + unsigned header_height, + int x_offset) = materialui_render_menu_entry_default; + +/* ============================== + * materialui_render_menu_entry() END + * ============================== */ + +/* ============================== + * materialui_render_selected_entry_aux() START + * ============================== */ + +/* Draws any auxiliary items required for the + * currently selected menu entry */ + +/* Used for 'desktop'-layout playlist displays. + * Draws thumbnails + metadata for currently + * selected item. + * > MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP */ +static void materialui_render_selected_entry_aux_playlist_desktop( + materialui_handle_t *mui, void *userdata, + unsigned video_width, unsigned video_height, + unsigned header_height, int x_offset, + file_list_t *list, size_t selection) +{ + materialui_node_t *node = (materialui_node_t*)file_list_get_userdata_at_offset(list, selection); + float background_x = (float)(x_offset + (int)mui->landscape_optimization.border_width); + float background_y = (float)header_height; + /* Note: If landscape optimisations are enabled, + * need to allow space for a second divider at + * the left hand edge of the sidebar */ + int background_width = mui->thumbnail_width_max + (mui->margin * 2) + + (mui->entry_divider_width * (mui->landscape_optimization.enabled ? + 2 : 1)); + int background_height = (int)video_height - (int)header_height - + (int)mui->nav_bar_layout_height - (int)mui->status_bar.height; + float thumbnail_x = background_x + (float)mui->margin + + (mui->landscape_optimization.enabled ? mui->entry_divider_width : 0); + float thumbnail_y = background_y + (float)mui->margin; + + /* Sanity check */ + if ((background_width <= 0) || + (background_height <= 0)) + return; + + /* Draw sidebar background + * > Surface */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + background_x, + background_y, + (unsigned)background_width, + (unsigned)background_height, + video_width, + video_height, + mui->colors.side_bar_background); + + /* > Divider */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + background_x + (float)background_width - (float)mui->entry_divider_width, + background_y, + mui->entry_divider_width, + (unsigned)background_height, + video_width, + video_height, + mui->colors.entry_divider); + + /* > Additional divider */ + if (mui->landscape_optimization.enabled) + gfx_display_draw_quad( + userdata, + video_width, + video_height, + background_x, + background_y, + mui->entry_divider_width, + (unsigned)background_height, + video_width, + video_height, + mui->colors.entry_divider); + + /* Draw thumbnails */ + + /* > Primary */ + if (node) + materialui_draw_thumbnail( + mui, + &node->thumbnails.primary, + userdata, + video_width, + video_height, + thumbnail_x, + thumbnail_y, + 1.0f); + + /* > Secondary */ + if (node) + materialui_draw_thumbnail( + mui, + &node->thumbnails.secondary, + userdata, + video_width, + video_height, + thumbnail_x, + thumbnail_y + (float)mui->thumbnail_height_max + (float)mui->margin, + 1.0f); + + /* Draw status bar */ + if (mui->status_bar.enabled) + { + float status_bar_x = background_x; + float status_bar_y = (float)(video_height - mui->nav_bar_layout_height - mui->status_bar.height); + int status_bar_width = (int)video_width - (int)(mui->landscape_optimization.border_width * 2) - + (int)mui->nav_bar_layout_width; + int text_width = status_bar_width - (int)(mui->margin * 2); + + /* Sanity check */ + if (status_bar_width <= 0) + return; + + /* Status bar overlaps list entries + * > Must flush list font before attempting + * to draw it */ + materialui_font_flush(video_width, video_height, &mui->font_data.list); + + /* Background + * > Surface */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + status_bar_x, + status_bar_y, + (unsigned)status_bar_width, + mui->status_bar.height, + video_width, + video_height, + mui->colors.status_bar_background); + + /* > Shadow + * (For symmetry, header and status bar + * shadows have the same height) */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + status_bar_x, + status_bar_y, + (unsigned)status_bar_width, + mui->header_shadow_height, + video_width, + video_height, + mui->colors.status_bar_shadow); + + /* Text */ + if ((text_width > 0) && !string_is_empty(mui->status_bar.str)) + { + bool draw_text_outside = (x_offset != 0); + uint32_t text_color = mui->colors.status_bar_text; + float text_x = 0.0f; + char metadata_buf[MENU_SUBLABEL_MAX_LENGTH]; + + metadata_buf[0] = '\0'; + + /* Set text opacity */ + text_color = (text_color & 0xFFFFFF00) | + (unsigned)((255.0f * mui->transition_alpha * mui->status_bar.alpha) + 0.5f); + + /* Apply ticker */ + if (mui->use_smooth_ticker) + { + mui->ticker_smooth.font = mui->font_data.hint.font; + mui->ticker_smooth.selected = true; + mui->ticker_smooth.field_width = (unsigned)text_width; + mui->ticker_smooth.src_str = mui->status_bar.str; + mui->ticker_smooth.dst_str = metadata_buf; + mui->ticker_smooth.dst_str_len = sizeof(metadata_buf); + + gfx_animation_ticker_smooth(&mui->ticker_smooth); + + /* If ticker is inactive, centre the text */ + if (!gfx_animation_ticker_smooth(&mui->ticker_smooth)) + text_x = (float)(text_width - mui->ticker_str_width) / 2.0f; + } + else + { + mui->ticker.selected = true; + mui->ticker.s = metadata_buf; + mui->ticker.len = (size_t)(text_width / mui->font_data.hint.glyph_width); + mui->ticker.str = mui->status_bar.str; + + /* If ticker is inactive, centre the text */ + if (!gfx_animation_ticker(&mui->ticker)) + { + int str_width = (int)(utf8len(metadata_buf) * + mui->font_data.hint.glyph_width); + + text_x = (float)(text_width - str_width) / 2.0f; + } + } + + text_x += (float)mui->ticker_x_offset + status_bar_x + (float)mui->margin; + + /* Draw metadata string */ + gfx_display_draw_text(mui->font_data.hint.font, metadata_buf, + text_x, + status_bar_y + ((float)mui->status_bar.height * 0.5f) + + (float)mui->font_data.hint.line_centre_offset, + video_width, video_height, + text_color, + TEXT_ALIGN_LEFT, 1.0f, false, 0, draw_text_outside); + } + } +} + +static void (*materialui_render_selected_entry_aux)( + materialui_handle_t *mui, void *userdata, + unsigned video_width, unsigned video_height, + unsigned header_height, int x_offset, + file_list_t *list, size_t selection) = NULL; + +/* ============================== + * materialui_render_selected_entry_aux() END + * ============================== */ + +static void materialui_render_scrollbar( + materialui_handle_t *mui, void *userdata, + unsigned video_width, unsigned video_height) { /* Do nothing if scrollbar is disabled */ if (!mui->scrollbar.active) @@ -3434,16 +4379,15 @@ static void materialui_render_scrollbar( mui->scrollbar.y, mui->scrollbar.width, mui->scrollbar.height, - width, height, + video_width, + video_height, mui->colors.scrollbar); } /* Draws current menu list */ static void materialui_render_menu_list( - materialui_handle_t *mui, - void *userdata, + materialui_handle_t *mui, void *userdata, unsigned video_width, unsigned video_height, - unsigned width, unsigned height, int x_offset) { size_t i; @@ -3458,6 +4402,10 @@ static void materialui_render_menu_list( !mui->show_fullscreen_thumbnails && (mui->touch_feedback_alpha >= 0.5f) && (mui->touch_feedback_selection == menu_input_get_pointer_selection()); + bool entry_value_enabled = (mui->list_view_type == MUI_LIST_VIEW_DEFAULT); + bool entry_sublabel_enabled = + (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON) && + (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP); list = menu_entries_get_selection_buf_ptr(0); if (!list) @@ -3470,6 +4418,7 @@ static void materialui_render_menu_list( for (i = first_entry; i <= last_entry; i++) { bool entry_selected = (selection == i); + bool entry_is_last = (i == last_entry); bool touch_feedback_active = touch_feedback_enabled && (mui->touch_feedback_selection == i); materialui_node_t *node = (materialui_node_t*)file_list_get_userdata_at_offset(list, i); menu_entry_t entry; @@ -3481,82 +4430,38 @@ static void materialui_render_menu_list( /* Get current entry */ menu_entry_init(&entry); entry.path_enabled = false; - entry.value_enabled = (mui->list_view_type == MUI_LIST_VIEW_DEFAULT); - entry.sublabel_enabled = (mui->list_view_type != MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON); + entry.value_enabled = entry_value_enabled; + entry.sublabel_enabled = entry_sublabel_enabled; menu_entry_get(&entry, 0, i, NULL, true); - /* 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, - userdata, - video_width, - video_height, - node, - &entry, - entry_selected, - touch_feedback_active, - header_height, - width, - height, - x_offset); - break; - case MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON: - materialui_render_menu_entry_playlist_dual_icon( - mui, - userdata, - video_width, - video_height, - 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, - userdata, - video_width, - video_height, - node, - &entry, - entry_selected, - touch_feedback_active, - header_height, - width, - height, - x_offset); - break; - } + /* Render entry: label, value + associated icons */ + materialui_render_menu_entry( + mui, + userdata, + video_width, + video_height, + node, + &entry, + entry_selected, + entry_is_last, + touch_feedback_active, + header_height, + x_offset); } + /* Draw any auxiliary items required for the + * currently selected entry */ + if (materialui_render_selected_entry_aux) + materialui_render_selected_entry_aux( + mui, userdata, + video_width, video_height, + header_height, x_offset, + list, selection); + /* Draw scrollbar */ materialui_render_scrollbar( - mui, - userdata, - video_width, - video_height, - width, height); + mui, userdata, + video_width, video_height); } static size_t materialui_list_get_size(void *data, enum menu_list_type type) @@ -3651,16 +4556,15 @@ static void materialui_render_landscape_border( void *userdata, unsigned video_width, unsigned video_height, - unsigned width, unsigned height, unsigned header_height, int x_offset) + unsigned header_height, int x_offset) { - if (mui->landscape_entry_margin > mui->margin) + if (mui->landscape_optimization.enabled) { - unsigned border_width = mui->landscape_entry_margin - mui->margin; - unsigned border_height = height - header_height - mui->nav_bar_layout_height; + unsigned border_height = video_height - header_height - mui->nav_bar_layout_height; int left_x = x_offset; - int right_x = - x_offset + (int)width - (int)mui->landscape_entry_margin + - (int)mui->margin - (int)mui->nav_bar_layout_width; + int right_x = x_offset + (int)video_width - + (int)mui->nav_bar_layout_width - + (int)mui->landscape_optimization.border_width; int y = (int)header_height; /* Draw left border */ @@ -3670,10 +4574,10 @@ static void materialui_render_landscape_border( video_height, left_x, y, - border_width, + mui->landscape_optimization.border_width, border_height, - width, - height, + video_width, + video_height, mui->colors.landscape_border_shadow_left); /* Draw right border */ @@ -3683,20 +4587,18 @@ static void materialui_render_landscape_border( video_height, right_x, y, - border_width, + mui->landscape_optimization.border_width, border_height, - width, - height, + video_width, + video_height, mui->colors.landscape_border_shadow_right); } } static void materialui_render_selection_highlight( - materialui_handle_t *mui, - void *userdata, - unsigned video_width, - unsigned video_height, - unsigned width, unsigned height, unsigned header_height, int x_offset, + materialui_handle_t *mui, void *userdata, + unsigned video_width, unsigned video_height, + unsigned header_height, int x_offset, size_t selection, float *color) { /* Only draw highlight if selection is onscreen */ @@ -3704,19 +4606,10 @@ static void materialui_render_selection_highlight( { file_list_t *list = NULL; materialui_node_t *node = NULL; - int highlight_x = x_offset; - int highlight_y = 0; - int highlight_width = (int)width - (int)mui->nav_bar_layout_width; - int highlight_height = 0; - - /* If landscape optimisations are enabled/active, - * adjust highlight layout */ - if (mui->landscape_entry_margin > 0) - { - highlight_x += (int)mui->landscape_entry_margin - (int)mui->margin; - highlight_width -= (int)(2 * mui->landscape_entry_margin) - (int)(2 * mui->margin); - highlight_width = (highlight_width < 0) ? 0 : highlight_width; - } + int highlight_x; + int highlight_y; + int highlight_width; + int highlight_height; list = menu_entries_get_selection_buf_ptr(0); if (!list) @@ -3727,11 +4620,13 @@ static void materialui_render_selection_highlight( return; /* Now we have a valid node, can determine - * highlight y position and height... - * > Note: We round y position down and add 1 to + * highlight position and size... + * > Note: We round x/y position down and add 1 to * the height in order to avoid obvious 'seams' * when entries have dividers (rounding errors * would otherwise cause 1px vertical gaps) */ + highlight_x = (int)(x_offset + node->x); + highlight_width = (int)(node->entry_width + 0.5f); highlight_y = (int)((float)header_height - mui->scroll_y + node->y); highlight_height = (int)(node->entry_height + 1.5f); @@ -3744,8 +4639,8 @@ static void materialui_render_selection_highlight( highlight_y, (unsigned)highlight_width, (unsigned)highlight_height, - width, - height, + video_width, + video_height, color); } } @@ -3754,7 +4649,7 @@ static void materialui_render_entry_touch_feedback( materialui_handle_t *mui, void *userdata, unsigned video_width, unsigned video_height, - unsigned width, unsigned height, unsigned header_height, int x_offset, + unsigned header_height, int x_offset, size_t current_selection) { /* Check whether pointer is currently @@ -3770,10 +4665,10 @@ static void materialui_render_entry_touch_feedback( * currently selected for feedback animations */ if (pointer_active) pointer_active = (mui->touch_feedback_selection == menu_input_get_pointer_selection()) && - (mui->pointer.x > mui->landscape_entry_margin) && - (mui->pointer.x < width - mui->landscape_entry_margin - mui->nav_bar_layout_width) && + (mui->pointer.x > mui->landscape_optimization.border_width) && + (mui->pointer.x < video_width - mui->landscape_optimization.border_width - mui->nav_bar_layout_width) && (mui->pointer.y >= header_height) && - (mui->pointer.y <= height - mui->nav_bar_layout_height); + (mui->pointer.y <= video_height - mui->nav_bar_layout_height - mui->status_bar.height); /* Touch feedback highlight fades in when pointer * is held stationary on a menu entry */ @@ -3815,7 +4710,7 @@ static void materialui_render_entry_touch_feedback( /* Draw highlight */ materialui_render_selection_highlight( mui, userdata, video_width, video_height, - width, height, header_height, x_offset, + header_height, x_offset, mui->touch_feedback_selection, higlight_color); } @@ -3824,13 +4719,12 @@ static void materialui_render_entry_touch_feedback( static void materialui_render_header( materialui_handle_t *mui, void *userdata, - unsigned video_width, unsigned video_height, - unsigned width, unsigned height) + unsigned video_width, unsigned video_height) { char menu_title_buf[255]; settings_t *settings = config_get_ptr(); size_t menu_title_margin = 0; - int usable_sys_bar_width = (int)width - (int)mui->nav_bar_layout_width; + int usable_sys_bar_width = (int)video_width - (int)mui->nav_bar_layout_width; int usable_title_bar_width = usable_sys_bar_width; size_t sys_bar_battery_width = 0; size_t sys_bar_clock_width = 0; @@ -3860,10 +4754,10 @@ static void materialui_render_header( video_height, 0, mui->sys_bar_height + mui->title_bar_height, - width, + video_width, mui->header_shadow_height, - width, - height, + video_width, + video_height, mui->colors.header_shadow); /* > Title bar background */ @@ -3873,10 +4767,10 @@ static void materialui_render_header( video_height, 0, 0, - width, + video_width, mui->sys_bar_height + mui->title_bar_height, - width, - height, + video_width, + video_height, mui->colors.title_bar_background); /* > System bar background */ @@ -3886,10 +4780,10 @@ static void materialui_render_header( video_height, 0, 0, - width, + video_width, mui->sys_bar_height, - width, - height, + video_width, + video_height, mui->colors.sys_bar_background); /* System bar items */ @@ -3962,14 +4856,12 @@ static void materialui_render_header( video_height, mui->sys_bar_icon_size, (uintptr_t)texture_battery, - (int)width - ( + (int)video_width - ( (int)mui->sys_bar_cache.battery_percent_width + (int)mui->sys_bar_margin + (int)mui->sys_bar_icon_size + (int)mui->nav_bar_layout_width), 0, - width, - height, 0, 1, mui->colors.sys_bar_icon); @@ -3977,9 +4869,9 @@ static void materialui_render_header( /* Draw percent text */ gfx_display_draw_text(mui->font_data.hint.font, mui->sys_bar_cache.battery_percent_str, - (int)width - ((int)mui->sys_bar_cache.battery_percent_width + (int)mui->sys_bar_margin + (int)mui->nav_bar_layout_width), + (int)video_width - ((int)mui->sys_bar_cache.battery_percent_width + (int)mui->sys_bar_margin + (int)mui->nav_bar_layout_width), sys_bar_text_y, - width, height, mui->colors.sys_bar_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); + video_width, video_height, mui->colors.sys_bar_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); sys_bar_battery_width = mui->sys_bar_cache.battery_percent_width + mui->sys_bar_margin + mui->sys_bar_icon_size; @@ -4030,12 +4922,12 @@ static void materialui_render_header( gfx_display_draw_text(mui->font_data.hint.font, mui->sys_bar_cache.timedate_str, - (int)width - ( + (int)video_width - ( (int)sys_bar_clock_width + (int)sys_bar_battery_width + (int)mui->nav_bar_layout_width), sys_bar_text_y, - width, height, mui->colors.sys_bar_text, + video_width, video_height, mui->colors.sys_bar_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); usable_sys_bar_width -= sys_bar_clock_width; @@ -4082,7 +4974,7 @@ static void materialui_render_header( gfx_display_draw_text(mui->font_data.hint.font, core_title_buf, (int)mui->ticker_x_offset + (int)mui->sys_bar_margin, sys_bar_text_y, - width, height, mui->colors.sys_bar_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); + video_width, video_height, mui->colors.sys_bar_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); } /* Title bar items */ @@ -4102,8 +4994,6 @@ static void materialui_render_header( mui->textures.list[MUI_TEXTURE_BACK], 0, (int)mui->sys_bar_height, - width, - height, 0, 1, mui->colors.header_icon); @@ -4120,10 +5010,8 @@ static void materialui_render_header( video_height, mui->icon_size, mui->textures.list[MUI_TEXTURE_SEARCH], - (int)width - (int)mui->icon_size - (int)mui->nav_bar_layout_width, + (int)video_width - (int)mui->icon_size - (int)mui->nav_bar_layout_width, (int)mui->sys_bar_height, - width, - height, 0, 1, mui->colors.header_icon); @@ -4142,10 +5030,8 @@ static void materialui_render_header( video_height, mui->icon_size, mui->textures.list[MUI_TEXTURE_SWITCH_VIEW], - (int)width - (2 * (int)mui->icon_size) - (int)mui->nav_bar_layout_width, + (int)video_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); @@ -4232,30 +5118,29 @@ static void materialui_render_header( gfx_display_draw_text(mui->font_data.title.font, menu_title_buf, title_x, (int)(mui->sys_bar_height + (mui->title_bar_height / 2.0f) + mui->font_data.title.line_centre_offset), - width, height, mui->colors.header_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); + video_width, video_height, mui->colors.header_text, TEXT_ALIGN_LEFT, 1.0f, false, 0, false); } -/* Use seperate functions for bottom/right navigation +/* Use separate functions for bottom/right navigation * bars. This involves substantial code duplication, but if * we try to handle this with a single function then * things get incredibly messy and inefficient... */ static void materialui_render_nav_bar_bottom( materialui_handle_t *mui, void *userdata, - unsigned video_width, unsigned video_height, - unsigned width, unsigned height) + unsigned video_width, unsigned video_height) { unsigned i; - unsigned nav_bar_width = width; + unsigned nav_bar_width = video_width; unsigned nav_bar_height = mui->nav_bar.width; int nav_bar_x = 0; - int nav_bar_y = (int)height - (int)mui->nav_bar.width; + int nav_bar_y = (int)video_height - (int)mui->nav_bar.width; unsigned num_tabs = mui->nav_bar.num_menu_tabs + MUI_NAV_BAR_NUM_ACTION_TABS; - float tab_width = (float)width / (float)num_tabs; + float tab_width = (float)video_width / (float)num_tabs; unsigned tab_width_int = (unsigned)(tab_width + 0.5f); unsigned selection_marker_width = tab_width_int; unsigned selection_marker_height = mui->nav_bar.selection_marker_width; - int selection_marker_y = (int)height - (int)mui->nav_bar.selection_marker_width; + int selection_marker_y = (int)video_height - (int)mui->nav_bar.selection_marker_width; /* Draw navigation bar background */ @@ -4268,8 +5153,8 @@ static void materialui_render_nav_bar_bottom( nav_bar_y, nav_bar_width, nav_bar_height, - width, - height, + video_width, + video_height, mui->colors.nav_bar_background); /* > Divider */ @@ -4281,8 +5166,8 @@ static void materialui_render_nav_bar_bottom( nav_bar_y, nav_bar_width, mui->nav_bar.divider_width, - width, - height, + video_width, + video_height, mui->colors.divider); /* Draw tabs */ @@ -4296,8 +5181,6 @@ static void materialui_render_nav_bar_bottom( mui->textures.list[mui->nav_bar.back_tab.texture_index], (int)((0.5f * tab_width) - ((float)mui->icon_size / 2.0f)), nav_bar_y, - width, - height, 0, 1, mui->nav_bar.back_tab.enabled ? @@ -4312,8 +5195,6 @@ static void materialui_render_nav_bar_bottom( mui->textures.list[mui->nav_bar.resume_tab.texture_index], (int)((((float)num_tabs - 0.5f) * tab_width) - ((float)mui->icon_size / 2.0f)), nav_bar_y, - width, - height, 0, 1, mui->nav_bar.resume_tab.enabled ? @@ -4335,8 +5216,6 @@ static void materialui_render_nav_bar_bottom( mui->textures.list[tab->texture_index], (((float)i + 1.5f) * tab_width) - ((float)mui->icon_size / 2.0f), nav_bar_y, - width, - height, 0, 1, draw_color); @@ -4350,8 +5229,8 @@ static void materialui_render_nav_bar_bottom( selection_marker_y, selection_marker_width, selection_marker_height, - width, - height, + video_width, + video_height, draw_color); } } @@ -4360,20 +5239,19 @@ static void materialui_render_nav_bar_right( materialui_handle_t *mui, void *userdata, unsigned video_width, - unsigned video_height, - unsigned width, unsigned height) + unsigned video_height) { unsigned i; unsigned nav_bar_width = mui->nav_bar.width; - unsigned nav_bar_height = height; - int nav_bar_x = (int)width - (int)mui->nav_bar.width; + unsigned nav_bar_height = video_height; + int nav_bar_x = (int)video_width - (int)mui->nav_bar.width; int nav_bar_y = 0; unsigned num_tabs = mui->nav_bar.num_menu_tabs + MUI_NAV_BAR_NUM_ACTION_TABS; - float tab_height = (float)height / (float)num_tabs; + float tab_height = (float)video_height / (float)num_tabs; unsigned tab_height_int = (unsigned)(tab_height + 0.5f); unsigned selection_marker_width = mui->nav_bar.selection_marker_width; unsigned selection_marker_height = tab_height_int; - int selection_marker_x = (int)width - (int)mui->nav_bar.selection_marker_width; + int selection_marker_x = (int)video_width - (int)mui->nav_bar.selection_marker_width; /* Draw navigation bar background */ @@ -4386,8 +5264,8 @@ static void materialui_render_nav_bar_right( nav_bar_y, nav_bar_width, nav_bar_height, - width, - height, + video_width, + video_height, mui->colors.nav_bar_background); /* > Divider */ @@ -4399,8 +5277,8 @@ static void materialui_render_nav_bar_right( nav_bar_y, mui->nav_bar.divider_width, nav_bar_height, - width, - height, + video_width, + video_height, mui->colors.divider); /* Draw tabs */ @@ -4414,8 +5292,6 @@ static void materialui_render_nav_bar_right( mui->textures.list[mui->nav_bar.back_tab.texture_index], nav_bar_x, (int)((((float)num_tabs - 0.5f) * tab_height) - ((float)mui->icon_size / 2.0f)), - width, - height, 0, 1, mui->nav_bar.back_tab.enabled ? @@ -4430,8 +5306,6 @@ static void materialui_render_nav_bar_right( mui->textures.list[mui->nav_bar.resume_tab.texture_index], nav_bar_x, (int)((0.5f * tab_height) - ((float)mui->icon_size / 2.0f)), - width, - height, 0, 1, mui->nav_bar.resume_tab.enabled ? @@ -4453,8 +5327,6 @@ static void materialui_render_nav_bar_right( mui->textures.list[tab->texture_index], nav_bar_x, (((float)i + 1.5f) * tab_height) - ((float)mui->icon_size / 2.0f), - width, - height, 0, 1, draw_color); @@ -4468,8 +5340,8 @@ static void materialui_render_nav_bar_right( (int)((i + 1) * tab_height_int), selection_marker_width, selection_marker_height, - width, - height, + video_width, + video_height, draw_color); } } @@ -4478,15 +5350,13 @@ static void materialui_render_nav_bar( materialui_handle_t *mui, void *userdata, unsigned video_width, - unsigned video_height, - unsigned width, unsigned height) + unsigned video_height) { switch (mui->nav_bar.location) { case MUI_NAV_BAR_LOCATION_RIGHT: materialui_render_nav_bar_right( - mui, userdata, video_width, video_height, - width, height); + mui, userdata, video_width, video_height); break; case MUI_NAV_BAR_LOCATION_HIDDEN: /* Draw nothing */ @@ -4495,8 +5365,7 @@ static void materialui_render_nav_bar( case MUI_NAV_BAR_LOCATION_BOTTOM: default: materialui_render_nav_bar_bottom( - mui, userdata, video_width, video_height, - width, height); + mui, userdata, video_width, video_height); break; } } @@ -4514,8 +5383,14 @@ static bool materialui_get_selected_thumbnails( file_list_t *list = NULL; materialui_node_t *node = NULL; - /* Ensure selection is on screen */ - if (!materialui_entry_onscreen(mui, selection)) + /* Ensure selection is on screen + * > Special case: When viewing 'desktop'-layout + * playlists skip this check, since thumbnails + * for the selected item are always shown via + * the sidebar regardless of whether the current + * selection is on screen */ + if ((mui->list_view_type != MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) && + !materialui_entry_onscreen(mui, selection)) return false; /* Get currently selected node */ @@ -4661,7 +5536,7 @@ static void materialui_render_fullscreen_thumbnails( materialui_handle_t *mui, void *userdata, unsigned video_width, unsigned video_height, - unsigned width, unsigned height, unsigned header_height, + unsigned header_height, size_t selection) { /* Check whether fullscreen thumbnails are visible */ @@ -4727,8 +5602,8 @@ static void materialui_render_fullscreen_thumbnails( goto error; /* Get dimensions of list view */ - view_width = (int)width - (int)mui->nav_bar_layout_width; - view_height = (int)height - (int)mui->nav_bar_layout_height - (int)header_height; + view_width = (int)video_width - (int)mui->nav_bar_layout_width; + view_height = (int)video_height - (int)mui->nav_bar_layout_height - (int)header_height; /* Check screen orientation * > When using portrait layouts, primary is shown @@ -4870,8 +5745,8 @@ static void materialui_render_fullscreen_thumbnails( header_height, (unsigned)view_width, (unsigned)view_height, - width, - height, + video_width, + video_height, mui->colors.screen_fade); /* Draw thumbnails @@ -4889,8 +5764,8 @@ static void materialui_render_fullscreen_thumbnails( ((thumbnail_box_height - (int)primary_thumbnail_draw_height) >> 1), (unsigned)primary_thumbnail_draw_width + mui->margin, (unsigned)primary_thumbnail_draw_height + mui->margin, - width, - height, + video_width, + video_height, mui->colors.surface_background); /* Thumbnail */ @@ -4923,8 +5798,8 @@ static void materialui_render_fullscreen_thumbnails( ((thumbnail_box_height - (int)secondary_thumbnail_draw_height) >> 1), (unsigned)secondary_thumbnail_draw_width + mui->margin, (unsigned)secondary_thumbnail_draw_height + mui->margin, - width, - height, + video_width, + video_height, mui->colors.surface_background); /* Thumbnail */ @@ -4976,12 +5851,12 @@ static void materialui_colors_set_transition_alpha(materialui_handle_t *mui) gfx_display_set_alpha(mui->colors.list_switch_off, alpha); gfx_display_set_alpha(mui->colors.list_switch_off_background, alpha); gfx_display_set_alpha(mui->colors.scrollbar, alpha); - gfx_display_set_alpha(mui->colors.missing_thumbnail_icon, alpha); + gfx_display_set_alpha(mui->colors.entry_divider, alpha); /* Landscape border shadow only fades if: * - Landscape border is shown * - We are currently performing a slide animation */ - if ((mui->landscape_entry_margin != 0) && + if (mui->landscape_optimization.enabled && (mui->transition_x_offset != 0.0f)) { float border_shadow_alpha = @@ -4992,6 +5867,20 @@ static void materialui_colors_set_transition_alpha(materialui_handle_t *mui) mui->colors.landscape_border_shadow_right[3] = border_shadow_alpha; mui->colors.landscape_border_shadow_right[11] = border_shadow_alpha; } + + /* Sidebar and status bar only fade if we are + * currently viewing a playlist 'desktop'-layout */ + if (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) + { + float status_bar_shadow_alpha = + mui->colors.status_bar_shadow_opacity * alpha; + + gfx_display_set_alpha(mui->colors.side_bar_background, alpha); + gfx_display_set_alpha(mui->colors.status_bar_background, alpha); + + mui->colors.status_bar_shadow[11] = status_bar_shadow_alpha; + mui->colors.status_bar_shadow[15] = status_bar_shadow_alpha; + } } } @@ -5015,12 +5904,12 @@ static void materialui_colors_reset_transition_alpha(materialui_handle_t *mui) gfx_display_set_alpha(mui->colors.list_switch_off, 1.0f); gfx_display_set_alpha(mui->colors.list_switch_off_background, 1.0f); gfx_display_set_alpha(mui->colors.scrollbar, 1.0f); - gfx_display_set_alpha(mui->colors.missing_thumbnail_icon, 1.0f); + gfx_display_set_alpha(mui->colors.entry_divider, 1.0f); /* Landscape border shadow only fades if: * - Landscape border is shown * - We are currently performing a slide animation */ - if ((mui->landscape_entry_margin != 0) && + if (mui->landscape_optimization.enabled && (mui->transition_x_offset != 0.0f)) { float border_shadow_alpha = @@ -5031,6 +5920,20 @@ static void materialui_colors_reset_transition_alpha(materialui_handle_t *mui) mui->colors.landscape_border_shadow_right[3] = border_shadow_alpha; mui->colors.landscape_border_shadow_right[11] = border_shadow_alpha; } + + /* Sidebar and status bar only fade if we are + * currently viewing a playlist 'desktop'-layout */ + if (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) + { + float status_bar_shadow_alpha = + mui->colors.status_bar_shadow_opacity; + + gfx_display_set_alpha(mui->colors.side_bar_background, 1.0f); + gfx_display_set_alpha(mui->colors.status_bar_background, 1.0f); + + mui->colors.status_bar_shadow[11] = status_bar_shadow_alpha; + mui->colors.status_bar_shadow[15] = status_bar_shadow_alpha; + } } } @@ -5043,17 +5946,15 @@ static void materialui_update_scrollbar( /* Do nothing if scrollbar is disabled */ if (mui->scrollbar.active) { - int view_height = (int)height - (int)header_height - (int)mui->nav_bar_layout_height; - int y_max = view_height + (int)header_height - (int)(mui->scrollbar.width + mui->scrollbar.height); + int view_height = (int)height - (int)header_height - + (int)mui->nav_bar_layout_height - (int)mui->status_bar.height; + int y_max = view_height + (int)header_height - + (int)(mui->scrollbar.width + mui->scrollbar.height); /* Get X position */ - mui->scrollbar.x = x_offset + (int)width - (int)mui->scrollbar.width - (int)mui->nav_bar_layout_width; - - /* > Scrollbar must be offset by the current - * landscape border width when landscape optimisations - * are enabled */ - if (mui->landscape_entry_margin > mui->margin) - mui->scrollbar.x -= (int)mui->landscape_entry_margin - (int)mui->margin; + mui->scrollbar.x = x_offset + (int)width - (int)mui->scrollbar.width - + (int)mui->landscape_optimization.border_width - + (int)mui->nav_bar_layout_width; /* Get Y position */ mui->scrollbar.y = (int)header_height + (int)(mui->scroll_y * (float)view_height / mui->content_height); @@ -5082,8 +5983,6 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) float menu_wallpaper_opacity = video_info->menu_wallpaper_opacity; float menu_framebuffer_opacity = video_info->menu_framebuffer_opacity; void *userdata = video_info->userdata; - unsigned width = video_info->width; - unsigned height = video_info->height; unsigned video_width = video_info->width; unsigned video_height = video_info->height; unsigned @@ -5094,16 +5993,12 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) if (!mui) return; - gfx_display_set_viewport(width, height); + gfx_display_set_viewport(video_width, video_height); /* Clear text */ - font_driver_bind_block(mui->font_data.title.font, &mui->font_data.title.raster_block); - font_driver_bind_block(mui->font_data.list.font, &mui->font_data.list.raster_block); - font_driver_bind_block(mui->font_data.hint.font, &mui->font_data.hint.raster_block); - - mui->font_data.title.raster_block.carr.coords.vertices = 0; - mui->font_data.list.raster_block.carr.coords.vertices = 0; - mui->font_data.hint.raster_block.carr.coords.vertices = 0; + materialui_font_bind(&mui->font_data.title); + materialui_font_bind(&mui->font_data.list); + materialui_font_bind(&mui->font_data.hint); /* Update theme colours, if required */ if (mui->color_theme != materialui_color_theme) @@ -5135,32 +6030,31 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) /* Get x offset for list items, required by * menu transition 'slide' animations */ - list_x_offset = (int)(mui->transition_x_offset * (float)((int)width - (int)mui->nav_bar_layout_width)); + list_x_offset = (int)(mui->transition_x_offset * (float)((int)video_width - (int)mui->nav_bar_layout_width)); /* Draw background */ materialui_render_background(mui, userdata, video_width, video_height, libretro_running, menu_wallpaper_opacity, - menu_framebuffer_opacity - ); + menu_framebuffer_opacity); /* Draw landscape border * (does nothing in portrait mode, or if landscape * optimisations are disabled) */ - materialui_render_landscape_border( - mui, userdata, - video_width, - video_height, - width, height, header_height, list_x_offset); + materialui_render_landscape_border(mui, userdata, + video_width, video_height, + header_height, list_x_offset); /* Draw 'highlighted entry' selection box */ materialui_render_selection_highlight( - mui, userdata, video_width, video_height, width, height, header_height, list_x_offset, selection, + mui, userdata, video_width, video_height, + header_height, list_x_offset, selection, mui->colors.list_highlighted_background); /* Draw 'short press' touch feedback highlight */ materialui_render_entry_touch_feedback( - mui, userdata, video_width, video_height, width, height, header_height, list_x_offset, selection); + mui, userdata, video_width, video_height, + header_height, list_x_offset, selection); /* Draw menu list * > Must update scrollbar draw position before @@ -5169,42 +6063,33 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) * like this because we need to track its * position in order to enable fast navigation * via scrollbar 'dragging' */ - materialui_update_scrollbar(mui, width, height, header_height, list_x_offset); - materialui_render_menu_list(mui, - userdata, - video_width, video_height, - width, height, list_x_offset); + materialui_update_scrollbar(mui, video_width, video_height, header_height, list_x_offset); + materialui_render_menu_list(mui, userdata, + video_width, video_height, list_x_offset); /* Flush first layer of text * > Menu list only uses list and hint fonts */ - font_driver_flush(width, height, mui->font_data.list.font); - font_driver_flush(width, height, mui->font_data.hint.font); - - mui->font_data.list.raster_block.carr.coords.vertices = 0; - mui->font_data.hint.raster_block.carr.coords.vertices = 0; + materialui_font_flush(video_width, video_height, &mui->font_data.list); + materialui_font_flush(video_width, video_height, &mui->font_data.hint); /* Draw fullscreen thumbnails, if currently active * > Must be done *after* we flush the first layer * of text */ - materialui_render_fullscreen_thumbnails( - mui, userdata, video_width, - video_height, width, height, header_height, selection); + materialui_render_fullscreen_thumbnails(mui, userdata, + video_width, video_height, header_height, selection); /* Draw title + system bar */ - materialui_render_header(mui, userdata, video_width, - video_height, width, height); + materialui_render_header(mui, userdata, + video_width, video_height); /* Draw navigation bar */ materialui_render_nav_bar(mui, userdata, - video_width, video_height, width, height); + video_width, video_height); /* Flush second layer of text * > Title + system bar only use title and hint fonts */ - font_driver_flush(width, height, mui->font_data.title.font); - font_driver_flush(width, height, mui->font_data.hint.font); - - mui->font_data.title.raster_block.carr.coords.vertices = 0; - mui->font_data.hint.raster_block.carr.coords.vertices = 0; + materialui_font_flush(video_width, video_height, &mui->font_data.title); + materialui_font_flush(video_width, video_height, &mui->font_data.hint); /* Handle onscreen keyboard */ if (menu_input_dialog_get_display_kb()) @@ -5222,13 +6107,16 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) userdata, video_width, video_height, - 0, 0, width, height, width, height, mui->colors.screen_fade); + 0, 0, + video_width, video_height, + video_width, video_height, + mui->colors.screen_fade); /* Draw message box */ snprintf(msg, sizeof(msg), "%s\n%s", label, str); materialui_render_messagebox(mui, userdata, video_width, video_height, - height / 4, msg); + video_height / 4, msg); /* Draw onscreen keyboard */ gfx_display_draw_keyboard( @@ -5243,8 +6131,7 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) /* Flush message box & osk text * > Message box & osk only use list font */ - font_driver_flush(width, height, mui->font_data.list.font); - mui->font_data.list.raster_block.carr.coords.vertices = 0; + materialui_font_flush(video_width, video_height, &mui->font_data.list); } /* Draw message box */ @@ -5257,18 +6144,20 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) userdata, video_width, video_height, - 0, 0, width, height, width, height, mui->colors.screen_fade); + 0, 0, + video_width, video_height, + video_width, video_height, + mui->colors.screen_fade); /* Draw message box */ materialui_render_messagebox(mui, userdata, video_width, video_height, - height / 2, mui->msgbox); + video_height / 2, mui->msgbox); mui->msgbox[0] = '\0'; /* Flush message box text * > Message box only uses list font */ - font_driver_flush(width, height, mui->font_data.list.font); - mui->font_data.list.raster_block.carr.coords.vertices = 0; + materialui_font_flush(video_width, video_height, &mui->font_data.list); } /* Draw mouse cursor */ @@ -5293,8 +6182,8 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) mui->textures.list[MUI_TEXTURE_POINTER], mui->pointer.x, mui->pointer.y, - width, - height); + video_width, + video_height); } /* Undo any transparency adjustments caused @@ -5302,11 +6191,11 @@ static void materialui_frame(void *data, video_frame_info_t *video_info) materialui_colors_reset_transition_alpha(mui); /* Unbind fonts */ - font_driver_bind_block(mui->font_data.title.font, NULL); - font_driver_bind_block(mui->font_data.list.font, NULL); - font_driver_bind_block(mui->font_data.hint.font, NULL); + materialui_font_unbind(&mui->font_data.title); + materialui_font_unbind(&mui->font_data.list); + materialui_font_unbind(&mui->font_data.hint); - gfx_display_unset_viewport(width, height); + gfx_display_unset_viewport(video_width, video_height); } /* Determines current list view type, based on @@ -5371,6 +6260,9 @@ static void materialui_set_list_view_type( case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE: mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE; break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP: + mui->list_view_type = MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP; + break; case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DISABLED: default: mui->list_view_type = MUI_LIST_VIEW_PLAYLIST; @@ -5379,6 +6271,45 @@ static void materialui_set_list_view_type( } } } + + /* List view type has changed -> assign + * relevant function pointers */ + switch (mui->list_view_type) + { + case MUI_LIST_VIEW_PLAYLIST: + materialui_compute_entries_box = materialui_compute_entries_box_playlist_list; + materialui_render_process_entry = materialui_render_process_entry_default; + materialui_render_menu_entry = materialui_render_menu_entry_playlist_list; + materialui_render_selected_entry_aux = NULL; + break; + 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_compute_entries_box = materialui_compute_entries_box_playlist_list; + materialui_render_process_entry = materialui_render_process_entry_playlist_thumb_list; + materialui_render_menu_entry = materialui_render_menu_entry_playlist_list; + materialui_render_selected_entry_aux = NULL; + break; + case MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON: + materialui_compute_entries_box = materialui_compute_entries_box_playlist_dual_icon; + materialui_render_process_entry = materialui_render_process_entry_playlist_dual_icon; + materialui_render_menu_entry = materialui_render_menu_entry_playlist_dual_icon; + materialui_render_selected_entry_aux = NULL; + break; + case MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP: + materialui_compute_entries_box = materialui_compute_entries_box_playlist_desktop; + materialui_render_process_entry = materialui_render_process_entry_playlist_desktop; + materialui_render_menu_entry = materialui_render_menu_entry_playlist_desktop; + materialui_render_selected_entry_aux = materialui_render_selected_entry_aux_playlist_desktop; + break; + case MUI_LIST_VIEW_DEFAULT: + default: + materialui_compute_entries_box = materialui_compute_entries_box_default; + materialui_render_process_entry = materialui_render_process_entry_default; + materialui_render_menu_entry = materialui_render_menu_entry_default; + materialui_render_selected_entry_aux = NULL; + break; + } } /* Determines whether landscape optimisations should @@ -5387,8 +6318,6 @@ static void materialui_set_list_view_type( static void materialui_set_landscape_optimisations_enable( materialui_handle_t *mui) { - bool optimize_landscape_layout = false; - /* In landscape orientations, menu lists are too wide * (to the extent that they are rather uncomfortable * to look at...) @@ -5397,7 +6326,9 @@ static void materialui_set_landscape_optimisations_enable( * the screen */ /* Disable optimisations by default */ - mui->landscape_entry_margin = 0; + mui->landscape_optimization.enabled = false; + mui->landscape_optimization.border_width = 0; + mui->landscape_optimization.entry_margin = 0; /* Early out if current orientation is portrait */ if (mui->is_portrait) @@ -5408,7 +6339,7 @@ static void materialui_set_landscape_optimisations_enable( switch (mui->last_landscape_layout_optimization) { case MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION_ALWAYS: - optimize_landscape_layout = true; + mui->landscape_optimization.enabled = true; break; case MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION_EXCLUDE_THUMBNAIL_VIEWS: @@ -5418,12 +6349,13 @@ static void materialui_set_landscape_optimisations_enable( case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_MEDIUM: case MUI_LIST_VIEW_PLAYLIST_THUMB_LIST_LARGE: case MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON: - optimize_landscape_layout = false; + case MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP: + mui->landscape_optimization.enabled = false; break; case MUI_LIST_VIEW_PLAYLIST: case MUI_LIST_VIEW_DEFAULT: default: - optimize_landscape_layout = true; + mui->landscape_optimization.enabled = true; break; } @@ -5431,12 +6363,12 @@ static void materialui_set_landscape_optimisations_enable( case MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION_DISABLED: default: - optimize_landscape_layout = false; + mui->landscape_optimization.enabled = false; break; } - /* Calculate landscape entry margin size, if required */ - if (optimize_landscape_layout) + /* Calculate landscape border size, if required */ + if (mui->landscape_optimization.enabled) { /* After testing various approaches, it seems that * simply enforcing a 4:3 aspect ratio produces the @@ -5446,9 +6378,87 @@ static void materialui_set_landscape_optimisations_enable( ((float)(mui->last_width - mui->nav_bar_layout_width) - (base_aspect * (float)mui->last_height)) / 2.0f; - /* Note: Want to round down here */ if (landscape_margin > 1.0f) - mui->landscape_entry_margin = (unsigned)landscape_margin; + { + float entry_margin = 0.0f; + float border_width = 0.0f; + + /* When landscape optimisations are active, + * we increase the effective width of the list + * view by up to 'mui->margin', and any remaining + * 'landscape_margin' space is filled with a + * (shadow gradient) border */ + entry_margin = (landscape_margin >= (float)mui->margin) ? + (float)mui->margin : landscape_margin; + border_width = landscape_margin - entry_margin; + + /* Note: In all cases, we want to round down + * when converting these to integers */ + mui->landscape_optimization.entry_margin = (unsigned)entry_margin; + mui->landscape_optimization.border_width = (unsigned)border_width; + } + /* If margin is less than 1px, disable optimisations */ + else + mui->landscape_optimization.enabled = false; + } +} + +/* Initialises status bar, determining current + * enable state based on view mode and user + * configuration */ +static void materialui_status_bar_init( + materialui_handle_t *mui, settings_t *settings) +{ + bool playlist_show_sublabels = settings->bools.playlist_show_sublabels; + gfx_animation_ctx_tag alpha_tag = (uintptr_t)&mui->status_bar.alpha; + + /* Kill any existing fade in animation */ + if (mui->status_bar.enabled || + (mui->status_bar.alpha > 0.0f)) + gfx_animation_kill_by_tag(&alpha_tag); + + /* Reset base parameters */ + mui->status_bar.cached = false; + mui->status_bar.last_selected = 0; + mui->status_bar.delay_timer = 0.0f; + mui->status_bar.alpha = 0.0f; + mui->status_bar.height = 0; + mui->status_bar.str[0] = '\0'; + mui->status_bar.runtime_fallback_str[0] = '\0'; + mui->status_bar.last_played_fallback_str[0] = '\0'; + + /* Determine enable state */ + mui->status_bar.enabled = + (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) && + playlist_show_sublabels; + + if (mui->status_bar.enabled) + { + int n; + + /* Determine status bar height */ + mui->status_bar.height = (unsigned)(((float)mui->font_data.hint.line_height * 1.6f) + 0.5f); + + /* Cache fallback runtime/last played strings + * (Why do we do this here instead of once in + * materialui_init()? Because re-caching the + * values each time allows us to handle changes + * in user interface language settings) */ + n = snprintf(mui->status_bar.runtime_fallback_str, + sizeof(mui->status_bar.runtime_fallback_str), "%s %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_RUNTIME), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED)); + + if ((n < 0) || (n >= 255)) + n = 0; /* Silence GCC warnings... */ + + n = snprintf(mui->status_bar.last_played_fallback_str, + sizeof(mui->status_bar.last_played_fallback_str), "%s %s", + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLIST_SUBLABEL_LAST_PLAYED), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DISABLED)); + + if ((n < 0) || (n >= 255)) + n = 0; /* Silence GCC warnings... */ } } @@ -5522,22 +6532,59 @@ static void materialui_set_thumbnail_dimensions(materialui_handle_t *mui) * and either side of thumbnails) */ int usable_width = (int)mui->last_width - (int)(mui->margin * 3) - - (int)(mui->landscape_entry_margin * 2) - (int)mui->nav_bar_layout_width; + (int)(mui->landscape_optimization.border_width * 2) - + (int)mui->nav_bar_layout_width; - /* > Sanity check */ + /* Sanity check */ if (usable_width < 2) { mui->thumbnail_width_max = 0; mui->thumbnail_height_max = 0; } + else + { + /* Get maximum thumbnail width */ + mui->thumbnail_width_max = (usable_width >> 1); - /* > Get maximum thumbnail width */ - mui->thumbnail_width_max = (usable_width >> 1); + /* Set thumbnail height based on max width */ + mui->thumbnail_height_max = + (unsigned)(((float)mui->thumbnail_width_max * + (1.0f / MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO)) + 0.5f); + } + } + break; - /* > Set thumbnail height based on max width */ - mui->thumbnail_height_max = - (unsigned)(((float)mui->thumbnail_width_max * - (1.0f / MUI_THUMBNAIL_DEFAULT_ASPECT_RATIO)) + 0.5f); + case MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP: + { + /* This view shows two thumbnail icons + * for the selected entry, one below the + * other across the full height of the + * list area */ + + /* > Get total usable height + * (list view height minus vertical padding + * between thumbnails minus status bar height) */ + unsigned header_height = gfx_display_get_header_height(); + int usable_height = (int)mui->last_height - (int)header_height - + (int)(mui->margin * 3) - (int)mui->nav_bar_layout_height - + (int)mui->status_bar.height; + + /* Sanity check */ + if (usable_height < 2) + { + mui->thumbnail_width_max = 0; + mui->thumbnail_height_max = 0; + } + else + { + /* Get maximum thumbnail height */ + mui->thumbnail_height_max = (usable_height >> 1); + + /* 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; @@ -5620,7 +6667,8 @@ static void materialui_set_secondary_thumbnail_enable( { int usable_width = 0; int thumbnail_margin = 0; - bool menu_materialui_dual_thumbnail_list_view_enable = settings->bools.menu_materialui_dual_thumbnail_list_view_enable; + bool menu_materialui_dual_thumbnail_list_view_enable = + settings->bools.menu_materialui_dual_thumbnail_list_view_enable; /* Disable by default */ mui->secondary_thumbnail_enabled = false; @@ -5640,9 +6688,9 @@ static void materialui_set_secondary_thumbnail_enable( * width to display them */ /* > Get total usable width */ - usable_width = - (int)mui->last_width - (int)(mui->margin * 2) - - (int)(mui->landscape_entry_margin * 2) - (int)mui->nav_bar_layout_width; + usable_width = (int)mui->last_width - (int)(mui->margin * 2) - + (int)(mui->landscape_optimization.border_width * 2) - + (int)mui->nav_bar_layout_width; /* > Account for additional padding (margins) when * using portrait orientations */ @@ -5651,8 +6699,7 @@ static void materialui_set_secondary_thumbnail_enable( /* > Account for additional padding (margins) when * using landscape orientations */ else - if (mui->landscape_entry_margin < mui->margin) - thumbnail_margin = (int)(mui->margin - mui->landscape_entry_margin); + thumbnail_margin = (int)mui->margin; /* > Get remaining (text) width after drawing * primary + secondary thumbnails */ @@ -5666,6 +6713,7 @@ static void materialui_set_secondary_thumbnail_enable( } break; case MUI_LIST_VIEW_PLAYLIST_THUMB_DUAL_ICON: + case MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP: /* List view requires secondary thumbnails * > Attempt to force enable, but set * mui->secondary_thumbnail_enabled to 'true' @@ -5702,6 +6750,7 @@ static void materialui_update_list_view(materialui_handle_t *mui) settings->uints.menu_materialui_thumbnail_view_portrait, settings->uints.menu_materialui_thumbnail_view_landscape); materialui_set_landscape_optimisations_enable(mui); + materialui_status_bar_init(mui, settings); materialui_set_thumbnail_dimensions(mui); materialui_set_secondary_thumbnail_enable(mui, settings); @@ -6005,12 +7054,22 @@ static void *materialui_init(void **userdata, bool video_is_threaded) /* Set thumbnail fade duration to default */ gfx_thumbnail_set_fade_duration(-1.0f); + /* Enable fade in animation for missing thumbnails */ + gfx_thumbnail_set_fade_missing(true); + /* Ensure that fullscreen thumbnails are inactive */ mui->show_fullscreen_thumbnails = false; mui->fullscreen_thumbnail_selection = 0; mui->fullscreen_thumbnail_alpha = 0.0f; mui->fullscreen_thumbnail_label[0] = '\0'; + /* Ensure status bar has sane initial values */ + mui->status_bar.enabled = false; + mui->status_bar.height = 0; + mui->status_bar.str[0] = '\0'; + mui->status_bar.runtime_fallback_str[0] = '\0'; + mui->status_bar.last_played_fallback_str[0] = '\0'; + gfx_animation_set_update_time_cb(materialui_menu_animation_update_time); return menu; @@ -7289,7 +8348,7 @@ static int materialui_pointer_down(void *userdata, /* Check whether pointer down event is within * vertical list region */ if ((y < header_height) || - (y > height - mui->nav_bar_layout_height)) + (y > height - mui->nav_bar_layout_height - mui->status_bar.height)) return 0; /* Determine horizontal width of scrollbar @@ -7301,12 +8360,12 @@ static int materialui_pointer_down(void *userdata, * screen width), need to increase 'grab box' size * (otherwise the active region is too close to the * navigation bar) */ - if (!mui->is_portrait) + if (!mui->is_portrait && mui->last_auto_rotate_nav_bar) { - if (mui->landscape_entry_margin <= mui->margin) + if (mui->landscape_optimization.border_width <= mui->margin) drag_margin_horz += (int)mui->margin; - else if (mui->landscape_entry_margin <= 2 * mui->margin) - drag_margin_horz += (int)((2 * mui->margin) - mui->landscape_entry_margin); + else if (mui->landscape_optimization.border_width <= 2 * mui->margin) + drag_margin_horz += (int)((2 * mui->margin) - mui->landscape_optimization.entry_margin); } /* Check whether pointer X position is within @@ -7364,7 +8423,8 @@ static int materialui_pointer_up_swipe_horz_plain_list( else { float content_height_fraction = mui->content_height * 0.1f; - float display_height = (int)height - (int)header_height - (int)mui->nav_bar_layout_height; + float display_height = (int)height - (int)header_height - + (int)mui->nav_bar_layout_height - (int)mui->status_bar.height; float scroll_offset = (display_height > content_height_fraction) ? display_height : content_height_fraction; @@ -7655,10 +8715,49 @@ static int materialui_pointer_up(void *userdata, return materialui_menu_entry_action(mui, entry, selection, MENU_ACTION_CANCEL); } /* Tap/press menu item: Activate and/or select item */ - else if ((ptr < entries_end) && - (x > mui->landscape_entry_margin) && - (x < width - mui->landscape_entry_margin - mui->nav_bar_layout_width)) + else if ((y < height - mui->nav_bar_layout_height - mui->status_bar.height) && + (ptr < entries_end) && + (x > mui->landscape_optimization.border_width) && + (x < width - mui->landscape_optimization.border_width - mui->nav_bar_layout_width)) { + file_list_t *list = NULL; + materialui_node_t *node = NULL; + int entry_x; + int entry_y; + + /* Special case: If we are currently viewing + * a 'desktop'-layout playlist, pressing the + * sidebar toggles fullscreen thumbnails */ + if ((mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) && + (x < mui->landscape_optimization.border_width + mui->thumbnail_width_max + (mui->margin * 2))) + { + materialui_show_fullscreen_thumbnails(mui, selection); + break; + } + + /* Get node (entry) associated with current + * pointer item */ + list = menu_entries_get_selection_buf_ptr(0); + if (!list) + break; + + node = (materialui_node_t*)file_list_get_userdata_at_offset(list, ptr); + if (!node) + break; + + /* Get pointer item x/y position */ + entry_x = (int)node->x; + entry_y = (int)((float)header_height - mui->scroll_y + node->y); + + /* Check if pointer location is within the + * bounds of the pointer item */ + if ((x < entry_x) || + (x > (entry_x + node->entry_width)) || + (y < entry_y) || + (y > (entry_y + node->entry_height))) + break; + + /* Pointer input is valid - perform action */ if (gesture == MENU_INPUT_GESTURE_TAP) { /* A 'tap' always produces a menu action */ @@ -7803,8 +8902,10 @@ static void materialui_list_insert( node->has_icon = false; node->icon_texture_index = 0; + node->entry_width = 0.0f; node->entry_height = 0.0f; node->text_height = 0.0f; + node->x = 0.0f; node->y = 0.0f; if (!thumbnail_reset) @@ -8342,6 +9443,8 @@ static void materialui_get_thumbnail_system(void *userdata, char *s, size_t len) static void materialui_refresh_thumbnail_image(void *userdata, unsigned i) { materialui_handle_t *mui = (materialui_handle_t*)userdata; + size_t selection = menu_navigation_get_selection(); + bool refresh_enabled = false; if (!mui) return; @@ -8352,9 +9455,16 @@ static void materialui_refresh_thumbnail_image(void *userdata, unsigned i) (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST)) return; - /* Only refresh thumbnails if the current entry is - * on-screen */ - if (materialui_entry_onscreen(mui, (size_t)i)) + /* Only refresh thumbnails if: + * - This is *not* a 'desktop'-layout playlist and + * the current entry is on-screen + * - This *is* a 'desktop'-layout playlist and current + * entry is selected */ + refresh_enabled = (mui->list_view_type == MUI_LIST_VIEW_PLAYLIST_THUMB_DESKTOP) ? + (i == selection) : + materialui_entry_onscreen(mui, (size_t)i); + + if (refresh_enabled) { file_list_t *list = menu_entries_get_selection_buf_ptr(0); materialui_node_t *node = NULL; diff --git a/menu/drivers/ozone/ozone.c b/menu/drivers/ozone/ozone.c index 35ee3b66f0..af435345ec 100644 --- a/menu/drivers/ozone/ozone.c +++ b/menu/drivers/ozone/ozone.c @@ -195,6 +195,7 @@ static void *ozone_init(void **userdata, bool video_is_threaded) gfx_thumbnail_set_stream_delay(-1.0f); gfx_thumbnail_set_fade_duration(-1.0f); + gfx_thumbnail_set_fade_missing(false); ozone_sidebar_update_collapse(ozone, false); diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index 9aa8f4d8cb..38f198701c 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -5380,6 +5380,7 @@ static void *xmb_init(void **userdata, bool video_is_threaded) gfx_thumbnail_set_stream_delay(-1.0f); gfx_thumbnail_set_fade_duration(-1.0f); + gfx_thumbnail_set_fade_missing(false); xmb->use_ps3_layout = xmb_use_ps3_layout(settings, width, height); xmb->last_use_ps3_layout = xmb->use_ps3_layout; diff --git a/menu/menu_defines.h b/menu/menu_defines.h index 166bd6ba59..58c294c581 100644 --- a/menu/menu_defines.h +++ b/menu/menu_defines.h @@ -205,6 +205,7 @@ enum materialui_thumbnail_view_landscape MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_SMALL, MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_MEDIUM, MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE, + MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP, MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LAST }; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 58ebff44f6..5764bacf92 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -4133,6 +4133,11 @@ static void setting_get_string_representation_uint_materialui_menu_thumbnail_vie msg_hash_to_str( MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_LIST_LARGE), len); break; + case MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP), len); + break; default: break; } diff --git a/msg_hash.h b/msg_hash.h index 5c68f629fc..98d28d8847 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -660,6 +660,7 @@ enum msg_hash_enums 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_MATERIALUI_THUMBNAIL_VIEW_LANDSCAPE_DESKTOP, MENU_LABEL(MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION), MENU_ENUM_LABEL_VALUE_MATERIALUI_LANDSCAPE_LAYOUT_OPTIMIZATION_DISABLED, diff --git a/runtime_file.c b/runtime_file.c index bd096dce82..db17eace70 100644 --- a/runtime_file.c +++ b/runtime_file.c @@ -1020,7 +1020,7 @@ void runtime_update_playlist( runtime_log_t *runtime_log = NULL; const struct playlist_entry *entry = NULL; struct playlist_entry update_entry = {0}; -#if defined(HAVE_MENU) && defined(HAVE_OZONE) +#if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI)) const char *menu_ident = menu_driver_ident(); #endif @@ -1078,15 +1078,20 @@ void runtime_update_playlist( free(runtime_log); } -#if defined(HAVE_MENU) && defined(HAVE_OZONE) - /* Ozone requires runtime/last played strings to be - * populated even when no runtime is recorded */ - if (string_is_equal(menu_ident, "ozone")) +#if defined(HAVE_MENU) && (defined(HAVE_OZONE) || defined(HAVE_MATERIALUI)) + /* Ozone and GLUI require runtime/last played strings + * to be populated even when no runtime is recorded */ + if (update_entry.runtime_status != PLAYLIST_RUNTIME_VALID) { - if (update_entry.runtime_status != PLAYLIST_RUNTIME_VALID) + if (string_is_equal(menu_ident, "ozone") || + string_is_equal(menu_ident, "glui")) { runtime_log_get_runtime_str(NULL, runtime_str, sizeof(runtime_str)); runtime_log_get_last_played_str(NULL, last_played_str, sizeof(last_played_str), timedate_style); + + /* While runtime data does not exist, the playlist + * entry does now contain valid information... */ + update_entry.runtime_status = PLAYLIST_RUNTIME_VALID; } } #endif