/* RetroArch - A frontend for libretro. * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2014-2017 - Jean-André Santoni * Copyright (C) 2016-2019 - Brad Parker * Copyright (C) 2018 - Alfredo Monclús * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CONFIG_H #include "../../config.h" #endif #include "../../frontend/frontend_driver.h" #include "../menu_driver.h" #include "../menu_entries.h" #include "../../gfx/gfx_animation.h" #include "../../gfx/gfx_thumbnail_path.h" #include "../../gfx/gfx_thumbnail.h" #include "../../core_info.h" #include "../../core.h" #include "../../input/input_osk.h" #include "../../verbosity.h" #include "../../configuration.h" #include "../../tasks/tasks_internal.h" #include "../../cheevos/badges.h" #include "../../content.h" #define XMB_RIBBON_ROWS 64 #define XMB_RIBBON_COLS 64 #define XMB_RIBBON_VERTICES 2*XMB_RIBBON_COLS*XMB_RIBBON_ROWS-2*XMB_RIBBON_COLS #ifndef XMB_DELAY #define XMB_DELAY 166.66667f #endif /* Specifies minimum period (in usec) between * tab switch events when input repeat is * active (i.e. when navigating between top level * menu categories by *holding* left/right on * RetroPad or keyboard) * > Note: We want to set a value of 100 ms * here, but doing so leads to bad pacing when * running at 60 Hz (due to random frame time * deviations - input repeat cycles always take * slightly more or less than 100 ms, so tab * switches occur every n or (n + 1) frames, * which gives the appearance of stuttering). * Reducing the delay by 1 ms accommodates * any timing fluctuations, resulting in * smooth motion */ #define XMB_TAB_SWITCH_REPEAT_DELAY 99000 #if 0 #define XMB_DEBUG #endif /* NOTE: If you change this you HAVE to update * xmb_alloc_node() and xmb_copy_node() */ typedef struct { float alpha; float label_alpha; float zoom; float x; float y; uintptr_t icon; uintptr_t content_icon; char *fullpath; } xmb_node_t; enum { XMB_TEXTURE_MAIN_MENU = 0, XMB_TEXTURE_SETTINGS, XMB_TEXTURE_HISTORY, XMB_TEXTURE_FAVORITES, XMB_TEXTURE_MUSICS, #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) XMB_TEXTURE_MOVIES, #endif #ifdef HAVE_NETWORKING XMB_TEXTURE_NETPLAY, XMB_TEXTURE_ROOM, XMB_TEXTURE_ROOM_LAN, XMB_TEXTURE_ROOM_RELAY, #endif #ifdef HAVE_IMAGEVIEWER XMB_TEXTURE_IMAGES, #endif XMB_TEXTURE_SETTING, XMB_TEXTURE_SUBSETTING, XMB_TEXTURE_ARROW, XMB_TEXTURE_RUN, XMB_TEXTURE_CLOSE, XMB_TEXTURE_RESUME, XMB_TEXTURE_SAVESTATE, XMB_TEXTURE_LOADSTATE, XMB_TEXTURE_UNDO, XMB_TEXTURE_CORE_INFO, XMB_TEXTURE_BLUETOOTH, XMB_TEXTURE_WIFI, XMB_TEXTURE_CORE_OPTIONS, XMB_TEXTURE_INPUT_REMAPPING_OPTIONS, XMB_TEXTURE_CHEAT_OPTIONS, XMB_TEXTURE_DISK_OPTIONS, XMB_TEXTURE_SHADER_OPTIONS, XMB_TEXTURE_ACHIEVEMENT_LIST, XMB_TEXTURE_SCREENSHOT, XMB_TEXTURE_RELOAD, XMB_TEXTURE_RENAME, XMB_TEXTURE_FILE, XMB_TEXTURE_FOLDER, XMB_TEXTURE_ZIP, XMB_TEXTURE_FAVORITE, XMB_TEXTURE_ADD_FAVORITE, XMB_TEXTURE_MUSIC, XMB_TEXTURE_IMAGE, XMB_TEXTURE_MOVIE, XMB_TEXTURE_CORE, XMB_TEXTURE_RDB, XMB_TEXTURE_CURSOR, XMB_TEXTURE_SWITCH_ON, XMB_TEXTURE_SWITCH_OFF, XMB_TEXTURE_CLOCK, XMB_TEXTURE_BATTERY_FULL, XMB_TEXTURE_BATTERY_CHARGING, XMB_TEXTURE_BATTERY_80, XMB_TEXTURE_BATTERY_60, XMB_TEXTURE_BATTERY_40, XMB_TEXTURE_BATTERY_20, XMB_TEXTURE_POINTER, XMB_TEXTURE_ADD, XMB_TEXTURE_KEY, XMB_TEXTURE_KEY_HOVER, XMB_TEXTURE_DIALOG_SLICE, XMB_TEXTURE_ACHIEVEMENTS, XMB_TEXTURE_AUDIO, XMB_TEXTURE_EXIT, XMB_TEXTURE_FRAMESKIP, XMB_TEXTURE_INFO, XMB_TEXTURE_HELP, XMB_TEXTURE_NETWORK, XMB_TEXTURE_POWER, XMB_TEXTURE_SAVING, XMB_TEXTURE_UPDATER, XMB_TEXTURE_VIDEO, XMB_TEXTURE_RECORD, XMB_TEXTURE_INPUT_SETTINGS, XMB_TEXTURE_MIXER, XMB_TEXTURE_LOG, XMB_TEXTURE_OSD, XMB_TEXTURE_UI, XMB_TEXTURE_USER, XMB_TEXTURE_PRIVACY, XMB_TEXTURE_LATENCY, XMB_TEXTURE_DRIVERS, XMB_TEXTURE_PLAYLIST, XMB_TEXTURE_QUICKMENU, XMB_TEXTURE_REWIND, XMB_TEXTURE_OVERLAY, XMB_TEXTURE_OVERRIDE, XMB_TEXTURE_NOTIFICATIONS, XMB_TEXTURE_STREAM, XMB_TEXTURE_SHUTDOWN, XMB_TEXTURE_INPUT_DPAD_U, XMB_TEXTURE_INPUT_DPAD_D, XMB_TEXTURE_INPUT_DPAD_L, XMB_TEXTURE_INPUT_DPAD_R, XMB_TEXTURE_INPUT_STCK_U, XMB_TEXTURE_INPUT_STCK_D, XMB_TEXTURE_INPUT_STCK_L, XMB_TEXTURE_INPUT_STCK_R, XMB_TEXTURE_INPUT_STCK_P, XMB_TEXTURE_INPUT_SELECT, XMB_TEXTURE_INPUT_START, XMB_TEXTURE_INPUT_BTN_U, XMB_TEXTURE_INPUT_BTN_D, XMB_TEXTURE_INPUT_BTN_L, XMB_TEXTURE_INPUT_BTN_R, XMB_TEXTURE_INPUT_LB, XMB_TEXTURE_INPUT_RB, XMB_TEXTURE_INPUT_LT, XMB_TEXTURE_INPUT_RT, XMB_TEXTURE_INPUT_ADC, XMB_TEXTURE_INPUT_BIND_ALL, XMB_TEXTURE_INPUT_MOUSE, XMB_TEXTURE_INPUT_LGUN, XMB_TEXTURE_INPUT_TURBO, XMB_TEXTURE_CHECKMARK, XMB_TEXTURE_MENU_ADD, XMB_TEXTURE_BRIGHTNESS, XMB_TEXTURE_PAUSE, XMB_TEXTURE_DEFAULT, XMB_TEXTURE_DEFAULT_CONTENT, XMB_TEXTURE_MENU_APPLY_TOGGLE, XMB_TEXTURE_MENU_APPLY_COG, XMB_TEXTURE_DISC, XMB_TEXTURE_LAST }; enum { XMB_SYSTEM_TAB_MAIN = 0, XMB_SYSTEM_TAB_SETTINGS, XMB_SYSTEM_TAB_HISTORY, XMB_SYSTEM_TAB_FAVORITES, XMB_SYSTEM_TAB_MUSIC, #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) XMB_SYSTEM_TAB_VIDEO, #endif #ifdef HAVE_IMAGEVIEWER XMB_SYSTEM_TAB_IMAGES, #endif #ifdef HAVE_NETWORKING XMB_SYSTEM_TAB_NETPLAY, #endif XMB_SYSTEM_TAB_ADD, /* End of this enum - use the last one to determine num of possible tabs */ XMB_SYSTEM_TAB_MAX_LENGTH }; typedef struct xmb_handle { bool mouse_show; bool use_ps3_layout; bool last_use_ps3_layout; bool assets_missing; bool is_playlist; bool is_db_manager_list; bool is_file_list; bool is_quick_menu; uint8_t system_tab_end; uint8_t tabs[XMB_SYSTEM_TAB_MAX_LENGTH]; int depth; int old_depth; int icon_size; int cursor_size; size_t categories_selection_ptr; size_t categories_selection_ptr_old; size_t selection_ptr_old; unsigned categories_active_idx; unsigned categories_active_idx_old; float x; float alpha; float above_subitem_offset; float above_item_offset; float active_item_factor; float under_item_offset; float shadow_offset; float font_size; float font2_size; float last_scale_factor; float margins_screen_left; float margins_screen_top; float margins_setting_left; float margins_title_left; float margins_title_top; float margins_title_bottom; float margins_label_left; float margins_label_top; float icon_spacing_horizontal; float icon_spacing_vertical; float items_active_alpha; float items_active_zoom; float items_passive_alpha; float items_passive_zoom; float margins_dialog; float margins_slice; float textures_arrow_alpha; float categories_x_pos; float categories_passive_alpha; float categories_passive_zoom; float categories_active_zoom; float categories_active_alpha; char title_name[255]; char *box_message; char *bg_file_path; file_list_t *selection_buf_old; file_list_t *horizontal_list; struct { uintptr_t bg; uintptr_t list[XMB_TEXTURE_LAST]; } textures; xmb_node_t main_menu_node; #ifdef HAVE_IMAGEVIEWER xmb_node_t images_tab_node; #endif xmb_node_t music_tab_node; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) xmb_node_t video_tab_node; #endif xmb_node_t settings_tab_node; xmb_node_t history_tab_node; xmb_node_t favorites_tab_node; xmb_node_t add_tab_node; xmb_node_t netplay_tab_node; menu_input_pointer_t pointer; font_data_t *font; font_data_t *font2; video_font_raster_block_t raster_block; video_font_raster_block_t raster_block2; gfx_thumbnail_path_data_t *thumbnail_path_data; struct { gfx_thumbnail_t right; gfx_thumbnail_t left; gfx_thumbnail_t savestate; } thumbnails; /* These have to be huge, because global->name.savestate * has a hard-coded size of 8192... * (the extra space here is required to silence compiler * warnings...) */ char savestate_thumbnail_file_path[8204]; char prev_savestate_thumbnail_file_path[8204]; bool fullscreen_thumbnails_available; bool show_fullscreen_thumbnails; float fullscreen_thumbnail_alpha; size_t fullscreen_thumbnail_selection; char fullscreen_thumbnail_label[255]; /* Keeps track of the last time tabs were switched * via a MENU_ACTION_LEFT/MENU_ACTION_RIGHT event */ retro_time_t last_tab_switch_time; } xmb_handle_t; float scale_mod[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; static float coord_shadow[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static float coord_black[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static float coord_white[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; static float item_color[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; float gradient_dark_purple[16] = { 20/255.0, 13/255.0, 20/255.0, 1.0, 20/255.0, 13/255.0, 20/255.0, 1.0, 92/255.0, 44/255.0, 92/255.0, 1.0, 148/255.0, 90/255.0, 148/255.0, 1.0, }; float gradient_midnight_blue[16] = { 44/255.0, 62/255.0, 80/255.0, 1.0, 44/255.0, 62/255.0, 80/255.0, 1.0, 44/255.0, 62/255.0, 80/255.0, 1.0, 44/255.0, 62/255.0, 80/255.0, 1.0, }; float gradient_golden[16] = { 174/255.0, 123/255.0, 44/255.0, 1.0, 205/255.0, 174/255.0, 84/255.0, 1.0, 58/255.0, 43/255.0, 24/255.0, 1.0, 58/255.0, 43/255.0, 24/255.0, 1.0, }; float gradient_legacy_red[16] = { 171/255.0, 70/255.0, 59/255.0, 1.0, 171/255.0, 70/255.0, 59/255.0, 1.0, 190/255.0, 80/255.0, 69/255.0, 1.0, 190/255.0, 80/255.0, 69/255.0, 1.0, }; float gradient_electric_blue[16] = { 1/255.0, 2/255.0, 67/255.0, 1.0, 1/255.0, 73/255.0, 183/255.0, 1.0, 1/255.0, 93/255.0, 194/255.0, 1.0, 3/255.0, 162/255.0, 254/255.0, 1.0, }; float gradient_apple_green[16] = { 102/255.0, 134/255.0, 58/255.0, 1.0, 122/255.0, 131/255.0, 52/255.0, 1.0, 82/255.0, 101/255.0, 35/255.0, 1.0, 63/255.0, 95/255.0, 30/255.0, 1.0, }; float gradient_undersea[16] = { 23/255.0, 18/255.0, 41/255.0, 1.0, 30/255.0, 72/255.0, 114/255.0, 1.0, 52/255.0, 88/255.0, 110/255.0, 1.0, 69/255.0, 125/255.0, 140/255.0, 1.0, }; float gradient_volcanic_red[16] = { 1.0, 0.0, 0.1, 1.00, 1.0, 0.1, 0.0, 1.00, 0.1, 0.0, 0.1, 1.00, 0.1, 0.0, 0.1, 1.00, }; float gradient_dark[16] = { 0.1, 0.1, 0.1, 1.00, 0.1, 0.1, 0.1, 1.00, 0.0, 0.0, 0.0, 1.00, 0.0, 0.0, 0.0, 1.00, }; float gradient_light[16] = { 1.0, 1.0, 1.0, 1.00, 1.0, 1.0, 1.0, 1.00, 1.0, 1.0, 1.0, 1.00, 1.0, 1.0, 1.0, 1.00, }; float gradient_morning_blue[16] = { 221/255.0, 241/255.0, 254/255.0, 1.00, 135/255.0, 206/255.0, 250/255.0, 1.00, 1.0, 1.0, 1.0, 1.00, 170/255.0, 200/255.0, 252/255.0, 1.00, }; float gradient_sunbeam[16] = { 20/255.0, 13/255.0, 20/255.0, 1.0, 30/255.0, 72/255.0, 114/255.0, 1.0, 1.0, 1.0, 1.0, 1.00, 0.1, 0.0, 0.1, 1.00, }; float gradient_lime_green[16] = { 209/255.0, 255/255.0, 82/255.0, 1.0, 146/255.0, 232/255.0, 66/255.0, 1.0, 82/255.0, 101/255.0, 35/255.0, 1.0, 63/255.0, 95/255.0, 30/255.0, 1.0, }; float gradient_pikachu_yellow[16] = { 63/255.0, 63/255.0, 1/255.0, 1.0, 174/255.0, 174/255.0, 1/255.0, 1.0, 191/255.0, 194/255.0, 1/255.0, 1.0, 254/255.0, 221/255.0, 3/255.0, 1.0, }; float gradient_gamecube_purple[16] = { 40/255.0, 20/255.0, 91/255.0, 1.0, 160/255.0, 140/255.0, 211/255.0, 1.0, 107/255.0, 92/255.0, 177/255.0, 1.0, 84/255.0, 71/255.0, 132/255.0, 1.0, }; float gradient_famicom_red[16] = { 255/255.0, 191/255.0, 171/255.0, 1.0, 119/255.0, 49/255.0, 28/255.0, 1.0, 148/255.0, 10/255.0, 36/255.0, 1.0, 206/255.0, 126/255.0, 110/255.0, 1.0, }; float gradient_flaming_hot[16] = { 231/255.0, 53/255.0, 53/255.0, 1.0, 242/255.0, 138/255.0, 97/255.0, 1.0, 236/255.0, 97/255.0, 76/255.0, 1.0, 255/255.0, 125/255.0, 3/255.0, 1.0, }; float gradient_ice_cold[16] = { 66/255.0, 183/255.0, 229/255.0, 1.0, 29/255.0, 164/255.0, 255/255.0, 1.0, 176/255.0, 255/255.0, 247/255.0, 1.0, 174/255.0, 240/255.0, 255/255.0, 1.0, }; float gradient_midgar[16] = { 255/255.0, 0/255.0, 0/255.0, 1.0, 0/255.0, 0/255.0, 255/255.0, 1.0, 0/255.0, 255/255.0, 0/255.0, 1.0, 32/255.0, 32/255.0, 32/255.0, 1.0, }; static void xmb_calculate_visible_range(const xmb_handle_t *xmb, unsigned height, size_t list_size, unsigned current, unsigned *first, unsigned *last); const char* xmb_theme_ident(void) { settings_t *settings = config_get_ptr(); unsigned menu_xmb_theme = settings->uints.menu_xmb_theme; switch (menu_xmb_theme) { case XMB_ICON_THEME_FLATUI: return "flatui"; case XMB_ICON_THEME_RETROACTIVE: return "retroactive"; case XMB_ICON_THEME_RETROSYSTEM: return "retrosystem"; case XMB_ICON_THEME_PIXEL: return "pixel"; case XMB_ICON_THEME_NEOACTIVE: return "neoactive"; case XMB_ICON_THEME_SYSTEMATIC: return "systematic"; case XMB_ICON_THEME_DOTART: return "dot-art"; case XMB_ICON_THEME_CUSTOM: return "custom"; case XMB_ICON_THEME_MONOCHROME_INVERTED: return "monochrome"; case XMB_ICON_THEME_AUTOMATIC: return "automatic"; case XMB_ICON_THEME_AUTOMATIC_INVERTED: return "automatic"; case XMB_ICON_THEME_MONOCHROME: default: break; } return "monochrome"; } /* NOTE: This exists because calloc()ing xmb_node_t is expensive * when you can have big lists like MAME and fba playlists */ static xmb_node_t *xmb_alloc_node(void) { xmb_node_t *node = (xmb_node_t*)malloc(sizeof(*node)); if (!node) return NULL; node->alpha = node->label_alpha = 0; node->zoom = node->x = node->y = 0; node->icon = node->content_icon = 0; node->fullpath = NULL; return node; } static void xmb_free_node(xmb_node_t *node) { if (!node) return; if (node->fullpath) free(node->fullpath); node->fullpath = NULL; free(node); } /** * @brief frees all xmb_node_t in a file_list_t * * file_list_t asumes userdata holds a simple structure and * free()'s it. Can't change this at the time because other * code depends on this behavior. * * @param list * @param actiondata whether to free actiondata too */ static void xmb_free_list_nodes(file_list_t *list, bool actiondata) { unsigned i, size = (unsigned)file_list_get_size(list); for (i = 0; i < size; ++i) { xmb_free_node((xmb_node_t*)file_list_get_userdata_at_offset(list, i)); /* file_list_set_userdata() doesn't accept NULL */ list->list[i].userdata = NULL; if (actiondata) file_list_free_actiondata(list, i); } } static xmb_node_t *xmb_copy_node(const xmb_node_t *old_node) { xmb_node_t *new_node = (xmb_node_t*)malloc(sizeof(*new_node)); if (!new_node) return NULL; *new_node = *old_node; new_node->fullpath = old_node->fullpath ? strdup(old_node->fullpath) : NULL; return new_node; } static float *xmb_gradient_ident(unsigned xmb_color_theme) { switch (xmb_color_theme) { case XMB_THEME_DARK_PURPLE: return &gradient_dark_purple[0]; case XMB_THEME_MIDNIGHT_BLUE: return &gradient_midnight_blue[0]; case XMB_THEME_GOLDEN: return &gradient_golden[0]; case XMB_THEME_ELECTRIC_BLUE: return &gradient_electric_blue[0]; case XMB_THEME_APPLE_GREEN: return &gradient_apple_green[0]; case XMB_THEME_UNDERSEA: return &gradient_undersea[0]; case XMB_THEME_VOLCANIC_RED: return &gradient_volcanic_red[0]; case XMB_THEME_DARK: return &gradient_dark[0]; case XMB_THEME_LIGHT: return &gradient_light[0]; case XMB_THEME_MORNING_BLUE: return &gradient_morning_blue[0]; case XMB_THEME_SUNBEAM: return &gradient_sunbeam[0]; case XMB_THEME_LIME: return &gradient_lime_green[0]; case XMB_THEME_MIDGAR: return &gradient_midgar[0]; case XMB_THEME_PIKACHU_YELLOW: return &gradient_pikachu_yellow[0]; case XMB_THEME_GAMECUBE_PURPLE: return &gradient_gamecube_purple[0]; case XMB_THEME_FAMICOM_RED: return &gradient_famicom_red[0]; case XMB_THEME_FLAMING_HOT: return &gradient_flaming_hot[0]; case XMB_THEME_ICE_COLD: return &gradient_ice_cold[0]; case XMB_THEME_LEGACY_RED: default: break; } return &gradient_legacy_red[0]; } static size_t xmb_list_get_selection(void *data) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return 0; return xmb->categories_selection_ptr; } static size_t xmb_list_get_size(void *data, enum menu_list_type type) { xmb_handle_t *xmb = (xmb_handle_t*)data; switch (type) { case MENU_LIST_PLAIN: return menu_entries_get_stack_size(0); case MENU_LIST_HORIZONTAL: if (xmb && xmb->horizontal_list) return file_list_get_size(xmb->horizontal_list); break; case MENU_LIST_TABS: return xmb->system_tab_end; } return 0; } static void *xmb_list_get_entry(void *data, enum menu_list_type type, unsigned i) { size_t list_size = 0; xmb_handle_t *xmb = (xmb_handle_t*)data; switch (type) { case MENU_LIST_PLAIN: { file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); list_size = menu_entries_get_stack_size(0); if (i < list_size) return (void*)&menu_stack->list[i]; } break; case MENU_LIST_HORIZONTAL: if (xmb && xmb->horizontal_list) list_size = file_list_get_size(xmb->horizontal_list); if (i < list_size) return (void*)&xmb->horizontal_list->list[i]; break; default: break; } return NULL; } static INLINE float xmb_item_y(const xmb_handle_t *xmb, int i, size_t current) { float iy = xmb->icon_spacing_vertical; if (i < (int)current) if (xmb->depth > 1) iy *= (i - (int)current + xmb->above_subitem_offset); else iy *= (i - (int)current + xmb->above_item_offset); else iy *= (i - (int)current + xmb->under_item_offset); if (i == (int)current) iy = xmb->icon_spacing_vertical * xmb->active_item_factor; return iy; } static void xmb_draw_icon( void *userdata, unsigned video_width, unsigned video_height, bool xmb_shadows_enable, int icon_size, math_matrix_4x4 *mymat, uintptr_t texture, float x, float y, unsigned width, unsigned height, float alpha, float rotation, float scale_factor, float *color, float shadow_offset) { gfx_display_ctx_draw_t draw; struct video_coords coords; if ( (x < (-icon_size / 2.0f)) || (x > width) || (y < (icon_size / 2.0f)) || (y > height + icon_size) ) return; coords.vertices = 4; coords.vertex = NULL; coords.tex_coord = NULL; coords.lut_tex_coord = NULL; draw.width = icon_size; draw.height = icon_size; draw.rotation = rotation; draw.scale_factor = scale_factor; #if defined(VITA) || defined(WIIU) draw.width *= scale_factor; draw.height *= scale_factor; #endif draw.coords = &coords; draw.matrix_data = mymat; draw.texture = texture; draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; draw.pipeline.id = 0; if (xmb_shadows_enable) { gfx_display_set_alpha(coord_shadow, color[3] * 0.35f); coords.color = coord_shadow; draw.x = x + shadow_offset; draw.y = height - y - shadow_offset; #if defined(VITA) || defined(WIIU) if (scale_factor < 1) { draw.x = draw.x + (icon_size-draw.width)/2; draw.y = draw.y + (icon_size-draw.width)/2; } #endif gfx_display_draw(&draw, userdata, video_width, video_height); } coords.color = (const float*)color; draw.x = x; draw.y = height - y; #if defined(VITA) || defined(WIIU) if (scale_factor < 1) { draw.x = draw.x + (icon_size-draw.width)/2; draw.y = draw.y + (icon_size-draw.width)/2; } #endif gfx_display_draw(&draw, userdata, video_width, video_height); } static void xmb_draw_text( bool xmb_shadows_enable, xmb_handle_t *xmb, const char *str, float x, float y, float scale_factor, float alpha, enum text_alignment text_align, unsigned width, unsigned height, font_data_t* font) { uint32_t color; uint8_t a8; settings_t *settings; if (alpha > xmb->alpha) alpha = xmb->alpha; a8 = 255 * alpha; /* Avoid drawing 100% transparent text */ if (a8 == 0) return; settings = config_get_ptr(); color = FONT_COLOR_RGBA( settings->uints.menu_font_color_red, settings->uints.menu_font_color_green, settings->uints.menu_font_color_blue, a8); gfx_display_draw_text(font, str, x, y, width, height, color, text_align, scale_factor, xmb_shadows_enable, xmb->shadow_offset, false); } static void xmb_messagebox(void *data, const char *message) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb || string_is_empty(message)) return; xmb->box_message = strdup(message); } static void xmb_render_messagebox_internal( void *userdata, unsigned video_width, unsigned video_height, xmb_handle_t *xmb, const char *message) { unsigned i, y_position; int x, y, longest_width = 0; float line_height = 0; int usable_width = 0; struct string_list *list = NULL; char wrapped_message[MENU_SUBLABEL_MAX_LENGTH]; wrapped_message[0] = '\0'; /* Sanity check */ if (string_is_empty(message) || !xmb || !xmb->font) goto end; usable_width = (int)video_width - (xmb->margins_dialog * 8); if (usable_width < 1) goto end; /* Split message into lines */ word_wrap( wrapped_message, message, usable_width / (xmb->font_size * 0.6f), true, 0); list = string_split(wrapped_message, "\n"); if (!list || list->elems == 0) goto end; line_height = xmb->font->size * 1.2; y_position = video_height / 2; if (menu_input_dialog_get_display_kb()) y_position = video_height / 4; x = video_width / 2; y = y_position - (list->size-1) * line_height / 2; /* find the longest line width */ for (i = 0; i < list->size; i++) { const char *msg = list->elems[i].data; if (!string_is_empty(msg)) { int width = font_driver_get_message_width( xmb->font, msg, (unsigned)strlen(msg), 1); longest_width = (width > longest_width) ? width : longest_width; } } gfx_display_blend_begin(userdata); gfx_display_draw_texture_slice( userdata, video_width, video_height, x - longest_width/2 - xmb->margins_dialog, y + xmb->margins_slice - xmb->margins_dialog, 256, 256, longest_width + xmb->margins_dialog * 2, line_height * list->size + xmb->margins_dialog * 2, video_width, video_height, NULL, xmb->margins_slice, xmb->last_scale_factor, xmb->textures.list[XMB_TEXTURE_DIALOG_SLICE]); for (i = 0; i < list->size; i++) { const char *msg = list->elems[i].data; if (msg) gfx_display_draw_text(xmb->font, msg, x - longest_width/2.0, y + (i+0.75) * line_height, video_width, video_height, 0x444444ff, TEXT_ALIGN_LEFT, 1.0f, false, 0.0f, false); } if (menu_input_dialog_get_display_kb()) gfx_display_draw_keyboard( userdata, video_width, video_height, xmb->textures.list[XMB_TEXTURE_KEY_HOVER], xmb->font, input_event_get_osk_grid(), input_event_get_osk_ptr(), 0xffffffff); end: if (list) string_list_free(list); } static void xmb_update_savestate_thumbnail_path(void *data, unsigned i) { settings_t *settings = config_get_ptr(); xmb_handle_t *xmb = (xmb_handle_t*)data; int state_slot = settings->ints.state_slot; bool savestate_thumbnail_enable = settings->bools.savestate_thumbnail_enable; if (!xmb) return; /* Cache previous savestate thumbnail path */ strlcpy( xmb->prev_savestate_thumbnail_file_path, xmb->savestate_thumbnail_file_path, sizeof(xmb->prev_savestate_thumbnail_file_path)); xmb->savestate_thumbnail_file_path[0] = '\0'; /* Savestate thumbnails are only relevant * when viewing the quick menu */ if (!xmb->is_quick_menu) return; if (savestate_thumbnail_enable) { menu_entry_t entry; menu_entry_init(&entry); entry.path_enabled = false; entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, i, NULL, true); if (!string_is_empty(entry.label)) { if (string_is_equal(entry.label, "state_slot") || string_is_equal(entry.label, "loadstate") || string_is_equal(entry.label, "savestate")) { char path[8204]; global_t *global = global_get_ptr(); path[0] = '\0'; if (global) { if (state_slot > 0) snprintf(path, sizeof(path), "%s%d", global->name.savestate, state_slot); else if (state_slot < 0) fill_pathname_join_delim(path, global->name.savestate, "auto", '.', sizeof(path)); else strlcpy(path, global->name.savestate, sizeof(path)); } strlcat(path, ".png", sizeof(path)); if (path_is_valid(path)) strlcpy( xmb->savestate_thumbnail_file_path, path, sizeof(xmb->savestate_thumbnail_file_path)); } } } } static void xmb_update_thumbnail_image(void *data) { const char *core_name = NULL; xmb_handle_t *xmb = (xmb_handle_t*)data; size_t selection = menu_navigation_get_selection(); playlist_t *playlist = playlist_get_cached(); settings_t *settings = config_get_ptr(); unsigned thumbnail_upscale_threshold = settings->uints.gfx_thumbnail_upscale_threshold; bool network_on_demand_thumbnails = settings->bools.network_on_demand_thumbnails; if (!xmb) return; gfx_thumbnail_cancel_pending_requests(); /* imageviewer content requires special treatment... */ gfx_thumbnail_get_core_name(xmb->thumbnail_path_data, &core_name); if (string_is_equal(core_name, "imageviewer")) { gfx_thumbnail_reset(&xmb->thumbnails.right); gfx_thumbnail_reset(&xmb->thumbnails.left); /* Right thumbnail */ if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)) gfx_thumbnail_request( xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT, playlist, selection, &xmb->thumbnails.right, thumbnail_upscale_threshold, network_on_demand_thumbnails); /* Left thumbnail */ else if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) gfx_thumbnail_request( xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT, playlist, selection, &xmb->thumbnails.left, thumbnail_upscale_threshold, network_on_demand_thumbnails); } else { /* Right thumbnail */ gfx_thumbnail_request( xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT, playlist, selection, &xmb->thumbnails.right, thumbnail_upscale_threshold, network_on_demand_thumbnails); /* Left thumbnail */ gfx_thumbnail_request( xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT, playlist, selection, &xmb->thumbnails.left, thumbnail_upscale_threshold, network_on_demand_thumbnails); } } static unsigned xmb_get_system_tab(xmb_handle_t *xmb, unsigned i) { if (i <= xmb->system_tab_end) return xmb->tabs[i]; return UINT_MAX; } static void xmb_refresh_thumbnail_image(void *data, unsigned i) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return; /* Only refresh thumbnails if thumbnails are enabled */ if ( gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) || gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) { unsigned depth = (unsigned)xmb_list_get_size(xmb, MENU_LIST_PLAIN); unsigned xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); /* Only refresh thumbnails if we are viewing a playlist or * the quick menu... */ if (((((xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS && depth == 1) || (xmb_system_tab < XMB_SYSTEM_TAB_SETTINGS && depth == 4)) && xmb->is_playlist)) || xmb->is_quick_menu) xmb_update_thumbnail_image(xmb); } } static void xmb_set_thumbnail_system(void *data, char*s, size_t len) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return; gfx_thumbnail_set_system( xmb->thumbnail_path_data, s, playlist_get_cached()); } static void xmb_get_thumbnail_system(void *data, char*s, size_t len) { xmb_handle_t *xmb = (xmb_handle_t*)data; const char *system = NULL; if (!xmb) return; if (gfx_thumbnail_get_system(xmb->thumbnail_path_data, &system)) strlcpy(s, system, len); } static void xmb_unload_thumbnail_textures(void *data) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return; gfx_thumbnail_cancel_pending_requests(); gfx_thumbnail_reset(&xmb->thumbnails.right); gfx_thumbnail_reset(&xmb->thumbnails.left); gfx_thumbnail_reset(&xmb->thumbnails.savestate); } static void xmb_set_thumbnail_content(void *data, const char *s) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return; /* Disable fullscreen thumbnails by default, * and only enable if thumbnail content is * actually set * > This is the easiest method for verifying * that we are currently viewing a relevant * menu type */ xmb->fullscreen_thumbnails_available = false; if (xmb->is_playlist) { /* Playlist content */ if (string_is_empty(s)) { size_t selection = menu_navigation_get_selection(); gfx_thumbnail_set_content_playlist(xmb->thumbnail_path_data, playlist_get_cached(), selection); xmb->fullscreen_thumbnails_available = true; } } else if (xmb->is_db_manager_list) { /* Database list content */ if (string_is_empty(s)) { menu_entry_t entry; size_t selection = menu_navigation_get_selection(); menu_entry_init(&entry); entry.label_enabled = false; entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, selection, NULL, true); if (!string_is_empty(entry.path)) { gfx_thumbnail_set_content(xmb->thumbnail_path_data, entry.path); xmb->fullscreen_thumbnails_available = true; } } } else if (string_is_equal(s, "imageviewer")) { /* Filebrowser image updates */ menu_entry_t entry; size_t selection = menu_navigation_get_selection(); file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); xmb_node_t *node = (xmb_node_t*)file_list_get_userdata_at_offset(selection_buf, selection); if (node) { menu_entry_init(&entry); entry.label_enabled = false; entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, selection, NULL, true); if ( !string_is_empty(entry.path) && !string_is_empty(node->fullpath)) { gfx_thumbnail_set_content_image(xmb->thumbnail_path_data, node->fullpath, entry.path); xmb->fullscreen_thumbnails_available = true; } } } else if (!string_is_empty(s)) { /* Annoying leftovers... * This is required to ensure that thumbnails are * updated correctly when navigating deeply through * the sublevels of database manager lists. * Showing thumbnails on database entries is a * pointless nuisance and a waste of CPU cycles, IMHO... */ gfx_thumbnail_set_content(xmb->thumbnail_path_data, s); xmb->fullscreen_thumbnails_available = true; } } static void xmb_update_savestate_thumbnail_image(void *data) { xmb_handle_t *xmb = (xmb_handle_t*)data; settings_t *settings = config_get_ptr(); unsigned thumbnail_upscale_threshold = settings->uints.gfx_thumbnail_upscale_threshold; if (!xmb) return; /* Savestate thumbnails are only relevant * when viewing the quick menu */ if (!xmb->is_quick_menu) return; /* If path is empty, just reset thumbnail */ if (string_is_empty(xmb->savestate_thumbnail_file_path)) gfx_thumbnail_reset(&xmb->thumbnails.savestate); else { /* Only request thumbnail if: * > Thumbnail has never been loaded *OR* * > Thumbnail path has changed */ if ((xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_UNKNOWN) || !string_is_equal(xmb->savestate_thumbnail_file_path, xmb->prev_savestate_thumbnail_file_path)) gfx_thumbnail_request_file( xmb->savestate_thumbnail_file_path, &xmb->thumbnails.savestate, thumbnail_upscale_threshold); } } static void xmb_selection_pointer_changed( xmb_handle_t *xmb, bool allow_animations) { unsigned i, end, height; uintptr_t tag; size_t num = 0; int threshold = 0; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); if (!xmb) return; end = (unsigned)menu_entries_get_size(); threshold = xmb->icon_size * 10; video_driver_get_size(NULL, &height); tag = (uintptr_t)selection_buf; gfx_animation_kill_by_tag(&tag); menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &num); for (i = 0; i < end; i++) { float iy, real_iy; float ia = xmb->items_passive_alpha; float iz = xmb->items_passive_zoom; xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(selection_buf, i); if (!node) continue; iy = xmb_item_y(xmb, i, selection); real_iy = iy + xmb->margins_screen_top; if (i == selection) { unsigned depth = (unsigned)xmb_list_get_size(xmb, MENU_LIST_PLAIN); unsigned xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); ia = xmb->items_active_alpha; iz = xmb->items_active_zoom; if ( gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) || gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT) ) { bool update_thumbnails = false; /* Playlist updates */ if (((xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS && depth == 1) || (xmb_system_tab < XMB_SYSTEM_TAB_SETTINGS && depth == 4)) && xmb->is_playlist) { xmb_set_thumbnail_content(xmb, NULL); update_thumbnails = true; } /* Database list updates * (pointless nuisance...) */ else if (depth == 4 && xmb->is_db_manager_list) { xmb_set_thumbnail_content(xmb, NULL); update_thumbnails = true; } /* Filebrowser image updates */ else if (xmb->is_file_list) { menu_entry_t entry; unsigned entry_type; menu_entry_init(&entry); entry.path_enabled = false; entry.label_enabled = false; entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, selection, NULL, true); entry_type = entry.type; if ( (entry_type == FILE_TYPE_IMAGEVIEWER) || (entry_type == FILE_TYPE_IMAGE)) { xmb_set_thumbnail_content(xmb, "imageviewer"); update_thumbnails = true; } else { /* If this is a file list and current * entry is not an image, have to 'reset' * content + right/left thumbnails * (otherwise last loaded thumbnail will * persist, and be shown on the wrong entry) */ gfx_thumbnail_set_content(xmb->thumbnail_path_data, NULL); gfx_thumbnail_cancel_pending_requests(); gfx_thumbnail_reset(&xmb->thumbnails.right); gfx_thumbnail_reset(&xmb->thumbnails.left); } } if (update_thumbnails) xmb_update_thumbnail_image(xmb); } xmb_update_savestate_thumbnail_path(xmb, i); xmb_update_savestate_thumbnail_image(xmb); } if ( (!allow_animations) || (real_iy < -threshold || real_iy > height+threshold)) { node->alpha = node->label_alpha = ia; node->y = iy; node->zoom = iz; } else { settings_t *settings = config_get_ptr(); unsigned menu_xmb_animation_move_up_down = settings->uints.menu_xmb_animation_move_up_down; /* Move up/down animation */ gfx_animation_ctx_entry_t anim_entry; anim_entry.target_value = ia; anim_entry.subject = &node->alpha; anim_entry.tag = tag; anim_entry.cb = NULL; switch (menu_xmb_animation_move_up_down) { case 0: anim_entry.duration = XMB_DELAY; anim_entry.easing_enum = EASING_OUT_QUAD; break; case 1: anim_entry.duration = XMB_DELAY * 4; anim_entry.easing_enum = EASING_OUT_EXPO; break; } gfx_animation_push(&anim_entry); anim_entry.subject = &node->label_alpha; gfx_animation_push(&anim_entry); anim_entry.target_value = iz; anim_entry.subject = &node->zoom; gfx_animation_push(&anim_entry); anim_entry.target_value = iy; anim_entry.subject = &node->y; gfx_animation_push(&anim_entry); } } } static void xmb_list_open_old(xmb_handle_t *xmb, file_list_t *list, int dir, size_t current) { unsigned i, height = 0; int threshold = xmb->icon_size * 10; size_t end = 0; end = file_list_get_size(list); video_driver_get_size(NULL, &height); for (i = 0; i < end; i++) { float ia = 0; float real_y; xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); if (!node) continue; if (i == current) ia = xmb->items_active_alpha; if (dir == -1) ia = 0; real_y = node->y + xmb->margins_screen_top; if (real_y < -threshold || real_y > height+threshold) { node->alpha = ia; node->label_alpha = 0; node->x = xmb->icon_size * dir * -2; } else { gfx_animation_ctx_entry_t anim_entry; anim_entry.duration = XMB_DELAY; anim_entry.target_value = ia; anim_entry.subject = &node->alpha; anim_entry.easing_enum = EASING_OUT_QUAD; anim_entry.tag = (uintptr_t)list; anim_entry.cb = NULL; gfx_animation_push(&anim_entry); anim_entry.target_value = 0; anim_entry.subject = &node->label_alpha; gfx_animation_push(&anim_entry); anim_entry.target_value = xmb->icon_size * dir * -2; anim_entry.subject = &node->x; gfx_animation_push(&anim_entry); } } } static void xmb_list_open_new(xmb_handle_t *xmb, file_list_t *list, int dir, size_t current) { unsigned i, height; unsigned xmb_system_tab = 0; size_t skip = 0; int threshold = xmb->icon_size * 10; size_t end = file_list_get_size(list); video_driver_get_size(NULL, &height); for (i = 0; i < end; i++) { float ia; float real_y; xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); if (!node) continue; if (dir == 1) { node->alpha = 0; node->label_alpha = 0; } else if (dir == -1) { if (i != current) node->alpha = 0; node->label_alpha = 0; } node->x = xmb->icon_size * dir * 2; node->y = xmb_item_y(xmb, i, current); node->zoom = xmb->categories_passive_zoom; real_y = node->y + xmb->margins_screen_top; if (i == current) { node->zoom = xmb->categories_active_zoom; ia = xmb->items_active_alpha; } else ia = xmb->items_passive_alpha; if (real_y < -threshold || real_y > height+threshold) { node->alpha = node->label_alpha = ia; node->x = 0; } else { gfx_animation_ctx_entry_t anim_entry; anim_entry.duration = XMB_DELAY; anim_entry.target_value = ia; anim_entry.subject = &node->alpha; anim_entry.easing_enum = EASING_OUT_QUAD; anim_entry.tag = (uintptr_t)list; anim_entry.cb = NULL; gfx_animation_push(&anim_entry); anim_entry.subject = &node->label_alpha; gfx_animation_push(&anim_entry); anim_entry.target_value = 0; anim_entry.subject = &node->x; gfx_animation_push(&anim_entry); } } xmb->old_depth = xmb->depth; menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &skip); xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); if (xmb_system_tab <= XMB_SYSTEM_TAB_SETTINGS) { if ( gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) || gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) { /* This code is horrible, full of hacks... * This hack ensures that thumbnails are not cleared * when selecting an entry from a collection via * 'load content'... */ if (xmb->depth != 5) xmb_unload_thumbnail_textures(xmb); if (xmb->is_playlist || xmb->is_db_manager_list) { xmb_set_thumbnail_content(xmb, NULL); xmb_update_thumbnail_image(xmb); } } } } static xmb_node_t *xmb_node_allocate_userdata( xmb_handle_t *xmb, unsigned i) { xmb_node_t *tmp = NULL; xmb_node_t *node = xmb_alloc_node(); if (!node) { RARCH_ERR("XMB node could not be allocated.\n"); return NULL; } node->alpha = xmb->categories_passive_alpha; node->zoom = xmb->categories_passive_zoom; if ((i + xmb->system_tab_end) == xmb->categories_active_idx) { node->alpha = xmb->categories_active_alpha; node->zoom = xmb->categories_active_zoom; } tmp = (xmb_node_t*)file_list_get_userdata_at_offset( xmb->horizontal_list, i); xmb_free_node(tmp); file_list_set_userdata(xmb->horizontal_list, i, node); return node; } static xmb_node_t* xmb_get_userdata_from_horizontal_list( xmb_handle_t *xmb, unsigned i) { return (xmb_node_t*) file_list_get_userdata_at_offset(xmb->horizontal_list, i); } static void xmb_push_animations(xmb_node_t *node, uintptr_t tag, float ia, float ix) { gfx_animation_ctx_entry_t anim_entry; anim_entry.duration = XMB_DELAY; anim_entry.target_value = ia; anim_entry.subject = &node->alpha; anim_entry.easing_enum = EASING_OUT_QUAD; anim_entry.tag = tag; anim_entry.cb = NULL; gfx_animation_push(&anim_entry); anim_entry.subject = &node->label_alpha; gfx_animation_push(&anim_entry); anim_entry.target_value = ix; anim_entry.subject = &node->x; gfx_animation_push(&anim_entry); } static void xmb_list_switch_old(xmb_handle_t *xmb, file_list_t *list, int dir, size_t current) { unsigned i, height; size_t end = file_list_get_size(list); float ix = -xmb->icon_spacing_horizontal * dir; float ia = 0; unsigned first = 0; unsigned last = (unsigned)(end > 0 ? end - 1 : 0); video_driver_get_size(NULL, &height); xmb_calculate_visible_range(xmb, height, end, (unsigned)current, &first, &last); for (i = 0; i < end; i++) { xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); if (!node) continue; if (i >= first && i <= last) xmb_push_animations(node, (uintptr_t)list, ia, ix); else { node->alpha = node->label_alpha = ia; node->x = ix; } } } static void xmb_list_switch_new(xmb_handle_t *xmb, file_list_t *list, int dir, size_t current) { unsigned i, first, last, height; size_t end = 0; settings_t *settings = config_get_ptr(); bool menu_dynamic_wallpaper_enable = settings->bools.menu_dynamic_wallpaper_enable; const char *dir_dynamic_wallpapers = settings->paths.directory_dynamic_wallpapers; if (menu_dynamic_wallpaper_enable) { size_t path_size = PATH_MAX_LENGTH * sizeof(char); char *path = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); char *tmp = string_replace_substring(xmb->title_name, "/", " "); path[0] = '\0'; if (tmp) { fill_pathname_join_noext( path, dir_dynamic_wallpapers, tmp, path_size); free(tmp); } strlcat(path, ".png", path_size); if (!path_is_valid(path)) fill_pathname_application_special(path, path_size, APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_BG); if (!string_is_equal(path, xmb->bg_file_path)) { if (path_is_valid(path)) { task_push_image_load(path, video_driver_supports_rgba(), 0, menu_display_handle_wallpaper_upload, NULL); if (!string_is_empty(xmb->bg_file_path)) free(xmb->bg_file_path); xmb->bg_file_path = strdup(path); } } free(path); } end = file_list_get_size(list); first = 0; last = (unsigned)(end > 0 ? end - 1 : 0); video_driver_get_size(NULL, &height); xmb_calculate_visible_range(xmb, height, end, (unsigned)current, &first, &last); for (i = 0; i < end; i++) { xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); float ia = xmb->items_passive_alpha; if (!node) continue; node->x = xmb->icon_spacing_horizontal * dir; node->alpha = 0; node->label_alpha = 0; if (i == current) ia = xmb->items_active_alpha; if (i >= first && i <= last) xmb_push_animations(node, (uintptr_t)list, ia, 0); else { node->x = 0; node->alpha = node->label_alpha = ia; } } } static void xmb_set_title(xmb_handle_t *xmb) { if (xmb->categories_selection_ptr <= xmb->system_tab_end) menu_entries_get_title(xmb->title_name, sizeof(xmb->title_name)); else { const char *path = NULL; menu_entries_get_at_offset( xmb->horizontal_list, xmb->categories_selection_ptr - (xmb->system_tab_end + 1), &path, NULL, NULL, NULL, NULL); if (!path) return; fill_pathname_base_noext( xmb->title_name, path, sizeof(xmb->title_name)); } } static xmb_node_t* xmb_get_node(xmb_handle_t *xmb, unsigned i) { switch (xmb_get_system_tab(xmb, i)) { case XMB_SYSTEM_TAB_SETTINGS: return &xmb->settings_tab_node; #ifdef HAVE_IMAGEVIEWER case XMB_SYSTEM_TAB_IMAGES: return &xmb->images_tab_node; #endif case XMB_SYSTEM_TAB_MUSIC: return &xmb->music_tab_node; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) case XMB_SYSTEM_TAB_VIDEO: return &xmb->video_tab_node; #endif case XMB_SYSTEM_TAB_HISTORY: return &xmb->history_tab_node; case XMB_SYSTEM_TAB_FAVORITES: return &xmb->favorites_tab_node; #ifdef HAVE_NETWORKING case XMB_SYSTEM_TAB_NETPLAY: return &xmb->netplay_tab_node; #endif case XMB_SYSTEM_TAB_ADD: return &xmb->add_tab_node; default: if (i > xmb->system_tab_end) return xmb_get_userdata_from_horizontal_list( xmb, i - (xmb->system_tab_end + 1)); } return &xmb->main_menu_node; } static void xmb_list_switch_horizontal_list(xmb_handle_t *xmb) { unsigned j; settings_t *settings = config_get_ptr(); size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end; unsigned xmb_animation_horizontal_highlight = settings->uints.menu_xmb_animation_horizontal_highlight; for (j = 0; j <= list_size; j++) { gfx_animation_ctx_entry_t entry; float ia = xmb->categories_passive_alpha; float iz = xmb->categories_passive_zoom; xmb_node_t *node = xmb_get_node(xmb, j); if (!node) continue; if (j == xmb->categories_active_idx) { ia = xmb->categories_active_alpha; iz = xmb->categories_active_zoom; } /* Horizontal icon animation */ entry.target_value = ia; entry.subject = &node->alpha; /* TODO/FIXME - integer conversion resulted in change of sign */ entry.tag = -1; entry.cb = NULL; switch (xmb_animation_horizontal_highlight) { case 0: entry.duration = XMB_DELAY; entry.easing_enum = EASING_OUT_QUAD; break; case 1: entry.duration = XMB_DELAY + (XMB_DELAY / 2); entry.easing_enum = EASING_IN_SINE; break; case 2: entry.duration = XMB_DELAY * 2; entry.easing_enum = EASING_OUT_BOUNCE; break; } gfx_animation_push(&entry); entry.target_value = iz; entry.subject = &node->zoom; gfx_animation_push(&entry); } } static void xmb_list_switch(xmb_handle_t *xmb) { gfx_animation_ctx_entry_t anim_entry; int dir = -1; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); settings_t *settings = config_get_ptr(); bool menu_horizontal_animation = settings->bools.menu_horizontal_animation; if (xmb->categories_selection_ptr > xmb->categories_selection_ptr_old) dir = 1; xmb->categories_active_idx += dir; xmb_list_switch_horizontal_list(xmb); anim_entry.duration = XMB_DELAY; anim_entry.target_value = xmb->icon_spacing_horizontal * -(float)xmb->categories_selection_ptr; anim_entry.subject = &xmb->categories_x_pos; anim_entry.easing_enum = EASING_OUT_QUAD; /* TODO/FIXME - integer conversion resulted in change of sign */ anim_entry.tag = -1; anim_entry.cb = NULL; if (anim_entry.subject) gfx_animation_push(&anim_entry); dir = -1; if (xmb->categories_selection_ptr > xmb->categories_selection_ptr_old) dir = 1; xmb_list_switch_old(xmb, xmb->selection_buf_old, dir, xmb->selection_ptr_old); /* Check if we are to have horizontal animations. */ if (menu_horizontal_animation) xmb_list_switch_new(xmb, selection_buf, dir, selection); xmb->categories_active_idx_old = (unsigned)xmb->categories_selection_ptr; if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) || gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) { xmb_unload_thumbnail_textures(xmb); if (xmb->is_playlist) { xmb_set_thumbnail_content(xmb, NULL); xmb_update_thumbnail_image(xmb); } } } static void xmb_list_open_horizontal_list(xmb_handle_t *xmb) { unsigned j; size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end; for (j = 0; j <= list_size; j++) { gfx_animation_ctx_entry_t anim_entry; float ia = 0; xmb_node_t *node = xmb_get_node(xmb, j); if (!node) continue; if (j == xmb->categories_active_idx) ia = xmb->categories_active_alpha; else if (xmb->depth <= 1) ia = xmb->categories_passive_alpha; anim_entry.duration = XMB_DELAY; anim_entry.target_value = ia; anim_entry.subject = &node->alpha; anim_entry.easing_enum = EASING_OUT_QUAD; /* TODO/FIXME - integer conversion resulted in change of sign */ anim_entry.tag = -1; anim_entry.cb = NULL; if (anim_entry.subject) gfx_animation_push(&anim_entry); } } static void xmb_context_destroy_horizontal_list(xmb_handle_t *xmb) { unsigned i; size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL); for (i = 0; i < list_size; i++) { const char *path = NULL; xmb_node_t *node = xmb_get_userdata_from_horizontal_list(xmb, i); if (!node) continue; file_list_get_at_offset(xmb->horizontal_list, i, &path, NULL, NULL, NULL); if (!path || !string_ends_with_size(path, ".lpl", strlen(path), STRLEN_CONST(".lpl"))) continue; video_driver_texture_unload(&node->icon); video_driver_texture_unload(&node->content_icon); } } static void xmb_init_horizontal_list(xmb_handle_t *xmb) { menu_displaylist_info_t info; settings_t *settings = config_get_ptr(); const char *dir_playlist = settings->paths.directory_playlist; bool menu_content_show_playlists = settings->bools.menu_content_show_playlists; menu_displaylist_info_init(&info); info.list = xmb->horizontal_list; info.path = strdup(dir_playlist); info.label = strdup( msg_hash_to_str(MENU_ENUM_LABEL_PLAYLISTS_TAB)); info.exts = strdup("lpl"); info.type_default = FILE_TYPE_PLAIN; info.enum_idx = MENU_ENUM_LABEL_PLAYLISTS_TAB; if (menu_content_show_playlists && !string_is_empty(info.path)) { if (menu_displaylist_ctl(DISPLAYLIST_DATABASE_PLAYLISTS_HORIZONTAL, &info)) { size_t i; for (i = 0; i < xmb->horizontal_list->size; i++) xmb_node_allocate_userdata(xmb, (unsigned)i); menu_displaylist_process(&info); } } menu_displaylist_info_free(&info); } static void xmb_toggle_horizontal_list(xmb_handle_t *xmb) { unsigned i; size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end; for (i = 0; i <= list_size; i++) { xmb_node_t *node = xmb_get_node(xmb, i); if (!node) continue; node->alpha = 0; node->zoom = xmb->categories_passive_zoom; if (i == xmb->categories_active_idx) { node->alpha = xmb->categories_active_alpha; node->zoom = xmb->categories_active_zoom; } else if (xmb->depth <= 1) node->alpha = xmb->categories_passive_alpha; } } static void xmb_context_reset_horizontal_list( xmb_handle_t *xmb) { unsigned i; int depth; /* keep this integer */ size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL); xmb->categories_x_pos = xmb->icon_spacing_horizontal * -(float)xmb->categories_selection_ptr; depth = (xmb->depth > 1) ? 2 : 1; xmb->x = xmb->icon_size * -(depth*2-2); for (i = 0; i < list_size; i++) { const char *path = NULL; xmb_node_t *node = xmb_get_userdata_from_horizontal_list(xmb, i); if (!node) { node = xmb_node_allocate_userdata(xmb, i); if (!node) continue; } file_list_get_at_offset(xmb->horizontal_list, i, &path, NULL, NULL, NULL); if (!path) continue; if (!string_ends_with_size(path, ".lpl", strlen(path), STRLEN_CONST(".lpl"))) continue; { struct texture_image ti; char *sysname = (char*) malloc(PATH_MAX_LENGTH * sizeof(char)); char *iconpath = (char*) malloc(PATH_MAX_LENGTH * sizeof(char)); char *texturepath = (char*) malloc(PATH_MAX_LENGTH * sizeof(char)); char *content_texturepath = (char*) malloc(PATH_MAX_LENGTH * sizeof(char)); iconpath[0] = sysname[0] = texturepath[0] = content_texturepath[0] = '\0'; fill_pathname_base_noext(sysname, path, PATH_MAX_LENGTH * sizeof(char)); fill_pathname_application_special(iconpath, PATH_MAX_LENGTH * sizeof(char), APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_ICONS); fill_pathname_join_concat(texturepath, iconpath, sysname, ".png", PATH_MAX_LENGTH * sizeof(char)); /* If the playlist icon doesn't exist return default */ if (!path_is_valid(texturepath)) fill_pathname_join_concat(texturepath, iconpath, "default", ".png", PATH_MAX_LENGTH * sizeof(char)); ti.width = 0; ti.height = 0; ti.pixels = NULL; ti.supports_rgba = video_driver_supports_rgba(); if (image_texture_load(&ti, texturepath)) { if (ti.pixels) { video_driver_texture_unload(&node->icon); video_driver_texture_load(&ti, TEXTURE_FILTER_MIPMAP_LINEAR, &node->icon); } image_texture_free(&ti); } fill_pathname_join_delim(sysname, sysname, "content.png", '-', PATH_MAX_LENGTH * sizeof(char)); strlcat(content_texturepath, iconpath, PATH_MAX_LENGTH * sizeof(char)); strlcat(content_texturepath, sysname, PATH_MAX_LENGTH * sizeof(char)); /* If the content icon doesn't exist return default-content */ if (!path_is_valid(content_texturepath)) { strlcat(iconpath, "default", PATH_MAX_LENGTH * sizeof(char)); fill_pathname_join_delim(content_texturepath, iconpath, "content.png", '-', PATH_MAX_LENGTH * sizeof(char)); } if (image_texture_load(&ti, content_texturepath)) { if (ti.pixels) { video_driver_texture_unload(&node->content_icon); video_driver_texture_load(&ti, TEXTURE_FILTER_MIPMAP_LINEAR, &node->content_icon); } image_texture_free(&ti); } free(sysname); free(iconpath); free(texturepath); free(content_texturepath); } } xmb_toggle_horizontal_list(xmb); } static void xmb_refresh_horizontal_list(xmb_handle_t *xmb) { xmb_context_destroy_horizontal_list(xmb); if (xmb->horizontal_list) { xmb_free_list_nodes(xmb->horizontal_list, false); file_list_free(xmb->horizontal_list); } xmb->horizontal_list = NULL; menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); xmb->horizontal_list = (file_list_t*) malloc(sizeof(file_list_t)); xmb->horizontal_list->list = NULL; xmb->horizontal_list->capacity = 0; xmb->horizontal_list->size = 0; if (xmb->horizontal_list) xmb_init_horizontal_list(xmb); xmb_context_reset_horizontal_list(xmb); } static int xmb_environ(enum menu_environ_cb type, void *data, void *userdata) { xmb_handle_t *xmb = (xmb_handle_t*)userdata; switch (type) { case MENU_ENVIRON_ENABLE_MOUSE_CURSOR: if (!xmb) return -1; xmb->mouse_show = true; break; case MENU_ENVIRON_DISABLE_MOUSE_CURSOR: if (!xmb) return -1; xmb->mouse_show = false; break; case MENU_ENVIRON_RESET_HORIZONTAL_LIST: if (!xmb) return -1; xmb_refresh_horizontal_list(xmb); break; default: return -1; } return 0; } static void xmb_list_open(xmb_handle_t *xmb) { gfx_animation_ctx_entry_t entry; settings_t *settings = config_get_ptr(); unsigned menu_xmb_animation_opening_main_menu = settings->uints.menu_xmb_animation_opening_main_menu; int dir = 0; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); xmb->depth = (int)xmb_list_get_size(xmb, MENU_LIST_PLAIN); if (xmb->depth > xmb->old_depth) dir = 1; else if (xmb->depth < xmb->old_depth) dir = -1; else return; /* If menu hasn't changed, do nothing */ xmb_list_open_horizontal_list(xmb); xmb_list_open_old(xmb, xmb->selection_buf_old, dir, xmb->selection_ptr_old); xmb_list_open_new(xmb, selection_buf, dir, selection); /* Main Menu opening animation */ entry.target_value = xmb->icon_size * -(xmb->depth*2-2); entry.subject = &xmb->x; /* TODO/FIXME - integer conversion resulted in change of sign */ entry.tag = -1; entry.cb = NULL; switch (menu_xmb_animation_opening_main_menu) { case 0: entry.easing_enum = EASING_OUT_QUAD; entry.duration = XMB_DELAY; break; case 1: entry.easing_enum = EASING_OUT_CIRC; entry.duration = XMB_DELAY * 2; break; case 2: entry.easing_enum = EASING_OUT_EXPO; entry.duration = XMB_DELAY * 3; break; case 3: entry.easing_enum = EASING_OUT_BOUNCE; entry.duration = XMB_DELAY * 4; break; } switch (xmb->depth) { case 1: case 2: gfx_animation_push(&entry); entry.target_value = xmb->depth - 1; entry.subject = &xmb->textures_arrow_alpha; gfx_animation_push(&entry); break; } xmb->old_depth = xmb->depth; } static void xmb_populate_entries(void *data, const char *path, const char *label, unsigned k) { xmb_handle_t *xmb = (xmb_handle_t*)data; unsigned xmb_system_tab; if (!xmb) return; /* Determine whether this is a playlist */ xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); xmb->is_playlist = (xmb_system_tab == XMB_SYSTEM_TAB_FAVORITES) || (xmb_system_tab == XMB_SYSTEM_TAB_HISTORY) || #ifdef HAVE_IMAGEVIEWER (xmb_system_tab == XMB_SYSTEM_TAB_IMAGES) || #endif string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_PLAYLIST_LIST)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_FAVORITES_LIST)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_IMAGES_LIST)); xmb->is_playlist = xmb->is_playlist && !string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RDB_ENTRY_DETAIL)); /* Determine whether this is a database manager list */ xmb->is_db_manager_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST)); /* Determine whether this is a 'file list' * (needed for handling thumbnails when viewing images * via 'load content') * > Note: MENU_ENUM_LABEL_FAVORITES is always set * as the 'label' when navigating directories after * selecting 'load content' */ xmb->is_file_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES)); /* Determine whether this is the quick menu */ xmb->is_quick_menu = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS)) || string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_SETTINGS)); if (menu_driver_ctl(RARCH_MENU_CTL_IS_PREVENT_POPULATE, NULL)) { xmb_selection_pointer_changed(xmb, false); menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); return; } xmb_set_title(xmb); if (xmb->categories_selection_ptr != xmb->categories_active_idx_old) xmb_list_switch(xmb); else xmb_list_open(xmb); /* By default, fullscreen thumbnails are only * enabled on playlists, database manager * lists and file lists, in cases where ordinary * thumbnails would normally be shown * > This is refined on a case-by-case basis * inside xmb_set_thumbnail_content() */ xmb->fullscreen_thumbnails_available = (xmb->is_playlist || xmb->is_db_manager_list || xmb->is_file_list) && !xmb->is_quick_menu && !((xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS) && (xmb->depth > 2)); /* Hack: XMB gets into complete muddle when * performing 'complex' directory navigation * via 'load content'. We have to work around * this by resetting thumbnails whenever a * file list is populated... */ if (xmb->is_file_list) { gfx_thumbnail_set_content(xmb->thumbnail_path_data, NULL); gfx_thumbnail_cancel_pending_requests(); gfx_thumbnail_reset(&xmb->thumbnails.right); gfx_thumbnail_reset(&xmb->thumbnails.left); } } static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, xmb_node_t *core_node, xmb_node_t *node, enum msg_hash_enums enum_idx, unsigned type, bool active, bool checked) { switch (enum_idx) { case MENU_ENUM_LABEL_CORE_OPTIONS: case MENU_ENUM_LABEL_NAVIGATION_BROWSER_FILTER_SUPPORTED_EXTENSIONS_ENABLE: return xmb->textures.list[XMB_TEXTURE_CORE_OPTIONS]; case MENU_ENUM_LABEL_ADD_TO_FAVORITES: case MENU_ENUM_LABEL_ADD_TO_FAVORITES_PLAYLIST: return xmb->textures.list[XMB_TEXTURE_ADD_FAVORITE]; case MENU_ENUM_LABEL_PARENT_DIRECTORY: case MENU_ENUM_LABEL_UNDO_LOAD_STATE: case MENU_ENUM_LABEL_UNDO_SAVE_STATE: case MENU_ENUM_LABEL_RESET_CORE_ASSOCIATION: case MENU_ENUM_LABEL_PLAYLIST_MANAGER_RESET_CORES: return xmb->textures.list[XMB_TEXTURE_UNDO]; case MENU_ENUM_LABEL_CORE_INPUT_REMAPPING_OPTIONS: return xmb->textures.list[XMB_TEXTURE_INPUT_REMAPPING_OPTIONS]; case MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS: return xmb->textures.list[XMB_TEXTURE_CHEAT_OPTIONS]; case MENU_ENUM_LABEL_DISK_OPTIONS: case MENU_ENUM_LABEL_DISK_TRAY_EJECT: case MENU_ENUM_LABEL_DISK_TRAY_INSERT: case MENU_ENUM_LABEL_DISK_IMAGE_APPEND: case MENU_ENUM_LABEL_DISK_INDEX: return xmb->textures.list[XMB_TEXTURE_DISK_OPTIONS]; case MENU_ENUM_LABEL_SHADER_OPTIONS: return xmb->textures.list[XMB_TEXTURE_SHADER_OPTIONS]; case MENU_ENUM_LABEL_ACHIEVEMENT_LIST: case MENU_ENUM_LABEL_ACHIEVEMENT_LIST_HARDCORE: return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENT_LIST]; case MENU_ENUM_LABEL_SAVE_STATE: case MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE: case MENU_ENUM_LABEL_CORE_CREATE_BACKUP: return xmb->textures.list[XMB_TEXTURE_SAVESTATE]; case MENU_ENUM_LABEL_LOAD_STATE: case MENU_ENUM_LABEL_CONFIGURATIONS: case MENU_ENUM_LABEL_GAME_SPECIFIC_OPTIONS: case MENU_ENUM_LABEL_REMAP_FILE_LOAD: case MENU_ENUM_LABEL_AUTO_OVERRIDES_ENABLE: case MENU_ENUM_LABEL_AUTO_REMAPS_ENABLE: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET: case MENU_ENUM_LABEL_CHEAT_FILE_LOAD: case MENU_ENUM_LABEL_CHEAT_FILE_LOAD_APPEND: case MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD: case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST: return xmb->textures.list[XMB_TEXTURE_LOADSTATE]; case MENU_ENUM_LABEL_TAKE_SCREENSHOT: return xmb->textures.list[XMB_TEXTURE_SCREENSHOT]; case MENU_ENUM_LABEL_DELETE_ENTRY: return xmb->textures.list[XMB_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_RESTART_CONTENT: case MENU_ENUM_LABEL_REBOOT: case MENU_ENUM_LABEL_RESET_TO_DEFAULT_CONFIG: case MENU_ENUM_LABEL_CHEAT_RELOAD_CHEATS: case MENU_ENUM_LABEL_RESTART_RETROARCH: case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE: case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL: case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS: case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST: return xmb->textures.list[XMB_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_RENAME_ENTRY: return xmb->textures.list[XMB_TEXTURE_RENAME]; case MENU_ENUM_LABEL_RESUME_CONTENT: return xmb->textures.list[XMB_TEXTURE_RESUME]; case MENU_ENUM_LABEL_DIRECTORY_SETTINGS: case MENU_ENUM_LABEL_SCAN_DIRECTORY: case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CONTENT_DIR: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_PARENT: case MENU_ENUM_LABEL_FAVORITES: /* "Start Directory" */ case MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST: return xmb->textures.list[XMB_TEXTURE_FOLDER]; case MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR: return xmb->textures.list[XMB_TEXTURE_RDB]; /* Menu collection submenus */ case MENU_ENUM_LABEL_PLAYLISTS_TAB: return xmb->textures.list[XMB_TEXTURE_ZIP]; case MENU_ENUM_LABEL_GOTO_FAVORITES: return xmb->textures.list[XMB_TEXTURE_FAVORITE]; case MENU_ENUM_LABEL_GOTO_IMAGES: return xmb->textures.list[XMB_TEXTURE_IMAGE]; case MENU_ENUM_LABEL_GOTO_VIDEO: return xmb->textures.list[XMB_TEXTURE_MOVIE]; case MENU_ENUM_LABEL_GOTO_MUSIC: return xmb->textures.list[XMB_TEXTURE_MUSIC]; case MENU_ENUM_LABEL_LOAD_DISC: case MENU_ENUM_LABEL_DUMP_DISC: case MENU_ENUM_LABEL_DISC_INFORMATION: return xmb->textures.list[XMB_TEXTURE_DISC]; case MENU_ENUM_LABEL_CONTENT_SETTINGS: case MENU_ENUM_LABEL_UPDATE_ASSETS: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_GAME: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_GLOBAL: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_GAME: return xmb->textures.list[XMB_TEXTURE_QUICKMENU]; case MENU_ENUM_LABEL_START_CORE: case MENU_ENUM_LABEL_CHEAT_START_OR_CONT: return xmb->textures.list[XMB_TEXTURE_RUN]; case MENU_ENUM_LABEL_CORE_LIST: case MENU_ENUM_LABEL_SIDELOAD_CORE_LIST: case MENU_ENUM_LABEL_CORE_SETTINGS: case MENU_ENUM_LABEL_CORE_UPDATER_LIST: case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CORE: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE: case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CORE: case MENU_ENUM_LABEL_SET_CORE_ASSOCIATION: case MENU_ENUM_LABEL_CORE_INFORMATION: return xmb->textures.list[XMB_TEXTURE_CORE]; case MENU_ENUM_LABEL_LOAD_CONTENT_LIST: case MENU_ENUM_LABEL_SUBSYSTEM_SETTINGS: case MENU_ENUM_LABEL_SCAN_FILE: return xmb->textures.list[XMB_TEXTURE_FILE]; case MENU_ENUM_LABEL_ONLINE_UPDATER: case MENU_ENUM_LABEL_UPDATER_SETTINGS: return xmb->textures.list[XMB_TEXTURE_UPDATER]; case MENU_ENUM_LABEL_UPDATE_LAKKA: return xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; case MENU_ENUM_LABEL_UPDATE_CHEATS: return xmb->textures.list[XMB_TEXTURE_CHEAT_OPTIONS]; case MENU_ENUM_LABEL_THUMBNAILS_UPDATER_LIST: case MENU_ENUM_LABEL_PL_THUMBNAILS_UPDATER_LIST: case MENU_ENUM_LABEL_DOWNLOAD_PL_ENTRY_THUMBNAILS: return xmb->textures.list[XMB_TEXTURE_IMAGE]; case MENU_ENUM_LABEL_UPDATE_OVERLAYS: case MENU_ENUM_LABEL_ONSCREEN_OVERLAY_SETTINGS: #ifdef HAVE_VIDEO_LAYOUT case MENU_ENUM_LABEL_ONSCREEN_VIDEO_LAYOUT_SETTINGS: #endif return xmb->textures.list[XMB_TEXTURE_OVERLAY]; case MENU_ENUM_LABEL_UPDATE_CG_SHADERS: case MENU_ENUM_LABEL_UPDATE_GLSL_SHADERS: case MENU_ENUM_LABEL_UPDATE_SLANG_SHADERS: case MENU_ENUM_LABEL_AUTO_SHADERS_ENABLE: case MENU_ENUM_LABEL_VIDEO_SHADER_PARAMETERS: return xmb->textures.list[XMB_TEXTURE_SHADER_OPTIONS]; case MENU_ENUM_LABEL_INFORMATION: case MENU_ENUM_LABEL_INFORMATION_LIST: case MENU_ENUM_LABEL_SYSTEM_INFORMATION: case MENU_ENUM_LABEL_UPDATE_CORE_INFO_FILES: return xmb->textures.list[XMB_TEXTURE_INFO]; case MENU_ENUM_LABEL_UPDATE_DATABASES: case MENU_ENUM_LABEL_DATABASE_MANAGER_LIST: return xmb->textures.list[XMB_TEXTURE_RDB]; case MENU_ENUM_LABEL_CURSOR_MANAGER_LIST: return xmb->textures.list[XMB_TEXTURE_CURSOR]; case MENU_ENUM_LABEL_HELP_LIST: case MENU_ENUM_LABEL_HELP_CONTROLS: case MENU_ENUM_LABEL_HELP_LOADING_CONTENT: case MENU_ENUM_LABEL_HELP_SCANNING_CONTENT: case MENU_ENUM_LABEL_HELP_WHAT_IS_A_CORE: case MENU_ENUM_LABEL_HELP_CHANGE_VIRTUAL_GAMEPAD: case MENU_ENUM_LABEL_HELP_AUDIO_VIDEO_TROUBLESHOOTING: case MENU_ENUM_LABEL_HELP_SEND_DEBUG_INFO: return xmb->textures.list[XMB_TEXTURE_HELP]; case MENU_ENUM_LABEL_QUIT_RETROARCH: case MENU_ENUM_LABEL_BLOCK_SRAM_OVERWRITE: return xmb->textures.list[XMB_TEXTURE_EXIT]; case MENU_ENUM_LABEL_DRIVER_SETTINGS: return xmb->textures.list[XMB_TEXTURE_DRIVERS]; case MENU_ENUM_LABEL_VIDEO_SETTINGS: return xmb->textures.list[XMB_TEXTURE_VIDEO]; case MENU_ENUM_LABEL_AUDIO_SETTINGS: return xmb->textures.list[XMB_TEXTURE_AUDIO]; case MENU_ENUM_LABEL_AUDIO_MIXER_SETTINGS: return xmb->textures.list[XMB_TEXTURE_MIXER]; case MENU_ENUM_LABEL_SCREEN_RESOLUTION: return xmb->textures.list[XMB_TEXTURE_SUBSETTING]; case MENU_ENUM_LABEL_INPUT_SETTINGS: case MENU_ENUM_LABEL_UPDATE_AUTOCONFIG_PROFILES: case MENU_ENUM_LABEL_INPUT_USER_1_BINDS: case MENU_ENUM_LABEL_INPUT_USER_2_BINDS: case MENU_ENUM_LABEL_INPUT_USER_3_BINDS: case MENU_ENUM_LABEL_INPUT_USER_4_BINDS: case MENU_ENUM_LABEL_INPUT_USER_5_BINDS: case MENU_ENUM_LABEL_INPUT_USER_6_BINDS: case MENU_ENUM_LABEL_INPUT_USER_7_BINDS: case MENU_ENUM_LABEL_INPUT_USER_8_BINDS: case MENU_ENUM_LABEL_INPUT_USER_9_BINDS: case MENU_ENUM_LABEL_INPUT_USER_10_BINDS: case MENU_ENUM_LABEL_INPUT_USER_11_BINDS: case MENU_ENUM_LABEL_INPUT_USER_12_BINDS: case MENU_ENUM_LABEL_INPUT_USER_13_BINDS: case MENU_ENUM_LABEL_INPUT_USER_14_BINDS: case MENU_ENUM_LABEL_INPUT_USER_15_BINDS: case MENU_ENUM_LABEL_INPUT_USER_16_BINDS: case MENU_ENUM_LABEL_START_NET_RETROPAD: return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS]; case MENU_ENUM_LABEL_LATENCY_SETTINGS: return xmb->textures.list[XMB_TEXTURE_LATENCY]; case MENU_ENUM_LABEL_SAVING_SETTINGS: case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG: case MENU_ENUM_LABEL_SAVE_NEW_CONFIG: case MENU_ENUM_LABEL_CONFIG_SAVE_ON_EXIT: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE: case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_AS: case MENU_ENUM_LABEL_CHEAT_FILE_SAVE_AS: return xmb->textures.list[XMB_TEXTURE_SAVING]; case MENU_ENUM_LABEL_LOGGING_SETTINGS: return xmb->textures.list[XMB_TEXTURE_LOG]; case MENU_ENUM_LABEL_FASTFORWARD_RATIO: case MENU_ENUM_LABEL_FRAME_THROTTLE_SETTINGS: return xmb->textures.list[XMB_TEXTURE_FRAMESKIP]; case MENU_ENUM_LABEL_QUICK_MENU_START_RECORDING: case MENU_ENUM_LABEL_RECORDING_SETTINGS: return xmb->textures.list[XMB_TEXTURE_RECORD]; case MENU_ENUM_LABEL_QUICK_MENU_START_STREAMING: return xmb->textures.list[XMB_TEXTURE_STREAM]; case MENU_ENUM_LABEL_QUICK_MENU_STOP_STREAMING: case MENU_ENUM_LABEL_QUICK_MENU_STOP_RECORDING: case MENU_ENUM_LABEL_CHEAT_DELETE_ALL: case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CORE: case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_GAME: case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CONTENT_DIR: case MENU_ENUM_LABEL_CORE_DELETE: case MENU_ENUM_LABEL_DELETE_PLAYLIST: case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST: return xmb->textures.list[XMB_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_CORE_LOCK: return xmb->textures.list[XMB_TEXTURE_CORE]; case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS: return xmb->textures.list[XMB_TEXTURE_OSD]; case MENU_ENUM_LABEL_SHOW_WIMP: case MENU_ENUM_LABEL_USER_INTERFACE_SETTINGS: return xmb->textures.list[XMB_TEXTURE_UI]; #ifdef HAVE_LAKKA_SWITCH case MENU_ENUM_LABEL_SWITCH_GPU_PROFILE: #endif #if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX) case MENU_ENUM_LABEL_SWITCH_CPU_PROFILE: return xmb->textures.list[XMB_TEXTURE_POWER]; #endif case MENU_ENUM_LABEL_POWER_MANAGEMENT_SETTINGS: return xmb->textures.list[XMB_TEXTURE_POWER]; case MENU_ENUM_LABEL_RETRO_ACHIEVEMENTS_SETTINGS: return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENTS]; case MENU_ENUM_LABEL_PLAYLIST_SETTINGS: return xmb->textures.list[XMB_TEXTURE_PLAYLIST]; case MENU_ENUM_LABEL_USER_SETTINGS: return xmb->textures.list[XMB_TEXTURE_USER]; case MENU_ENUM_LABEL_PRIVACY_SETTINGS: return xmb->textures.list[XMB_TEXTURE_PRIVACY]; case MENU_ENUM_LABEL_REWIND_SETTINGS: return xmb->textures.list[XMB_TEXTURE_REWIND]; case MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS: return xmb->textures.list[XMB_TEXTURE_OVERRIDE]; case MENU_ENUM_LABEL_ONSCREEN_NOTIFICATIONS_SETTINGS: return xmb->textures.list[XMB_TEXTURE_NOTIFICATIONS]; #ifdef HAVE_NETWORKING case MENU_ENUM_LABEL_NETPLAY_ENABLE_HOST: return xmb->textures.list[XMB_TEXTURE_RUN]; case MENU_ENUM_LABEL_NETPLAY_DISCONNECT: return xmb->textures.list[XMB_TEXTURE_CLOSE]; case MENU_ENUM_LABEL_NETPLAY_ENABLE_CLIENT: return xmb->textures.list[XMB_TEXTURE_ROOM]; case MENU_ENUM_LABEL_NETPLAY_REFRESH_ROOMS: return xmb->textures.list[XMB_TEXTURE_RELOAD]; case MENU_ENUM_LABEL_NETWORK_INFORMATION: case MENU_ENUM_LABEL_NETWORK_SETTINGS: case MENU_ENUM_LABEL_WIFI_SETTINGS: case MENU_ENUM_LABEL_NETWORK_INFO_ENTRY: case MENU_ENUM_LABEL_NETWORK_HOSTING_SETTINGS: return xmb->textures.list[XMB_TEXTURE_NETWORK]; #endif case MENU_ENUM_LABEL_BLUETOOTH_SETTINGS: return xmb->textures.list[XMB_TEXTURE_BLUETOOTH]; case MENU_ENUM_LABEL_SHUTDOWN: return xmb->textures.list[XMB_TEXTURE_SHUTDOWN]; case MENU_ENUM_LABEL_CHEAT_APPLY_CHANGES: case MENU_ENUM_LABEL_SHADER_APPLY_CHANGES: return xmb->textures.list[XMB_TEXTURE_CHECKMARK]; case MENU_ENUM_LABEL_CHEAT_ADD_NEW_AFTER: case MENU_ENUM_LABEL_CHEAT_ADD_NEW_BEFORE: case MENU_ENUM_LABEL_CHEAT_ADD_NEW_TOP: case MENU_ENUM_LABEL_CHEAT_ADD_NEW_BOTTOM: return xmb->textures.list[XMB_TEXTURE_MENU_ADD]; case MENU_ENUM_LABEL_CHEAT_APPLY_AFTER_TOGGLE: return xmb->textures.list[XMB_TEXTURE_MENU_APPLY_TOGGLE]; case MENU_ENUM_LABEL_CHEAT_APPLY_AFTER_LOAD: case MENU_ENUM_LABEL_SAVESTATE_AUTO_INDEX: return xmb->textures.list[XMB_TEXTURE_MENU_APPLY_COG]; case MENU_ENUM_LABEL_SLOWMOTION_RATIO: return xmb->textures.list[XMB_TEXTURE_RESUME]; case MENU_ENUM_LABEL_START_VIDEO_PROCESSOR: return xmb->textures.list[XMB_TEXTURE_MOVIE]; default: break; } switch(type) { case FILE_TYPE_DIRECTORY: return xmb->textures.list[XMB_TEXTURE_FOLDER]; case FILE_TYPE_PLAIN: case FILE_TYPE_IN_CARCHIVE: return xmb->textures.list[XMB_TEXTURE_FILE]; case FILE_TYPE_RPL_ENTRY: if (core_node) return core_node->content_icon; switch (xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr)) { case XMB_SYSTEM_TAB_FAVORITES: return xmb->textures.list[XMB_TEXTURE_FAVORITE]; case XMB_SYSTEM_TAB_MUSIC: return xmb->textures.list[XMB_TEXTURE_MUSIC]; #ifdef HAVE_IMAGEVIEWER case XMB_SYSTEM_TAB_IMAGES: return xmb->textures.list[XMB_TEXTURE_IMAGE]; #endif #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) case XMB_SYSTEM_TAB_VIDEO: return xmb->textures.list[XMB_TEXTURE_MOVIE]; #endif default: break; } return xmb->textures.list[XMB_TEXTURE_FILE]; case MENU_SET_CDROM_INFO: case MENU_SET_CDROM_LIST: case MENU_SET_LOAD_CDROM_LIST: return xmb->textures.list[XMB_TEXTURE_DISC]; case FILE_TYPE_SHADER: case FILE_TYPE_SHADER_PRESET: return xmb->textures.list[XMB_TEXTURE_SHADER_OPTIONS]; case FILE_TYPE_CARCHIVE: return xmb->textures.list[XMB_TEXTURE_ZIP]; case FILE_TYPE_MUSIC: return xmb->textures.list[XMB_TEXTURE_MUSIC]; case FILE_TYPE_IMAGE: case FILE_TYPE_IMAGEVIEWER: return xmb->textures.list[XMB_TEXTURE_IMAGE]; case FILE_TYPE_MOVIE: return xmb->textures.list[XMB_TEXTURE_MOVIE]; case FILE_TYPE_CORE: case FILE_TYPE_DIRECT_LOAD: return xmb->textures.list[XMB_TEXTURE_CORE]; case FILE_TYPE_RDB: return xmb->textures.list[XMB_TEXTURE_RDB]; case FILE_TYPE_CURSOR: return xmb->textures.list[XMB_TEXTURE_CURSOR]; case FILE_TYPE_PLAYLIST_ENTRY: case MENU_SETTING_ACTION_RUN: return xmb->textures.list[XMB_TEXTURE_RUN]; case MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS: return xmb->textures.list[XMB_TEXTURE_RESUME]; case MENU_SETTING_ACTION_CLOSE: case MENU_SETTING_ACTION_CLOSE_HORIZONTAL: case MENU_SETTING_ACTION_DELETE_ENTRY: return xmb->textures.list[XMB_TEXTURE_CLOSE]; case MENU_SETTING_ACTION_SAVESTATE: return xmb->textures.list[XMB_TEXTURE_SAVESTATE]; case MENU_SETTING_ACTION_LOADSTATE: return xmb->textures.list[XMB_TEXTURE_LOADSTATE]; case FILE_TYPE_RDB_ENTRY: return xmb->textures.list[XMB_TEXTURE_CORE_INFO]; case MENU_SETTING_ACTION_CORE_OPTIONS: return xmb->textures.list[XMB_TEXTURE_CORE_OPTIONS]; case MENU_SETTING_ACTION_CORE_INPUT_REMAPPING_OPTIONS: return xmb->textures.list[XMB_TEXTURE_INPUT_REMAPPING_OPTIONS]; case MENU_SETTING_ACTION_CORE_CHEAT_OPTIONS: return xmb->textures.list[XMB_TEXTURE_CHEAT_OPTIONS]; case MENU_SETTING_ACTION_CORE_DISK_OPTIONS: return xmb->textures.list[XMB_TEXTURE_DISK_OPTIONS]; case MENU_SETTING_ACTION_CORE_SHADER_OPTIONS: return xmb->textures.list[XMB_TEXTURE_SHADER_OPTIONS]; case MENU_SETTING_ACTION_SCREENSHOT: return xmb->textures.list[XMB_TEXTURE_SCREENSHOT]; case MENU_SETTING_ACTION_RESET: return xmb->textures.list[XMB_TEXTURE_RELOAD]; case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS: return xmb->textures.list[XMB_TEXTURE_PAUSE]; #ifdef HAVE_LAKKA_SWITCH case MENU_SET_SWITCH_BRIGHTNESS: return xmb->textures.list[XMB_TEXTURE_BRIGHTNESS]; #endif case MENU_SETTING_GROUP: return xmb->textures.list[XMB_TEXTURE_SETTING]; case MENU_INFO_MESSAGE: return xmb->textures.list[XMB_TEXTURE_CORE_INFO]; case MENU_BLUETOOTH: return xmb->textures.list[XMB_TEXTURE_BLUETOOTH]; case MENU_WIFI: return xmb->textures.list[XMB_TEXTURE_WIFI]; #ifdef HAVE_NETWORKING case MENU_ROOM: return xmb->textures.list[XMB_TEXTURE_ROOM]; case MENU_ROOM_LAN: return xmb->textures.list[XMB_TEXTURE_ROOM_LAN]; case MENU_ROOM_RELAY: return xmb->textures.list[XMB_TEXTURE_ROOM_RELAY]; #endif } #ifdef HAVE_CHEEVOS if ( (type >= MENU_SETTINGS_CHEEVOS_START) && (type < MENU_SETTINGS_NETPLAY_ROOMS_START) ) { int index = type - MENU_SETTINGS_CHEEVOS_START; uintptr_t badge_texture = cheevos_get_menu_badge_texture(index); if (badge_texture) return badge_texture; /* Should be replaced with placeholder badge icon. */ return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENTS]; } #endif if ( (type >= MENU_SETTINGS_INPUT_BEGIN) && (type <= MENU_SETTINGS_INPUT_DESC_END) ) { unsigned input_id; if (type < MENU_SETTINGS_INPUT_DESC_BEGIN) /* Input User # Binds only */ { input_id = MENU_SETTINGS_INPUT_BEGIN; if ( type == input_id + 1) return xmb->textures.list[XMB_TEXTURE_INPUT_ADC]; if ( type == input_id + 2) return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS]; if ( type == input_id + 3) return xmb->textures.list[XMB_TEXTURE_INPUT_BIND_ALL]; if ( type == input_id + 4) return xmb->textures.list[XMB_TEXTURE_RELOAD]; if ( type == input_id + 5) return xmb->textures.list[XMB_TEXTURE_SAVING]; if ( type == input_id + 6) return xmb->textures.list[XMB_TEXTURE_INPUT_MOUSE]; if ((type > (input_id + 30)) && (type < (input_id + 42))) return xmb->textures.list[XMB_TEXTURE_INPUT_LGUN]; if ( type == input_id + 42) return xmb->textures.list[XMB_TEXTURE_INPUT_TURBO]; /* align to use the same code of Quickmenu controls */ input_id = input_id + 7; } else { /* Quickmenu controls repeats the same icons for all users */ input_id = MENU_SETTINGS_INPUT_DESC_BEGIN; while (type > (input_id + 23)) { input_id = (input_id + 24) ; } } /* This is utilized for both Input # Binds and Quickmenu controls */ if ( type == input_id ) return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_D]; if ( type == (input_id + 1)) return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_L]; if ( type == (input_id + 2)) return xmb->textures.list[XMB_TEXTURE_INPUT_SELECT]; if ( type == (input_id + 3)) return xmb->textures.list[XMB_TEXTURE_INPUT_START]; if ( type == (input_id + 4)) return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_U]; if ( type == (input_id + 5)) return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_D]; if ( type == (input_id + 6)) return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_L]; if ( type == (input_id + 7)) return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_R]; if ( type == (input_id + 8)) return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_R]; if ( type == (input_id + 9)) return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_U]; if ( type == (input_id + 10)) return xmb->textures.list[XMB_TEXTURE_INPUT_LB]; if ( type == (input_id + 11)) return xmb->textures.list[XMB_TEXTURE_INPUT_RB]; if ( type == (input_id + 12)) return xmb->textures.list[XMB_TEXTURE_INPUT_LT]; if ( type == (input_id + 13)) return xmb->textures.list[XMB_TEXTURE_INPUT_RT]; if ( type == (input_id + 14)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_P]; if ( type == (input_id + 15)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_P]; if ( type == (input_id + 16)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_R]; if ( type == (input_id + 17)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_L]; if ( type == (input_id + 18)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_D]; if ( type == (input_id + 19)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_U]; if ( type == (input_id + 20)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_R]; if ( type == (input_id + 21)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_L]; if ( type == (input_id + 22)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_D]; if ( type == (input_id + 23)) return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_U]; } if (checked) return xmb->textures.list[XMB_TEXTURE_CHECKMARK]; if (type == MENU_SETTING_ACTION) return xmb->textures.list[XMB_TEXTURE_SETTING]; return xmb->textures.list[XMB_TEXTURE_SUBSETTING]; } static void xmb_calculate_visible_range(const xmb_handle_t *xmb, unsigned height, size_t list_size, unsigned current, unsigned *first, unsigned *last) { unsigned j; float base_y = xmb->margins_screen_top; *first = 0; *last = (unsigned)(list_size ? list_size - 1 : 0); if (current) { for (j = current; j-- > 0; ) { float bottom = xmb_item_y(xmb, j, current) + base_y + xmb->icon_size; if (bottom < 0) break; *first = j; } } for (j = current+1; j < list_size; j++) { float top = xmb_item_y(xmb, j, current) + base_y; if (top > height) break; *last = j; } } static int xmb_draw_item( void *userdata, unsigned video_width, unsigned video_height, bool xmb_shadows_enable, math_matrix_4x4 *mymat, xmb_handle_t *xmb, xmb_node_t *core_node, file_list_t *list, float *color, size_t i, size_t current, unsigned width, unsigned height ) { menu_entry_t entry; float icon_x, icon_y, label_offset; gfx_animation_ctx_ticker_t ticker; gfx_animation_ctx_ticker_smooth_t ticker_smooth; char tmp[255]; unsigned ticker_x_offset = 0; const char *ticker_str = NULL; unsigned entry_type = 0; const float half_size = xmb->icon_size / 2.0f; uintptr_t texture_switch = 0; bool do_draw_text = false; unsigned ticker_limit = 35 * scale_mod[0]; unsigned line_ticker_width = 45 * scale_mod[3]; xmb_node_t * node = (xmb_node_t*) file_list_get_userdata_at_offset(list, i); settings_t *settings = config_get_ptr(); bool use_smooth_ticker = settings->bools.menu_ticker_smooth; enum gfx_animation_ticker_type menu_ticker_type = (enum gfx_animation_ticker_type)settings->uints.menu_ticker_type; unsigned xmb_thumbnail_scale_factor = settings->uints.menu_xmb_thumbnail_scale_factor; bool menu_xmb_vertical_thumbnails = settings->bools.menu_xmb_vertical_thumbnails; bool menu_show_sublabels = settings->bools.menu_show_sublabels; /* Initial ticker configuration */ if (use_smooth_ticker) { ticker_smooth.idx = gfx_animation_get_ticker_pixel_idx(); ticker_smooth.font = xmb->font; ticker_smooth.font_scale = 1.0f; ticker_smooth.type_enum = menu_ticker_type; ticker_smooth.spacer = NULL; ticker_smooth.x_offset = &ticker_x_offset; ticker_smooth.dst_str_width = NULL; } else { ticker.idx = gfx_animation_get_ticker_idx(); ticker.type_enum = menu_ticker_type; ticker.spacer = NULL; } if (!node) return 0; tmp[0] = '\0'; icon_y = xmb->margins_screen_top + node->y + half_size; if (icon_y < half_size) return 0; if (icon_y > height + xmb->icon_size) return -1; icon_x = node->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal - half_size; if (icon_x < -half_size || icon_x > width) return 0; menu_entry_init(&entry); entry.label_enabled = false; entry.sublabel_enabled = (i == current); menu_entry_get(&entry, 0, i, list, true); entry_type = entry.type; if (entry_type == FILE_TYPE_CONTENTLIST_ENTRY) { char entry_path[PATH_MAX_LENGTH] = {0}; strlcpy(entry_path, entry.path, sizeof(entry_path)); fill_short_pathname_representation(entry_path, entry_path, sizeof(entry_path)); if (!string_is_empty(entry_path)) strlcpy(entry.path, entry_path, sizeof(entry.path)); } if (string_is_equal(entry.value, msg_hash_to_str(MENU_ENUM_LABEL_DISABLED)) || (string_is_equal(entry.value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF)))) { if (xmb->textures.list[XMB_TEXTURE_SWITCH_OFF]) texture_switch = xmb->textures.list[XMB_TEXTURE_SWITCH_OFF]; else do_draw_text = true; } else if (string_is_equal(entry.value, msg_hash_to_str(MENU_ENUM_LABEL_ENABLED)) || (string_is_equal(entry.value, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ON)))) { if (xmb->textures.list[XMB_TEXTURE_SWITCH_ON]) texture_switch = xmb->textures.list[XMB_TEXTURE_SWITCH_ON]; else do_draw_text = true; } else { if (!string_is_empty(entry.value)) { bool found = false; if (string_is_equal(entry.value, "...")) found = true; else if (string_starts_with_size(entry.value, "(", STRLEN_CONST("(")) && string_ends_with (entry.value, ")") ) { if ( string_is_equal(entry.value, "(PRESET)") || string_is_equal(entry.value, "(SHADER)") || string_is_equal(entry.value, "(COMP)") || string_is_equal(entry.value, "(CORE)") || string_is_equal(entry.value, "(MOVIE)") || string_is_equal(entry.value, "(MUSIC)") || string_is_equal(entry.value, "(DIR)") || string_is_equal(entry.value, "(RDB)") || string_is_equal(entry.value, "(CURSOR)")|| string_is_equal(entry.value, "(CFILE)") || string_is_equal(entry.value, "(FILE)") || string_is_equal(entry.value, "(IMAGE)") ) found = true; } if (!found) do_draw_text = true; } else do_draw_text = true; } if (string_is_empty(entry.value)) { if ((xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || !xmb->use_ps3_layout || (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) && ((xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_PENDING))) || (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT) && ((xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_PENDING)) && menu_xmb_vertical_thumbnails) ) { ticker_limit = 40 * scale_mod[1]; line_ticker_width = 50 * scale_mod[3]; /* Can increase text length if thumbnail is downscaled */ if (xmb_thumbnail_scale_factor < 100) { float ticker_scale_factor = 1.0f - ((float)xmb_thumbnail_scale_factor / 100.0f); ticker_limit += (unsigned)(ticker_scale_factor * 15.0f * scale_mod[1]); line_ticker_width += (unsigned)(ticker_scale_factor * 10.0f * scale_mod[3]); } } else { ticker_limit = 70 * scale_mod[2]; line_ticker_width = 60 * scale_mod[3]; } } menu_entry_get_rich_label(&entry, &ticker_str); if (use_smooth_ticker) { ticker_smooth.selected = (i == current); ticker_smooth.field_width = xmb->font_size * 0.5f * ticker_limit; ticker_smooth.src_str = ticker_str; ticker_smooth.dst_str = tmp; ticker_smooth.dst_str_len = sizeof(tmp); if (ticker_smooth.src_str) gfx_animation_ticker_smooth(&ticker_smooth); } else { ticker.s = tmp; ticker.len = ticker_limit; ticker.str = ticker_str; ticker.selected = (i == current); if (ticker.str) gfx_animation_ticker(&ticker); } label_offset = xmb->margins_label_top; if (menu_show_sublabels) { if (i == current && width > 320 && height > 240 && !string_is_empty(entry.sublabel)) { char entry_sublabel[MENU_SUBLABEL_MAX_LENGTH]; char entry_sublabel_top_fade[MENU_SUBLABEL_MAX_LENGTH >> 2]; char entry_sublabel_bottom_fade[MENU_SUBLABEL_MAX_LENGTH >> 2]; gfx_animation_ctx_line_ticker_t line_ticker; gfx_animation_ctx_line_ticker_smooth_t line_ticker_smooth; float ticker_y_offset = 0.0f; float ticker_top_fade_y_offset = 0.0f; float ticker_bottom_fade_y_offset = 0.0f; float ticker_top_fade_alpha = 0.0f; float ticker_bottom_fade_alpha = 0.0f; float sublabel_x = node->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->margins_label_left; float sublabel_y = xmb->margins_screen_top + node->y + (xmb->margins_label_top * 3.5f); entry_sublabel[0] = '\0'; entry_sublabel_top_fade[0] = '\0'; entry_sublabel_bottom_fade[0] = '\0'; if (use_smooth_ticker) { line_ticker_smooth.fade_enabled = true; line_ticker_smooth.type_enum = menu_ticker_type; line_ticker_smooth.idx = gfx_animation_get_ticker_pixel_line_idx(); line_ticker_smooth.font = xmb->font2; line_ticker_smooth.font_scale = 1.0f; line_ticker_smooth.field_width = (unsigned)(xmb->font2_size * 0.6f * line_ticker_width); /* The calculation here is incredibly obtuse. I think * this is correct... (c.f. xmb_item_y()) */ line_ticker_smooth.field_height = (unsigned)( (xmb->icon_spacing_vertical * ((1 + xmb->under_item_offset) - xmb->active_item_factor)) - (xmb->margins_label_top * 3.5f) - xmb->under_item_offset); /* This last one is just a little extra padding (seems to help) */ line_ticker_smooth.src_str = entry.sublabel; line_ticker_smooth.dst_str = entry_sublabel; line_ticker_smooth.dst_str_len = sizeof(entry_sublabel); line_ticker_smooth.y_offset = &ticker_y_offset; line_ticker_smooth.top_fade_str = entry_sublabel_top_fade; line_ticker_smooth.top_fade_str_len = sizeof(entry_sublabel_top_fade); line_ticker_smooth.top_fade_y_offset = &ticker_top_fade_y_offset; line_ticker_smooth.top_fade_alpha = &ticker_top_fade_alpha; line_ticker_smooth.bottom_fade_str = entry_sublabel_bottom_fade; line_ticker_smooth.bottom_fade_str_len = sizeof(entry_sublabel_bottom_fade); line_ticker_smooth.bottom_fade_y_offset = &ticker_bottom_fade_y_offset; line_ticker_smooth.bottom_fade_alpha = &ticker_bottom_fade_alpha; gfx_animation_line_ticker_smooth(&line_ticker_smooth); } else { line_ticker.type_enum = menu_ticker_type; line_ticker.idx = gfx_animation_get_ticker_idx(); line_ticker.line_len = (size_t)(line_ticker_width); /* Note: max_lines should be calculated at runtime, * but this is a nuisance. There is room for 4 lines * to be displayed when using all existing XMB themes, * so leave this value hard coded for now. */ line_ticker.max_lines = 4; line_ticker.s = entry_sublabel; line_ticker.len = sizeof(entry_sublabel); line_ticker.str = entry.sublabel; gfx_animation_line_ticker(&line_ticker); } label_offset = - xmb->margins_label_top; /* Draw sublabel */ xmb_draw_text(xmb_shadows_enable, xmb, entry_sublabel, sublabel_x, ticker_y_offset + sublabel_y, 1, node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font2); /* Draw top/bottom line fade effect, if required */ if (use_smooth_ticker) { if (!string_is_empty(entry_sublabel_top_fade) && ticker_top_fade_alpha > 0.0f) xmb_draw_text(xmb_shadows_enable, xmb, entry_sublabel_top_fade, sublabel_x, ticker_top_fade_y_offset + sublabel_y, 1, ticker_top_fade_alpha * node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font2); if (!string_is_empty(entry_sublabel_bottom_fade) && ticker_bottom_fade_alpha > 0.0f) xmb_draw_text(xmb_shadows_enable, xmb, entry_sublabel_bottom_fade, sublabel_x, ticker_bottom_fade_y_offset + sublabel_y, 1, ticker_bottom_fade_alpha * node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font2); } } } xmb_draw_text(xmb_shadows_enable, xmb, tmp, (float)ticker_x_offset + node->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->margins_label_left, xmb->margins_screen_top + node->y + label_offset, 1, node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font); tmp[0] = '\0'; if (use_smooth_ticker) { ticker_smooth.selected = (i == current); ticker_smooth.field_width = xmb->font_size * 0.5f * 35 * scale_mod[7]; ticker_smooth.src_str = entry.value; ticker_smooth.dst_str = tmp; ticker_smooth.dst_str_len = sizeof(tmp); if (!string_is_empty(entry.value)) gfx_animation_ticker_smooth(&ticker_smooth); } else { ticker.s = tmp; ticker.len = 35 * scale_mod[7]; ticker.selected = (i == current); ticker.str = entry.value; if (!string_is_empty(entry.value)) gfx_animation_ticker(&ticker); } if (do_draw_text) xmb_draw_text(xmb_shadows_enable, xmb, tmp, (float)ticker_x_offset + node->x + + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->margins_label_left + xmb->margins_setting_left, xmb->margins_screen_top + node->y + xmb->margins_label_top, 1, node->label_alpha, TEXT_ALIGN_LEFT, width, height, xmb->font); gfx_display_set_alpha(color, MIN(node->alpha, xmb->alpha)); if ( (!xmb->assets_missing) && (color[3] != 0) && ( (entry.checked) || !((entry_type >= MENU_SETTING_DROPDOWN_ITEM) && (entry_type <= MENU_SETTING_DROPDOWN_SETTING_UINT_ITEM_SPECIAL)) ) ) { math_matrix_4x4 mymat_tmp; gfx_display_ctx_rotate_draw_t rotate_draw; uintptr_t texture = xmb_icon_get_id(xmb, core_node, node, entry.enum_idx, entry_type, (i == current), entry.checked); float x = icon_x; float y = icon_y; float rotation = 0; float scale_factor = node->zoom; rotate_draw.matrix = &mymat_tmp; rotate_draw.rotation = rotation; rotate_draw.scale_x = scale_factor; rotate_draw.scale_y = scale_factor; rotate_draw.scale_z = 1; rotate_draw.scale_enable = true; gfx_display_rotate_z(&rotate_draw, userdata); xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, &mymat_tmp, texture, x, y, width, height, 1.0, rotation, scale_factor, &color[0], xmb->shadow_offset); } gfx_display_set_alpha(color, MIN(node->alpha, xmb->alpha)); if (texture_switch != 0 && color[3] != 0 && !xmb->assets_missing) xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, mymat, texture_switch, node->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal + xmb->icon_size / 2.0 + xmb->margins_setting_left, xmb->margins_screen_top + node->y + xmb->icon_size / 2.0, width, height, node->alpha, 0, 1, &color[0], xmb->shadow_offset); return 0; } static void xmb_draw_items( void *userdata, unsigned video_width, unsigned video_height, bool xmb_shadows_enable, xmb_handle_t *xmb, file_list_t *list, size_t current, size_t cat_selection_ptr, float *color, unsigned width, unsigned height) { size_t i; unsigned first, last; math_matrix_4x4 mymat; gfx_display_ctx_rotate_draw_t rotate_draw; xmb_node_t *core_node = NULL; size_t end = 0; if (!list || !list->size || !xmb) return; if (cat_selection_ptr > xmb->system_tab_end) core_node = xmb_get_userdata_from_horizontal_list( xmb, (unsigned)(cat_selection_ptr - (xmb->system_tab_end + 1))); end = file_list_get_size(list); rotate_draw.matrix = &mymat; rotate_draw.rotation = 0; rotate_draw.scale_x = 1; rotate_draw.scale_y = 1; rotate_draw.scale_z = 1; rotate_draw.scale_enable = true; gfx_display_rotate_z(&rotate_draw, userdata); menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i); if (list == xmb->selection_buf_old) { xmb_node_t *node = (xmb_node_t*) file_list_get_userdata_at_offset(list, current); if (node && (uint8_t)(255 * node->alpha) == 0) return; i = 0; } first = (unsigned)i; last = (unsigned)(end - 1); xmb_calculate_visible_range(xmb, height, end, (unsigned)current, &first, &last); gfx_display_blend_begin(userdata); for (i = first; i <= last; i++) { if (xmb_draw_item( userdata, video_width, video_height, xmb_shadows_enable, &mymat, xmb, core_node, list, color, i, current, width, height) == -1) break; } gfx_display_blend_end(userdata); } static INLINE bool xmb_use_ps3_layout( settings_t *settings, unsigned width, unsigned height) { unsigned menu_xmb_layout = settings->uints.menu_xmb_layout; switch (menu_xmb_layout) { case 1: /* PS3 */ return true; case 2: /* PSP */ return false; case 0: default: /* Automatic * > Use PSP layout on tiny screens */ return (width > 320) && (height > 240); } } static INLINE float xmb_get_scale_factor( settings_t *settings, bool use_ps3_layout, unsigned width) { float menu_scale_factor = settings->floats.menu_scale_factor; float scale_factor; /* PS3 Layout */ if (use_ps3_layout) scale_factor = (menu_scale_factor * (float)width) / 1920.0f; /* PSP Layout */ else #ifdef _3DS scale_factor = menu_scale_factor / 4.0f; #else scale_factor = ((menu_scale_factor * (float)width) / 1920.0f) * 1.5f; #endif /* Apply safety limit */ return (scale_factor >= 0.1f) ? scale_factor : 0.1f; } static void xmb_context_reset_internal(xmb_handle_t *xmb, bool is_threaded, bool reinit_textures); static void xmb_render(void *data, unsigned width, unsigned height, bool is_idle) { size_t i; float scale_factor; xmb_handle_t *xmb = (xmb_handle_t*)data; settings_t *settings = config_get_ptr(); unsigned end = (unsigned)menu_entries_get_size(); if (!xmb) return; xmb->use_ps3_layout = xmb_use_ps3_layout(settings, width, height); scale_factor = xmb_get_scale_factor(settings, xmb->use_ps3_layout, width); if ((xmb->use_ps3_layout != xmb->last_use_ps3_layout) || (scale_factor != xmb->last_scale_factor)) { xmb->last_use_ps3_layout = xmb->use_ps3_layout; xmb->last_scale_factor = scale_factor; xmb_context_reset_internal(xmb, video_driver_is_threaded(), false); } /* This must be set every frame when using a pointer, * otherwise touchscreen input breaks when changing * orientation */ gfx_display_set_width(width); gfx_display_set_height(height); /* Read pointer state */ menu_input_get_pointer_state(&xmb->pointer); if (xmb->pointer.type != MENU_POINTER_DISABLED) { size_t selection = menu_navigation_get_selection(); int16_t margin_top = (int16_t)xmb->margins_screen_top; int16_t margin_left = (int16_t)xmb->margins_screen_left; int16_t margin_right = (int16_t)((float)width - xmb->margins_screen_left); int16_t pointer_x = xmb->pointer.x; int16_t pointer_y = xmb->pointer.y; /* When determining current pointer selection, we * only track pointer movements between the left * and right screen margins */ if ((pointer_x > margin_left) && (pointer_x < margin_right)) { unsigned first = 0; unsigned last = end; if (height) xmb_calculate_visible_range(xmb, height, end, (unsigned)selection, &first, &last); for (i = first; i <= last; i++) { float entry_size = (i == selection) ? xmb->icon_spacing_vertical * xmb->active_item_factor : xmb->icon_spacing_vertical; float half_entry_size = entry_size * 0.5f; float y_curr; int y1; int y2; y_curr = xmb_item_y(xmb, (int)i, selection) + xmb->margins_screen_top; y1 = (int)((y_curr - half_entry_size) + 0.5f); y2 = (int)((y_curr + half_entry_size) + 0.5f); if ((pointer_y > y1) && (pointer_y < y2)) { menu_input_set_pointer_selection(i); break; } } } /* Areas beyond the top/right margins are used * as a sort of virtual dpad: * - Above top margin: navigate left/right * - Beyond right margin: navigate up/down */ if ((pointer_y < margin_top) || (pointer_x > margin_right)) { menu_entry_t entry; bool get_entry = false; switch (xmb->pointer.press_direction) { case MENU_INPUT_PRESS_DIRECTION_UP: if (pointer_x > margin_right) get_entry = true; break; case MENU_INPUT_PRESS_DIRECTION_DOWN: /* Note: Direction is inverted, since 'down' should * move list downwards */ if (pointer_x > margin_right) get_entry = true; break; case MENU_INPUT_PRESS_DIRECTION_LEFT: /* Navigate left * Note: At the top level, navigating left * means switching to the 'next' horizontal list, * which is actually a movement to the *right* */ if (pointer_y < margin_top) get_entry = true; break; case MENU_INPUT_PRESS_DIRECTION_RIGHT: /* Navigate right * Note: At the top level, navigating right * means switching to the 'previous' horizontal list, * which is actually a movement to the *left* */ if (pointer_y < margin_top) get_entry = true; break; case MENU_INPUT_PRESS_DIRECTION_NONE: default: break; } if (get_entry) { menu_entry_init(&entry); entry.path_enabled = false; entry.label_enabled = false; entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, selection, NULL, true); } switch (xmb->pointer.press_direction) { case MENU_INPUT_PRESS_DIRECTION_UP: /* Note: Direction is inverted, since 'up' should * move list upwards */ if (pointer_x > margin_right) menu_entry_action(&entry, selection, MENU_ACTION_DOWN); break; case MENU_INPUT_PRESS_DIRECTION_DOWN: /* Note: Direction is inverted, since 'down' should * move list downwards */ if (pointer_x > margin_right) menu_entry_action(&entry, selection, MENU_ACTION_UP); break; case MENU_INPUT_PRESS_DIRECTION_LEFT: /* Navigate left * Note: At the top level, navigating left * means switching to the 'next' horizontal list, * which is actually a movement to the *right* */ if (pointer_y < margin_top) menu_entry_action( &entry, selection, (xmb->depth == 1) ? MENU_ACTION_RIGHT : MENU_ACTION_LEFT); break; case MENU_INPUT_PRESS_DIRECTION_RIGHT: /* Navigate right * Note: At the top level, navigating right * means switching to the 'previous' horizontal list, * which is actually a movement to the *left* */ if (pointer_y < margin_top) menu_entry_action( &entry, selection, (xmb->depth == 1) ? MENU_ACTION_LEFT : MENU_ACTION_RIGHT); break; default: /* Do nothing */ break; } } } menu_entries_ctl(MENU_ENTRIES_CTL_START_GET, &i); if (i >= end) { i = 0; menu_entries_ctl(MENU_ENTRIES_CTL_SET_START, &i); } gfx_animation_ctl(MENU_ANIMATION_CTL_CLEAR_ACTIVE, NULL); } static bool xmb_shader_pipeline_active(unsigned menu_shader_pipeline) { if (string_is_not_equal(menu_driver_ident(), "xmb")) return false; if (menu_shader_pipeline == XMB_SHADER_PIPELINE_WALLPAPER) return false; return true; } static void xmb_draw_bg( xmb_handle_t *xmb, void *userdata, unsigned video_width, unsigned video_height, unsigned menu_shader_pipeline, unsigned xmb_color_theme, float menu_wallpaper_opacity, bool libretro_running, float alpha, uintptr_t texture_id, float *coord_black, float *coord_white) { gfx_display_ctx_draw_t draw; draw.x = 0; draw.y = 0; draw.texture = texture_id; draw.width = video_width; draw.height = video_height; draw.color = &coord_black[0]; draw.vertex = NULL; draw.tex_coord = NULL; draw.vertex_count = 4; draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; draw.pipeline.id = 0; draw.pipeline.active = xmb_shader_pipeline_active(menu_shader_pipeline); gfx_display_blend_begin(userdata); gfx_display_set_viewport(video_width, video_height); #ifdef HAVE_SHADERPIPELINE if (menu_shader_pipeline > XMB_SHADER_PIPELINE_WALLPAPER && (xmb_color_theme != XMB_THEME_WALLPAPER)) { draw.color = xmb_gradient_ident(xmb_color_theme); if (libretro_running) gfx_display_set_alpha(draw.color, coord_black[3]); else gfx_display_set_alpha(draw.color, coord_white[3]); gfx_display_draw_gradient(&draw, userdata, video_width, video_height, menu_wallpaper_opacity ); draw.pipeline.id = VIDEO_SHADER_MENU_2; switch (menu_shader_pipeline) { case XMB_SHADER_PIPELINE_RIBBON: draw.pipeline.id = VIDEO_SHADER_MENU; break; case XMB_SHADER_PIPELINE_SIMPLE_SNOW: draw.pipeline.id = VIDEO_SHADER_MENU_3; break; case XMB_SHADER_PIPELINE_SNOW: draw.pipeline.id = VIDEO_SHADER_MENU_4; break; case XMB_SHADER_PIPELINE_BOKEH: draw.pipeline.id = VIDEO_SHADER_MENU_5; break; case XMB_SHADER_PIPELINE_SNOWFLAKE: draw.pipeline.id = VIDEO_SHADER_MENU_6; break; default: break; } gfx_display_draw_pipeline(&draw, userdata, video_width, video_height); } else #endif { uintptr_t texture = draw.texture; if (xmb_color_theme != XMB_THEME_WALLPAPER) draw.color = xmb_gradient_ident(xmb_color_theme); if (libretro_running) gfx_display_set_alpha(draw.color, coord_black[3]); else gfx_display_set_alpha(draw.color, coord_white[3]); if (xmb_color_theme != XMB_THEME_WALLPAPER) gfx_display_draw_gradient(&draw, userdata, video_width, video_height, menu_wallpaper_opacity); { bool add_opacity = false; draw.texture = texture; gfx_display_set_alpha(draw.color, coord_white[3]); if (draw.texture) draw.color = &coord_white[0]; if (libretro_running || xmb_color_theme == XMB_THEME_WALLPAPER) add_opacity = true; gfx_display_draw_bg(&draw, userdata, add_opacity, menu_wallpaper_opacity); } } gfx_display_draw(&draw, userdata, video_width, video_height); gfx_display_blend_end(userdata); } static void xmb_draw_dark_layer( xmb_handle_t *xmb, void *userdata, unsigned width, unsigned height) { gfx_display_ctx_draw_t draw; struct video_coords coords; float black[16] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, }; gfx_display_set_alpha(black, MIN(xmb->alpha, 0.75)); coords.vertices = 4; coords.vertex = NULL; coords.tex_coord = NULL; coords.lut_tex_coord = NULL; coords.color = &black[0]; draw.x = 0; draw.y = 0; draw.width = width; draw.height = height; draw.coords = &coords; draw.matrix_data = NULL; draw.texture = gfx_display_white_texture; draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP; draw.pipeline.id = 0; gfx_display_blend_begin(userdata); gfx_display_draw(&draw, userdata, width, height); gfx_display_blend_end(userdata); } /* Disables the fullscreen thumbnail view, with * an optional fade out animation */ static void xmb_hide_fullscreen_thumbnails( xmb_handle_t *xmb, bool animate) { uintptr_t alpha_tag = (uintptr_t)&xmb->fullscreen_thumbnail_alpha; /* Kill any existing fade in/out animations */ gfx_animation_kill_by_tag(&alpha_tag); /* Check whether animations are enabled */ if (animate && (xmb->fullscreen_thumbnail_alpha > 0.0f)) { gfx_animation_ctx_entry_t animation_entry; /* Configure fade out animation */ animation_entry.easing_enum = EASING_OUT_QUAD; animation_entry.tag = alpha_tag; animation_entry.duration = gfx_thumbnail_get_fade_duration(); animation_entry.target_value = 0.0f; animation_entry.subject = &xmb->fullscreen_thumbnail_alpha; animation_entry.cb = NULL; animation_entry.userdata = NULL; /* Push animation */ gfx_animation_push(&animation_entry); } /* No animation - just set thumbnail alpha to zero */ else xmb->fullscreen_thumbnail_alpha = 0.0f; /* Disable fullscreen thumbnails */ xmb->show_fullscreen_thumbnails = false; } /* Enables (and triggers a fade in of) the fullscreen * thumbnail view */ static void xmb_show_fullscreen_thumbnails( xmb_handle_t *xmb, size_t selection) { menu_entry_t selected_entry; gfx_animation_ctx_entry_t animation_entry; const char *core_name = NULL; const char *thumbnail_label = NULL; uintptr_t alpha_tag = (uintptr_t) &xmb->fullscreen_thumbnail_alpha; /* Before showing fullscreen thumbnails, must * ensure that any existing fullscreen thumbnail * view is disabled... */ xmb_hide_fullscreen_thumbnails(xmb, false); /* Sanity check: Return immediately if this is * a menu without thumbnail support */ if (!xmb->fullscreen_thumbnails_available) return; /* We can only enable fullscreen thumbnails if * current selection has at least one valid thumbnail * and all thumbnails for current selection are already * loaded/available */ gfx_thumbnail_get_core_name(xmb->thumbnail_path_data, &core_name); if (string_is_equal(core_name, "imageviewer")) { /* imageviewer content requires special treatment, * since only one thumbnail can ever be loaded * at a time */ if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)) { if (xmb->thumbnails.right.status != GFX_THUMBNAIL_STATUS_AVAILABLE) return; } else if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) { if (xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_AVAILABLE) return; } else return; } else { bool left_thumbnail_enabled = gfx_thumbnail_is_enabled( xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT); if ((xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE) && (left_thumbnail_enabled && ((xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_MISSING) && (xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_AVAILABLE)))) return; if ((xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_MISSING) && (!left_thumbnail_enabled || (xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_AVAILABLE))) return; } /* Cache selected entry label * (used as title when fullscreen thumbnails * are shown) */ xmb->fullscreen_thumbnail_label[0] = '\0'; /* > Get menu entry */ menu_entry_init(&selected_entry); selected_entry.path_enabled = false; selected_entry.value_enabled = false; selected_entry.sublabel_enabled = false; menu_entry_get(&selected_entry, 0, selection, NULL, true); /* > Get entry label */ menu_entry_get_rich_label(&selected_entry, &thumbnail_label); /* > Sanity check */ if (!string_is_empty(thumbnail_label)) strlcpy( xmb->fullscreen_thumbnail_label, thumbnail_label, sizeof(xmb->fullscreen_thumbnail_label)); /* Configure fade in animation */ animation_entry.easing_enum = EASING_OUT_QUAD; animation_entry.tag = alpha_tag; animation_entry.duration = gfx_thumbnail_get_fade_duration(); animation_entry.target_value = 1.0f; animation_entry.subject = &xmb->fullscreen_thumbnail_alpha; animation_entry.cb = NULL; animation_entry.userdata = NULL; /* Push animation */ gfx_animation_push(&animation_entry); /* Enable fullscreen thumbnails */ xmb->fullscreen_thumbnail_selection = selection; xmb->show_fullscreen_thumbnails = true; } static void xmb_draw_fullscreen_thumbnails( xmb_handle_t *xmb, void *userdata, unsigned video_width, unsigned video_height, bool xmb_shadows_enable, unsigned xmb_color_theme, settings_t *settings, size_t selection) { /* Check whether fullscreen thumbnails are visible */ if (xmb->fullscreen_thumbnail_alpha > 0.0f) { int header_margin; int thumbnail_box_width; int thumbnail_box_height; int right_thumbnail_x; int left_thumbnail_x; int thumbnail_y; gfx_thumbnail_shadow_t thumbnail_shadow; gfx_thumbnail_t *right_thumbnail = NULL; gfx_thumbnail_t *left_thumbnail = NULL; int view_width = (int)video_width; int view_height = (int)video_height; int thumbnail_margin = (int)(xmb->icon_size / 2.0f); bool show_right_thumbnail = false; bool show_left_thumbnail = false; unsigned num_thumbnails = 0; float right_thumbnail_draw_width = 0.0f; float right_thumbnail_draw_height = 0.0f; float left_thumbnail_draw_width = 0.0f; float left_thumbnail_draw_height = 0.0f; float *menu_color = xmb_gradient_ident(xmb_color_theme); /* XMB doesn't have a proper theme interface, so * hard-code this alpha value for now... */ float background_alpha = 0.75f; float background_color[16] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, }; uint32_t title_color = 0xFFFFFF00; float header_alpha = 0.6f; float header_color[16] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, }; int frame_width = (int)(xmb->icon_size / 6.0f); float frame_color[16] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; bool show_header = !string_is_empty(xmb->fullscreen_thumbnail_label); int header_height = show_header ? (int)((float)xmb->font_size * 1.2f) + (frame_width * 2) : 0; bool xmb_vertical_thumbnails = settings->bools.menu_xmb_vertical_thumbnails; bool menu_ticker_smooth = settings->bools.menu_ticker_smooth; enum gfx_animation_ticker_type menu_ticker_type = (enum gfx_animation_ticker_type)settings->uints.menu_ticker_type; /* Sanity check: Return immediately if this is * a menu without thumbnails and we are not currently * 'fading out' the fullscreen thumbnail view */ if (!xmb->fullscreen_thumbnails_available && xmb->show_fullscreen_thumbnails) goto error; /* Safety check: ensure that current * selection matches the entry selected when * fullscreen thumbnails were enabled * > Note that we exclude this check if we are * currently viewing the quick menu and the * thumbnail view is fading out. This enables * a smooth transition if the user presses * RetroPad A or keyboard 'return' to enter the * quick menu while fullscreen thumbnails are * being displayed */ if ((selection != xmb->fullscreen_thumbnail_selection) && (!xmb->is_quick_menu || xmb->show_fullscreen_thumbnails)) goto error; /* Get thumbnail pointers * > Order is swapped when using 'vertical disposition' */ if (xmb_vertical_thumbnails) { right_thumbnail = &xmb->thumbnails.left; left_thumbnail = &xmb->thumbnails.right; } else { right_thumbnail = &xmb->thumbnails.right; left_thumbnail = &xmb->thumbnails.left; } /* Get number of 'active' thumbnails */ show_right_thumbnail = (right_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE); show_left_thumbnail = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE); if (show_right_thumbnail) num_thumbnails++; if (show_left_thumbnail) num_thumbnails++; /* Do nothing if both thumbnails are missing * > Note: Baring inexplicable internal errors, this * can never happen... */ if (num_thumbnails < 1) goto error; /* Get base thumbnail dimensions + draw positions */ /* > Thumbnail bounding box height + y position * are fixed */ header_margin = (header_height > thumbnail_margin) ? header_height : thumbnail_margin; thumbnail_box_height = view_height - header_margin - thumbnail_margin; thumbnail_y = header_margin; /* Thumbnail bounding box width and x position * depend upon number of active thumbnails */ if (num_thumbnails == 2) { thumbnail_box_width = (view_width - (thumbnail_margin * 3) - frame_width) >> 1; left_thumbnail_x = thumbnail_margin; right_thumbnail_x = left_thumbnail_x + thumbnail_box_width + frame_width + thumbnail_margin; } else { thumbnail_box_width = view_width - (thumbnail_margin * 2); left_thumbnail_x = thumbnail_margin; right_thumbnail_x = left_thumbnail_x; } /* Sanity check */ if ((thumbnail_box_width < 1) || (thumbnail_box_height < 1)) goto error; /* Get thumbnail draw dimensions * > Note: The following code is a bit awkward, since * we have to do things in a very specific order * - i.e. we cannot determine proper thumbnail * layout until we have thumbnail draw dimensions. * and we cannot get draw dimensions until we have * the bounding box dimensions... */ if (show_right_thumbnail) { gfx_thumbnail_get_draw_dimensions( right_thumbnail, thumbnail_box_width, thumbnail_box_height, 1.0f, &right_thumbnail_draw_width, &right_thumbnail_draw_height); /* Sanity check */ if ((right_thumbnail_draw_width <= 0.0f) || (right_thumbnail_draw_height <= 0.0f)) goto error; } if (show_left_thumbnail) { gfx_thumbnail_get_draw_dimensions( left_thumbnail, thumbnail_box_width, thumbnail_box_height, 1.0f, &left_thumbnail_draw_width, &left_thumbnail_draw_height); /* Sanity check */ if ((left_thumbnail_draw_width <= 0.0f) || (left_thumbnail_draw_height <= 0.0f)) goto error; } /* Adjust thumbnail draw positions to achieve * uniform appearance (accounting for actual * draw dimensions...) */ if (num_thumbnails == 2) { int left_padding = (thumbnail_box_width - (int)left_thumbnail_draw_width) >> 1; int right_padding = (thumbnail_box_width - (int)right_thumbnail_draw_width) >> 1; /* Move thumbnails as close together as possible, * and horizontally centre the resultant 'block' * of images */ left_thumbnail_x += right_padding; right_thumbnail_x -= left_padding; } /* Set colour values */ /* > Background */ gfx_display_set_alpha( background_color, background_alpha * xmb->fullscreen_thumbnail_alpha); /* > Header background */ header_color[11] = header_alpha * xmb->fullscreen_thumbnail_alpha; header_color[15] = header_color[11]; /* > Title text */ title_color |= (unsigned)((255.0f * xmb->fullscreen_thumbnail_alpha) + 0.5f); /* > Thumbnail frame */ if (menu_color) { float mean_menu_color[3]; /* The menu gradients are not entirely consistent... * The best we can do here is take the mean of the * first and last vertex colours... */ mean_menu_color[0] = (menu_color[0] + menu_color[12]) / 2.0f; mean_menu_color[1] = (menu_color[1] + menu_color[13]) / 2.0f; mean_menu_color[2] = (menu_color[2] + menu_color[14]) / 2.0f; memcpy(frame_color, mean_menu_color, sizeof(mean_menu_color)); memcpy(frame_color + 4, mean_menu_color, sizeof(mean_menu_color)); memcpy(frame_color + 8, mean_menu_color, sizeof(mean_menu_color)); memcpy(frame_color + 12, mean_menu_color, sizeof(mean_menu_color)); } gfx_display_set_alpha( frame_color, xmb->fullscreen_thumbnail_alpha); /* Darken background */ gfx_display_draw_quad( userdata, video_width, video_height, 0, 0, (unsigned)view_width, (unsigned)view_height, (unsigned)view_width, (unsigned)view_height, background_color); /* Draw header */ if (show_header) { /* Background */ gfx_display_draw_quad( userdata, video_width, video_height, 0, 0, (unsigned)view_width, (unsigned)(header_height - frame_width), (unsigned)view_width, (unsigned)view_height, header_color); /* Title text */ if (menu_ticker_smooth) { char title_buf[255]; gfx_animation_ctx_ticker_smooth_t ticker_smooth; int title_x = 0; unsigned ticker_x_offset = 0; unsigned ticker_str_width = 0; title_buf[0] = '\0'; ticker_smooth.idx = gfx_animation_get_ticker_pixel_idx(); ticker_smooth.font = xmb->font; ticker_smooth.font_scale = 1.0f; ticker_smooth.type_enum = menu_ticker_type; ticker_smooth.spacer = NULL; ticker_smooth.x_offset = &ticker_x_offset; ticker_smooth.dst_str_width = &ticker_str_width; ticker_smooth.selected = true; ticker_smooth.field_width = (unsigned)view_width; ticker_smooth.src_str = xmb->fullscreen_thumbnail_label; ticker_smooth.dst_str = title_buf; ticker_smooth.dst_str_len = sizeof(title_buf); /* If ticker is not active, centre the title text */ if (!gfx_animation_ticker_smooth(&ticker_smooth)) title_x = (view_width - (int)ticker_str_width) >> 1; title_x += (int)ticker_x_offset; gfx_display_draw_text( xmb->font, title_buf, title_x, xmb->font_size, (unsigned)view_width, (unsigned)view_height, title_color, TEXT_ALIGN_LEFT, 1.0f, false, 0.0f, false); } /* Note: The non-smooth ticker is a complete failure * here, since actual text width is unknown. This * causes the text to be horizontally offset - we * cannot fix this. * All we can do in this case is just draw the text * as-is, horizontally centred, and if the ends get * clipped than so be it... */ else gfx_display_draw_text( xmb->font, xmb->fullscreen_thumbnail_label, view_width >> 1, xmb->font_size, (unsigned)view_width, (unsigned)view_height, title_color, TEXT_ALIGN_CENTER, 1.0f, false, 0.0f, false); } /* Draw thumbnails */ /* > Configure shadow effect */ if (xmb_shadows_enable) { float shadow_offset = xmb->icon_size / 24.0f; thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_DROP; thumbnail_shadow.alpha = 0.35f; thumbnail_shadow.drop.x_offset = shadow_offset; thumbnail_shadow.drop.y_offset = shadow_offset; } else thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_NONE; /* > Right */ if (show_right_thumbnail) { /* Background */ gfx_display_draw_quad( userdata, video_width, video_height, right_thumbnail_x - frame_width + ((thumbnail_box_width - (int)right_thumbnail_draw_width) >> 1), thumbnail_y - frame_width + ((thumbnail_box_height - (int)right_thumbnail_draw_height) >> 1), (unsigned)right_thumbnail_draw_width + (frame_width << 1), (unsigned)right_thumbnail_draw_height + (frame_width << 1), (unsigned)view_width, (unsigned)view_height, frame_color); /* Thumbnail */ gfx_thumbnail_draw( userdata, video_width, video_height, right_thumbnail, right_thumbnail_x, thumbnail_y, (unsigned)thumbnail_box_width, (unsigned)thumbnail_box_height, GFX_THUMBNAIL_ALIGN_CENTRE, xmb->fullscreen_thumbnail_alpha, 1.0f, &thumbnail_shadow); } /* > Left */ if (show_left_thumbnail) { /* Background */ gfx_display_draw_quad( userdata, video_width, video_height, left_thumbnail_x - frame_width + ((thumbnail_box_width - (int)left_thumbnail_draw_width) >> 1), thumbnail_y - frame_width + ((thumbnail_box_height - (int)left_thumbnail_draw_height) >> 1), (unsigned)left_thumbnail_draw_width + (frame_width << 1), (unsigned)left_thumbnail_draw_height + (frame_width << 1), (unsigned)view_width, (unsigned)view_height, frame_color); /* Thumbnail */ gfx_thumbnail_draw( userdata, video_width, video_height, left_thumbnail, left_thumbnail_x, thumbnail_y, (unsigned)thumbnail_box_width, (unsigned)thumbnail_box_height, GFX_THUMBNAIL_ALIGN_CENTRE, xmb->fullscreen_thumbnail_alpha, 1.0f, &thumbnail_shadow); } } return; error: /* If fullscreen thumbnails are enabled at * this point, must disable them immediately... */ if (xmb->show_fullscreen_thumbnails) xmb_hide_fullscreen_thumbnails(xmb, false); } static void xmb_frame(void *data, video_frame_info_t *video_info) { math_matrix_4x4 mymat; unsigned i; gfx_display_ctx_rotate_draw_t rotate_draw; char msg[1024]; char title_msg[255]; char title_truncated[255]; gfx_thumbnail_shadow_t thumbnail_shadow; size_t selection = 0; size_t percent_width = 0; bool render_background = false; file_list_t *selection_buf = NULL; const float under_thumb_margin = 0.96f; float left_thumbnail_margin_width = 0.0f; float right_thumbnail_margin_width = 0.0f; float thumbnail_margin_height_under = 0.0f; float thumbnail_margin_height_full = 0.0f; float left_thumbnail_margin_x = 0.0f; float right_thumbnail_margin_x = 0.0f; float pseudo_font_length = 0.0f; xmb_handle_t *xmb = (xmb_handle_t*)data; settings_t *settings = config_get_ptr(); unsigned xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); bool fade_tab_icons = false; float fade_tab_icons_x_threshold = 0.0f; bool menu_core_enable = settings->bools.menu_core_enable; float thumbnail_scale_factor = (float)settings->uints.menu_xmb_thumbnail_scale_factor / 100.0f; bool menu_xmb_vertical_thumbnails = settings->bools.menu_xmb_vertical_thumbnails; void *userdata = video_info->userdata; unsigned video_width = video_info->width; unsigned video_height = video_info->height; bool xmb_shadows_enable = video_info->xmb_shadows_enable; float xmb_alpha_factor = video_info->xmb_alpha_factor; bool timedate_enable = video_info->timedate_enable; bool battery_level_enable = video_info->battery_level_enable; bool video_fullscreen = video_info->fullscreen; bool menu_mouse_enable = video_info->menu_mouse_enable; unsigned xmb_color_theme = video_info->xmb_color_theme; bool libretro_running = video_info->libretro_running; unsigned menu_shader_pipeline = video_info->menu_shader_pipeline; float menu_wallpaper_opacity = video_info->menu_wallpaper_opacity; if (!xmb) return; msg[0] = '\0'; title_msg[0] = '\0'; title_truncated[0] = '\0'; pseudo_font_length = xmb->icon_spacing_horizontal * 4 - xmb->icon_size / 4.0f; left_thumbnail_margin_width = xmb->icon_size * 3.4f; right_thumbnail_margin_width = (float)video_width - (xmb->icon_size / 6) - (xmb->margins_screen_left * scale_mod[5]) - xmb->icon_spacing_horizontal - pseudo_font_length; thumbnail_margin_height_under = ((float)video_height * under_thumb_margin) - xmb->margins_screen_top - xmb->icon_size; thumbnail_margin_height_full = (float)video_height - xmb->margins_title_top - ((xmb->icon_size / 4.0f) * 2.0f); left_thumbnail_margin_x = xmb->icon_size / 6.0f; right_thumbnail_margin_x = (float)video_width - (xmb->icon_size / 6.0f) - right_thumbnail_margin_width; /* Configure shadow effect */ if (xmb_shadows_enable) { /* Drop shadow for thumbnails needs to be larger * than for text/icons, and also needs to scale * with screen dimensions */ float shadow_offset = xmb->shadow_offset * 1.5f * xmb->last_scale_factor; shadow_offset = (shadow_offset > xmb->shadow_offset) ? shadow_offset : xmb->shadow_offset; thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_DROP; thumbnail_shadow.alpha = 0.35f; thumbnail_shadow.drop.x_offset = shadow_offset; thumbnail_shadow.drop.y_offset = shadow_offset; } else thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_NONE; font_driver_bind_block(xmb->font, &xmb->raster_block); font_driver_bind_block(xmb->font2, &xmb->raster_block2); xmb->raster_block.carr.coords.vertices = 0; xmb->raster_block2.carr.coords.vertices = 0; gfx_display_set_alpha(coord_black, MIN( (float)xmb_alpha_factor / 100, xmb->alpha)); gfx_display_set_alpha(coord_white, xmb->alpha); xmb_draw_bg( xmb, userdata, video_width, video_height, menu_shader_pipeline, xmb_color_theme, menu_wallpaper_opacity, libretro_running, xmb->alpha, xmb->textures.bg, coord_black, coord_white); selection = menu_navigation_get_selection(); strlcpy(title_truncated, xmb->title_name, sizeof(title_truncated)); if (selection > 1) { /* skip 25 utf8 multi-byte chars */ char *end = title_truncated; for(i = 0; i < 25 && *end; i++) { end++; while ((*end & 0xC0) == 0x80) end++; } *end = '\0'; } /* Title text */ xmb_draw_text(xmb_shadows_enable, xmb, title_truncated, xmb->margins_title_left, xmb->margins_title_top, 1, 1, TEXT_ALIGN_LEFT, video_width, video_height, xmb->font); if (menu_core_enable) { menu_entries_get_core_title(title_msg, sizeof(title_msg)); xmb_draw_text(xmb_shadows_enable, xmb, title_msg, xmb->margins_title_left, video_height - xmb->margins_title_bottom, 1, 1, TEXT_ALIGN_LEFT, video_width, video_height, xmb->font); } rotate_draw.matrix = &mymat; rotate_draw.rotation = 0; rotate_draw.scale_x = 1; rotate_draw.scale_y = 1; rotate_draw.scale_z = 1; rotate_draw.scale_enable = true; gfx_display_rotate_z(&rotate_draw, userdata); /**************************/ /* Draw thumbnails: START */ /**************************/ /* Note: This is incredibly ugly, but there are * so many combinations here that we would go insane * trying to rationalise this any further... */ /* Save state thumbnail, right side */ if (xmb->is_quick_menu && ((xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || (xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_PENDING))) { float thumb_width = right_thumbnail_margin_width; float thumb_height = thumbnail_margin_height_full; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = right_thumbnail_margin_x + ((thumb_width - scaled_thumb_width) / 2.0f); float thumb_y = xmb->margins_title_top + (xmb->icon_size / 4.0f) + ((thumb_height - scaled_thumb_height) / 2.0f); gfx_thumbnail_draw( userdata, video_width, video_height, &xmb->thumbnails.savestate, thumb_x, thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_CENTRE, 1.0f, 1.0f, &thumbnail_shadow); } /* This is used for hiding thumbnails when going into sub-levels in the * Quick Menu as well as when selecting "Information" for a playlist entry. * NOTE: This is currently a pretty crude check, simply going by menu depth * and not specifically identifying which menu we're actually in. */ else if (!((xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS) && (xmb->depth > 2))) { bool show_right_thumbnail = (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_PENDING); bool show_left_thumbnail = (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_AVAILABLE) || (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_PENDING); /* Check if we are using the proper PS3 layout, * or the aborted PSP layout */ if (xmb->use_ps3_layout) { /* Check if user has selected vertically * stacked thumbnails */ if (menu_xmb_vertical_thumbnails) { /* Right + left thumbnails, right side */ if (show_right_thumbnail && show_left_thumbnail) { float thumb_width = right_thumbnail_margin_width; float thumb_height = (thumbnail_margin_height_full - (xmb->icon_size / 4.0f)) / 2.0f; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = right_thumbnail_margin_x + ((thumb_width - scaled_thumb_width) / 2.0f); float thumb_y_base = xmb->margins_title_top + (xmb->icon_size / 4.0f); float thumb_y_offset = (thumb_height - scaled_thumb_height) / 2.0f; float right_thumb_y = thumb_y_base + thumb_y_offset; float left_thumb_y = thumb_y_base + thumb_height + (xmb->icon_size / 4) + thumb_y_offset; gfx_thumbnail_draw( userdata, video_width, video_height, &xmb->thumbnails.right, thumb_x, right_thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_CENTRE, 1.0f, 1.0f, &thumbnail_shadow); gfx_thumbnail_draw( userdata, video_width, video_height, &xmb->thumbnails.left, thumb_x, left_thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_CENTRE, 1.0f, 1.0f, &thumbnail_shadow); /* Horizontal tab icons overlapping the top * right image must be faded out */ fade_tab_icons = true; fade_tab_icons_x_threshold = thumb_x; } /* Right *or* left, right side */ else if (show_right_thumbnail || show_left_thumbnail) { float thumb_width = right_thumbnail_margin_width; float thumb_height = thumbnail_margin_height_under; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = right_thumbnail_margin_x + ((thumb_width - scaled_thumb_width) / 2.0f); float thumb_y = xmb->margins_screen_top + xmb->icon_size; gfx_thumbnail_draw( userdata, video_width, video_height, show_right_thumbnail ? &xmb->thumbnails.right : &xmb->thumbnails.left, thumb_x, thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_TOP, 1.0f, 1.0f, &thumbnail_shadow); } } else { /* Right thumbnail, right side */ if (show_right_thumbnail) { float thumb_width = right_thumbnail_margin_width; float thumb_height = thumbnail_margin_height_under; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = right_thumbnail_margin_x + ((thumb_width - scaled_thumb_width) / 2.0f); float thumb_y = xmb->margins_screen_top + xmb->icon_size; gfx_thumbnail_draw( userdata, video_width, video_height, &xmb->thumbnails.right, thumb_x, thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_TOP, 1.0f, 1.0f, &thumbnail_shadow); } /* Left thumbnail, left side */ if (show_left_thumbnail) { float y_offset = ((xmb->depth != 1) ? 1.2f : 0.0f) * xmb->icon_size; float thumb_width = left_thumbnail_margin_width; float thumb_height = thumbnail_margin_height_under - xmb->margins_title_bottom - y_offset; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = left_thumbnail_margin_x + ((thumb_width - scaled_thumb_width) / 2.0f); float thumb_y = xmb->margins_screen_top + xmb->icon_size + y_offset; gfx_thumbnail_draw( userdata, video_width, video_height, &xmb->thumbnails.left, thumb_x, thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_TOP, 1.0f, 1.0f, &thumbnail_shadow); } } } /* This is the PSP layout - thumbnails are only * drawn on the left hand side * > If left thumbnail is available, show it * > If not, show right thumbnail instead * (if available) */ else if (show_right_thumbnail || show_left_thumbnail) { float y_offset = ((xmb->depth != 1) ? 1.2f : -0.25f) * xmb->icon_size; float thumb_width = xmb->icon_size * 2.4f; float thumb_height = thumbnail_margin_height_under - xmb->margins_title_bottom - (xmb->icon_size / 6.0f) - y_offset; float scaled_thumb_width = thumb_width * thumbnail_scale_factor; float scaled_thumb_height = thumb_height * thumbnail_scale_factor; float thumb_x = (thumb_width - scaled_thumb_width) / 2.0f; float thumb_y = xmb->margins_screen_top + xmb->icon_size + y_offset; /* Very small thumbnails look ridiculous * > Impose a minimum size limit */ if (thumb_height > xmb->icon_size) gfx_thumbnail_draw( userdata, video_width, video_height, show_left_thumbnail ? &xmb->thumbnails.left : &xmb->thumbnails.right, thumb_x, thumb_y, scaled_thumb_width > 0.0f ? (unsigned)scaled_thumb_width : 0, scaled_thumb_height > 0.0f ? (unsigned)scaled_thumb_height : 0, GFX_THUMBNAIL_ALIGN_TOP, 1.0f, 1.0f, &thumbnail_shadow); } } /**************************/ /* Draw thumbnails: END */ /**************************/ /* Clock image */ gfx_display_set_alpha(item_color, MIN(xmb->alpha, 1.00f)); if (battery_level_enable) { gfx_display_ctx_powerstate_t powerstate; char msg[12]; msg[0] = '\0'; powerstate.s = msg; powerstate.len = sizeof(msg); menu_display_powerstate(&powerstate); if (powerstate.battery_enabled) { size_t x_pos = xmb->icon_size / 6; size_t x_pos_icon = xmb->margins_title_left; if (coord_white[3] != 0 && !xmb->assets_missing) { gfx_display_blend_begin(userdata); xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, &mymat, xmb->textures.list[ powerstate.charging? XMB_TEXTURE_BATTERY_CHARGING : (powerstate.percent > 80)? XMB_TEXTURE_BATTERY_FULL : (powerstate.percent > 60)? XMB_TEXTURE_BATTERY_80 : (powerstate.percent > 40)? XMB_TEXTURE_BATTERY_60 : (powerstate.percent > 20)? XMB_TEXTURE_BATTERY_40 : XMB_TEXTURE_BATTERY_20 ], video_width - (xmb->icon_size / 2) - x_pos_icon, xmb->icon_size, video_width, video_height, 1, 0, 1, &item_color[0], xmb->shadow_offset); gfx_display_blend_end(userdata); } percent_width = (unsigned) font_driver_get_message_width( xmb->font, msg, (unsigned)strlen(msg), 1); xmb_draw_text(xmb_shadows_enable, xmb, msg, video_width - xmb->margins_title_left - x_pos, xmb->margins_title_top, 1, 1, TEXT_ALIGN_RIGHT, video_width, video_height, xmb->font); } } if (timedate_enable) { gfx_display_ctx_datetime_t datetime; char timedate[255]; int x_pos = 0; if (coord_white[3] != 0 && !xmb->assets_missing) { int x_pos = 0; if (percent_width) x_pos = percent_width + (xmb->icon_size / 2.5); gfx_display_blend_begin(userdata); xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, &mymat, xmb->textures.list[XMB_TEXTURE_CLOCK], video_width - xmb->icon_size - x_pos, xmb->icon_size, video_width, video_height, 1, 0, 1, &item_color[0], xmb->shadow_offset); gfx_display_blend_end(userdata); } timedate[0] = '\0'; datetime.s = timedate; datetime.len = sizeof(timedate); datetime.time_mode = settings->uints.menu_timedate_style; datetime.date_separator = settings->uints.menu_timedate_date_separator; menu_display_timedate(&datetime); if (percent_width) x_pos = percent_width + (xmb->icon_size / 2.5); xmb_draw_text(xmb_shadows_enable, xmb, timedate, video_width - xmb->margins_title_left - xmb->icon_size / 4 - x_pos, xmb->margins_title_top, 1, 1, TEXT_ALIGN_RIGHT, video_width, video_height, xmb->font); } /* Arrow image */ gfx_display_set_alpha(item_color, MIN(xmb->textures_arrow_alpha, xmb->alpha)); if (coord_white[3] != 0 && !xmb->assets_missing) { gfx_display_blend_begin(userdata); xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, &mymat, xmb->textures.list[XMB_TEXTURE_ARROW], xmb->x + xmb->margins_screen_left + xmb->icon_spacing_horizontal - xmb->icon_size / 2.0 + xmb->icon_size, xmb->margins_screen_top + xmb->icon_size / 2.0 + xmb->icon_spacing_vertical * xmb->active_item_factor, video_width, video_height, xmb->textures_arrow_alpha, 0, 1, &item_color[0], xmb->shadow_offset); gfx_display_blend_end(userdata); } /* Horizontal tab icons */ if (!xmb->assets_missing) { gfx_display_blend_begin(userdata); for (i = 0; i <= xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end; i++) { xmb_node_t *node = xmb_get_node(xmb, i); if (!node) continue; gfx_display_set_alpha(item_color, MIN(node->alpha, xmb->alpha)); if (item_color[3] != 0) { gfx_display_ctx_rotate_draw_t rotate_draw; math_matrix_4x4 mymat; uintptr_t texture = node->icon; float x = xmb->x + xmb->categories_x_pos + xmb->margins_screen_left + xmb->icon_spacing_horizontal * (i + 1) - xmb->icon_size / 2.0; float y = xmb->margins_screen_top + xmb->icon_size / 2.0; float rotation = 0; float scale_factor = node->zoom; /* Check whether we need to fade out icons * overlapping the top right thumbnail image */ if (fade_tab_icons) { float x_threshold = fade_tab_icons_x_threshold - (xmb->icon_size * 1.5f); if (x > x_threshold) { float fade_alpha = item_color[3]; float fade_offset = (x - x_threshold) * 2.0f; fade_offset = (fade_offset > xmb->icon_size) ? xmb->icon_size : fade_offset; fade_alpha *= 1.0f - (fade_offset / xmb->icon_size); if (fade_alpha <= 0.0f) continue; gfx_display_set_alpha(item_color, fade_alpha); } } rotate_draw.matrix = &mymat; rotate_draw.rotation = rotation; rotate_draw.scale_x = scale_factor; rotate_draw.scale_y = scale_factor; rotate_draw.scale_z = 1; rotate_draw.scale_enable = true; gfx_display_rotate_z(&rotate_draw, userdata); xmb_draw_icon( userdata, video_width, video_height, xmb_shadows_enable, xmb->icon_size, &mymat, texture, x, y, video_width, video_height, 1.0, rotation, scale_factor, &item_color[0], xmb->shadow_offset); } } gfx_display_blend_end(userdata); } /* Vertical icons */ xmb_draw_items( userdata, video_width, video_height, xmb_shadows_enable, xmb, xmb->selection_buf_old, xmb->selection_ptr_old, (xmb_list_get_size(xmb, MENU_LIST_PLAIN) > 1) ? xmb->categories_selection_ptr : xmb->categories_selection_ptr_old, &item_color[0], video_width, video_height); selection_buf = menu_entries_get_selection_buf_ptr(0); xmb_draw_items( userdata, video_width, video_height, xmb_shadows_enable, xmb, selection_buf, selection, xmb->categories_selection_ptr, &item_color[0], video_width, video_height); font_driver_flush(video_width, video_height, xmb->font); font_driver_flush(video_width, video_height, xmb->font2); font_driver_bind_block(xmb->font, NULL); font_driver_bind_block(xmb->font2, NULL); /* Draw fullscreen thumbnails, if required */ xmb_draw_fullscreen_thumbnails( xmb, userdata, video_width, video_height, xmb_shadows_enable, xmb_color_theme, settings, selection); if (menu_input_dialog_get_display_kb()) { const char *str = menu_input_dialog_get_buffer(); const char *label = menu_input_dialog_get_label_buffer(); snprintf(msg, sizeof(msg), "%s\n%s", label, str); render_background = true; } if (!string_is_empty(xmb->box_message)) { strlcpy(msg, xmb->box_message, sizeof(msg)); free(xmb->box_message); xmb->box_message = NULL; render_background = true; } if (render_background) { xmb_draw_dark_layer(xmb, userdata, video_width, video_height); xmb_render_messagebox_internal(userdata, video_width, video_height, xmb, msg); } /* Cursor image */ if (xmb->mouse_show) { bool cursor_visible = video_fullscreen && menu_mouse_enable; gfx_display_set_alpha(coord_white, MIN(xmb->alpha, 1.00f)); gfx_display_draw_cursor( userdata, video_width, video_height, cursor_visible, &coord_white[0], xmb->cursor_size, xmb->textures.list[XMB_TEXTURE_POINTER], xmb->pointer.x, xmb->pointer.y, video_width, video_height); } gfx_display_unset_viewport(video_width, video_height); } static void xmb_layout_ps3(xmb_handle_t *xmb, int width) { unsigned new_font_size, new_header_height; float scale_factor = xmb->last_scale_factor; xmb->above_subitem_offset = 1.5; xmb->above_item_offset = -1.0; xmb->active_item_factor = 3.0; xmb->under_item_offset = 5.0; xmb->categories_active_zoom = 1.0; xmb->categories_passive_zoom = 0.5; xmb->items_active_zoom = 1.0; xmb->items_passive_zoom = 0.5; xmb->categories_active_alpha = 1.0; xmb->categories_passive_alpha = 0.85; xmb->items_active_alpha = 1.0; xmb->items_passive_alpha = 0.85; xmb->shadow_offset = 2.0; new_font_size = 32.0 * scale_factor; xmb->font2_size = 24.0 * scale_factor; new_header_height = 128.0 * scale_factor; xmb->cursor_size = 64.0 * scale_factor; xmb->icon_spacing_horizontal = 200.0 * scale_factor; xmb->icon_spacing_vertical = 64.0 * scale_factor; xmb->margins_screen_top = (256+32) * scale_factor; xmb->margins_screen_left = 336.0 * scale_factor; xmb->margins_title_left = 60 * scale_factor; xmb->margins_title_top = 60 * scale_factor + new_font_size / 3; xmb->margins_title_bottom = 60 * scale_factor - new_font_size / 3; xmb->margins_label_left = 85.0 * scale_factor; xmb->margins_label_top = new_font_size / 3.0; xmb->margins_setting_left = 600.0 * scale_factor * scale_mod[6]; xmb->margins_dialog = 48 * scale_factor; xmb->margins_slice = 16 * scale_factor; xmb->icon_size = 128.0 * scale_factor; xmb->font_size = new_font_size; gfx_display_set_header_height(new_header_height); } static void xmb_layout_psp(xmb_handle_t *xmb, int width) { unsigned new_font_size, new_header_height; float scale_factor = xmb->last_scale_factor; xmb->above_subitem_offset = 1.5; xmb->above_item_offset = -1.0; xmb->active_item_factor = 2.0; xmb->under_item_offset = 3.0; xmb->categories_active_zoom = 1.0; xmb->categories_passive_zoom = 1.0; xmb->items_active_zoom = 1.0; xmb->items_passive_zoom = 1.0; xmb->categories_active_alpha = 1.0; xmb->categories_passive_alpha = 0.85; xmb->items_active_alpha = 1.0; xmb->items_passive_alpha = 0.85; xmb->shadow_offset = 1.0; new_font_size = 32.0 * scale_factor; xmb->font2_size = 24.0 * scale_factor; new_header_height = 128.0 * scale_factor; xmb->margins_screen_top = (256+32) * scale_factor; xmb->cursor_size = 64.0; xmb->icon_spacing_horizontal = 250.0 * scale_factor; xmb->icon_spacing_vertical = 108.0 * scale_factor; xmb->margins_screen_left = 136.0 * scale_factor; xmb->margins_title_left = 60 * scale_factor; xmb->margins_title_top = 60 * scale_factor + new_font_size / 3; xmb->margins_title_bottom = 60 * scale_factor - new_font_size / 3; xmb->margins_label_left = 85.0 * scale_factor; xmb->margins_label_top = new_font_size / 3.0; xmb->margins_setting_left = 600.0 * scale_factor; xmb->margins_dialog = 48 * scale_factor; xmb->margins_slice = 16 * scale_factor; xmb->icon_size = 128.0 * scale_factor; xmb->font_size = new_font_size; gfx_display_set_header_height(new_header_height); } static void xmb_layout(xmb_handle_t *xmb) { unsigned width, height, i, current, end; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); video_driver_get_size(&width, &height); if (xmb->use_ps3_layout) xmb_layout_ps3(xmb, width); else xmb_layout_psp(xmb, width); #ifdef XMB_DEBUG RARCH_LOG("[XMB] margin screen left: %.2f\n", xmb->margins_screen_left); RARCH_LOG("[XMB] margin screen top: %.2f\n", xmb->margins_screen_top); RARCH_LOG("[XMB] margin title left: %.2f\n", xmb->margins_title_left); RARCH_LOG("[XMB] margin title top: %.2f\n", xmb->margins_title_top); RARCH_LOG("[XMB] margin title bott: %.2f\n", xmb->margins_title_bottom); RARCH_LOG("[XMB] margin label left: %.2f\n", xmb->margins_label_left); RARCH_LOG("[XMB] margin label top: %.2f\n", xmb->margins_label_top); RARCH_LOG("[XMB] margin sett left: %.2f\n", xmb->margins_setting_left); RARCH_LOG("[XMB] icon spacing hor: %.2f\n", xmb->icon_spacing_horizontal); RARCH_LOG("[XMB] icon spacing ver: %.2f\n", xmb->icon_spacing_vertical); RARCH_LOG("[XMB] icon size: %.2f\n", xmb->icon_size); #endif current = (unsigned)selection; end = (unsigned)menu_entries_get_size(); for (i = 0; i < end; i++) { float ia = xmb->items_passive_alpha; float iz = xmb->items_passive_zoom; xmb_node_t *node = (xmb_node_t*)file_list_get_userdata_at_offset( selection_buf, i); if (!node) continue; if (i == current) { ia = xmb->items_active_alpha; iz = xmb->items_active_alpha; } node->alpha = ia; node->label_alpha = ia; node->zoom = iz; node->y = xmb_item_y(xmb, i, current); } if (xmb->depth <= 1) return; current = (unsigned)xmb->selection_ptr_old; end = (unsigned)file_list_get_size(xmb->selection_buf_old); for (i = 0; i < end; i++) { float ia = 0; float iz = xmb->items_passive_zoom; xmb_node_t *node = (xmb_node_t*)file_list_get_userdata_at_offset( xmb->selection_buf_old, i); if (!node) continue; if (i == current) { ia = xmb->items_active_alpha; iz = xmb->items_active_alpha; } node->alpha = ia; node->label_alpha = 0; node->zoom = iz; node->y = xmb_item_y(xmb, i, current); node->x = xmb->icon_size * 1 * -2; } } static void xmb_ribbon_set_vertex(float *ribbon_verts, unsigned idx, unsigned row, unsigned col) { ribbon_verts[idx++] = ((float)col) / (XMB_RIBBON_COLS-1) * 2.0f - 1.0f; ribbon_verts[idx++] = ((float)row) / (XMB_RIBBON_ROWS-1) * 2.0f - 1.0f; } static void xmb_init_ribbon(xmb_handle_t * xmb) { video_coords_t coords; unsigned r, c, col; unsigned i = 0; video_coord_array_t *ca = gfx_display_get_coords_array(); unsigned vertices_total = XMB_RIBBON_VERTICES; float *dummy = (float*)calloc(4 * vertices_total, sizeof(float)); float *ribbon_verts = (float*)calloc(2 * vertices_total, sizeof(float)); /* Set up vertices */ for (r = 0; r < XMB_RIBBON_ROWS - 1; r++) { for (c = 0; c < XMB_RIBBON_COLS; c++) { col = r % 2 ? XMB_RIBBON_COLS - c - 1 : c; xmb_ribbon_set_vertex(ribbon_verts, i, r, col); xmb_ribbon_set_vertex(ribbon_verts, i + 2, r + 1, col); i += 4; } } coords.color = dummy; coords.vertex = ribbon_verts; coords.tex_coord = dummy; coords.lut_tex_coord = dummy; coords.vertices = vertices_total; video_coord_array_append(ca, &coords, coords.vertices); free(dummy); free(ribbon_verts); } static void xmb_menu_animation_update_time( float *ticker_pixel_increment, unsigned video_width, unsigned video_height) { menu_handle_t *menu = menu_driver_get_ptr(); xmb_handle_t *xmb = NULL; if (!menu) return; xmb = (xmb_handle_t*)menu->userdata; if (!xmb) return; *(ticker_pixel_increment) *= xmb->last_scale_factor; } static void *xmb_init(void **userdata, bool video_is_threaded) { unsigned width, height; int i; xmb_handle_t *xmb = NULL; settings_t *settings = config_get_ptr(); menu_handle_t *menu = (menu_handle_t*)calloc(1, sizeof(*menu)); float scale_value = settings->floats.menu_scale_factor * 100.0f; /* scaling multiplier formulas made from these values: */ /* xmb_scale 50 = {2.5, 2.5, 2, 1.7, 2.5, 4, 2.4, 2.5} */ /* xmb_scale 75 = { 2, 1.6, 1.6, 1.4, 1.5, 2.3, 1.9, 1.3} */ if (scale_value < 100) { /* text length & word wrap (base 35 apply to file browser, 1st column) */ scale_mod[0] = -0.03 * scale_value + 4.083; /* playlist text length when thumbnail is ON (small, base 40) */ scale_mod[1] = -0.03 * scale_value + 3.95; /* playlist text length when thumbnail is OFF (large, base 70) */ scale_mod[2] = -0.02 * scale_value + 3.033; /* sub-label length & word wrap */ scale_mod[3] = -0.014 * scale_value + 2.416; /* thumbnail size & vertical margin from top */ scale_mod[4] = -0.03 * scale_value + 3.916; /* thumbnail horizontal left margin (horizontal positioning) */ scale_mod[5] = -0.06 * scale_value + 6.933; /* margin before 2nd column start (shaders parameters, cheats...) */ scale_mod[6] = -0.028 * scale_value + 3.866; /* text length & word wrap (base 35 apply to 2nd column in cheats, shaders, etc) */ scale_mod[7] = 134.179 * pow(scale_value, -1.0778); for (i = 0; i < 8; i++) if (scale_mod[i] < 1) scale_mod[i] = 1; } if (!menu) return NULL; if (!gfx_display_init_first_driver(video_is_threaded)) { free(menu); return NULL; } video_driver_get_size(&width, &height); xmb = (xmb_handle_t*)calloc(1, sizeof(xmb_handle_t)); if (!xmb) { free(menu); return NULL; } *userdata = xmb; xmb->selection_buf_old = (file_list_t*) malloc(sizeof(file_list_t)); if (!xmb->selection_buf_old) goto error; xmb->selection_buf_old->list = NULL; xmb->selection_buf_old->capacity = 0; xmb->selection_buf_old->size = 0; xmb->categories_active_idx = 0; xmb->categories_active_idx_old = 0; xmb->x = 0; xmb->categories_x_pos = 0; xmb->textures_arrow_alpha = 0; xmb->depth = 1; xmb->old_depth = 1; xmb->alpha = 0; xmb->system_tab_end = 0; xmb->tabs[xmb->system_tab_end] = XMB_SYSTEM_TAB_MAIN; if (settings->bools.menu_content_show_settings && !settings->bools.kiosk_mode_enable) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_SETTINGS; if (settings->bools.menu_content_show_favorites) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_FAVORITES; if (settings->bools.menu_content_show_history) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_HISTORY; #ifdef HAVE_IMAGEVIEWER if (settings->bools.menu_content_show_images) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_IMAGES; #endif if (settings->bools.menu_content_show_music) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_MUSIC; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) if (settings->bools.menu_content_show_video) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_VIDEO; #endif #ifdef HAVE_NETWORKING if (settings->bools.menu_content_show_netplay) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_NETPLAY; #endif if ( settings->bools.menu_content_show_add && !settings->bools.kiosk_mode_enable) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_ADD; menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); /* TODO/FIXME - we don't use framebuffer at all * for XMB, we should refactor this dependency * away. */ gfx_display_set_width(width); gfx_display_set_height(height); gfx_display_allocate_white_texture(); xmb->horizontal_list = (file_list_t*)malloc(sizeof(file_list_t)); xmb->horizontal_list->list = NULL; xmb->horizontal_list->capacity = 0; xmb->horizontal_list->size = 0; if (xmb->horizontal_list) xmb_init_horizontal_list(xmb); xmb_init_ribbon(xmb); /* Thumbnail initialisation */ xmb->thumbnail_path_data = gfx_thumbnail_path_init(); if (!xmb->thumbnail_path_data) goto error; xmb->savestate_thumbnail_file_path[0] = '\0'; xmb->prev_savestate_thumbnail_file_path[0] = '\0'; xmb->fullscreen_thumbnails_available = false; xmb->show_fullscreen_thumbnails = false; xmb->fullscreen_thumbnail_alpha = 0.0f; xmb->fullscreen_thumbnail_selection = 0; xmb->fullscreen_thumbnail_label[0] = '\0'; 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; xmb->last_scale_factor = xmb_get_scale_factor(settings, xmb->use_ps3_layout, width); gfx_animation_set_update_time_cb(xmb_menu_animation_update_time); return menu; error: free(menu); if (xmb->selection_buf_old) free(xmb->selection_buf_old); xmb->selection_buf_old = NULL; if (xmb->horizontal_list) { xmb_free_list_nodes(xmb->horizontal_list, false); file_list_free(xmb->horizontal_list); } xmb->horizontal_list = NULL; gfx_animation_unset_update_time_cb(); return NULL; } static void xmb_free(void *data) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (xmb) { if (xmb->selection_buf_old) { xmb_free_list_nodes(xmb->selection_buf_old, false); file_list_free(xmb->selection_buf_old); } if (xmb->horizontal_list) { xmb_free_list_nodes(xmb->horizontal_list, false); file_list_free(xmb->horizontal_list); } xmb->selection_buf_old = NULL; xmb->horizontal_list = NULL; video_coord_array_free(&xmb->raster_block.carr); video_coord_array_free(&xmb->raster_block2.carr); if (!string_is_empty(xmb->box_message)) free(xmb->box_message); if (!string_is_empty(xmb->bg_file_path)) free(xmb->bg_file_path); if (xmb->thumbnail_path_data) free(xmb->thumbnail_path_data); } font_driver_bind_block(NULL, NULL); gfx_animation_unset_update_time_cb(); } static void xmb_context_bg_destroy(xmb_handle_t *xmb) { if (!xmb) return; video_driver_texture_unload(&xmb->textures.bg); video_driver_texture_unload(&gfx_display_white_texture); } static bool xmb_load_image(void *userdata, void *data, enum menu_image_type type) { xmb_handle_t *xmb = (xmb_handle_t*)userdata; if (!xmb) return false; switch (type) { case MENU_IMAGE_WALLPAPER: xmb_context_bg_destroy(xmb); video_driver_texture_unload(&xmb->textures.bg); video_driver_texture_load(data, TEXTURE_FILTER_MIPMAP_LINEAR, &xmb->textures.bg); gfx_display_allocate_white_texture(); break; case MENU_IMAGE_NONE: default: break; } return true; } static const char *xmb_texture_path(unsigned id) { switch (id) { case XMB_TEXTURE_MAIN_MENU: #if defined(HAVE_LAKKA) return "lakka.png"; #else return "retroarch.png"; #endif case XMB_TEXTURE_SETTINGS: return "settings.png"; case XMB_TEXTURE_HISTORY: return "history.png"; case XMB_TEXTURE_FAVORITES: return "favorites.png"; case XMB_TEXTURE_ADD_FAVORITE: return "add-favorite.png"; case XMB_TEXTURE_MUSICS: return "musics.png"; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) case XMB_TEXTURE_MOVIES: return "movies.png"; #endif #ifdef HAVE_IMAGEVIEWER case XMB_TEXTURE_IMAGES: return "images.png"; #endif case XMB_TEXTURE_SETTING: return "setting.png"; case XMB_TEXTURE_SUBSETTING: return "subsetting.png"; case XMB_TEXTURE_ARROW: return "arrow.png"; case XMB_TEXTURE_RUN: return "run.png"; case XMB_TEXTURE_CLOSE: return "close.png"; case XMB_TEXTURE_RESUME: return "resume.png"; case XMB_TEXTURE_CLOCK: return "clock.png"; case XMB_TEXTURE_BATTERY_FULL: return "battery-full.png"; case XMB_TEXTURE_BATTERY_CHARGING: return "battery-charging.png"; case XMB_TEXTURE_BATTERY_80: return "battery-80.png"; case XMB_TEXTURE_BATTERY_60: return "battery-60.png"; case XMB_TEXTURE_BATTERY_40: return "battery-40.png"; case XMB_TEXTURE_BATTERY_20: return "battery-20.png"; case XMB_TEXTURE_POINTER: return "pointer.png"; case XMB_TEXTURE_SAVESTATE: return "savestate.png"; case XMB_TEXTURE_LOADSTATE: return "loadstate.png"; case XMB_TEXTURE_UNDO: return "undo.png"; case XMB_TEXTURE_CORE_INFO: return "core-infos.png"; case XMB_TEXTURE_BLUETOOTH: return "bluetooth.png"; case XMB_TEXTURE_WIFI: return "wifi.png"; case XMB_TEXTURE_CORE_OPTIONS: return "core-options.png"; case XMB_TEXTURE_INPUT_REMAPPING_OPTIONS: return "core-input-remapping-options.png"; case XMB_TEXTURE_CHEAT_OPTIONS: return "core-cheat-options.png"; case XMB_TEXTURE_DISK_OPTIONS: return "core-disk-options.png"; case XMB_TEXTURE_SHADER_OPTIONS: return "core-shader-options.png"; case XMB_TEXTURE_ACHIEVEMENT_LIST: return "achievement-list.png"; case XMB_TEXTURE_SCREENSHOT: return "screenshot.png"; case XMB_TEXTURE_RELOAD: return "reload.png"; case XMB_TEXTURE_RENAME: return "rename.png"; case XMB_TEXTURE_FILE: return "file.png"; case XMB_TEXTURE_FOLDER: return "folder.png"; case XMB_TEXTURE_ZIP: return "zip.png"; case XMB_TEXTURE_MUSIC: return "music.png"; case XMB_TEXTURE_FAVORITE: return "favorites-content.png"; case XMB_TEXTURE_IMAGE: return "image.png"; case XMB_TEXTURE_MOVIE: return "movie.png"; case XMB_TEXTURE_CORE: return "core.png"; case XMB_TEXTURE_RDB: return "database.png"; case XMB_TEXTURE_CURSOR: return "cursor.png"; case XMB_TEXTURE_SWITCH_ON: return "on.png"; case XMB_TEXTURE_SWITCH_OFF: return "off.png"; case XMB_TEXTURE_ADD: return "add.png"; #ifdef HAVE_NETWORKING case XMB_TEXTURE_NETPLAY: return "netplay.png"; case XMB_TEXTURE_ROOM: return "menu_room.png"; case XMB_TEXTURE_ROOM_LAN: return "menu_room_lan.png"; case XMB_TEXTURE_ROOM_RELAY: return "menu_room_relay.png"; #endif case XMB_TEXTURE_KEY: return "key.png"; case XMB_TEXTURE_KEY_HOVER: return "key-hover.png"; case XMB_TEXTURE_DIALOG_SLICE: return "dialog-slice.png"; case XMB_TEXTURE_ACHIEVEMENTS: return "menu_achievements.png"; case XMB_TEXTURE_AUDIO: return "menu_audio.png"; case XMB_TEXTURE_DRIVERS: return "menu_drivers.png"; case XMB_TEXTURE_EXIT: return "menu_exit.png"; case XMB_TEXTURE_FRAMESKIP: return "menu_frameskip.png"; case XMB_TEXTURE_HELP: return "menu_help.png"; case XMB_TEXTURE_INFO: return "menu_info.png"; case XMB_TEXTURE_INPUT_SETTINGS: return "Libretro - Pad.png"; case XMB_TEXTURE_LATENCY: return "menu_latency.png"; case XMB_TEXTURE_NETWORK: return "menu_network.png"; case XMB_TEXTURE_POWER: return "menu_power.png"; case XMB_TEXTURE_RECORD: return "menu_record.png"; case XMB_TEXTURE_SAVING: return "menu_saving.png"; case XMB_TEXTURE_UPDATER: return "menu_updater.png"; case XMB_TEXTURE_VIDEO: return "menu_video.png"; case XMB_TEXTURE_MIXER: return "menu_mixer.png"; case XMB_TEXTURE_LOG: return "menu_log.png"; case XMB_TEXTURE_OSD: return "menu_osd.png"; case XMB_TEXTURE_UI: return "menu_ui.png"; case XMB_TEXTURE_USER: return "menu_user.png"; case XMB_TEXTURE_PRIVACY: return "menu_privacy.png"; case XMB_TEXTURE_PLAYLIST: return "menu_playlist.png"; case XMB_TEXTURE_DISC: return "disc.png"; case XMB_TEXTURE_QUICKMENU: return "menu_quickmenu.png"; case XMB_TEXTURE_REWIND: return "menu_rewind.png"; case XMB_TEXTURE_OVERLAY: return "menu_overlay.png"; case XMB_TEXTURE_OVERRIDE: return "menu_override.png"; case XMB_TEXTURE_NOTIFICATIONS: return "menu_notifications.png"; case XMB_TEXTURE_STREAM: return "menu_stream.png"; case XMB_TEXTURE_SHUTDOWN: return "menu_shutdown.png"; case XMB_TEXTURE_INPUT_DPAD_U: return "input_DPAD-U.png"; case XMB_TEXTURE_INPUT_DPAD_D: return "input_DPAD-D.png"; case XMB_TEXTURE_INPUT_DPAD_L: return "input_DPAD-L.png"; case XMB_TEXTURE_INPUT_DPAD_R: return "input_DPAD-R.png"; case XMB_TEXTURE_INPUT_STCK_U: return "input_STCK-U.png"; case XMB_TEXTURE_INPUT_STCK_D: return "input_STCK-D.png"; case XMB_TEXTURE_INPUT_STCK_L: return "input_STCK-L.png"; case XMB_TEXTURE_INPUT_STCK_R: return "input_STCK-R.png"; case XMB_TEXTURE_INPUT_STCK_P: return "input_STCK-P.png"; case XMB_TEXTURE_INPUT_BTN_U: return "input_BTN-U.png"; case XMB_TEXTURE_INPUT_BTN_D: return "input_BTN-D.png"; case XMB_TEXTURE_INPUT_BTN_L: return "input_BTN-L.png"; case XMB_TEXTURE_INPUT_BTN_R: return "input_BTN-R.png"; case XMB_TEXTURE_INPUT_LB: return "input_LB.png"; case XMB_TEXTURE_INPUT_RB: return "input_RB.png"; case XMB_TEXTURE_INPUT_LT: return "input_LT.png"; case XMB_TEXTURE_INPUT_RT: return "input_RT.png"; case XMB_TEXTURE_INPUT_SELECT: return "input_SELECT.png"; case XMB_TEXTURE_INPUT_START: return "input_START.png"; case XMB_TEXTURE_INPUT_ADC: return "input_ADC.png"; case XMB_TEXTURE_INPUT_BIND_ALL: return "input_BIND_ALL.png"; case XMB_TEXTURE_INPUT_MOUSE: return "input_MOUSE.png"; case XMB_TEXTURE_INPUT_LGUN: return "input_LGUN.png"; case XMB_TEXTURE_INPUT_TURBO: return "input_TURBO.png"; case XMB_TEXTURE_CHECKMARK: return "menu_check.png"; case XMB_TEXTURE_MENU_ADD: return "menu_add.png"; case XMB_TEXTURE_BRIGHTNESS: return "menu_brightness.png"; case XMB_TEXTURE_PAUSE: return "menu_pause.png"; case XMB_TEXTURE_DEFAULT: return "default.png"; case XMB_TEXTURE_DEFAULT_CONTENT: return "default-content.png"; case XMB_TEXTURE_MENU_APPLY_TOGGLE: return "menu_apply_toggle.png"; case XMB_TEXTURE_MENU_APPLY_COG: return "menu_apply_cog.png"; } return NULL; } static void xmb_context_reset_textures( xmb_handle_t *xmb, const char *iconpath) { unsigned i; settings_t *settings = config_get_ptr(); xmb->assets_missing = false; gfx_display_allocate_white_texture(); for (i = 0; i < XMB_TEXTURE_LAST; i++) { if (!gfx_display_reset_textures_list(xmb_texture_path(i), iconpath, &xmb->textures.list[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL)) { RARCH_WARN("[XMB] Asset missing: %s%s\n", iconpath, xmb_texture_path(i)); /* New extra battery icons could be missing */ if (i == XMB_TEXTURE_BATTERY_80 || i == XMB_TEXTURE_BATTERY_60 || i == XMB_TEXTURE_BATTERY_40 || i == XMB_TEXTURE_BATTERY_20) { if ( /* If there are no extra battery icons revert to the old behaviour */ !gfx_display_reset_textures_list(xmb_texture_path(XMB_TEXTURE_BATTERY_FULL), iconpath, &xmb->textures.list[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL) && !(settings->uints.menu_xmb_theme == XMB_ICON_THEME_CUSTOM) ) goto error; else continue; } /* If the icon is missing return the subsetting (because some themes are incomplete) */ if (!(i == XMB_TEXTURE_DIALOG_SLICE || i == XMB_TEXTURE_KEY_HOVER || i == XMB_TEXTURE_KEY)) { /* OSD Warning only if subsetting icon is missing */ if ( !gfx_display_reset_textures_list(xmb_texture_path(XMB_TEXTURE_SUBSETTING), iconpath, &xmb->textures.list[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL) && !(settings->uints.menu_xmb_theme == XMB_ICON_THEME_CUSTOM) ) { runloop_msg_queue_push(msg_hash_to_str(MSG_MISSING_ASSETS), 1, 256, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); /* Do not draw icons if subsetting is missing */ goto error; } /* Do not draw icons if this ones are is missing */ switch (i) { case XMB_TEXTURE_POINTER: case XMB_TEXTURE_ARROW: case XMB_TEXTURE_CLOCK: case XMB_TEXTURE_BATTERY_CHARGING: case XMB_TEXTURE_BATTERY_FULL: case XMB_TEXTURE_DEFAULT: case XMB_TEXTURE_DEFAULT_CONTENT: goto error; } } } } xmb->main_menu_node.icon = xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; xmb->main_menu_node.alpha = xmb->categories_active_alpha; xmb->main_menu_node.zoom = xmb->categories_active_zoom; xmb->settings_tab_node.icon = xmb->textures.list[XMB_TEXTURE_SETTINGS]; xmb->settings_tab_node.alpha = xmb->categories_active_alpha; xmb->settings_tab_node.zoom = xmb->categories_active_zoom; xmb->history_tab_node.icon = xmb->textures.list[XMB_TEXTURE_HISTORY]; xmb->history_tab_node.alpha = xmb->categories_active_alpha; xmb->history_tab_node.zoom = xmb->categories_active_zoom; xmb->favorites_tab_node.icon = xmb->textures.list[XMB_TEXTURE_FAVORITES]; xmb->favorites_tab_node.alpha = xmb->categories_active_alpha; xmb->favorites_tab_node.zoom = xmb->categories_active_zoom; xmb->music_tab_node.icon = xmb->textures.list[XMB_TEXTURE_MUSICS]; xmb->music_tab_node.alpha = xmb->categories_active_alpha; xmb->music_tab_node.zoom = xmb->categories_active_zoom; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) xmb->video_tab_node.icon = xmb->textures.list[XMB_TEXTURE_MOVIES]; xmb->video_tab_node.alpha = xmb->categories_active_alpha; xmb->video_tab_node.zoom = xmb->categories_active_zoom; #endif #ifdef HAVE_IMAGEVIEWER xmb->images_tab_node.icon = xmb->textures.list[XMB_TEXTURE_IMAGES]; xmb->images_tab_node.alpha = xmb->categories_active_alpha; xmb->images_tab_node.zoom = xmb->categories_active_zoom; #endif xmb->add_tab_node.icon = xmb->textures.list[XMB_TEXTURE_ADD]; xmb->add_tab_node.alpha = xmb->categories_active_alpha; xmb->add_tab_node.zoom = xmb->categories_active_zoom; #ifdef HAVE_NETWORKING xmb->netplay_tab_node.icon = xmb->textures.list[XMB_TEXTURE_NETPLAY]; xmb->netplay_tab_node.alpha = xmb->categories_active_alpha; xmb->netplay_tab_node.zoom = xmb->categories_active_zoom; #endif /* Recolor */ if ( (settings->uints.menu_xmb_theme == XMB_ICON_THEME_MONOCHROME_INVERTED) || (settings->uints.menu_xmb_theme == XMB_ICON_THEME_AUTOMATIC_INVERTED) ) memcpy(item_color, coord_black, sizeof(item_color)); else { if ( (settings->uints.menu_xmb_theme == XMB_ICON_THEME_MONOCHROME) || (settings->uints.menu_xmb_theme == XMB_ICON_THEME_AUTOMATIC) ) { for (i=0;i<16;i++) { if ((i==3) || (i==7) || (i==11) || (i==15)) { item_color[i] = 1; continue; } item_color[i] = 0.95; } } else memcpy(item_color, coord_white, sizeof(item_color)); } return; error: xmb->assets_missing = true; RARCH_WARN("[XMB] Critical asset missing, no icons will be drawn\n"); return; } static void xmb_context_reset_background(const char *iconpath) { settings_t *settings = config_get_ptr(); const char *path_menu_wp = settings->paths.path_menu_wallpaper; if (!string_is_empty(path_menu_wp)) { if (path_is_valid(path_menu_wp)) task_push_image_load(path_menu_wp, video_driver_supports_rgba(), 0, menu_display_handle_wallpaper_upload, NULL); } else if (!string_is_empty(iconpath)) { char path[PATH_MAX_LENGTH]; path[0] = '\0'; fill_pathname_join(path, iconpath, "bg.png", PATH_MAX_LENGTH * sizeof(char)); if (path_is_valid(path)) task_push_image_load(path, video_driver_supports_rgba(), 0, menu_display_handle_wallpaper_upload, NULL); } #ifdef ORBIS /* To avoid weird behaviour on orbis with remote host */ RARCH_LOG("[XMB] after task\n"); sleep(5); #endif } static void xmb_context_reset_internal(xmb_handle_t *xmb, bool is_threaded, bool reinit_textures) { char bg_file_path[PATH_MAX_LENGTH] = {0}; char *iconpath = (char*)malloc(PATH_MAX_LENGTH * sizeof(char)); iconpath[0] = '\0'; fill_pathname_application_special(bg_file_path, sizeof(bg_file_path), APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_BG); if (!string_is_empty(bg_file_path)) { if (!string_is_empty(xmb->bg_file_path)) free(xmb->bg_file_path); xmb->bg_file_path = strdup(bg_file_path); } fill_pathname_application_special(iconpath, PATH_MAX_LENGTH * sizeof(char), APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_ICONS); xmb_layout(xmb); if (xmb->font) { gfx_display_font_free(xmb->font); xmb->font = NULL; } if (xmb->font2) { gfx_display_font_free(xmb->font2); xmb->font2 = NULL; } xmb->font = gfx_display_font(APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_FONT, xmb->font_size, is_threaded); xmb->font2 = gfx_display_font(APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_FONT, xmb->font2_size, is_threaded); if (reinit_textures) { xmb_context_reset_textures(xmb, iconpath); xmb_context_reset_background(iconpath); } xmb_context_reset_horizontal_list(xmb); /* Only reload thumbnails if: * > Thumbnails are enabled * > This is a playlist, a database list, a file list * or the quick menu */ if (gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_RIGHT) || gfx_thumbnail_is_enabled(xmb->thumbnail_path_data, GFX_THUMBNAIL_LEFT)) { unsigned depth = (unsigned)xmb_list_get_size(xmb, MENU_LIST_PLAIN); unsigned xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr); if (((((xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS && depth == 1) || (xmb_system_tab < XMB_SYSTEM_TAB_SETTINGS && depth == 4)) && xmb->is_playlist)) || xmb->is_db_manager_list || xmb->is_file_list || xmb->is_quick_menu) xmb_update_thumbnail_image(xmb); } xmb_update_savestate_thumbnail_image(xmb); free(iconpath); } static void xmb_context_reset(void *data, bool is_threaded) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (xmb) xmb_context_reset_internal(xmb, is_threaded, true); video_driver_monitor_reset(); } static void xmb_navigation_clear(void *data, bool pending_push) { xmb_handle_t *xmb = (xmb_handle_t*)data; if (!pending_push) xmb_selection_pointer_changed(xmb, true); } static void xmb_navigation_pointer_changed(void *data) { xmb_handle_t *xmb = (xmb_handle_t*)data; xmb_selection_pointer_changed(xmb, true); } static void xmb_navigation_set(void *data, bool scroll) { xmb_handle_t *xmb = (xmb_handle_t*)data; xmb_selection_pointer_changed(xmb, true); } static void xmb_navigation_alphabet(void *data, size_t *unused) { xmb_handle_t *xmb = (xmb_handle_t*)data; xmb_selection_pointer_changed(xmb, true); } static void xmb_list_insert(void *userdata, file_list_t *list, const char *path, const char *fullpath, const char *unused, size_t list_size, unsigned entry_type) { int current = 0; int i = (int)list_size; xmb_node_t *node = NULL; xmb_handle_t *xmb = (xmb_handle_t*)userdata; size_t selection = menu_navigation_get_selection(); if (!xmb || !list) return; node = (xmb_node_t*)file_list_get_userdata_at_offset(list, i); if (!node) node = xmb_alloc_node(); if (!node) { RARCH_ERR("XMB node could not be allocated.\n"); return; } current = (int)selection; if (!string_is_empty(fullpath)) { if (node->fullpath) free(node->fullpath); node->fullpath = strdup(fullpath); } node->alpha = xmb->items_passive_alpha; node->zoom = xmb->items_passive_zoom; node->label_alpha = node->alpha; node->y = xmb_item_y(xmb, i, current); node->x = 0; if (i == current) { node->alpha = xmb->items_active_alpha; node->label_alpha = xmb->items_active_alpha; node->zoom = xmb->items_active_alpha; } file_list_set_userdata(list, i, node); } static void xmb_list_clear(file_list_t *list) { uintptr_t tag = (uintptr_t)list; gfx_animation_kill_by_tag(&tag); xmb_free_list_nodes(list, false); } static void xmb_list_free(file_list_t *list, size_t a, size_t b) { xmb_list_clear(list); } static void xmb_list_deep_copy(const file_list_t *src, file_list_t *dst, size_t first, size_t last) { size_t i, j = 0; uintptr_t tag = (uintptr_t)dst; gfx_animation_kill_by_tag(&tag); xmb_free_list_nodes(dst, true); file_list_clear(dst); file_list_reserve(dst, (last + 1) - first); for (i = first; i <= last; ++i) { struct item_file *d = &dst->list[j]; struct item_file *s = &src->list[i]; void *src_udata = s->userdata; void *src_adata = s->actiondata; *d = *s; d->alt = string_is_empty(d->alt) ? NULL : strdup(d->alt); d->path = string_is_empty(d->path) ? NULL : strdup(d->path); d->label = string_is_empty(d->label) ? NULL : strdup(d->label); if (src_udata) file_list_set_userdata(dst, j, (void*)xmb_copy_node((const xmb_node_t*)src_udata)); if (src_adata) { void *data = malloc(sizeof(menu_file_list_cbs_t)); memcpy(data, src_adata, sizeof(menu_file_list_cbs_t)); file_list_set_actiondata(dst, j, data); } ++j; } dst->size = j; } static void xmb_list_cache(void *data, enum menu_list_type type, unsigned action) { size_t stack_size, list_size; xmb_handle_t *xmb = (xmb_handle_t*)data; file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); settings_t *settings = config_get_ptr(); bool menu_horizontal_animation = settings->bools.menu_horizontal_animation; if (!xmb) return; /* Check whether to enable the horizontal animation. */ if (menu_horizontal_animation) { unsigned first = 0, last = 0; unsigned height = 0; video_driver_get_size(NULL, &height); /* FIXME: this shouldn't be happening at all */ if (selection >= selection_buf->size) selection = selection_buf->size ? selection_buf->size - 1 : 0; xmb->selection_ptr_old = selection; xmb_calculate_visible_range(xmb, height, selection_buf->size, (unsigned)xmb->selection_ptr_old, &first, &last); xmb_list_deep_copy(selection_buf, xmb->selection_buf_old, first, last); xmb->selection_ptr_old -= first; last -= first; first = 0; } else xmb->selection_ptr_old = 0; list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end; switch (type) { case MENU_LIST_PLAIN: break; case MENU_LIST_HORIZONTAL: xmb->categories_selection_ptr_old = xmb->categories_selection_ptr; switch (action) { case MENU_ACTION_LEFT: if (xmb->categories_selection_ptr == 0) { xmb->categories_selection_ptr = list_size; xmb->categories_active_idx = (unsigned)(list_size - 1); } else xmb->categories_selection_ptr--; break; default: if (xmb->categories_selection_ptr == list_size) { xmb->categories_selection_ptr = 0; xmb->categories_active_idx = 1; } else xmb->categories_selection_ptr++; break; } stack_size = menu_stack->size; if (menu_stack->list[stack_size - 1].label) free(menu_stack->list[stack_size - 1].label); menu_stack->list[stack_size - 1].label = NULL; switch (xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr)) { case XMB_SYSTEM_TAB_MAIN: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU)); menu_stack->list[stack_size - 1].type = MENU_SETTINGS; break; case XMB_SYSTEM_TAB_SETTINGS: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_SETTINGS_TAB)); menu_stack->list[stack_size - 1].type = MENU_SETTINGS_TAB; break; #ifdef HAVE_IMAGEVIEWER case XMB_SYSTEM_TAB_IMAGES: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_IMAGES_TAB)); menu_stack->list[stack_size - 1].type = MENU_IMAGES_TAB; break; #endif case XMB_SYSTEM_TAB_MUSIC: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_MUSIC_TAB)); menu_stack->list[stack_size - 1].type = MENU_MUSIC_TAB; break; #if defined(HAVE_FFMPEG) || defined(HAVE_MPV) case XMB_SYSTEM_TAB_VIDEO: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_VIDEO_TAB)); menu_stack->list[stack_size - 1].type = MENU_VIDEO_TAB; break; #endif case XMB_SYSTEM_TAB_HISTORY: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_HISTORY_TAB)); menu_stack->list[stack_size - 1].type = MENU_HISTORY_TAB; break; case XMB_SYSTEM_TAB_FAVORITES: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES_TAB)); menu_stack->list[stack_size - 1].type = MENU_FAVORITES_TAB; break; #ifdef HAVE_NETWORKING case XMB_SYSTEM_TAB_NETPLAY: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB)); menu_stack->list[stack_size - 1].type = MENU_NETPLAY_TAB; break; #endif case XMB_SYSTEM_TAB_ADD: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_ADD_TAB)); menu_stack->list[stack_size - 1].type = MENU_ADD_TAB; break; default: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)); menu_stack->list[stack_size - 1].type = MENU_SETTING_HORIZONTAL_MENU; break; } break; default: break; } } static void xmb_context_destroy(void *data) { unsigned i; xmb_handle_t *xmb = (xmb_handle_t*)data; if (!xmb) return; for (i = 0; i < XMB_TEXTURE_LAST; i++) video_driver_texture_unload(&xmb->textures.list[i]); xmb_unload_thumbnail_textures(xmb); xmb_context_destroy_horizontal_list(xmb); xmb_context_bg_destroy(xmb); gfx_display_font_free(xmb->font); gfx_display_font_free(xmb->font2); xmb->font = NULL; xmb->font2 = NULL; } static void xmb_toggle(void *userdata, bool menu_on) { gfx_animation_ctx_entry_t entry; bool tmp = false; xmb_handle_t *xmb = (xmb_handle_t*)userdata; if (!xmb) return; xmb->depth = (int)xmb_list_get_size(xmb, MENU_LIST_PLAIN); if (!menu_on) { xmb->alpha = 0; return; } /* Have to reset this, otherwise savestate * thumbnail won't update after selecting * 'save state' option */ gfx_thumbnail_reset(&xmb->thumbnails.savestate); entry.duration = XMB_DELAY * 2; entry.target_value = 1.0f; entry.subject = &xmb->alpha; entry.easing_enum = EASING_OUT_QUAD; /* TODO/FIXME - integer conversion resulted in change of sign */ entry.tag = -1; entry.cb = NULL; gfx_animation_push(&entry); tmp = !menu_entries_ctl(MENU_ENTRIES_CTL_NEEDS_REFRESH, NULL); if (tmp) menu_driver_ctl(RARCH_MENU_CTL_SET_PREVENT_POPULATE, NULL); else menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); xmb_toggle_horizontal_list(xmb); } static int xmb_deferred_push_content_actions(menu_displaylist_info_t *info) { if (!menu_displaylist_ctl( DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, info)) return -1; menu_displaylist_process(info); menu_displaylist_info_free(info); return 0; } static int xmb_list_bind_init_compare_label(menu_file_list_cbs_t *cbs) { if (cbs && cbs->enum_idx != MSG_UNKNOWN) { switch (cbs->enum_idx) { case MENU_ENUM_LABEL_CONTENT_ACTIONS: cbs->action_deferred_push = xmb_deferred_push_content_actions; break; default: return -1; } } return 0; } static int xmb_list_bind_init(menu_file_list_cbs_t *cbs, const char *path, const char *label, unsigned type, size_t idx) { if (xmb_list_bind_init_compare_label(cbs) == 0) return 0; return -1; } static int xmb_list_push(void *data, void *userdata, menu_displaylist_info_t *info, unsigned type) { menu_displaylist_ctx_parse_entry_t entry; int ret = -1; core_info_list_t *list = NULL; menu_handle_t *menu = (menu_handle_t*)data; settings_t *settings = config_get_ptr(); bool menu_show_load_core = settings->bools.menu_show_load_core; bool menu_show_load_content = settings->bools.menu_show_load_content; bool menu_content_show_pl = settings->bools.menu_content_show_playlists; bool menu_show_configurations = settings->bools.menu_show_configurations; bool menu_show_load_disc = settings->bools.menu_show_load_disc; bool menu_show_dump_disc = settings->bools.menu_show_dump_disc; bool menu_show_shutdown = settings->bools.menu_show_shutdown; bool menu_show_reboot = settings->bools.menu_show_reboot; bool menu_show_quit_retroarch = settings->bools.menu_show_quit_retroarch; bool menu_show_restart_ra = settings->bools.menu_show_restart_retroarch; bool menu_show_information = settings->bools.menu_show_information; bool menu_show_help = settings->bools.menu_show_help; bool kiosk_mode_enable = settings->bools.kiosk_mode_enable; #ifdef HAVE_QT bool desktop_menu_enable = settings->bools.desktop_menu_enable; #endif #if defined(HAVE_NETWORKING) && defined(HAVE_ONLINE_UPDATER) bool menu_show_online_updater = settings->bools.menu_show_online_updater; #endif bool menu_content_show_settings = settings->bools.menu_content_show_settings; const char *menu_content_show_settings_password = settings->paths.menu_content_show_settings_password; const char *kiosk_mode_password = settings->paths.kiosk_mode_password; switch (type) { case DISPLAYLIST_LOAD_CONTENT_LIST: menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES), msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES), MENU_ENUM_LABEL_FAVORITES, MENU_SETTING_ACTION, 0, 0); core_info_get_list(&list); if (core_info_list_num_info_files(list)) menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_DOWNLOADED_FILE_DETECT_CORE_LIST), msg_hash_to_str(MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST), MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST, MENU_SETTING_ACTION, 0, 0); if (menu_content_show_pl) menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB), msg_hash_to_str(MENU_ENUM_LABEL_PLAYLISTS_TAB), MENU_ENUM_LABEL_PLAYLISTS_TAB, MENU_SETTING_ACTION, 0, 0); if (frontend_driver_parse_drive_list(info->list, true) != 0) menu_entries_append_enum(info->list, "/", msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR, MENU_SETTING_ACTION, 0, 0); if (!kiosk_mode_enable) menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MENU_FILE_BROWSER_SETTINGS), msg_hash_to_str(MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS), MENU_ENUM_LABEL_MENU_FILE_BROWSER_SETTINGS, MENU_SETTING_ACTION, 0, 0); info->need_push = true; info->need_refresh = true; ret = 0; break; case DISPLAYLIST_MAIN_MENU: { rarch_system_info_t *system = runloop_get_system_info(); menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); entry.data = menu; entry.info = info; entry.parse_type = PARSE_ACTION; entry.add_empty_entry = false; if (rarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL)) { if (!rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL)) { entry.enum_idx = MENU_ENUM_LABEL_CONTENT_SETTINGS; menu_displaylist_setting(&entry); } } else { if (system->load_no_content) { entry.enum_idx = MENU_ENUM_LABEL_START_CORE; menu_displaylist_setting(&entry); } #ifndef HAVE_DYNAMIC if (frontend_driver_has_fork()) #endif { if (menu_show_load_core) { entry.enum_idx = MENU_ENUM_LABEL_CORE_LIST; menu_displaylist_setting(&entry); } } } if (menu_show_load_content) { entry.enum_idx = MENU_ENUM_LABEL_LOAD_CONTENT_LIST; menu_displaylist_setting(&entry); if (menu_displaylist_has_subsystems()) { entry.enum_idx = MENU_ENUM_LABEL_SUBSYSTEM_SETTINGS; menu_displaylist_setting(&entry); } } if (menu_show_load_disc) { entry.enum_idx = MENU_ENUM_LABEL_LOAD_DISC; menu_displaylist_setting(&entry); } if (menu_show_dump_disc) { entry.enum_idx = MENU_ENUM_LABEL_DUMP_DISC; menu_displaylist_setting(&entry); } entry.enum_idx = MENU_ENUM_LABEL_ADD_CONTENT_LIST; menu_displaylist_setting(&entry); #ifdef HAVE_QT if (desktop_menu_enable) { entry.enum_idx = MENU_ENUM_LABEL_SHOW_WIMP; menu_displaylist_setting(&entry); } #endif #if defined(HAVE_NETWORKING) #if defined(HAVE_ONLINE_UPDATER) if (menu_show_online_updater && !kiosk_mode_enable) { entry.enum_idx = MENU_ENUM_LABEL_ONLINE_UPDATER; menu_displaylist_setting(&entry); } #endif #endif if ( !menu_content_show_settings && !string_is_empty(menu_content_show_settings_password)) { entry.enum_idx = MENU_ENUM_LABEL_XMB_MAIN_MENU_ENABLE_SETTINGS; menu_displaylist_setting(&entry); } if (kiosk_mode_enable && !string_is_empty(kiosk_mode_password)) { entry.enum_idx = MENU_ENUM_LABEL_MENU_DISABLE_KIOSK_MODE; menu_displaylist_setting(&entry); } if (menu_show_information) { entry.enum_idx = MENU_ENUM_LABEL_INFORMATION_LIST; menu_displaylist_setting(&entry); } #if defined(HAVE_LAKKA_SWITCH) || defined(HAVE_LIBNX) entry.enum_idx = MENU_ENUM_LABEL_SWITCH_CPU_PROFILE; menu_displaylist_setting(&entry); #endif #ifdef HAVE_LAKKA_SWITCH entry.enum_idx = MENU_ENUM_LABEL_SWITCH_GPU_PROFILE; menu_displaylist_setting(&entry); entry.enum_idx = MENU_ENUM_LABEL_SWITCH_BACKLIGHT_CONTROL; menu_displaylist_setting(&entry); #endif if (menu_show_configurations && !kiosk_mode_enable) { entry.enum_idx = MENU_ENUM_LABEL_CONFIGURATIONS_LIST; menu_displaylist_setting(&entry); } if (menu_show_help) { entry.enum_idx = MENU_ENUM_LABEL_HELP_LIST; menu_displaylist_setting(&entry); } #if !defined(IOS) if (menu_show_restart_ra) { entry.enum_idx = MENU_ENUM_LABEL_RESTART_RETROARCH; menu_displaylist_setting(&entry); } if (menu_show_quit_retroarch) { entry.enum_idx = MENU_ENUM_LABEL_QUIT_RETROARCH; menu_displaylist_setting(&entry); } #endif if (menu_show_reboot) { entry.enum_idx = MENU_ENUM_LABEL_REBOOT; menu_displaylist_setting(&entry); } if (menu_show_shutdown) { entry.enum_idx = MENU_ENUM_LABEL_SHUTDOWN; menu_displaylist_setting(&entry); } info->need_push = true; ret = 0; } break; } return ret; } static bool xmb_menu_init_list(void *data) { menu_displaylist_info_t info; file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); menu_displaylist_info_init(&info); info.label = strdup( msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU)); info.exts = strdup("lpl"); info.type_default = FILE_TYPE_PLAIN; info.enum_idx = MENU_ENUM_LABEL_MAIN_MENU; menu_entries_append_enum(menu_stack, info.path, info.label, MENU_ENUM_LABEL_MAIN_MENU, info.type, info.flags, 0); info.list = selection_buf; if (!menu_displaylist_ctl(DISPLAYLIST_MAIN_MENU, &info)) goto error; info.need_push = true; if (!menu_displaylist_process(&info)) goto error; menu_displaylist_info_free(&info); return true; error: menu_displaylist_info_free(&info); return false; } static int xmb_pointer_up(void *userdata, unsigned x, unsigned y, unsigned ptr, enum menu_input_pointer_gesture gesture, menu_file_list_cbs_t *cbs, menu_entry_t *entry, unsigned action) { unsigned width; unsigned height; int16_t margin_top; int16_t margin_left; int16_t margin_right; xmb_handle_t *xmb = (xmb_handle_t*)userdata; size_t selection = menu_navigation_get_selection(); unsigned end = (unsigned)menu_entries_get_size(); if (!xmb) return -1; /* If fullscreen thumbnail view is enabled, * all input will disable it and otherwise * be ignored */ if (xmb->show_fullscreen_thumbnails) { xmb_hide_fullscreen_thumbnails(xmb, true); return 0; } video_driver_get_size(&width, &height); margin_top = (int16_t)xmb->margins_screen_top; margin_left = (int16_t)xmb->margins_screen_left; margin_right = (int16_t)((float)width - xmb->margins_screen_left); switch (gesture) { case MENU_INPUT_GESTURE_TAP: case MENU_INPUT_GESTURE_SHORT_PRESS: /* - A touch in the left margin: * > ...triggers a 'cancel' action beneath the top margin * > ...triggers a 'search' action above the top margin * - A touch in the right margin triggers a 'select' action * for the current item * - Between the left/right margins input is handled normally */ if (x < margin_left) { if (y >= margin_top) return menu_entry_action(entry, selection, MENU_ACTION_CANCEL); return menu_input_dialog_start_search() ? 0 : -1; } else if (x > margin_right) return menu_entry_action(entry, selection, MENU_ACTION_SELECT); else if (ptr <= (end - 1)) { /* If pointer item is already 'active', perform 'select' action */ if (ptr == selection) return menu_entry_action(entry, selection, MENU_ACTION_SELECT); /* ...otherwise navigate to the current pointer item */ menu_navigation_set_selection(ptr); menu_driver_navigation_set(false); } break; case MENU_INPUT_GESTURE_LONG_PRESS: /* 'Reset to default' action */ if ((ptr <= end - 1) && (ptr == selection)) return menu_entry_action(entry, selection, MENU_ACTION_START); break; case MENU_INPUT_GESTURE_SWIPE_LEFT: /* Navigate left * Note: At the top level, navigating left * means switching to the 'next' horizontal list, * which is actually a movement to the *right* */ if (y > margin_top) menu_entry_action( entry, selection, (xmb->depth == 1) ? MENU_ACTION_RIGHT : MENU_ACTION_LEFT); break; case MENU_INPUT_GESTURE_SWIPE_RIGHT: /* Navigate right * Note: At the top level, navigating right * means switching to the 'previous' horizontal list, * which is actually a movement to the *left* */ if (y > margin_top) menu_entry_action( entry, selection, (xmb->depth == 1) ? MENU_ACTION_LEFT : MENU_ACTION_RIGHT); break; case MENU_INPUT_GESTURE_SWIPE_UP: /* Swipe up in left margin: ascend alphabet */ if (x < margin_left) menu_entry_action(entry, selection, MENU_ACTION_SCROLL_DOWN); else if (x < margin_right) { /* Swipe up between left and right margins: * move selection pointer down by 1 'page' */ unsigned first = 0; unsigned last = end; if (height) xmb_calculate_visible_range(xmb, height, end, (unsigned)selection, &first, &last); if (last < end) { menu_navigation_set_selection((size_t)last); menu_driver_navigation_set(true); } else menu_driver_ctl(MENU_NAVIGATION_CTL_SET_LAST, NULL); } break; case MENU_INPUT_GESTURE_SWIPE_DOWN: /* Swipe down in left margin: descend alphabet */ if (x < margin_left) menu_entry_action(entry, selection, MENU_ACTION_SCROLL_UP); else if (x < margin_right) { /* Swipe down between left and right margins: * move selection pointer up by 1 'page' */ unsigned bottom_idx = (unsigned)selection + 1; size_t new_idx; unsigned step; /* Determine index of entry at bottom of screen * Note: cannot use xmb_calculate_visible_range() * here because there may not be sufficient entries * to reach the bottom of the screen - i.e. we just * want an index offset to subtract from the current * selection... */ for (;;) { float top = xmb_item_y(xmb, bottom_idx, selection) + xmb->margins_screen_top; if (top > height) { /* Since this checks the top position, the * final index is always 1 greater than it * should be... */ bottom_idx--; break; } bottom_idx++; } step = (bottom_idx >= selection) ? bottom_idx - selection : 0; new_idx = (selection > step) ? selection - step : 0; if (new_idx > 0) { menu_navigation_set_selection(new_idx); menu_driver_navigation_set(true); } else { bool pending_push = false; menu_driver_ctl(MENU_NAVIGATION_CTL_CLEAR, &pending_push); } } break; default: /* Ignore input */ break; } return 0; } static enum menu_action xmb_parse_menu_entry_action( xmb_handle_t *xmb, enum menu_action action) { enum menu_action new_action = action; /* If fullscreen thumbnail view is active, any * valid menu action will disable it... */ if (xmb->show_fullscreen_thumbnails) { if (action != MENU_ACTION_NOOP) { xmb_hide_fullscreen_thumbnails(xmb, true); /* ...and any action other than Select/OK * is ignored * > We allow pass-through of Select/OK since * users may want to run content directly * after viewing fullscreen thumbnails, * and having to press RetroPad A or the Return * key twice is navigationally confusing * > Note that we can only do this for non-pointer * input * > Note that we don't do this when viewing a * file list, since there is no quick menu * in this case - i.e. content loads directly, * and a sudden transition from fullscreen * thumbnail to content is jarring... */ if (xmb->is_file_list || ((action != MENU_ACTION_SELECT) && (action != MENU_ACTION_OK))) return MENU_ACTION_NOOP; } } /* Scan user inputs */ switch (action) { case MENU_ACTION_LEFT: case MENU_ACTION_RIGHT: /* Check whether left/right action will * trigger a tab switch event */ if (xmb->depth == 1) { retro_time_t current_time = menu_driver_get_current_time(); size_t scroll_accel = 0; /* Determine whether input repeat is * currently active * > This is always true when scroll * acceleration is greater than zero */ menu_driver_ctl(MENU_NAVIGATION_CTL_GET_SCROLL_ACCEL, &scroll_accel); if (scroll_accel > 0) { /* Ignore input action if tab switch period * is less than defined limit */ if ((current_time - xmb->last_tab_switch_time) < XMB_TAB_SWITCH_REPEAT_DELAY) { new_action = MENU_ACTION_NOOP; break; } } xmb->last_tab_switch_time = current_time; } break; case MENU_ACTION_START: /* If this is a menu with thumbnails, attempt * to show fullscreen thumbnail view */ if (xmb->fullscreen_thumbnails_available) { size_t selection = menu_navigation_get_selection(); xmb_show_fullscreen_thumbnails(xmb, selection); new_action = MENU_ACTION_NOOP; } break; default: /* In all other cases, pass through input * menu action without intervention */ break; } return new_action; } /* Menu entry action callback */ static int xmb_menu_entry_action( void *userdata, menu_entry_t *entry, size_t i, enum menu_action action) { xmb_handle_t *xmb = (xmb_handle_t*)userdata; /* Process input action */ enum menu_action new_action = xmb_parse_menu_entry_action(xmb, action); /* Call standard generic_menu_entry_action() function */ return generic_menu_entry_action(userdata, entry, i, new_action); } menu_ctx_driver_t menu_ctx_xmb = { NULL, xmb_messagebox, NULL, /* iterate */ xmb_render, xmb_frame, xmb_init, xmb_free, xmb_context_reset, xmb_context_destroy, xmb_populate_entries, xmb_toggle, xmb_navigation_clear, NULL, /*xmb_navigation_pointer_changed,*/ /* Note: navigation_set() is called each time navigation_increment/decrement() */ NULL, /*xmb_navigation_pointer_changed,*/ /* is called, so linking these just duplicates work... */ xmb_navigation_set, xmb_navigation_pointer_changed, xmb_navigation_alphabet, xmb_navigation_alphabet, xmb_menu_init_list, xmb_list_insert, NULL, xmb_list_free, xmb_list_clear, xmb_list_cache, xmb_list_push, xmb_list_get_selection, xmb_list_get_size, xmb_list_get_entry, NULL, xmb_list_bind_init, xmb_load_image, "xmb", xmb_environ, NULL, xmb_update_thumbnail_image, xmb_refresh_thumbnail_image, xmb_set_thumbnail_system, xmb_get_thumbnail_system, xmb_set_thumbnail_content, gfx_display_osk_ptr_at_pos, xmb_update_savestate_thumbnail_path, xmb_update_savestate_thumbnail_image, NULL, /* pointer_down */ xmb_pointer_up, xmb_menu_entry_action };