Files
RetroArch/menu/drivers/xmb.c
T

10442 lines
374 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <file/file_path.h>
#include <compat/posix_string.h>
#include <compat/strl.h>
#include <formats/image.h>
#include <string/stdstring.h>
#include <gfx/math/matrix_4x4.h>
#include <streams/file_stream.h>
#include <encodings/utf.h>
#include <features/features_cpu.h>
#include <array/rhmap.h>
#include <retro_math.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#include "../../frontend/frontend_driver.h"
#include "../menu_driver.h"
#include "../menu_entries.h"
#include "../menu_screensaver.h"
#include "../../gfx/gfx_animation.h"
#include "../../gfx/gfx_thumbnail.h"
#include "../../configuration.h"
#include "../../content.h"
#include "../../core_info.h"
#include "../../file_path_special.h"
#include "../../input/input_osk.h"
#include "../../tasks/tasks_internal.h"
#include "../../msg_hash_lbl_str.h"
#ifdef HAVE_AUDIOMIXER
#include "../../audio/audio_driver.h"
#endif
#ifdef HAVE_CHEEVOS
#include "../../cheevos/cheevos_menu.h"
#endif
#define XMB_RIBBON_ROWS (64)
#define XMB_RIBBON_COLS (64)
#define XMB_RIBBON_VERTICES (XMB_RIBBON_ROWS * (2 * XMB_RIBBON_COLS)) - (2 * XMB_RIBBON_COLS)
#define XMB_TAB_MAX_LENGTH 255
#define XMB_DELAY (166.66667f)
#define XMB_EASING_ALPHA EASING_OUT_CIRC
#define XMB_EASING_XY EASING_OUT_QUAD
/* 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)
/* XMB does not have a clean colour theme
* implementation. Until this is available,
* the menu screensaver tint will be set to
* a fixed colour: HTML WhiteSmoke */
#define XMB_SCREENSAVER_TINT 0xF5F5F5
/* Mean human reading speed for all western languages,
* characters per minute */
#define TICKER_CPM (1000.0f)
/* Base time for which a line should be shown, in us */
#define TICKER_LINE_DURATION_US(line_len) ((line_len * 60.0f * 1000.0f * 1000.0f) / TICKER_CPM)
/* Base time for which a line should be shown, in ms */
#define TICKER_LINE_DURATION_MS(line_len) ((line_len * 60.0f * 1000.0f) / TICKER_CPM)
/* Ticker updates (nominally) once every TICKER_SPEED us
* > Base number of ticks for which line should be shown */
#define TICKER_LINE_DISPLAY_TICKS(line_len) ((size_t)(TICKER_LINE_DURATION_US(line_len) / (float)TICKER_SPEED))
/* Smooth ticker updates (nominally) once every TICKER_PIXEL_PERIOD ms
* > Base number of ticks for which text should scroll
* from one line to the next */
#define TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len) ((size_t)(TICKER_LINE_DURATION_MS(line_len) / TICKER_PIXEL_PERIOD))
enum
{
XMB_TEXTURE_MAIN_MENU = 0,
XMB_TEXTURE_SETTINGS,
XMB_TEXTURE_HISTORY,
XMB_TEXTURE_FAVORITES,
#ifdef HAVE_IMAGEVIEWER
XMB_TEXTURE_IMAGES,
#endif
XMB_TEXTURE_MUSICS,
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
XMB_TEXTURE_MOVIES,
#endif
#ifdef HAVE_NETWORKING
XMB_TEXTURE_NETPLAY,
XMB_TEXTURE_NETPLAY_ALT,
XMB_TEXTURE_ROOM,
XMB_TEXTURE_ROOM_LAN,
XMB_TEXTURE_ROOM_RELAY,
#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_RECORDREPLAY,
XMB_TEXTURE_PLAYREPLAY,
XMB_TEXTURE_HALTREPLAY,
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_IMAGE,
XMB_TEXTURE_MUSIC,
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,
XMB_SYSTEM_TAB_CONTENTLESS_CORES,
#if defined(HAVE_LIBRETRODB)
XMB_SYSTEM_TAB_EXPLORE,
#endif
/* End of this enum - use the last one to determine num of possible tabs */
XMB_SYSTEM_TAB_MAX_LENGTH
};
enum xmb_pending_thumbnail_type
{
XMB_PENDING_THUMBNAIL_NONE = 0,
XMB_PENDING_THUMBNAIL_RIGHT,
XMB_PENDING_THUMBNAIL_LEFT,
XMB_PENDING_THUMBNAIL_BOTH,
XMB_PENDING_THUMBNAIL_ICONS
};
typedef struct
{
gfx_thumbnail_path_data_t thumbnail_path_data;
gfx_thumbnail_t icon;
} xmb_icons_t;
/* NOTE: If you change this you HAVE to update
* xmb_alloc_node() */
typedef struct
{
char *fullpath;
char *console_name;
uintptr_t icon;
uintptr_t content_icon;
xmb_icons_t thumbnail_icon;
float alpha;
float label_alpha;
float zoom;
float x;
float y;
} xmb_node_t;
enum xmb_drag_mode
{
XMB_DRAG_NONE = 0,
XMB_DRAG_DETECTING,
XMB_DRAG_HORIZONTAL,
XMB_DRAG_VERTICAL
};
typedef struct xmb_handle
{
/* 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; /* uint64_t alignment */
char *box_message;
char *bg_file_path;
file_list_t horizontal_list; /* ptr alignment */
/* Maps console tabs to playlist database names */
xmb_node_t **playlist_db_node_map;
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 contentless_cores_tab_node;
#if defined(HAVE_LIBRETRODB)
xmb_node_t explore_tab_node;
#endif
xmb_node_t netplay_tab_node;
menu_input_pointer_t pointer;
/* Touch drag state for direction locking */
enum xmb_drag_mode drag_mode;
float drag_start_x;
float drag_start_y;
float categories_drag_start_pos;
size_t drag_start_selection;
font_data_t *font;
font_data_t *font2;
video_font_raster_block_t raster_block;
video_font_raster_block_t raster_block2;
size_t (*word_wrap)(
char *s, size_t len,
const char *src, size_t src_len,
int line_width, int wideglyph_width, unsigned max_lines);
menu_screensaver_t *screensaver;
struct {
gfx_thumbnail_t right;
gfx_thumbnail_t left;
gfx_thumbnail_t icon;
gfx_thumbnail_t savestate;
enum xmb_pending_thumbnail_type pending;
enum xmb_pending_thumbnail_type pending_icons;
} thumbnails;
struct
{
uintptr_t bg;
uintptr_t list[XMB_TEXTURE_LAST];
} textures;
uintptr_t current_menu_icon;
size_t categories_selection_ptr;
size_t categories_selection_ptr_old;
size_t selection_ptr_old;
size_t fullscreen_thumbnail_selection;
/* size of the current list */
size_t list_size;
size_t tab_selection[XMB_TAB_MAX_LENGTH];
int depth;
int old_depth;
int icon_size;
int cursor_size;
int wideglyph_width;
unsigned categories_active_idx;
unsigned categories_active_idx_old;
unsigned ticker_limit;
unsigned draw_entry_delay;
float fullscreen_thumbnail_alpha;
float x;
float alpha;
float alpha_list;
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;
unsigned pending_context_reset;
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_title;
float margins_title_horizontal_offset;
float last_margins_title;
float last_margins_title_horizontal_offset;
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 categories_x_pos;
float categories_passive_alpha;
float categories_passive_zoom;
float categories_active_zoom;
float categories_active_alpha;
uint8_t system_tab_end;
uint8_t tabs[XMB_SYSTEM_TAB_MAX_LENGTH];
/* Frames remaining to retry xmb_set_title() after a context reset
* left an async-loaded sidebar / db-node icon unresolved. Capped
* so that a permanently-missing icon asset doesn't cause a
* perpetual retry loop. */
uint8_t current_menu_icon_retry;
char title_name[NAME_MAX_LENGTH];
char title_name_alt[NAME_MAX_LENGTH];
/* Cached texts showing current entry index / current list size */
char entry_index_str[32];
char entry_index_offset;
char savestate_thumbnail_file_path[PATH_MAX_LENGTH];
char fullscreen_thumbnail_label[NAME_MAX_LENGTH];
char thumbnails_left_status_prev;
char thumbnails_right_status_prev;
bool allow_horizontal_animation;
bool allow_dynamic_wallpaper;
bool fullscreen_thumbnails_available;
bool show_fullscreen_thumbnails;
bool want_fullscreen_thumbnails;
bool skip_thumbnail_reset;
/* Set by xmb_populate_entries when it wanted to run
* xmb_populate_dynamic_icons but deferred the work because the user
* is actively mashing / holding a navigation button
* (menu_st->scroll.acceleration > 0). xmb_render fires the actual
* work on a later frame, once input settles and acceleration drops
* to 0. This avoids the per-tab unload+path_data churn for
* intermediate tabs the user blows through during held-repeat tab
* traversal, without delaying the work behind any visible animation
* — the instant input lets go, the populate runs. */
bool pending_dynamic_icons_repopulate;
bool show_thumbnails;
bool show_mouse;
bool show_screensaver;
bool show_playlist_tabs;
bool use_ps3_layout;
bool last_use_ps3_layout;
bool assets_missing;
/* Favorites, History, Images, Music, Videos, user generated */
bool is_playlist;
bool is_playlist_tab;
bool is_playlist_information;
bool is_db_manager_list;
bool is_explore_list;
bool is_contentless_cores;
/* Load Content file browser */
bool is_file_list;
bool is_quick_menu;
bool is_state_slot;
bool libretro_running;
/* True from xmb_init() until the first xmb_frame() completes.
* Used to skip the toggle-fade-in that would otherwise render
* the very first frame at alpha=0 (a black screen) before the
* fade-in animation starts. */
bool is_first_frame;
/* Whether to show entry index for current list */
bool entry_idx_enabled;
/* Per-instance layout scale modifiers (was file-scope static) */
float scale_mod[8];
/* Base item color: 0,0,0 for inverted themes, 1,1,1 otherwise
* (was file-scope static xmb_item_color) */
float item_color_base[16];
/* Incremented on context destroy; the render path snapshots
* this at frame start and bails if it changes mid-frame.
* Prevents use-after-free on textures/fonts during driver
* reinit under threaded video. */
uint32_t context_generation;
/* Surface size last seen by xmb_frame. Updated every frame
* from video_info->{width,height} so non-render code paths
* (selection_pointer_changed, list_open_new, list_switch_new,
* list_cache, pointer_up, layout) can read the size without
* locking video_st via video_driver_get_output_size. */
unsigned last_width;
unsigned last_height;
} xmb_handle_t;
/* Constant color templates — safe to share across threads.
* Mutable scratch copies are created on the stack where needed. */
static const float xmb_color_black[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,
};
static const float xmb_color_white[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,
};
/* Forward declarations */
extern int action_switch_thumbnail(const char *path,
const char *label, unsigned type, size_t idx);
static int xmb_menu_entry_action(void *userdata,
menu_entry_t *entry, size_t i, enum menu_action action);
static bool xmb_load_image(void *userdata, void *data,
enum menu_image_type type);
static void xmb_context_bg_destroy(xmb_handle_t *xmb);
static void xmb_navigation_set(void *data, bool scroll);
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, const char *enum_path,
const char *enum_label, unsigned type, bool active,
bool checked);
static INLINE float xmb_item_y(const xmb_handle_t *xmb,
int i, size_t current)
{
float icon_spacing_vertical = xmb->icon_spacing_vertical;
if (i < (int)current)
{
if (xmb->depth > 1)
return icon_spacing_vertical * (i - (int)current + xmb->above_subitem_offset);
return icon_spacing_vertical * (i - (int)current + xmb->above_item_offset);
}
else if (i == (int)current)
return icon_spacing_vertical * xmb->active_item_factor;
return icon_spacing_vertical * (i - (int)current + xmb->under_item_offset);
}
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;
}
}
const char* xmb_theme_ident(void)
{
unsigned menu_xmb_theme = config_get_ptr()->uints.menu_xmb_theme;
switch (menu_xmb_theme)
{
case XMB_ICON_THEME_FLATUI:
return "flatui";
case XMB_ICON_THEME_FLATUX:
return "flatux";
case XMB_ICON_THEME_RETROSYSTEM:
return "retrosystem";
case XMB_ICON_THEME_PIXEL:
return "pixel";
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_DAITE:
return "daite";
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.
*
* Per-field init only — DO NOT add a wholesale memset of the
* thumbnail_path_data substruct here, that's the multi-KB block
* we're explicitly avoiding zeroing on the hot path. Only the
* small `gfx_thumbnail_t icon` substruct and the icon_path's
* first byte must be zero, because:
* - xmb_free_node -> gfx_thumbnail_reset reads `texture` and
* `flags` before initializing them; uninit `texture != 0`
* would feed garbage to video_driver_texture_unload, and
* uninit `flags & FADE_ACTIVE` would call
* gfx_animation_kill_by_tag on stale state.
* - The lazy thumbnail path resolution in xmb_render reads
* `icon_path[0]` to decide whether resolution is needed.
*
* gfx_thumbnail_init_blank() (rather than memset) is needed
* because gfx_thumbnail_t.status is now atomically-typed; a
* memset of a struct containing std::atomic<int> warns under
* CXX_BUILD's C++ compile of this file. */
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;
gfx_thumbnail_init_blank(&node->thumbnail_icon.icon);
node->thumbnail_icon.thumbnail_path_data.icon_path[0] = '\0';
node->fullpath = NULL;
node->console_name = NULL;
return node;
}
static void xmb_free_node(xmb_node_t *node)
{
if (!node)
return;
if (node->fullpath)
free(node->fullpath);
node->fullpath = NULL;
gfx_thumbnail_reset(&node->thumbnail_icon.icon);
free(node);
}
/**
* @brief frees all xmb_node_t in a file_list_t
*
* file_list_t assumes 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;
unsigned size = list ? (unsigned)list->size : 0;
for (i = 0; i < size; ++i)
{
xmb_free_node((xmb_node_t*)file_list_get_userdata_at_offset(list, i));
list->list[i].userdata = NULL;
if (actiondata)
file_list_free_actiondata(list, i);
}
}
static float *xmb_gradient_ident(unsigned xmb_color_theme)
{
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static float gradient_morning_blue[16] = {
221/255.0, 241/255.0, 254/255.0, 1.0,
135/255.0, 206/255.0, 250/255.0, 1.0,
0.7, 0.7, 0.7, 1.0,
170/255.0, 200/255.0, 252/255.0, 1.0,
};
static 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,
0.7, 0.7, 0.7, 1.0,
0.1, 0.0, 0.1, 1.0,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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,
};
static 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 float gradient_volcanic_red[16] = {
1.0, 0.0, 0.1, 1.0,
1.0, 0.1, 0.0, 1.0,
0.1, 0.0, 0.1, 1.0,
0.1, 0.0, 0.1, 1.0,
};
static float gradient_dark[16] = {
0.05, 0.05, 0.05, 1.0,
0.05, 0.05, 0.05, 1.0,
0.05, 0.05, 0.05, 1.0,
0.05, 0.05, 0.05, 1.0,
};
static float gradient_light[16] = {
0.50, 0.50, 0.50, 1.0,
0.50, 0.50, 0.50, 1.0,
0.50, 0.50, 0.50, 1.0,
0.50, 0.50, 0.50, 1.0,
};
static float gradient_gray_dark[16] = {
16/255.0, 16/255.0, 16/255.0, 1.0,
16/255.0, 16/255.0, 16/255.0, 1.0,
16/255.0, 16/255.0, 16/255.0, 1.0,
16/255.0, 16/255.0, 16/255.0, 1.0,
};
static float gradient_gray_light[16] = {
32/255.0, 32/255.0, 32/255.0, 1.0,
32/255.0, 32/255.0, 32/255.0, 1.0,
32/255.0, 32/255.0, 32/255.0, 1.0,
32/255.0, 32/255.0, 32/255.0, 1.0,
};
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:
return &gradient_legacy_red[0];
case XMB_THEME_GRAY_DARK:
return &gradient_gray_dark[0];
case XMB_THEME_GRAY_LIGHT:
return &gradient_gray_light[0];
default:
return &gradient_dark[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:
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
if (menu_list)
return MENU_LIST_GET_STACK_SIZE(menu_list, 0);
}
break;
case MENU_LIST_HORIZONTAL:
return xmb->horizontal_list.size;
case MENU_LIST_TABS:
return xmb->system_tab_end;
default:
break;
}
return 0;
}
static void *xmb_list_get_entry(void *data,
enum menu_list_type type, unsigned i)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
size_t list_size = 0;
switch (type)
{
case MENU_LIST_PLAIN:
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *menu_stack = MENU_LIST_GET_SELECTION(menu_list, 0);
list_size = MENU_LIST_GET_STACK_SIZE(menu_list, 0);
if (i < list_size)
return (void*)&menu_stack->list[i];
}
break;
case MENU_LIST_HORIZONTAL:
list_size = xmb->horizontal_list.size;
if (i < list_size)
return (void*)&xmb->horizontal_list.list[i];
break;
default:
break;
}
return NULL;
}
static void xmb_draw_icon(
void *userdata,
gfx_display_t *p_disp,
gfx_display_ctx_driver_t *dispctx,
unsigned video_width,
unsigned video_height,
bool shadows_enable,
int icon_size_x,
int icon_size_y,
uintptr_t texture,
float x,
float y,
unsigned width,
unsigned height,
float alpha,
float rotation,
float scale_factor,
float *color,
float shadow_offset,
math_matrix_4x4 *mymat)
{
gfx_display_ctx_draw_t draw;
struct video_coords coords;
math_matrix_4x4 mymat_tmp;
/* Skip drawing when the texture hasn't loaded yet
* (async loads start at 0 and are written on completion) */
if (!texture)
return;
if ( (x < (-icon_size_x / 2.0f))
|| (x > width)
|| (y < (icon_size_y / 2.0f))
|| (y > height + icon_size_y)
)
return;
if (!p_disp->dispctx->handles_transform)
{
float radians = rotation * (M_PI / 2.0f);
float cosine = cosf(radians);
float sine = sinf(radians);
gfx_display_rotate_z(p_disp, &mymat_tmp, cosine, sine, userdata);
if (scale_factor != 1.0f)
{
math_matrix_4x4 matrix_scaled = {
{ 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, 1.0f }
};
MAT_ELEM_4X4(matrix_scaled, 0, 0) = scale_factor;
MAT_ELEM_4X4(matrix_scaled, 1, 1) = scale_factor;
MAT_ELEM_4X4(matrix_scaled, 2, 2) = 1.0f;
matrix_4x4_multiply(mymat_tmp, matrix_scaled, mymat_tmp);
}
}
else if (mymat)
mymat_tmp = *mymat;
coords.vertices = 4;
coords.vertex = NULL;
coords.tex_coord = NULL;
coords.lut_tex_coord = NULL;
draw.width = icon_size_x;
draw.height = icon_size_y;
draw.rotation = rotation;
draw.scale_factor = scale_factor;
#if defined(VITA) || defined(WIIU) || defined(__PS3__)
draw.width *= scale_factor;
draw.height *= scale_factor;
#endif
draw.coords = &coords;
draw.matrix_data = &mymat_tmp;
draw.texture = texture;
draw.pipeline_id = 0;
if (shadows_enable)
{
float shadow_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,
};
gfx_display_set_alpha(shadow_color, color[3] * GFX_SHADOW_ALPHA * 0.75f);
coords.color = shadow_color;
draw.x = x + shadow_offset;
draw.y = height - y - shadow_offset;
#if defined(VITA) || defined(WIIU) || defined(__PS3__)
if (scale_factor < 1)
{
draw.x = draw.x + (icon_size_x-draw.width)/2;
draw.y = draw.y + (icon_size_y-draw.width)/2;
}
#endif
if (draw.height > 0 && draw.width > 0)
if (dispctx && dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
coords.color = (const float*)color;
draw.x = x;
draw.y = height - y;
#if defined(VITA) || defined(WIIU) || defined(__PS3__)
if (scale_factor < 1)
{
draw.x = draw.x + (icon_size_x-draw.width)/2;
draw.y = draw.y + (icon_size_y-draw.width)/2;
}
#endif
if (draw.height > 0 && draw.width > 0)
if (dispctx && dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
static void xmb_draw_text(
bool shadows_enable,
xmb_handle_t *xmb,
settings_t *settings,
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;
if (alpha > xmb->alpha)
alpha = xmb->alpha;
a8 = 0xFF * alpha;
/* Avoid drawing 100% transparent text */
if (a8 == 0)
return;
if ( (memcmp(str, "null", 4) == 0 && str[4] == '\0')
|| (memcmp(str, "OFF", 3) == 0 && str[3] == '\0'))
a8 = 0x7F * alpha;
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,
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 && message && *message)
{
/* Free any previously-set box_message before overwriting.
* The render path at line ~9215 consumes box_message
* exactly once (strlcpy to stack msg, then free + NULL),
* but nothing prevents xmb_messagebox from being called
* twice between renders (e.g. rapid error notifications,
* or a second messagebox triggered by a background task
* completion before the menu has repainted). Pre-patch
* the second strdup leaked the first one. */
if (xmb->box_message)
free(xmb->box_message);
xmb->box_message = strdup(message);
}
}
static void xmb_render_messagebox_internal(
void *userdata,
gfx_display_t *p_disp,
gfx_display_ctx_driver_t *dispctx,
unsigned video_width,
unsigned video_height,
xmb_handle_t *xmb,
const char *message,
math_matrix_4x4 *mymat)
{
unsigned i, line_count = 0;
int x, y = 0;
float line_height = 0;
int longest_width = 0;
int usable_width = 0;
unsigned y_position = 0;
bool input_dialog_display_kb = false;
size_t wrapped_len = 0;
char wrapped_message[MENU_LABEL_MAX_LENGTH];
char *lines[512];
char *start, *nl, *end;
if ((usable_width = (int)video_width - (xmb->margins_dialog * 8)) < 1)
return;
usable_width = (usable_width < 300) ? 300 : usable_width;
wrapped_len = (xmb->word_wrap)(
wrapped_message, sizeof(wrapped_message),
message, strlen(message),
usable_width / (xmb->font_size * 0.85f),
xmb->wideglyph_width, 0);
if (wrapped_len == 0)
return;
/* Single-pass: find lines, null-terminate, measure width */
start = wrapped_message;
end = wrapped_message + wrapped_len;
while (start < end && line_count < 512)
{
int width;
nl = (char *)memchr(start, '\n', end - start);
if (nl)
*nl = '\0';
lines[line_count++] = start;
if (*start)
{
width = font_driver_get_message_width(
xmb->font, start, (nl ? nl : end) - start, 1.0f);
if (width > longest_width)
longest_width = width;
if (longest_width > usable_width)
longest_width = usable_width;
}
start = nl ? nl + 1 : end + 1;
}
if (line_count == 0)
return;
input_dialog_display_kb = menu_input_dialog_get_display_kb();
line_height = xmb->font->size * 1.30f;
y_position = (input_dialog_display_kb)
? video_height / 4 : video_height / 2;
x = video_width / 2;
y = y_position - (line_count - 1) * line_height / 2;
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
gfx_display_draw_texture_slice(
p_disp,
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 * line_count) + (xmb->margins_dialog * 2),
video_width, video_height,
NULL,
xmb->margins_slice, xmb->last_scale_factor,
xmb->textures.list[XMB_TEXTURE_DIALOG_SLICE],
mymat);
for (i = 0; i < line_count; i++)
{
const char *msg = lines[i];
if (msg)
gfx_display_draw_text(xmb->font, msg,
x - (longest_width / 2.0),
y + ((i + 0.85) * line_height),
video_width, video_height, 0x444444ff,
TEXT_ALIGN_LEFT, 1.0f, false, 0.0f, false);
}
if (input_dialog_display_kb)
{
input_driver_state_t *input_st = input_state_get_ptr();
gfx_display_draw_keyboard(
p_disp,
userdata,
video_width,
video_height,
xmb->textures.list[XMB_TEXTURE_KEY_HOVER],
xmb->font,
input_st->osk_grid,
input_st->osk_ptr,
0xffffffff);
}
}
#ifdef HAVE_LIBRETRODB
static bool xmb_is_main_menu_explore(void)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, 0, NULL, true);
return entry.type == FILE_TYPE_RDB;
}
#endif
static void xmb_path_dynamic_wallpaper(xmb_handle_t *xmb, char *s, size_t len)
{
settings_t *settings = config_get_ptr();
bool menu_dynamic_wallpaper_enable = settings->bools.menu_dynamic_wallpaper_enable;
unsigned xmb_color_theme = settings->uints.menu_xmb_color_theme;
const char *path_menu_wallpaper = settings->paths.path_menu_wallpaper;
const char *dir_dynamic_wallpapers = settings->paths.directory_dynamic_wallpapers;
unsigned depth = (unsigned)xmb_list_get_size(xmb, MENU_LIST_PLAIN);
/* Dynamic wallpaper takes precedence as reset background,
* then comes 'menu_wallpaper', and then iconset 'bg.png' */
if (menu_dynamic_wallpaper_enable)
{
size_t _len = fill_pathname_join_special(s,
dir_dynamic_wallpapers,
xmb->title_name,
len);
strlcpy(s + _len, ".png", len - _len);
}
if (s && *s && path_is_valid(s))
;/* no-op */
else if (path_menu_wallpaper && *path_menu_wallpaper)
strlcpy(s, path_menu_wallpaper, len);
else if (xmb_color_theme == XMB_THEME_WALLPAPER)
fill_pathname_application_special(s, len,
APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_BG);
/* Keep previous wallpaper in playlists and explore lists */
if ( (xmb->categories_selection_ptr == XMB_SYSTEM_TAB_MAIN && depth > 3)
|| (xmb->categories_selection_ptr > xmb->system_tab_end && depth > 1)
#ifdef HAVE_LIBRETRODB
|| (xmb->categories_selection_ptr == xmb->system_tab_end && menu_explore_is_content_list())
|| (xmb->is_explore_list && !menu_explore_is_content_list() && !xmb_is_main_menu_explore())
#endif
)
{
if ((xmb->bg_file_path && *xmb->bg_file_path) && !path_is_valid(s))
strlcpy(s, xmb->bg_file_path, len);
}
}
static void xmb_update_dynamic_wallpaper(xmb_handle_t *xmb, bool reset)
{
char path[PATH_MAX_LENGTH];
path[0] = '\0';
xmb_path_dynamic_wallpaper(xmb, path, sizeof(path));
if (!string_is_equal(path, xmb->bg_file_path) || reset)
{
if (path_is_valid(path))
{
if (reset)
{
xmb_context_bg_destroy(xmb);
if (!gfx_display_reset_icon_texture(path,
&xmb->textures.bg, TEXTURE_FILTER_LINEAR,
NULL, NULL))
task_push_image_load(path,
(video_driver_get_disp_flags() & VIDEO_FLAG_USE_RGBA), 0,
menu_display_handle_wallpaper_upload, NULL);
gfx_display_init_white_texture();
}
else
task_push_image_load(path,
(video_driver_get_disp_flags() & VIDEO_FLAG_USE_RGBA), 0,
menu_display_handle_wallpaper_upload, NULL);
free(xmb->bg_file_path);
xmb->bg_file_path = strdup(path);
}
else
{
xmb_load_image(xmb, NULL, MENU_IMAGE_NONE);
free(xmb->bg_file_path);
xmb->bg_file_path = NULL;
}
}
}
static void xmb_update_savestate_thumbnail_path(void *data, unsigned i)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
settings_t *settings = config_get_ptr();
bool savestate_thumbnail;
/* Snapshot the current path on the stack before we clear it, so we
* can compare against the new path at the end to decide whether to
* reset the thumbnail cache. Previously this was a heap strdup
* assigned to a const char * that was never freed (leak on every
* selection change in the state-slot menu), performed BEFORE the
* !xmb NULL check below (null-deref if data was ever NULL), and
* used heap allocation for a value that lives in a fixed-size
* char[PATH_MAX_LENGTH] ivar. Same fix pattern as the materialui
* equivalent (93449d3): stack buffer, after the NULL guard. */
char old_path[PATH_MAX_LENGTH];
if (!xmb)
return;
savestate_thumbnail = settings->bools.savestate_thumbnail_enable;
strlcpy(old_path, xmb->savestate_thumbnail_file_path, sizeof(old_path));
if (xmb->skip_thumbnail_reset)
return;
xmb->savestate_thumbnail_file_path[0] = '\0';
/* Savestate thumbnails are only relevant
* when viewing the running quick menu or state slots */
if (!((xmb->is_quick_menu && menu_is_running_quick_menu()) || xmb->is_state_slot))
return;
xmb->fullscreen_thumbnails_available = false;
if (savestate_thumbnail)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_LABEL_ENABLED;
menu_entry_get(&entry, 0, i, NULL, true);
if (*entry.label)
{
unsigned _state_slot = string_to_unsigned(entry.label);
if ( _state_slot == MENU_ENUM_LABEL_STATE_SLOT
|| string_is_equal(entry.label, MENU_ENUM_LABEL_STATE_SLOT_RUN_STR)
|| string_is_equal(entry.label, MENU_ENUM_LABEL_STATE_SLOT_STR)
|| string_is_equal(entry.label, MENU_ENUM_LABEL_LOAD_STATE_STR)
|| string_is_equal(entry.label, MENU_ENUM_LABEL_SAVE_STATE_STR))
{
char path[PATH_MAX_LENGTH];
runloop_state_t *runloop_st = runloop_state_get_ptr();
int state_slot = settings->ints.state_slot;
/* State slot dropdown */
if ( _state_slot == MENU_ENUM_LABEL_STATE_SLOT
|| string_is_equal(entry.label, MENU_ENUM_LABEL_STATE_SLOT_RUN_STR))
{
state_slot = i - 1;
xmb->is_state_slot = true;
}
gfx_savestate_thumbnail_get_path(path, sizeof(path),
runloop_st->name.savestate, state_slot);
strlcpy(xmb->savestate_thumbnail_file_path, path,
sizeof(xmb->savestate_thumbnail_file_path));
if (!string_is_equal(old_path, xmb->savestate_thumbnail_file_path))
gfx_thumbnail_reset(&xmb->thumbnails.savestate);
xmb->fullscreen_thumbnails_available = true;
menu_update_fullscreen_thumbnail_label(
xmb->fullscreen_thumbnail_label,
sizeof(xmb->fullscreen_thumbnail_label),
xmb->is_quick_menu,
NULL);
}
}
}
}
static void xmb_update_thumbnail_image(void *data)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
struct menu_state *menu_st = menu_state_get_ptr();
if (!xmb)
return;
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
gfx_thumbnail_cancel_pending_requests();
if (!xmb->skip_thumbnail_reset)
{
gfx_thumbnail_reset(&xmb->thumbnails.right);
gfx_thumbnail_reset(&xmb->thumbnails.left);
gfx_thumbnail_reset(&xmb->thumbnails.icon);
}
/* imageviewer content requires special treatment... */
if (
*menu_st->thumbnail_path_data->content_core_name
&& string_is_equal(menu_st->thumbnail_path_data->content_core_name,
"imageviewer"))
{
/* Right thumbnail */
if (gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_RIGHT;
/* Left thumbnail */
else if (gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_LEFT;
}
else
{
/* Right thumbnail */
if (gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_RIGHT;
/* Left thumbnail */
if (gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
xmb->thumbnails.pending =
(xmb->thumbnails.pending == XMB_PENDING_THUMBNAIL_RIGHT)
? XMB_PENDING_THUMBNAIL_BOTH : XMB_PENDING_THUMBNAIL_LEFT;
}
if (xmb->want_fullscreen_thumbnails)
menu_update_fullscreen_thumbnail_label(
xmb->fullscreen_thumbnail_label,
sizeof(xmb->fullscreen_thumbnail_label),
xmb->is_quick_menu ? true : false,
xmb->title_name);
}
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 unsigned xmb_get_horizontal_selection_type(xmb_handle_t *xmb)
{
if (xmb->categories_selection_ptr > xmb->system_tab_end)
{
size_t i = xmb->categories_selection_ptr - xmb->system_tab_end - 1;
return (xmb->horizontal_list.size) ? xmb->horizontal_list.list[i].type : 0;
}
return 0;
}
static void xmb_refresh_thumbnail_image(void *data, size_t i)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
struct menu_state *menu_st = menu_state_get_ptr();
if (!xmb)
return;
/* Only refresh thumbnails if thumbnails are enabled */
if ( (xmb->is_quick_menu || xmb->is_playlist || xmb->is_explore_list)
&& ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
)
xmb_update_thumbnail_image(xmb);
}
static void xmb_unload_thumbnail_textures(void *data)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
if (!xmb)
return;
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
gfx_thumbnail_cancel_pending_requests();
gfx_thumbnail_reset(&xmb->thumbnails.right);
gfx_thumbnail_reset(&xmb->thumbnails.left);
gfx_thumbnail_reset(&xmb->thumbnails.icon);
gfx_thumbnail_reset(&xmb->thumbnails.savestate);
}
static void xmb_unload_icon_thumbnail_textures(void *xmb_handle_ptr)
{
xmb_handle_t *xmb = (xmb_handle_t*)xmb_handle_ptr;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t list_size = (unsigned)selection_buf->size;
size_t i;
if (!xmb)
return;
xmb->thumbnails.pending_icons = XMB_PENDING_THUMBNAIL_NONE;
gfx_thumbnail_cancel_pending_requests();
for (i = 0; i < list_size; i++)
{
xmb_node_t *node = (xmb_node_t*)selection_buf->list[i].userdata;
if (node)
{
gfx_thumbnail_reset(&node->thumbnail_icon.icon);
/* Clear resolved path so the lazy resolver in xmb_render sees
* "needs path resolution" on the next pending_icons pass. The
* upshot: we don't eagerly re-resolve paths for every visible
* entry here, we just mark them invalid and let the render
* dispatcher resolve one at a time under the per-frame cap. */
node->thumbnail_icon.thumbnail_path_data.icon_path[0] = '\0';
}
}
}
static void xmb_set_thumbnail_content(void *data, const char *s)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
struct menu_state *menu_st = menu_state_get_ptr();
size_t selection = menu_st->selection_ptr;
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 (!s || !*s)
{
menu_list_t *menu_list = menu_st->entries.list;
size_t list_size = (unsigned)MENU_LIST_GET_SELECTION(menu_list, 0)->size;
file_list_t *list = MENU_LIST_GET_SELECTION(menu_list, 0);
bool playlist_valid = false;
size_t playlist_index = selection;
/* Get playlist index corresponding
* to the selected entry */
if ( list
&& (selection < list_size)
&& (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
{
playlist_valid = true;
playlist_index = list->list[selection].entry_idx;
}
gfx_thumbnail_set_content_playlist(menu_st->thumbnail_path_data,
playlist_valid ? playlist_get_cached() : NULL, playlist_index);
xmb->fullscreen_thumbnails_available = true;
}
}
else if (xmb->is_db_manager_list)
{
/* Database list content */
if (!s || !*s)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_PATH_ENABLED;
menu_entry_get(&entry, 0, selection, NULL, true);
if (*entry.path)
{
gfx_thumbnail_set_content(menu_st->thumbnail_path_data, entry.path);
xmb->fullscreen_thumbnails_available = true;
}
}
}
#if defined(HAVE_LIBRETRODB)
else if (xmb->is_explore_list)
{
/* Explore list */
if (!s || !*s)
{
/* Selected entry */
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_PATH_ENABLED;
menu_entry_get(&entry, 0, selection, NULL, true);
xmb->fullscreen_thumbnails_available =
(menu_explore_set_playlist_thumbnail(entry.type, menu_st->thumbnail_path_data) >= 0);
}
}
#endif
else if (memcmp(s, "imageviewer", sizeof("imageviewer")) == 0)
{
/* Filebrowser image updates */
menu_entry_t entry;
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
xmb_node_t *node = (xmb_node_t*)selection_buf->list[selection].userdata;
if (node)
{
MENU_ENTRY_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_PATH_ENABLED;
menu_entry_get(&entry, 0, selection, NULL, true);
if ( *entry.path
&& (node->fullpath && *node->fullpath)
&& entry.type == FILE_TYPE_IMAGEVIEWER)
{
gfx_thumbnail_set_content_image(menu_st->thumbnail_path_data,
node->fullpath, entry.path);
xmb->fullscreen_thumbnails_available = true;
}
}
}
else if (s && *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(menu_st->thumbnail_path_data, s);
xmb->fullscreen_thumbnails_available = true;
}
}
static bool gfx_thumbnail_set_icon_playlist(
gfx_thumbnail_path_data_t *path_data, playlist_t *playlist, size_t idx)
{
const char *content_path = NULL;
const char *content_label = NULL;
const char *core_name = NULL;
const char *db_name = NULL;
const struct playlist_entry *entry = NULL;
if (!path_data)
return false;
/* When content is updated, must regenerate icon
* thumbnail paths */
path_data->icon_path[0] = '\0';
/* 'Reset' path_data content strings */
path_data->content_path[0] = '\0';
path_data->content_label_len = 0;
path_data->content_label[0] = '\0';
path_data->content_core_name[0] = '\0';
path_data->content_db_name[0] = '\0';
path_data->content_img[0] = '\0';
path_data->content_img_full[0] = '\0';
path_data->content_img_short[0] = '\0';
/* Must also reset playlist thumbnail display modes */
path_data->playlist_icon_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
path_data->playlist_index = 0;
if (!playlist)
return false;
if (idx >= playlist_get_size(playlist))
return false;
/* Read playlist values */
playlist_get_index(playlist, idx, &entry);
if (!entry)
return false;
content_path = entry->path;
content_label = entry->label;
core_name = entry->core_name;
db_name = entry->db_name;
/* Content without a path is invalid by definition */
if (!content_path || !*content_path)
return false;
/* Cache content path
* (This is required for imageviewer, history and favourites content) */
strlcpy(path_data->content_path,
content_path, sizeof(path_data->content_path));
/* Cache core name
* (This is required for imageviewer content) */
if (core_name && *core_name)
strlcpy(path_data->content_core_name,
core_name, sizeof(path_data->content_core_name));
/* Get content label */
if (content_label && *content_label)
path_data->content_label_len = strlcpy(path_data->content_label,
content_label, sizeof(path_data->content_label));
else
path_data->content_label_len = fill_pathname(
path_data->content_label,
path_basename(content_path),
"", sizeof(path_data->content_label));
/* Determine content image name */
{
char tmp_buf[NAME_MAX_LENGTH];
fill_pathname(tmp_buf, path_basename(path_data->content_path), "",
sizeof(tmp_buf));
gfx_thumbnail_fill_content_img(path_data->content_img_full,
sizeof(path_data->content_img_full), tmp_buf, false);
gfx_thumbnail_fill_content_img(path_data->content_img,
sizeof(path_data->content_img), path_data->content_label, false);
/* Explicit zero if full name is same as standard name - saves some queries later. */
if (string_is_equal(path_data->content_img, path_data->content_img_full))
path_data->content_img_full[0] = '\0';
gfx_thumbnail_fill_content_img(path_data->content_img_short,
sizeof(path_data->content_img_short), path_data->content_label, true);
}
/* Store playlist index */
path_data->playlist_index = idx;
/* Redundant error check... */
if (!*path_data->content_img)
return false;
/* Thumbnail image name is done -> now check if
* per-content database name is defined */
if (!db_name || !*db_name)
playlist_get_db_name(playlist, idx, &db_name);
if (db_name && *db_name)
{
/* Hack: There is only one MAME thumbnail repo,
* so filter any input starting with 'MAME...' */
if (strncmp(db_name, "MAME", 4) == 0)
{
path_data->content_db_name[0] = path_data->content_db_name[2] = 'M';
path_data->content_db_name[1] = 'A';
path_data->content_db_name[3] = 'E';
path_data->content_db_name[4] = '\0';
}
else
{
size_t i;
char tmp_buf[NAME_MAX_LENGTH];
const size_t __len = sizeof(tmp_buf) - 1;
for (i = 0; i < __len && db_name[i] && db_name[i] != '|'; i++)
tmp_buf[i] = db_name[i];
tmp_buf[i] = '\0';
fill_pathname(path_data->content_db_name,
tmp_buf, "", sizeof(path_data->content_db_name));
}
}
gfx_thumbnail_update_path(path_data, GFX_THUMBNAIL_ICON);
/* Playlist entry is valid -> it is now 'safe' to
* extract any remaining playlist metadata
* (i.e. thumbnail display modes) */
path_data->playlist_icon_mode = PLAYLIST_THUMBNAIL_MODE_DEFAULT;
return true;
}
static void xmb_set_dynamic_icon_content(
void *xmb_handle_ptr,
const char *s,
size_t selection,
xmb_icons_t *thumbnail_icon)
{
xmb_handle_t *xmb = (xmb_handle_t*)xmb_handle_ptr;
if (!xmb || !xmb->is_playlist)
return;
/* Playlist content */
if (!s || !*s)
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
size_t list_size = (unsigned)MENU_LIST_GET_SELECTION(menu_list, 0)->size;
file_list_t *list = MENU_LIST_GET_SELECTION(menu_list, 0);
bool playlist_valid = false;
size_t playlist_index = selection;
/* Get playlist index corresponding
* to the selected entry */
if ( list
&& (selection < list_size)
&& (list->list[selection].type == FILE_TYPE_RPL_ENTRY))
{
playlist_valid = true;
playlist_index = list->list[selection].entry_idx;
}
gfx_thumbnail_set_icon_playlist(&thumbnail_icon->thumbnail_path_data,
playlist_valid ? playlist_get_cached() : NULL, playlist_index);
}
}
static void xmb_update_savestate_thumbnail_image(void *data)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
unsigned upscale_threshold = config_get_ptr()->uints.gfx_thumbnail_upscale_threshold;
if (!xmb || xmb->skip_thumbnail_reset)
return;
/* If path is empty, just reset thumbnail */
if (!*xmb->savestate_thumbnail_file_path)
gfx_thumbnail_reset(&xmb->thumbnails.savestate);
else if (xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_UNKNOWN)
gfx_thumbnail_request_file(
xmb->savestate_thumbnail_file_path,
&xmb->thumbnails.savestate,
upscale_threshold);
xmb->thumbnails.savestate.flags |= GFX_THUMB_FLAG_CORE_ASPECT;
}
/* Is called when the pointer position changes
* within a list/sub-list (vertically) */
static void xmb_selection_pointer_changed(
xmb_handle_t *xmb, bool allow_animations)
{
unsigned i, end, height;
size_t num = 0;
int threshold = 0;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
uintptr_t tag = (uintptr_t)selection_buf;
size_t selection = menu_st->selection_ptr;
unsigned anim_move_up_down = config_get_ptr()->uints.menu_xmb_animation_move_up_down;
if (anim_move_up_down > 1)
allow_animations = false;
if (!xmb)
return;
end = (unsigned)MENU_LIST_GET_SELECTION(menu_list, 0)->size;
threshold = xmb->icon_size * 10;
menu_st->entries.begin = num;
height = xmb->last_height;
/* On cursor movement within a playlist, invalidate any in-flight
* icon thumbnail requests (they're for the previous cursor position
* and no longer relevant) and raise pending_icons so the render
* dispatcher will lazily resolve paths + load textures for the new
* visible range under its per-frame cap. We deliberately do NOT
* eagerly pre-resolve paths for all visible entries here — the
* selection pointer can move many times per second during held-down
* scrolling, and re-resolving up to ~20 paths per step (including
* path_is_valid syscalls, amplified ×5 by playlist_allow_non_png)
* is enough to blow frame budgets. Lazy resolution in the
* dispatcher gets the same visible result for a fraction of the
* work. The is_playlist / title-name gates match the ones the old
* xmb_refresh_visible_icon_paths helper used internally. */
if ( xmb->is_playlist
&& gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON)
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB))
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB))
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB)))
{
gfx_thumbnail_cancel_pending_requests();
xmb->thumbnails.pending_icons = XMB_PENDING_THUMBNAIL_ICONS;
}
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*)selection_buf->list[i].userdata;
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);
/* Update entry index text */
if (xmb->entry_idx_enabled)
{
size_t entry_idx_selection = selection + 1;
size_t list_size = MENU_LIST_GET_SELECTION(menu_list, 0)->size;
unsigned entry_idx_offset = xmb->entry_index_offset;
bool show_entry_idx = (xmb->is_playlist || xmb->is_explore_list) ? true : false;
if (xmb->is_explore_list)
{
if (entry_idx_selection > entry_idx_offset)
entry_idx_selection -= entry_idx_offset;
else
show_entry_idx = false;
}
if (!show_entry_idx)
xmb->entry_index_str[0] = '\0';
else
snprintf(xmb->entry_index_str, sizeof(xmb->entry_index_str),
"%lu/%lu", (unsigned long)entry_idx_selection,
(unsigned long)list_size);
}
ia = xmb->items_active_alpha;
iz = xmb->items_active_zoom;
if ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
{
bool update_thumbnails = false;
/* Playlist updates */
if (xmb->is_playlist)
{
xmb_set_thumbnail_content(xmb, NULL);
update_thumbnails = true;
}
/* Database + Explore list updates */
else if ((xmb->is_db_manager_list && depth <= 4)
|| xmb->is_explore_list)
{
xmb_set_thumbnail_content(xmb, NULL);
update_thumbnails = true;
xmb->skip_thumbnail_reset = false;
}
else if (xmb->is_db_manager_list && depth == 5)
xmb->skip_thumbnail_reset = true;
/* Filebrowser image updates */
else if (xmb->is_file_list)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, selection, NULL, true);
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) */
xmb->fullscreen_thumbnails_available = false;
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
gfx_thumbnail_set_content(menu_st->thumbnail_path_data, NULL);
gfx_thumbnail_cancel_pending_requests();
gfx_thumbnail_reset(&xmb->thumbnails.right);
gfx_thumbnail_reset(&xmb->thumbnails.left);
gfx_thumbnail_reset(&xmb->thumbnails.icon);
}
}
if (update_thumbnails && end > 1)
xmb_update_thumbnail_image(xmb);
}
if (end > 1)
{
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
{
/* 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 (anim_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_new(xmb_handle_t *xmb,
bool horizontal_animation,
bool savestate_thumbnail,
file_list_t *list, int dir, size_t current)
{
unsigned i;
unsigned height = xmb->last_height;
size_t skip = 0;
int threshold = xmb->icon_size * 10;
size_t end = list ? list->size : 0;
struct menu_state *menu_st = menu_state_get_ptr();
for (i = 0; i < end; i++)
{
float ia;
float real_y;
xmb_node_t *node = (xmb_node_t*)list->list[i].userdata;
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 ( !horizontal_animation
|| real_y < -threshold
|| real_y > height + threshold)
{
node->alpha = node->label_alpha = ia;
node->x = 0;
}
else
{
gfx_animation_ctx_entry_t anim_entry;
node->alpha /= 5;
anim_entry.duration = XMB_DELAY;
anim_entry.target_value = ia;
anim_entry.subject = &node->alpha;
anim_entry.easing_enum = XMB_EASING_ALPHA;
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;
anim_entry.easing_enum = XMB_EASING_XY;
gfx_animation_push(&anim_entry);
}
}
menu_st->entries.begin = skip;
if ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
{
if (xmb->is_playlist || xmb->is_db_manager_list || xmb->is_explore_list)
{
if ( !(xmb->is_db_manager_list && xmb->depth > 3)
&& !xmb->skip_thumbnail_reset)
xmb_unload_thumbnail_textures(xmb);
xmb_set_thumbnail_content(xmb, NULL);
xmb_update_thumbnail_image(xmb);
if (!xmb->is_explore_list)
xmb->skip_thumbnail_reset = false;
}
}
if ( savestate_thumbnail
&& ((xmb->is_quick_menu && xmb->depth >= 2)
|| (xmb->is_state_slot)))
{
/* This shows savestate thumbnail after
* opening savestate submenu */
xmb->skip_thumbnail_reset = false;
xmb_update_savestate_thumbnail_path(xmb, (unsigned)current);
xmb_update_savestate_thumbnail_image(xmb);
}
xmb->old_depth = xmb->depth;
}
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)
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);
xmb->horizontal_list.list[i].userdata = 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;
node->alpha /= 5;
anim_entry.duration = XMB_DELAY;
anim_entry.target_value = ia;
anim_entry.subject = &node->alpha;
anim_entry.easing_enum = XMB_EASING_ALPHA;
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;
anim_entry.easing_enum = XMB_EASING_XY;
gfx_animation_push(&anim_entry);
}
static void xmb_animation_list_alpha(xmb_handle_t *xmb, bool fade_in)
{
struct gfx_animation_ctx_entry entry;
entry.cb = NULL;
entry.duration = XMB_DELAY * 4;
entry.easing_enum = XMB_EASING_ALPHA;
entry.subject = &xmb->alpha_list;
entry.tag = (uintptr_t)NULL;
entry.target_value = (fade_in) ? 1.0f : 0.0f;
entry.userdata = xmb;
gfx_animation_push(&entry);
}
static void xmb_list_switch_new(xmb_handle_t *xmb,
file_list_t *list, int dir, size_t current)
{
unsigned i;
unsigned height = xmb->last_height;
unsigned last = 0;
unsigned first = 0;
size_t end = 0;
bool menu_hor_anim = config_get_ptr()->bools.menu_horizontal_animation;
if (list)
end = list->size;
if (end > 0)
last = (unsigned)(end - 1);
xmb_calculate_visible_range(xmb, height, end, (unsigned)current, &first, &last);
for (i = 0; i < end; i++)
{
xmb_node_t *node = (xmb_node_t*)list->list[i].userdata;
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)
{
if (!menu_hor_anim)
{
node->alpha = node->label_alpha = ia;
node->x = 0;
}
else
xmb_push_animations(node, (uintptr_t)list, ia, 0);
}
else
{
node->x = 0;
node->alpha = node->label_alpha = ia;
}
}
}
static enum msg_hash_enums xmb_search_enum(const char *label)
{
unsigned i;
enum msg_hash_enums enum_idx = MSG_UNKNOWN;
for (i = 0; i < MSG_LAST && enum_idx == MSG_UNKNOWN; i++)
{
if (string_is_equal(label, msg_hash_to_str((enum msg_hash_enums)i)))
enum_idx = (enum msg_hash_enums)i;
}
return enum_idx;
}
static void xmb_set_title(xmb_handle_t *xmb)
{
char *scrub_char_ptr = NULL;
xmb->title_name_alt[0] = '\0';
if ( (xmb->categories_selection_ptr <= xmb->system_tab_end)
|| (xmb->is_quick_menu && !menu_is_running_quick_menu())
|| (xmb->depth > 1))
{
menu_entries_get_title(xmb->title_name, sizeof(xmb->title_name));
strlcpy(xmb->title_name_alt, xmb->title_name, sizeof(xmb->title_name_alt));
}
else
{
xmb_node_t *node = (xmb_node_t*)file_list_get_userdata_at_offset(
&xmb->horizontal_list,
xmb->categories_selection_ptr - (xmb->system_tab_end + 1));
const char *path = xmb->horizontal_list.list[
xmb->categories_selection_ptr - (xmb->system_tab_end + 1)].path;
if (!path)
return;
/* Use real title for dynamic backgrounds */
fill_pathname(xmb->title_name, path_basename(path), "", sizeof(xmb->title_name));
/* Set alternative title for visible header */
if (node && node->console_name)
{
strlcpy(xmb->title_name_alt,
node->console_name,
sizeof(xmb->title_name_alt));
/* Add current search terms to alternative title */
menu_entries_search_append_terms_string(
xmb->title_name_alt, sizeof(xmb->title_name_alt));
}
}
while ((scrub_char_ptr = strchr(xmb->title_name, '/')))
*scrub_char_ptr = '-';
while ((scrub_char_ptr = strchr(xmb->title_name, ':')))
*scrub_char_ptr = '_';
if (*xmb->title_name)
{
int sub = string_find_index_substring_string(xmb->title_name, " (");
if (sub > 0)
xmb->title_name[sub] = '\0';
}
if (config_get_ptr()->uints.menu_xmb_current_menu_icon)
{
char label_temp[NAME_MAX_LENGTH];
unsigned type = FILE_TYPE_NONE;
size_t entry_idx = 0;
enum msg_hash_enums enum_idx = MSG_UNKNOWN;
const char *path = NULL;
const char *label = NULL;
const char *label_original = NULL;
uintptr_t texture = xmb->textures.list[XMB_TEXTURE_QUICKMENU];
bool search = true;
/* Preserve and decrement any in-flight retry countdown from
* xmb_render(). Fresh fallback (prev_retry == 0) (re)arms to
* ~1s @ 60fps; ongoing retry counts down one frame at a time
* so a permanently-missing asset can't spin forever. Cleared
* unconditionally first — any fallback branch below will set
* the next value. */
uint8_t prev_retry = xmb->current_menu_icon_retry;
xmb->current_menu_icon_retry = 0;
menu_entries_get_last_stack(&path, &label, &type, &enum_idx, &entry_idx);
label_original = label;
/* Direct exceptions */
if (string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_CORE_SYSTEM_FILES_LIST_STR))
enum_idx = MENU_ENUM_LABEL_DOWNLOAD_CORE_SYSTEM_FILES;
else if (string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_CORE_CONTENT_DIRS_LIST_STR))
enum_idx = MENU_ENUM_LABEL_DOWNLOAD_CORE_CONTENT_DIRS;
else if (string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_CORE_LIST_SET_STR))
enum_idx = MENU_ENUM_LABEL_CORE_LIST;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU)))
enum_idx = MENU_ENUM_LABEL_MAIN_MENU;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_SETTINGS_TAB)))
enum_idx = MENU_ENUM_LABEL_SETTINGS_TAB;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HISTORY_TAB)))
enum_idx = MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY;
else if (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_FAVORITES_TAB)))
enum_idx = MENU_ENUM_LABEL_GOTO_FAVORITES;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_IMAGES_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_IMAGES_TAB)))
enum_idx = MENU_ENUM_LABEL_GOTO_IMAGES;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_MUSIC_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_MUSIC_TAB)))
enum_idx = MENU_ENUM_LABEL_GOTO_MUSIC;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_VIDEO_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_VIDEO_TAB)))
enum_idx = MENU_ENUM_LABEL_GOTO_VIDEO;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_ADD_TAB)))
enum_idx = MENU_ENUM_LABEL_ADD_CONTENT_LIST;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)))
enum_idx = MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST)))
enum_idx = MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB)))
enum_idx = MENU_ENUM_LABEL_EXPLORE_TAB;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)))
enum_idx = MENU_ENUM_LABEL_HORIZONTAL_MENU;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS)))
enum_idx = MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_USER_BINDS_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_REMAPPINGS_PORT_LIST))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DEVICE_TYPE)))
enum_idx = MENU_ENUM_LABEL_INPUT_DEVICE_INDEX;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_SELECT_RESERVED_DEVICE)))
enum_idx = MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME;
else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION))
|| string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION_KBD)))
{
unsigned input_id;
if (type < MENU_SETTINGS_INPUT_DESC_KBD_BEGIN)
input_id = MENU_SETTINGS_INPUT_DESC_BEGIN;
else
input_id = MENU_SETTINGS_INPUT_DESC_KBD_BEGIN;
while (type > (input_id + (RARCH_ANALOG_BIND_LIST_END - 1)))
input_id = (input_id + RARCH_ANALOG_BIND_LIST_END);
type = type - input_id;
enum_idx = MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION;
}
else if (string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_RETROPAD_BIND_STR))
enum_idx = (enum msg_hash_enums)atoi(path);
else if (string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_STR))
{
enum_idx = (enum msg_hash_enums)atoi(path);
if ( enum_idx >= MENU_ENUM_LABEL_INPUT_DEVICE_INDEX
&& enum_idx <= MENU_ENUM_LABEL_INPUT_DEVICE_INDEX_LAST)
enum_idx = MENU_ENUM_LABEL_INPUT_DEVICE_INDEX;
else if (enum_idx >= MENU_ENUM_LABEL_INPUT_MOUSE_INDEX
&& enum_idx <= MENU_ENUM_LABEL_INPUT_MOUSE_INDEX_LAST)
enum_idx = MENU_ENUM_LABEL_INPUT_MOUSE_INDEX;
else if (enum_idx >= MENU_ENUM_LABEL_INPUT_PLAYER_ANALOG_DPAD_MODE
&& enum_idx <= MENU_ENUM_LABEL_INPUT_PLAYER_ANALOG_DPAD_MODE_LAST)
enum_idx = MENU_ENUM_LABEL_INPUT_PLAYER_ANALOG_DPAD_MODE;
else if (enum_idx >= MENU_ENUM_LABEL_INPUT_REMAP_PORT
&& enum_idx <= MENU_ENUM_LABEL_INPUT_REMAP_PORT_LAST)
enum_idx = MENU_ENUM_LABEL_INPUT_REMAP_PORT;
else if (enum_idx >= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE
&& enum_idx <= MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE_LAST)
enum_idx = MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE;
}
if (type == MENU_SETTING_ACTION_CORE_OPTIONS)
path = xmb->title_name;
else if (string_ends_with(path, FILE_PATH_RDB_EXTENSION))
type = FILE_TYPE_RDB_ENTRY;
if (type == FILE_TYPE_RDB_ENTRY || enum_idx != MSG_UNKNOWN)
search = false;
if (search)
{
/* Due to inconsistent standards in 'msg_hash_lbl.h'
* and in order to avoid adding each and every
* deferred enum variant in 'xmb_icon_get_id()'
* search must be done in separate segments first
* removing prefix and then suffix if warranted. */
if (string_starts_with(label, "deferred_"))
{
strlcpy(label_temp, label + STRLEN_CONST("deferred_"), sizeof(label_temp));
label = label_temp;
}
if (enum_idx == MSG_UNKNOWN)
enum_idx = xmb_search_enum(label);
if (enum_idx == MSG_UNKNOWN)
{
if (string_ends_with(label, "_list"))
{
label_temp[strlen(label_temp) - STRLEN_CONST("_list")] = '\0';
label = label_temp;
enum_idx = xmb_search_enum(label);
}
else if (string_starts_with(label, "dropdown_box_list_"))
{
label = label_original;
enum_idx = xmb_search_enum(label);
}
}
}
switch (enum_idx)
{
default:
break;
case MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS:
case MENU_ENUM_LABEL_HORIZONTAL_MENU:
{
const struct playlist_entry *pl_entry = NULL;
xmb_node_t *db_node = NULL;
/* Horizontal menu */
if (xmb->categories_selection_ptr > xmb->system_tab_end)
{
xmb_node_t *sidebar_node = NULL;
size_t i = xmb->categories_selection_ptr - xmb->system_tab_end - 1;
/* Explore views */
if (string_ends_with_size(xmb->horizontal_list.list[i].label, ".lvw",
strlen(xmb->horizontal_list.list[i].label), STRLEN_CONST(".lvw")))
{
texture = xmb->textures.list[XMB_TEXTURE_CURSOR];
goto end;
}
/* Playlists */
sidebar_node = (xmb_node_t*)file_list_get_userdata_at_offset(&xmb->horizontal_list, i);
if (sidebar_node && sidebar_node->icon)
texture = sidebar_node->icon;
else if (sidebar_node)
/* Async load still in flight (e.g. right after a
* fullscreen toggle triggered xmb_context_reset) —
* ask xmb_render() to retry. Fresh trigger arms to
* 60 frames (~1s); ongoing retry decrements one
* step so a missing asset terminates the loop. */
xmb->current_menu_icon_retry = prev_retry ? prev_retry - 1 : 60;
}
/* Playlists entries */
entry_idx = menu_state_get_ptr()->thumbnail_path_data->playlist_index;
playlist_get_index(playlist_get_cached(), entry_idx, &pl_entry);
if ( pl_entry
&& (pl_entry->db_name && *pl_entry->db_name)
&& (db_node = RHMAP_GET_STR(xmb->playlist_db_node_map, pl_entry->db_name)))
{
/* Sidebar / content icons load asynchronously via
* xmb_context_reset_horizontal_list(). A context reset
* (e.g. fullscreen toggle) will zero these handles and
* re-queue the loads; xmb_set_title() runs before the
* tasks complete. Fall back to the existing `texture`
* (XMB_TEXTURE_QUICKMENU by default, or sidebar_node->icon
* if that happened to be set above) rather than assigning
* a 0 handle — which would blank the icon until the next
* navigation refresh. Flag pending so xmb_render() retries. */
uintptr_t db_icon = (enum_idx == MENU_ENUM_LABEL_HORIZONTAL_MENU)
? db_node->icon : db_node->content_icon;
if (db_icon)
texture = db_icon;
else
xmb->current_menu_icon_retry = prev_retry ? prev_retry - 1 : 60;
}
else
{
const playlist_config_t *pl_config = playlist_get_config(playlist_get_cached());
if (string_ends_with(pl_config->path, FILE_PATH_CONTENT_IMAGE_HISTORY))
texture = xmb->textures.list[XMB_TEXTURE_IMAGE];
else if (string_ends_with(pl_config->path, FILE_PATH_CONTENT_MUSIC_HISTORY))
texture = xmb->textures.list[XMB_TEXTURE_MUSIC];
else if (string_ends_with(pl_config->path, FILE_PATH_CONTENT_VIDEO_HISTORY))
texture = xmb->textures.list[XMB_TEXTURE_MOVIE];
else if (xmb->categories_selection_ptr < xmb->system_tab_end)
texture = xmb->textures.list[XMB_TEXTURE_FILE];
}
goto end;
break;
}
#ifdef HAVE_LIBRETRODB
case MENU_ENUM_LABEL_EXPLORE_TAB:
if (xmb_is_main_menu_explore() || !menu_explore_is_content_list())
enum_idx = MENU_ENUM_LABEL_GOTO_EXPLORE;
else
enum_idx = MENU_ENUM_LABEL_EXPLORE_ITEM;
break;
case MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST:
if (!string_is_equal(path, MENU_ENUM_LABEL_GOTO_EXPLORE_STR))
enum_idx = MENU_ENUM_LABEL_EXPLORE_ITEM;
else
enum_idx = MENU_ENUM_LABEL_GOTO_EXPLORE;
break;
#endif
}
texture = xmb_icon_get_id(xmb, NULL, NULL, enum_idx, path, label, type, 0, false);
end:
xmb->current_menu_icon = texture;
}
}
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;
#if defined(HAVE_LIBRETRODB)
case XMB_SYSTEM_TAB_EXPLORE:
return &xmb->explore_tab_node;
#endif
case XMB_SYSTEM_TAB_CONTENTLESS_CORES:
return &xmb->contentless_cores_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,
bool horizontal_animation,
unsigned animation_horizontal_highlight)
{
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 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;
}
if ( !xmb->allow_horizontal_animation
|| !horizontal_animation)
{
node->alpha = ia;
node->zoom = iz;
continue;
}
/* Horizontal icon highlight 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 (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_tab_set_selection(void *data)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
if (xmb)
{
size_t tab_selection = xmb->tab_selection[xmb->categories_selection_ptr];
if (tab_selection)
{
struct menu_state *menu_st = menu_state_get_ptr();
menu_st->selection_ptr = tab_selection;
xmb_selection_pointer_changed(xmb, false);
}
}
}
static void xmb_populate_dynamic_icons(xmb_handle_t *xmb)
{
struct menu_state *menu_st = menu_state_get_ptr();
if (!gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
return;
/* Used when the displayed list changes (new playlist pushed or
* context reset): wipe the per-node icon textures and path_data so
* the render dispatcher will lazily re-resolve and re-load them for
* currently-visible entries, under its per-frame cap. Doing the
* path resolution upfront for every visible entry here would cost
* one path_is_valid syscall per entry (or five per entry with
* playlist_allow_non_png enabled, due to the .png/.jpg/.jpeg/.bmp/
* .tga fallback chain in gfx_thumbnail_update_path) — enough to
* blow a frame budget on the populate frame. */
xmb_unload_icon_thumbnail_textures(xmb);
if ( xmb->is_playlist
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB))
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB))
&& !string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB)))
xmb->thumbnails.pending_icons = XMB_PENDING_THUMBNAIL_ICONS;
}
static void xmb_list_switch(xmb_handle_t *xmb)
{
gfx_animation_ctx_entry_t anim_entry;
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t selection = 0;
int dir = -1;
unsigned remember_selection_type = settings->uints.menu_remember_selection;
unsigned xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr);
bool xmb_main_tab_selected = false;
bool horizontal_animation = settings->bools.menu_horizontal_animation;
int list_delta = 0;
unsigned animation_horizontal_highlight
= settings->uints.menu_xmb_animation_horizontal_highlight;
if (xmb->categories_selection_ptr > xmb->categories_selection_ptr_old)
dir = 1;
list_delta = (int)xmb->categories_selection_ptr
- (int)xmb->categories_selection_ptr_old;
xmb->categories_active_idx += dir;
if ( (xmb_system_tab == XMB_SYSTEM_TAB_MAIN)
|| (xmb_system_tab == XMB_SYSTEM_TAB_SETTINGS))
xmb_main_tab_selected = true;
/* Restore last selection per tab */
if ( (remember_selection_type == MENU_REMEMBER_SELECTION_ALWAYS)
|| ((remember_selection_type == MENU_REMEMBER_SELECTION_PLAYLISTS) && (xmb->is_playlist))
|| ((remember_selection_type == MENU_REMEMBER_SELECTION_MAIN) && (xmb_main_tab_selected)))
xmb_tab_set_selection(xmb);
/* Selection needs to be taken after tab restore */
selection = menu_st->selection_ptr;
xmb_list_switch_horizontal_list(xmb, horizontal_animation,
animation_horizontal_highlight);
/* Horizontal tab icon scroll */
if (horizontal_animation)
{
uintptr_t cat_tag = (uintptr_t)&xmb->categories_x_pos;
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 = XMB_EASING_XY;
anim_entry.tag = cat_tag;
anim_entry.cb = NULL;
/* Kill any in-flight tween on categories_x_pos before pushing a
* new one. gfx_animation_push appends rather than replaces, so
* rapid left/right input would otherwise stack two or more tweens
* that fight each frame over the same float (last-write-wins in
* iteration order) and the visible settled position would depend
* on which one happens to be appended last. Tagging the tween
* and killing prior ones makes back-to-back transitions atomic
* and gives a reliable "settled" predicate for callers that want
* to know when the horizontal scroll is done. */
gfx_animation_kill_by_tag(&cat_tag);
if (anim_entry.subject)
gfx_animation_push(&anim_entry);
}
else
{
/* Direct-set path: also kill any in-flight tween so it doesn't
* clobber the assignment on the next animation tick. This matters
* when menu_horizontal_animation was toggled off while a previous
* tween was still running. */
uintptr_t cat_tag = (uintptr_t)&xmb->categories_x_pos;
gfx_animation_kill_by_tag(&cat_tag);
xmb->categories_x_pos = xmb->icon_spacing_horizontal * -(float)xmb->categories_selection_ptr;
}
/* Check if we are to have horizontal animations. */
if (horizontal_animation)
xmb_list_switch_new(xmb, selection_buf, list_delta, selection);
xmb->categories_active_idx_old = (unsigned)xmb->categories_selection_ptr;
if ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
{
xmb_unload_thumbnail_textures(xmb);
if (xmb->is_playlist || xmb->is_explore_list)
{
xmb_set_thumbnail_content(xmb, NULL);
xmb_update_thumbnail_image(xmb);
}
}
}
static void xmb_list_open_horizontal_list(xmb_handle_t *xmb, bool animate)
{
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;
if (animate)
{
anim_entry.duration = XMB_DELAY;
anim_entry.target_value = ia;
anim_entry.subject = &node->alpha;
anim_entry.easing_enum = XMB_EASING_ALPHA;
/* 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);
}
else
node->alpha = ia;
}
}
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;
if (!(path = xmb->horizontal_list.list[i].path))
continue;
if (string_ends_with_size(path, ".lpl", strlen(path), STRLEN_CONST(".lpl")))
{
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 truncate_playlist_name = settings->bools.ozone_truncate_playlist_name;
bool sort_after_truncate = settings->bools.ozone_sort_after_truncate_playlist_name;
menu_displaylist_info_init(&info);
info.list = &xmb->horizontal_list;
info.path = strdup(dir_playlist);
info.label = strdup(MENU_ENUM_LABEL_PLAYLISTS_TAB_STR);
info.exts = strldup("lpl", sizeof("lpl"));
info.type_default = FILE_TYPE_PLAIN;
info.enum_idx = MENU_ENUM_LABEL_PLAYLISTS_TAB;
if (info.path && *info.path)
{
size_t i;
if (menu_displaylist_ctl(DISPLAYLIST_DATABASE_PLAYLISTS_HORIZONTAL, &info, settings))
{
for (i = 0; i < xmb->horizontal_list.size; i++)
xmb_node_allocate_userdata(xmb, (unsigned)i);
menu_displaylist_process(&info);
}
/* Loop through list and set console names */
for (i = 0; i < xmb->horizontal_list.size; i++)
{
char playlist_file_noext[NAME_MAX_LENGTH];
char *console_name = NULL;
const char *playlist_file = xmb->horizontal_list.list[i].path;
if (!playlist_file)
{
file_list_set_alt_at_offset(&xmb->horizontal_list, i, NULL);
continue;
}
/* Remove extension */
fill_pathname(playlist_file_noext, path_basename(playlist_file), "",
sizeof(playlist_file_noext));
console_name = playlist_file_noext;
/* Truncate playlist names, if required
* > Format: "Vendor - Console"
Remove everything before the hyphen
and the subsequent space */
if (truncate_playlist_name)
{
bool hyphen_found = false;
for (;;)
{
/* Check for "- " */
if (*console_name == '\0')
break;
else if (*console_name == '-' && *(console_name + 1) == ' ')
{
hyphen_found = true;
break;
}
console_name++;
}
if (hyphen_found)
console_name += 2;
else
console_name = playlist_file_noext;
}
/* Assign console name to list */
file_list_set_alt_at_offset(&xmb->horizontal_list, i, console_name);
}
/* If playlist names were truncated and option is
* enabled, re-sort list by console name */
if ( truncate_playlist_name
&& sort_after_truncate
&& xmb->horizontal_list.size > 0)
file_list_sort_on_alt(&xmb->horizontal_list);
}
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;
}
}
/* File-static generation counters for async icon loads.
* Two counters: one for horizontal list playlist icons,
* one for context_reset static textures. Separate because
* horizontal list rebuilds must not invalidate context textures. */
static uint64_t xmb_icon_load_gen = 0;
static uint64_t xmb_ctx_icon_load_gen = 0;
static void xmb_context_reset_horizontal_list(xmb_handle_t *xmb)
{
unsigned i;
char iconpath[PATH_MAX_LENGTH];
char icons_path_default[PATH_MAX_LENGTH];
bool supports_rgba = (video_driver_get_disp_flags() & VIDEO_FLAG_USE_RGBA);
int depth = 1;
size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL);
uintptr_t tag = (uintptr_t)&xmb->x;
uintptr_t cat_tag = (uintptr_t)&xmb->categories_x_pos;
gfx_animation_kill_by_tag(&tag);
/* Also kill tweens on categories_x_pos: a pending tween would
* otherwise clobber the direct assignment below on the next
* animation tick. */
gfx_animation_kill_by_tag(&cat_tag);
xmb->categories_x_pos = xmb->icon_spacing_horizontal * -(float)xmb->categories_selection_ptr;
if (xmb->depth > 1)
depth++;
xmb->x = xmb->icon_size * (xmb->use_ps3_layout ? 1.1f : 0.7f) * -(depth * 2 - 2);
RHMAP_FREE(xmb->playlist_db_node_map);
/* Invalidate any in-flight async icon loads */
xmb_icon_load_gen++;
fill_pathname_application_special(iconpath, sizeof(iconpath),
APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_ICONS);
fill_pathname_join_special(icons_path_default, iconpath,
"default", sizeof(icons_path_default));
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)
if (!(node = xmb_node_allocate_userdata(xmb, i)))
continue;
if (!(path = xmb->horizontal_list.list[i].path))
continue;
if (string_ends_with_size(path, ".lpl", strlen(path), STRLEN_CONST(".lpl")))
{
size_t __len, syslen;
char sysname[NAME_MAX_LENGTH];
char texturepath[PATH_MAX_LENGTH];
const char *console_name = NULL;
RHMAP_SET_STR(xmb->playlist_db_node_map, path, node);
syslen = fill_pathname(sysname, path_basename(path), "",
sizeof(sysname));
__len = fill_pathname_join_special(texturepath, iconpath, sysname,
sizeof(texturepath));
strlcpy(texturepath + __len, ".png", sizeof(texturepath) - __len);
if (!path_is_valid(texturepath))
{
__len = fill_pathname_join_special(texturepath, iconpath, "default",
sizeof(texturepath));
strlcpy(texturepath + __len, ".png", sizeof(texturepath) - __len);
}
gfx_display_load_icon(texturepath, supports_rgba,
&node->icon, xmb_icon_load_gen,
&xmb_icon_load_gen);
strlcpy(sysname + syslen, "-content.png", sizeof(sysname) - syslen);
fill_pathname_join_special(texturepath, iconpath, sysname,
sizeof(texturepath));
if (!path_is_valid(texturepath))
fill_pathname_join_delim(texturepath, icons_path_default,
FILE_PATH_CONTENT_BASENAME, '-', sizeof(texturepath));
gfx_display_load_icon(texturepath, supports_rgba,
&node->content_icon, xmb_icon_load_gen,
&xmb_icon_load_gen);
console_name = xmb->horizontal_list.list[i].alt
? xmb->horizontal_list.list[i].alt
: xmb->horizontal_list.list[i].path;
if (node->console_name)
free(node->console_name);
if (console_name)
node->console_name = strdup(console_name);
else
node->console_name = strdup(path);
}
else if (string_ends_with_size(xmb->horizontal_list.list[i].label, ".lvw",
strlen(xmb->horizontal_list.list[i].label), STRLEN_CONST(".lvw")))
{
/* Free any previously-set console_name before
* overwriting; matches the .lpl branch above.
* xmb_context_reset_horizontal_list is called on every
* theme change / refresh, so without this free the
* console_name from the previous reset leaks. */
if (node->console_name)
free(node->console_name);
node->console_name = strdup(path + strlen(msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_VIEW)) + 2);
node->icon = xmb->textures.list[XMB_TEXTURE_CURSOR];
}
}
xmb_toggle_horizontal_list(xmb);
}
static void xmb_refresh_horizontal_list(xmb_handle_t *xmb)
{
struct menu_state *menu_st = menu_state_get_ptr();
xmb_context_destroy_horizontal_list(xmb);
/* Free the db_node_map BEFORE the nodes it borrows from.
* The map's values are non-owning pointers into the
* horizontal_list's userdata slots, so once those slots are
* free()d the map's stored pointers are dangling. Reverse
* the order to keep the dangling-pointer window closed. */
RHMAP_FREE(xmb->playlist_db_node_map);
xmb_free_list_nodes(&xmb->horizontal_list, false);
file_list_deinitialize(&xmb->horizontal_list);
menu_st->flags |= MENU_ST_FLAG_PREVENT_POPULATE;
xmb_init_horizontal_list(xmb);
xmb_context_reset_horizontal_list(xmb);
}
static void xmb_refresh_system_tabs_list(xmb_handle_t *xmb)
{
settings_t *settings = config_get_ptr();
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_first)
{
if (settings->bools.menu_content_show_favorites)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_FAVORITES;
if (settings->bools.menu_content_show_history && settings->bools.history_list_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_HISTORY;
}
else
{
if (settings->bools.menu_content_show_history && settings->bools.history_list_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_HISTORY;
if (settings->bools.menu_content_show_favorites)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_FAVORITES;
}
#ifdef HAVE_IMAGEVIEWER
if (settings->bools.menu_content_show_images && settings->bools.history_list_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_IMAGES;
#endif
if (settings->bools.menu_content_show_music && settings->bools.history_list_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_MUSIC;
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
if (settings->bools.menu_content_show_video && settings->bools.history_list_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_VIDEO;
#endif
if ( settings->uints.menu_content_show_add_entry == MENU_ADD_CONTENT_ENTRY_DISPLAY_PLAYLISTS_TAB
&& !settings->bools.kiosk_mode_enable)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_ADD;
#if defined(HAVE_DYNAMIC)
if ( settings->uints.menu_content_show_contentless_cores
!= MENU_CONTENTLESS_CORES_DISPLAY_NONE)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_CONTENTLESS_CORES;
#endif
#if defined(HAVE_LIBRETRODB)
if (settings->bools.menu_content_show_explore)
xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_EXPLORE;
#endif
}
static int xmb_environ(enum menu_environ_cb type, void *data, void *userdata)
{
xmb_handle_t *xmb = (xmb_handle_t*)userdata;
if (!xmb)
return -1;
switch (type)
{
case MENU_ENVIRON_ENABLE_MOUSE_CURSOR:
xmb->show_mouse = true;
break;
case MENU_ENVIRON_DISABLE_MOUSE_CURSOR:
xmb->show_mouse = false;
break;
case MENU_ENVIRON_RESET_HORIZONTAL_LIST:
xmb_refresh_system_tabs_list(xmb);
xmb_refresh_horizontal_list(xmb);
break;
case MENU_ENVIRON_ENABLE_SCREENSAVER:
xmb->show_screensaver = true;
break;
case MENU_ENVIRON_DISABLE_SCREENSAVER:
xmb->show_screensaver = false;
break;
default:
return -1;
}
return 0;
}
static void xmb_list_open(xmb_handle_t *xmb,
unsigned animation_opening_main_menu,
bool horizontal_animation,
bool st_thumbnail_enable)
{
gfx_animation_ctx_entry_t entry;
uintptr_t tag;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t selection = menu_st->selection_ptr;
int dir = 0;
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, horizontal_animation);
xmb_list_open_new(xmb, horizontal_animation, st_thumbnail_enable,
selection_buf, dir, selection);
/* Main Menu opening animation */
/* Align icon size with left thumbnail area */
entry.target_value = xmb->icon_size * (xmb->use_ps3_layout ? 1.1f : 0.7f) * -(xmb->depth * 2 - 2);
entry.subject = &xmb->x;
tag = (uintptr_t)&xmb->x;
entry.tag = tag;
entry.cb = NULL;
switch (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;
}
if (xmb->depth <= 2)
{
if (horizontal_animation)
gfx_animation_push(&entry);
else
xmb->x = entry.target_value;
}
xmb->old_depth = xmb->depth;
}
/* Is called whenever the list/sub-list changes */
static void xmb_populate_entries(void *data,
const char *path, const char *label, unsigned k)
{
unsigned xmb_system_tab, xmb_horizontal_type;
xmb_handle_t *xmb = (xmb_handle_t*)data;
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
bool playlist_show_entry_idx = settings->bools.playlist_show_entry_idx;
bool was_db_manager_list = false;
int depth = (unsigned)xmb_list_get_size(xmb, MENU_LIST_PLAIN);
if (!xmb)
return;
xmb->show_playlist_tabs = settings->bools.menu_content_show_playlist_tabs;
xmb->skip_thumbnail_reset = false;
if (xmb->is_quick_menu && depth < xmb->old_depth)
xmb->skip_thumbnail_reset = true;
xmb_system_tab = xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr);
xmb_horizontal_type = ((xmb_system_tab == UINT_MAX)
? xmb_get_horizontal_selection_type(xmb) : 0);
/* Determine whether this is a playlist */
xmb->is_playlist =
( depth == 1
&& ( (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
|| (xmb_system_tab == XMB_SYSTEM_TAB_MUSIC)
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
|| (xmb_system_tab == XMB_SYSTEM_TAB_VIDEO)
#endif
)
)
|| string_is_equal(label, MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_PLAYLIST_LIST_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_FAVORITES_LIST_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_IMAGES_LIST_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_MUSIC_LIST_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_VIDEO_LIST_STR);
xmb->is_playlist |=
(xmb_horizontal_type == FILE_TYPE_PLAYLIST_COLLECTION)
&& ( (xmb_system_tab > XMB_SYSTEM_TAB_SETTINGS && depth == 1)
|| (xmb_system_tab < XMB_SYSTEM_TAB_SETTINGS && depth == 4));
xmb->is_playlist =
xmb->is_playlist
&& !string_is_equal(label, MENU_ENUM_LABEL_INFORMATION_STR)
&& !string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_RDB_ENTRY_DETAIL_STR);
xmb->is_playlist_tab = !xmb->is_playlist && string_is_equal(label,
MENU_ENUM_LABEL_PLAYLISTS_TAB_STR);
xmb->is_playlist_information =
string_is_equal(label, MENU_ENUM_LABEL_INFORMATION_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_RDB_ENTRY_DETAIL_STR);
/* Determine whether this is a database manager list */
was_db_manager_list = xmb->is_db_manager_list && depth >= 4;
xmb->is_db_manager_list = string_is_equal(label,
MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST_STR);
if (was_db_manager_list)
{
xmb->is_db_manager_list = true;
xmb->skip_thumbnail_reset = true;
}
/* Determine whether this is the contentless cores menu */
xmb->is_contentless_cores =
string_is_equal(label,
MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB_STR)
|| string_is_equal(label,
MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST_STR);
/* 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, MENU_ENUM_LABEL_FAVORITES_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_MENU_WALLPAPER_STR);
/* Determine whether this is the quick menu */
xmb->is_quick_menu =
string_is_equal(label,
MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS_STR)
|| string_is_equal(label,
MENU_ENUM_LABEL_CONTENT_SETTINGS_STR)
|| string_is_equal(label,
MENU_ENUM_LABEL_SAVESTATE_LIST_STR);
xmb->is_state_slot =
string_to_unsigned(path) == MENU_ENUM_LABEL_STATE_SLOT
|| string_is_equal(label, MENU_ENUM_LABEL_STATE_SLOT_RUN_STR);
#if defined(HAVE_LIBRETRODB)
/* Explore list */
xmb->is_explore_list =
string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST_STR)
|| string_is_equal(label, MENU_ENUM_LABEL_EXPLORE_TAB_STR)
|| xmb_horizontal_type == MENU_EXPLORE_TAB;
if (xmb->is_explore_list)
{
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_LABEL_ENABLED;
menu_entry_get(&entry, 0, 0, NULL, true);
/* Quick Menu under Explore list must also be Quick Menu */
if ( string_is_equal(entry.label, MENU_ENUM_LABEL_RUN_STR)
|| string_is_equal(entry.label, MENU_ENUM_LABEL_RESUME_CONTENT_STR)
|| string_is_equal(entry.label, MENU_ENUM_LABEL_STATE_SLOT_STR)
)
{
xmb->is_quick_menu = true;
xmb->is_explore_list = false;
}
else if (!xmb->is_quick_menu && depth < xmb->old_depth)
xmb->skip_thumbnail_reset = true;
/* 'is_playlist' must be cleared for 'xmb_set_thumbnail_content' */
if (xmb->is_explore_list)
xmb->is_playlist = false;
}
#endif
if ((menu_st->flags & MENU_ST_FLAG_PREVENT_POPULATE) > 0)
{
xmb_selection_pointer_changed(xmb, false);
menu_st->flags &= ~MENU_ST_FLAG_PREVENT_POPULATE;
/* Refresh title for search terms */
xmb_set_title(xmb);
return;
}
if (xmb->categories_selection_ptr != xmb->categories_active_idx_old)
xmb_list_switch(xmb);
else
xmb_list_open(xmb,
settings->uints.menu_xmb_animation_opening_main_menu,
settings->bools.menu_horizontal_animation,
settings->bools.savestate_thumbnail_enable);
xmb_set_title(xmb);
if (xmb->is_playlist)
{
if (settings->uints.menu_icon_thumbnails)
{
/* Defer the expensive dynamic-icon unload + path_data refresh
* when the user is currently mashing or holding a navigation
* button (menu_st->scroll.acceleration > 0). Running it for
* every intermediate tab during rapid left/right traversal
* is pure waste — the work gets clobbered by the next tab's
* populate ~16 ms later before any of it becomes visible.
* xmb_render fires the deferred call on the next frame where
* acceleration has dropped to 0 (i.e. input has settled on
* whichever tab the user actually stopped on). */
if (menu_st->scroll.acceleration > 0)
xmb->pending_dynamic_icons_repopulate = true;
else
xmb_populate_dynamic_icons(xmb);
}
}
else if (xmb->thumbnails.pending_icons != XMB_PENDING_THUMBNAIL_NONE)
xmb_unload_icon_thumbnail_textures(xmb);
if (xmb->allow_dynamic_wallpaper)
xmb_update_dynamic_wallpaper(xmb, false);
/* Determine whether to show entry index */
xmb->entry_index_str[0] = '\0';
xmb->entry_idx_enabled = playlist_show_entry_idx;
if ( !xmb->is_quick_menu
&& (xmb->is_playlist || xmb->is_explore_list))
{
size_t entry_idx_selection = menu_st->selection_ptr + 1;
size_t list_size = MENU_LIST_GET_SELECTION(menu_list, 0)->size;
unsigned entry_idx_offset = 0;
playlist_show_entry_idx = (xmb->is_playlist || xmb->is_explore_list)
? playlist_show_entry_idx : false;
if (xmb->is_explore_list)
{
entry_idx_offset = 2;
if ( string_is_equal(label, MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST_STR)
|| xmb_horizontal_type == MENU_EXPLORE_TAB)
entry_idx_offset = 1;
if (entry_idx_selection > entry_idx_offset)
entry_idx_selection -= entry_idx_offset;
else
playlist_show_entry_idx = false;
if (list_size >= entry_idx_offset)
list_size -= entry_idx_offset;
}
xmb->list_size = list_size;
xmb->entry_index_offset = entry_idx_offset;
if (!playlist_show_entry_idx)
xmb->entry_index_str[0] = '\0';
else
snprintf(xmb->entry_index_str, sizeof(xmb->entry_index_str),
"%lu/%lu", (unsigned long)entry_idx_selection,
(unsigned long)list_size);
}
/* Cache previous state to prevent thumbnail flashing */
xmb->thumbnails_left_status_prev = xmb->thumbnails.left.status;
xmb->thumbnails_right_status_prev = xmb->thumbnails.right.status;
/* 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_system_tab > XMB_SYSTEM_TAB_SETTINGS && xmb->depth > 2);
if ( (xmb->is_quick_menu || xmb->is_state_slot)
&& *xmb->savestate_thumbnail_file_path)
{
xmb->fullscreen_thumbnails_available = true;
}
else if (xmb->is_quick_menu)
xmb->fullscreen_thumbnails_available = !menu_is_running_quick_menu();
else if (xmb->is_explore_list)
{
xmb_set_thumbnail_content(xmb, "");
xmb_update_thumbnail_image(xmb);
}
/* 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... */
else if (xmb->is_file_list)
{
xmb->fullscreen_thumbnails_available = false;
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
gfx_thumbnail_set_content(menu_st->thumbnail_path_data, NULL);
gfx_thumbnail_cancel_pending_requests();
gfx_thumbnail_reset(&xmb->thumbnails.right);
gfx_thumbnail_reset(&xmb->thumbnails.left);
gfx_thumbnail_reset(&xmb->thumbnails.icon);
xmb_set_thumbnail_content(xmb, "imageviewer");
xmb_update_thumbnail_image(xmb);
}
}
#define TEXTURE_RETROPAD(id) \
switch (id) \
{ \
case RETRO_DEVICE_ID_JOYPAD_UP: \
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_U]; \
case RETRO_DEVICE_ID_JOYPAD_DOWN: \
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_D]; \
case RETRO_DEVICE_ID_JOYPAD_LEFT: \
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_L]; \
case RETRO_DEVICE_ID_JOYPAD_RIGHT: \
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_R]; \
case RETRO_DEVICE_ID_JOYPAD_B: \
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_D]; \
case RETRO_DEVICE_ID_JOYPAD_A: \
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_R]; \
case RETRO_DEVICE_ID_JOYPAD_Y: \
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_L]; \
case RETRO_DEVICE_ID_JOYPAD_X: \
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_U]; \
case RETRO_DEVICE_ID_JOYPAD_SELECT: \
return xmb->textures.list[XMB_TEXTURE_INPUT_SELECT]; \
case RETRO_DEVICE_ID_JOYPAD_START: \
return xmb->textures.list[XMB_TEXTURE_INPUT_START]; \
case RETRO_DEVICE_ID_JOYPAD_L: \
return xmb->textures.list[XMB_TEXTURE_INPUT_LB]; \
case RETRO_DEVICE_ID_JOYPAD_R: \
return xmb->textures.list[XMB_TEXTURE_INPUT_RB]; \
case RETRO_DEVICE_ID_JOYPAD_L2: \
return xmb->textures.list[XMB_TEXTURE_INPUT_LT]; \
case RETRO_DEVICE_ID_JOYPAD_R2: \
return xmb->textures.list[XMB_TEXTURE_INPUT_RT]; \
case RETRO_DEVICE_ID_JOYPAD_L3: \
case RETRO_DEVICE_ID_JOYPAD_R3: \
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_P]; \
case 19: /* Left Analog Up */ \
case 23: /* Right Analog Up */ \
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_U]; \
case 18: /* Left Analog Down */ \
case 22: /* Right Analog Down */ \
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_D]; \
case 17: /* Left Analog Left */ \
case 21: /* Right Analog Left */ \
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_L]; \
case 16: /* Left Analog Right */ \
case 20: /* Right Analog Right */ \
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_R]; \
default: \
break; \
} \
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, const char *enum_path,
const char *enum_label, unsigned type, bool active,
bool checked)
{
switch (enum_idx)
{
case MENU_ENUM_LABEL_MAIN_MENU:
return xmb->textures.list[XMB_TEXTURE_MAIN_MENU];
case MENU_ENUM_LABEL_SETTINGS_TAB:
return xmb->textures.list[XMB_TEXTURE_SETTINGS];
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_OPTIONS:
return xmb->textures.list[XMB_TEXTURE_CORE_OPTIONS];
case MENU_ENUM_LABEL_CORE_OPTION_OVERRIDE_LIST:
case MENU_ENUM_LABEL_REMAP_FILE_MANAGER_LIST:
return xmb->textures.list[XMB_TEXTURE_SETTING];
case MENU_ENUM_LABEL_ADD_TO_FAVORITES:
case MENU_ENUM_LABEL_ADD_TO_FAVORITES_PLAYLIST:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_ADD_TO_FAVORITES:
return xmb->textures.list[XMB_TEXTURE_ADD_FAVORITE];
case MENU_ENUM_LABEL_ADD_TO_PLAYLIST:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_ADD_TO_PLAYLIST:
return xmb->textures.list[XMB_TEXTURE_PLAYLIST];
case MENU_ENUM_LABEL_CREATE_NEW_PLAYLIST:
case MENU_ENUM_LABEL_SIDELOAD_CORE_LIST:
return xmb->textures.list[XMB_TEXTURE_ADD];
case MENU_ENUM_LABEL_PARENT_DIRECTORY:
case MENU_ENUM_LABEL_UNDO_LOAD_STATE:
case MENU_ENUM_LABEL_UNDO_SAVE_STATE:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_UNDO_SAVE_LOAD_STATE:
case MENU_ENUM_LABEL_RESET_CORE_ASSOCIATION:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_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_DEFERRED_DROPDOWN_BOX_LIST_DISK_INDEX:
case MENU_ENUM_LABEL_DISK_INDEX:
return xmb->textures.list[XMB_TEXTURE_DISK_OPTIONS];
case MENU_ENUM_LABEL_DISK_TRAY_EJECT:
case MENU_ENUM_LABEL_DISK_TRAY_INSERT:
return xmb->textures.list[XMB_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_DISK_IMAGE_APPEND:
return xmb->textures.list[XMB_TEXTURE_MENU_ADD];
case MENU_ENUM_LABEL_SHADER_OPTIONS:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SHADERS:
return xmb->textures.list[XMB_TEXTURE_SHADER_OPTIONS];
case MENU_ENUM_LABEL_ACHIEVEMENT_LIST:
return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENT_LIST];
case MENU_ENUM_LABEL_SAVESTATE_LIST:
case MENU_ENUM_LABEL_SAVE_STATE:
case MENU_ENUM_LABEL_CORE_CREATE_BACKUP:
case MENU_ENUM_LABEL_GAME_SPECIFIC_CORE_OPTIONS_CREATE:
case MENU_ENUM_LABEL_FOLDER_SPECIFIC_CORE_OPTIONS_CREATE:
case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CORE:
case MENU_ENUM_LABEL_REMAP_FILE_SAVE_CONTENT_DIR:
case MENU_ENUM_LABEL_REMAP_FILE_SAVE_GAME:
case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CORE:
case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR:
case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG_OVERRIDE_GAME:
case MENU_ENUM_LABEL_NETWORK_ON_DEMAND_THUMBNAILS:
return xmb->textures.list[XMB_TEXTURE_SAVESTATE];
case MENU_ENUM_LABEL_LOAD_STATE:
case MENU_ENUM_LABEL_STATE_SLOT_RUN:
case MENU_ENUM_LABEL_CONFIGURATIONS:
case MENU_ENUM_LABEL_GAME_SPECIFIC_OPTIONS:
case MENU_ENUM_LABEL_REMAP_FILE_LOAD:
case MENU_ENUM_LABEL_OVERRIDE_FILE_LOAD:
case MENU_ENUM_LABEL_AUTO_OVERRIDES_ENABLE:
case MENU_ENUM_LABEL_AUTO_REMAPS_ENABLE:
case MENU_ENUM_LABEL_INITIAL_DISK_CHANGE_ENABLE:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_PREPEND:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_APPEND:
case MENU_ENUM_LABEL_CHEAT_FILE_LOAD:
case MENU_ENUM_LABEL_CHEAT_FILE_LOAD_APPEND:
case MENU_ENUM_LABEL_CORE_RESTORE_BACKUP_LIST:
return xmb->textures.list[XMB_TEXTURE_LOADSTATE];
case MENU_ENUM_LABEL_TAKE_SCREENSHOT:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_TAKE_SCREENSHOT:
return xmb->textures.list[XMB_TEXTURE_SCREENSHOT];
case MENU_ENUM_LABEL_CORE_LIST_UNLOAD:
case MENU_ENUM_LABEL_DELETE_ENTRY:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_CLOSE_CONTENT:
return xmb->textures.list[XMB_TEXTURE_CLOSE];
case MENU_ENUM_LABEL_RESTART_CONTENT:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_RESTART_CONTENT:
case MENU_ENUM_LABEL_REBOOT:
case MENU_ENUM_LABEL_CHEAT_COPY_AFTER:
case MENU_ENUM_LABEL_CHEAT_COPY_BEFORE:
case MENU_ENUM_LABEL_CHEAT_RELOAD_CHEATS:
case MENU_ENUM_LABEL_RESTART_RETROARCH:
case MENU_ENUM_LABEL_CLOUD_SYNC_SYNC_NOW:
case MENU_ENUM_LABEL_FRAME_TIME_COUNTER_SETTINGS:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_CLEAN_PLAYLIST:
case MENU_ENUM_LABEL_PLAYLIST_MANAGER_REFRESH_PLAYLIST:
case MENU_ENUM_LABEL_CLOUD_SYNC_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_RELOAD];
case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE:
/* Only show icon in Throttle settings */
if (xmb->depth < 3)
return xmb->textures.list[XMB_TEXTURE_RELOAD];
break;
case MENU_ENUM_LABEL_RENAME_ENTRY:
return xmb->textures.list[XMB_TEXTURE_RENAME];
case MENU_ENUM_LABEL_RESUME_CONTENT:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_RESUME_CONTENT:
return xmb->textures.list[XMB_TEXTURE_RESUME];
case MENU_ENUM_LABEL_DIRECTORY_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_DIRECTORY:
case MENU_ENUM_LABEL_SCAN_DIRECTORY:
case MENU_ENUM_LABEL_MANUAL_CONTENT_SCAN_LIST:
case MENU_ENUM_LABEL_FAVORITES: /* "Start Directory" */
case MENU_ENUM_LABEL_DOWNLOADED_FILE_DETECT_CORE_LIST:
case MENU_ENUM_LABEL_DOWNLOAD_CORE_CONTENT_DIRS:
return xmb->textures.list[XMB_TEXTURE_FOLDER];
case MENU_ENUM_LABEL_ADD_CONTENT_LIST:
if (xmb->depth > 1 || string_is_equal(enum_label, MENU_ENUM_LABEL_ADD_TAB_STR))
return xmb->textures.list[XMB_TEXTURE_ADD];
return xmb->textures.list[XMB_TEXTURE_MENU_ADD];
case MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR:
case MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB:
return xmb->textures.list[XMB_TEXTURE_RDB];
/* Menu collection submenus */
case MENU_ENUM_LABEL_PLAYLISTS_TAB:
return xmb->textures.list[XMB_TEXTURE_PLAYLIST];
case MENU_ENUM_LABEL_LOAD_CONTENT_HISTORY:
return xmb->textures.list[XMB_TEXTURE_HISTORY];
case MENU_ENUM_LABEL_GOTO_FAVORITES:
return xmb->textures.list[XMB_TEXTURE_FAVORITES];
#ifdef HAVE_IMAGEVIEWER
case MENU_ENUM_LABEL_GOTO_IMAGES:
return xmb->textures.list[XMB_TEXTURE_IMAGES];
#endif
case MENU_ENUM_LABEL_GOTO_MUSIC:
return xmb->textures.list[XMB_TEXTURE_MUSICS];
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
case MENU_ENUM_LABEL_GOTO_VIDEO:
return xmb->textures.list[XMB_TEXTURE_MOVIES];
#endif
case MENU_ENUM_LABEL_GOTO_EXPLORE:
if (xmb->depth > 1 && !string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_EXPLORE)))
return xmb->textures.list[XMB_TEXTURE_CURSOR];
return xmb->textures.list[XMB_TEXTURE_RDB];
case MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES:
return xmb->textures.list[XMB_TEXTURE_CORE];
case MENU_ENUM_LABEL_LOAD_DISC:
case MENU_ENUM_LABEL_DUMP_DISC:
#ifdef HAVE_LAKKA
case MENU_ENUM_LABEL_EJECT_DISC:
#endif
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:
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_CORE_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_CORE:
case MENU_ENUM_LABEL_CORE_UPDATER_LIST:
case MENU_ENUM_LABEL_CORE_MANAGER_LIST:
case MENU_ENUM_LABEL_CORE_MANAGER_ENTRY:
case MENU_ENUM_LABEL_UPDATE_INSTALLED_CORES:
case MENU_ENUM_LABEL_SWITCH_INSTALLED_CORES_PFD:
case MENU_ENUM_LABEL_SET_CORE_ASSOCIATION:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SET_CORE_ASSOCIATION:
case MENU_ENUM_LABEL_CORE_INFORMATION:
return xmb->textures.list[XMB_TEXTURE_CORE];
case MENU_ENUM_LABEL_CORE_INFO_ENTRY:
if (strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MISSING_REQUIRED)))
return xmb->textures.list[XMB_TEXTURE_CLOSE];
else if (strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MISSING_OPTIONAL)))
return xmb->textures.list[XMB_TEXTURE_INFO];
else if (strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PRESENT_REQUIRED))
|| strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PRESENT_OPTIONAL)))
return xmb->textures.list[XMB_TEXTURE_CHECKMARK];
break;
case MENU_ENUM_LABEL_LOAD_CONTENT_LIST:
case MENU_ENUM_LABEL_SUBSYSTEM_SETTINGS:
case MENU_ENUM_LABEL_SCAN_FILE:
case MENU_ENUM_LABEL_DOWNLOAD_CORE_SYSTEM_FILES:
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:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_CHEATS:
return xmb->textures.list[XMB_TEXTURE_CHEAT_OPTIONS];
case MENU_ENUM_LABEL_PL_THUMBNAILS_UPDATER_LIST:
case MENU_ENUM_LABEL_DOWNLOAD_PL_ENTRY_THUMBNAILS:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_DOWNLOAD_THUMBNAILS:
return xmb->textures.list[XMB_TEXTURE_IMAGE];
case MENU_ENUM_LABEL_UPDATE_OVERLAYS:
case MENU_ENUM_LABEL_ONSCREEN_OVERLAY_SETTINGS:
case MENU_ENUM_LABEL_CONTENT_SHOW_OVERLAYS:
return xmb->textures.list[XMB_TEXTURE_OVERLAY];
case MENU_ENUM_LABEL_OVERLAY_LIGHTGUN_SETTINGS:
case MENU_ENUM_LABEL_OVERLAY_MOUSE_SETTINGS:
case MENU_ENUM_LABEL_OSK_OVERLAY_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_SETTING];
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:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_INFORMATION:
return xmb->textures.list[XMB_TEXTURE_INFO];
case MENU_ENUM_LABEL_UPDATE_DATABASES:
case MENU_ENUM_LABEL_DATABASE_MANAGER:
case MENU_ENUM_LABEL_DATABASE_MANAGER_LIST:
case MENU_ENUM_LABEL_RDB_ENTRY_DETAIL:
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:
return xmb->textures.list[XMB_TEXTURE_HELP];
case MENU_ENUM_LABEL_QUIT_RETROARCH:
return xmb->textures.list[XMB_TEXTURE_EXIT];
/* Settings icons */
case MENU_ENUM_LABEL_DRIVER_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_DRIVERS:
return xmb->textures.list[XMB_TEXTURE_DRIVERS];
case MENU_ENUM_LABEL_VIDEO_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_VIDEO:
return xmb->textures.list[XMB_TEXTURE_VIDEO];
case MENU_ENUM_LABEL_AUDIO_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_AUDIO:
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_CONFIGURATION_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_CONFIGURATION:
case MENU_ENUM_LABEL_SETTINGS_SHOW_FILE_BROWSER:
return xmb->textures.list[XMB_TEXTURE_SETTING];
case MENU_ENUM_LABEL_INPUT_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_INPUT:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_CONTROLS:
case MENU_ENUM_LABEL_UPDATE_AUTOCONFIG_PROFILES:
case MENU_ENUM_LABEL_INPUT_LIBRETRO_DEVICE:
case MENU_ENUM_LABEL_INPUT_DEVICE_INDEX:
case MENU_ENUM_LABEL_INPUT_RETROPAD_BINDS:
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_INPUT_MOUSE_INDEX:
return xmb->textures.list[XMB_TEXTURE_INPUT_MOUSE];
case MENU_ENUM_LABEL_INPUT_PLAYER_ANALOG_DPAD_MODE:
return xmb->textures.list[XMB_TEXTURE_INPUT_ADC];
case MENU_ENUM_LABEL_INPUT_TURBO_FIRE_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_INPUT_TURBO];
case MENU_ENUM_LABEL_INPUT_TURBO_BIND:
case MENU_ENUM_LABEL_INPUT_TURBO_BUTTON:
{
settings_t *settings = config_get_ptr();
int turbo_bind = settings->ints.input_turbo_bind;
if (enum_idx == MENU_ENUM_LABEL_INPUT_TURBO_BUTTON)
turbo_bind = settings->uints.input_turbo_button;
TEXTURE_RETROPAD(turbo_bind)
break;
}
case MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_DESCRIPTION:
case MENU_ENUM_LABEL_DEFERRED_DROPDOWN_BOX_LIST_INPUT_RETROPAD_BIND:
TEXTURE_RETROPAD(type)
break;
case MENU_ENUM_LABEL_LATENCY_SETTINGS:
case MENU_ENUM_LABEL_CONTENT_SHOW_LATENCY:
case MENU_ENUM_LABEL_SETTINGS_SHOW_LATENCY:
case MENU_ENUM_LABEL_MENU_THROTTLE_FRAMERATE:
return xmb->textures.list[XMB_TEXTURE_LATENCY];
case MENU_ENUM_LABEL_SAVING_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_SAVING:
case MENU_ENUM_LABEL_SAVE_CURRENT_CONFIG:
case MENU_ENUM_LABEL_SAVE_NEW_CONFIG:
case MENU_ENUM_LABEL_SAVE_AS_CONFIG:
case MENU_ENUM_LABEL_SAVE_MAIN_CONFIG:
case MENU_ENUM_LABEL_CONFIG_SAVE_ON_EXIT:
case MENU_ENUM_LABEL_REMAP_SAVE_ON_EXIT:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CURRENT:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_AS:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_GLOBAL:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_CORE:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_PARENT:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_SAVE_GAME:
case MENU_ENUM_LABEL_CHEAT_FILE_SAVE_AS:
case MENU_ENUM_LABEL_OVERRIDE_FILE_SAVE_AS:
case MENU_ENUM_LABEL_REMAP_FILE_SAVE_AS:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVESTATE_SUBMENU:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_LOAD_STATE:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_REPLAY:
return xmb->textures.list[XMB_TEXTURE_SAVING];
case MENU_ENUM_LABEL_LOGGING_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_LOGGING:
return xmb->textures.list[XMB_TEXTURE_LOG];
case MENU_ENUM_LABEL_FASTFORWARD_RATIO:
case MENU_ENUM_LABEL_FRAME_THROTTLE_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_FRAME_THROTTLE:
case MENU_ENUM_LABEL_FASTFORWARD_FRAMESKIP:
return xmb->textures.list[XMB_TEXTURE_FRAMESKIP];
case MENU_ENUM_LABEL_QUICK_MENU_START_RECORDING:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_START_RECORDING:
case MENU_ENUM_LABEL_RECORDING_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_RECORDING:
return xmb->textures.list[XMB_TEXTURE_RECORD];
case MENU_ENUM_LABEL_QUICK_MENU_START_STREAMING:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_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:
case MENU_ENUM_LABEL_CHEAT_DELETE_ALL:
case MENU_ENUM_LABEL_CHEAT_DELETE_MATCH:
case MENU_ENUM_LABEL_CORE_DELETE:
case MENU_ENUM_LABEL_DELETE_PLAYLIST:
case MENU_ENUM_LABEL_CORE_DELETE_BACKUP_LIST:
case MENU_ENUM_LABEL_VIDEO_FILTER_REMOVE:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_REMOVE_GLOBAL:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_REMOVE_CORE:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_REMOVE_PARENT:
case MENU_ENUM_LABEL_VIDEO_SHADER_PRESET_REMOVE_GAME:
case MENU_ENUM_LABEL_AUDIO_DSP_PLUGIN_REMOVE:
case MENU_ENUM_LABEL_GAME_SPECIFIC_CORE_OPTIONS_REMOVE:
case MENU_ENUM_LABEL_FOLDER_SPECIFIC_CORE_OPTIONS_REMOVE:
case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CORE:
case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_CONTENT_DIR:
case MENU_ENUM_LABEL_REMAP_FILE_REMOVE_GAME:
case MENU_ENUM_LABEL_REMOVE_CURRENT_CONFIG_OVERRIDE_CORE:
case MENU_ENUM_LABEL_REMOVE_CURRENT_CONFIG_OVERRIDE_CONTENT_DIR:
case MENU_ENUM_LABEL_REMOVE_CURRENT_CONFIG_OVERRIDE_GAME:
return xmb->textures.list[XMB_TEXTURE_CLOSE];
case MENU_ENUM_LABEL_RESET_TO_DEFAULT_CONFIG:
case MENU_ENUM_LABEL_CORE_OPTIONS_RESET:
case MENU_ENUM_LABEL_REMAP_FILE_RESET:
case MENU_ENUM_LABEL_OVERRIDE_UNLOAD:
case MENU_ENUM_LABEL_MENU_DISABLE_KIOSK_MODE:
case MENU_ENUM_LABEL_XMB_MAIN_MENU_ENABLE_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_UNDO];
case MENU_ENUM_LABEL_CORE_OPTIONS_FLUSH:
case MENU_ENUM_LABEL_REMAP_FILE_FLUSH:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_CORE_OPTIONS_FLUSH:
return xmb->textures.list[XMB_TEXTURE_FILE];
case MENU_ENUM_LABEL_CORE_LOCK:
case MENU_ENUM_LABEL_CORE_SET_STANDALONE_EXEMPT:
return xmb->textures.list[XMB_TEXTURE_CORE];
case MENU_ENUM_LABEL_ONSCREEN_DISPLAY_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_ONSCREEN_DISPLAY:
return xmb->textures.list[XMB_TEXTURE_OSD];
case MENU_ENUM_LABEL_SHOW_WIMP:
case MENU_ENUM_LABEL_USER_INTERFACE_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_USER_INTERFACE:
return xmb->textures.list[XMB_TEXTURE_UI];
#if 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:
case MENU_ENUM_LABEL_SETTINGS_SHOW_POWER_MANAGEMENT:
return xmb->textures.list[XMB_TEXTURE_POWER];
case MENU_ENUM_LABEL_RETRO_ACHIEVEMENTS_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_ACHIEVEMENTS:
return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENTS];
case MENU_ENUM_LABEL_PLAYLIST_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_PLAYLISTS:
return xmb->textures.list[XMB_TEXTURE_PLAYLIST];
case MENU_ENUM_LABEL_USER_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_USER:
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:
case MENU_ENUM_LABEL_CONTENT_SHOW_REWIND:
return xmb->textures.list[XMB_TEXTURE_REWIND];
case MENU_ENUM_LABEL_INPUT_REMAP_PORT:
case MENU_ENUM_LABEL_INPUT_DEVICE_RESERVATION_TYPE:
case MENU_ENUM_LABEL_INPUT_DEVICE_RESERVED_DEVICE_NAME:
case MENU_ENUM_LABEL_QUICK_MENU_OVERRIDE_OPTIONS:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CORE_OVERRIDES:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_CONTENT_DIR_OVERRIDES:
case MENU_ENUM_LABEL_QUICK_MENU_SHOW_SAVE_GAME_OVERRIDES:
return xmb->textures.list[XMB_TEXTURE_OVERRIDE];
case MENU_ENUM_LABEL_ONSCREEN_NOTIFICATIONS_SETTINGS:
case MENU_ENUM_LABEL_CHEEVOS_APPEARANCE_SETTINGS:
return xmb->textures.list[XMB_TEXTURE_NOTIFICATIONS];
#ifdef HAVE_NETWORKING
case MENU_ENUM_LABEL_NETPLAY:
return xmb->textures.list[XMB_TEXTURE_NETPLAY];
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];
#ifdef HAVE_NETPLAYDISCOVERY
case MENU_ENUM_LABEL_NETPLAY_REFRESH_LAN:
return xmb->textures.list[XMB_TEXTURE_RELOAD];
#endif
case MENU_ENUM_LABEL_NETWORK_INFORMATION:
case MENU_ENUM_LABEL_NETWORK_SETTINGS:
case MENU_ENUM_LABEL_SETTINGS_SHOW_NETWORK:
case MENU_ENUM_LABEL_WIFI_SETTINGS:
case MENU_ENUM_LABEL_NETWORK_INFO_ENTRY:
case MENU_ENUM_LABEL_NETWORK_HOSTING_SETTINGS:
case MENU_ENUM_LABEL_NETPLAY_LOBBY_FILTERS:
case MENU_ENUM_LABEL_NETPLAY_KICK:
case MENU_ENUM_LABEL_NETPLAY_BAN:
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_COPY_MATCH:
case MENU_ENUM_LABEL_CHEAT_ADD_MATCHES:
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:
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];
#ifdef HAVE_LIBRETRODB
case MENU_ENUM_LABEL_EXPLORE_ITEM:
{
uintptr_t icon = menu_explore_get_entry_icon(type);
if (icon)
return icon;
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_SAVE_VIEW)))
return xmb->textures.list[XMB_TEXTURE_SAVING];
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_DELETE_VIEW)))
return xmb->textures.list[XMB_TEXTURE_CLOSE];
else if (type != FILE_TYPE_RDB)
return xmb->textures.list[XMB_TEXTURE_CURSOR];
break;
}
#endif
case MENU_ENUM_LABEL_CONTENTLESS_CORE:
{
uintptr_t icon = menu_contentless_cores_get_entry_icon(enum_label);
if (icon)
return icon;
break;
}
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:
case FILE_TYPE_PLAYLIST_COLLECTION:
if (core_node)
return core_node->content_icon;
switch (xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr))
{
case XMB_SYSTEM_TAB_MAIN:
{
const struct playlist_entry *pl_entry = NULL;
xmb_node_t *db_node = NULL;
playlist_get_index(playlist_get_cached(), 0, &pl_entry);
if ( pl_entry
&& (pl_entry->db_name && *pl_entry->db_name)
&& (db_node = RHMAP_GET_STR(xmb->playlist_db_node_map, pl_entry->db_name)))
{
switch (type)
{
case FILE_TYPE_RPL_ENTRY:
return db_node->content_icon;
case FILE_TYPE_PLAYLIST_COLLECTION:
return db_node->icon;
default:
break;
}
}
else
{
if (string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB)))
return xmb->textures.list[XMB_TEXTURE_IMAGE];
else if (string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB)))
return xmb->textures.list[XMB_TEXTURE_MUSIC];
else if (string_is_equal(xmb->title_name, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB)))
return xmb->textures.list[XMB_TEXTURE_MOVIE];
}
}
break;
#ifdef HAVE_IMAGEVIEWER
case XMB_SYSTEM_TAB_IMAGES:
return xmb->textures.list[XMB_TEXTURE_IMAGE];
#endif
case XMB_SYSTEM_TAB_MUSIC:
return xmb->textures.list[XMB_TEXTURE_MUSIC];
#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_IMAGE:
case FILE_TYPE_IMAGEVIEWER:
return xmb->textures.list[XMB_TEXTURE_IMAGE];
case FILE_TYPE_MUSIC:
return xmb->textures.list[XMB_TEXTURE_MUSIC];
case FILE_TYPE_MOVIE:
return xmb->textures.list[XMB_TEXTURE_MOVIE];
case FILE_TYPE_CORE:
case FILE_TYPE_DOWNLOAD_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_RDB_ENTRY:
return xmb->textures.list[XMB_TEXTURE_FILE];
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 MENU_SETTING_ACTION_PLAYREPLAY:
return xmb->textures.list[XMB_TEXTURE_PLAYREPLAY];
case MENU_SETTING_ACTION_RECORDREPLAY:
return xmb->textures.list[XMB_TEXTURE_RECORDREPLAY];
case MENU_SETTING_ACTION_HALTREPLAY:
return xmb->textures.list[XMB_TEXTURE_HALTREPLAY];
case MENU_SETTING_ACTION_CORE_OPTIONS:
if (string_starts_with(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_VIDEO];
else if (string_starts_with(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_AUDIO_SETTINGS))
|| string_starts_with(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SOUND_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_AUDIO];
else if (string_starts_with(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_INPUT_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS];
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ONSCREEN_DISPLAY_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_OSD];
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_LATENCY_SETTINGS))
|| string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_TIMING_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_LATENCY];
else if (string_starts_with(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PERFORMANCE_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_FRAMESKIP];
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MEDIA_SETTINGS))
|| string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_STORAGE_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_RDB];
else if (string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SYSTEM_SETTINGS))
|| string_is_equal(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SPECS_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_DRIVERS];
else if (strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_HACKS_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_POWER];
else if (strstr(enum_path, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MAPPING_SETTINGS)))
return xmb->textures.list[XMB_TEXTURE_OVERRIDE];
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];
case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE:
return xmb->textures.list[XMB_TEXTURE_NETWORK];
case MENU_SET_SCREEN_BRIGHTNESS:
return xmb->textures.list[XMB_TEXTURE_BRIGHTNESS];
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
case MENU_SETTINGS_INPUT_LIBRETRO_DEVICE:
return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS];
case MENU_SETTINGS_INPUT_ANALOG_DPAD_MODE:
return xmb->textures.list[XMB_TEXTURE_INPUT_ADC];
case MENU_SETTINGS_INPUT_INPUT_REMAP_PORT:
return xmb->textures.list[XMB_TEXTURE_OVERRIDE];
}
#ifdef HAVE_CHEEVOS
if ( type >= MENU_SETTINGS_CHEEVOS_START
&& type < MENU_SETTINGS_NETPLAY_ROOMS_START)
{
char buffer[64];
int index = type - MENU_SETTINGS_CHEEVOS_START;
uintptr_t badge_texture = rcheevos_menu_get_badge_texture(index);
if (badge_texture)
return badge_texture;
/* No state means it's a header, show info icon */
if (!rcheevos_menu_get_state(index, buffer, sizeof(buffer)))
return xmb->textures.list[XMB_TEXTURE_INFO];
/* Placeholder badge image was not found, show generic menu icon */
return xmb->textures.list[XMB_TEXTURE_ACHIEVEMENTS];
}
#endif
if ( type >= MENU_SETTINGS_INPUT_BEGIN
&& type <= MENU_SETTINGS_INPUT_DESC_KBD_END)
{
size_t enum_label_len = strlen(enum_label);
/* This part is only utilized by Input User # Binds */
if (type < MENU_SETTINGS_INPUT_DESC_BEGIN)
{
if ( string_ends_with_size(enum_label, "_joypad_index", enum_label_len, STRLEN_CONST("_joypad_index")))
return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS];
else if (string_ends_with_size(enum_label, "_mouse_index", enum_label_len, STRLEN_CONST("_mouse_index")))
return xmb->textures.list[XMB_TEXTURE_INPUT_MOUSE];
else if (string_ends_with_size(enum_label, "_analog_dpad_mode", enum_label_len, STRLEN_CONST("_analog_dpad_mode")))
return xmb->textures.list[XMB_TEXTURE_INPUT_ADC];
else if (string_ends_with_size(enum_label, "_bind_all", enum_label_len, STRLEN_CONST("_bind_all")))
return xmb->textures.list[XMB_TEXTURE_INPUT_BIND_ALL];
else if (string_ends_with_size(enum_label, "_bind_defaults", enum_label_len, STRLEN_CONST("_bind_defaults")))
return xmb->textures.list[XMB_TEXTURE_RELOAD];
else if (string_ends_with_size(enum_label, "_save_autoconfig", enum_label_len, STRLEN_CONST("_save_autoconfig")))
return xmb->textures.list[XMB_TEXTURE_SAVING];
else if (string_ends_with_size(enum_label, "_turbo", enum_label_len, STRLEN_CONST("_turbo"))
|| string_ends_with_size(enum_label, "_hold", enum_label_len, STRLEN_CONST("_hold")))
return xmb->textures.list[XMB_TEXTURE_INPUT_TURBO];
else if (strstr(enum_label, "_gun_"))
return xmb->textures.list[XMB_TEXTURE_INPUT_LGUN];
else if (string_starts_with_size(enum_label, "input_device_reserv", STRLEN_CONST("input_device_reserv")))
return xmb->textures.list[XMB_TEXTURE_OVERRIDE];
}
/* This is used for both Input Port Binds and Quickmenu controls */
if ( string_ends_with_size(enum_label, "_up", enum_label_len, STRLEN_CONST("_up")))
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_U];
else if (string_ends_with_size(enum_label, "_down", enum_label_len, STRLEN_CONST("_down")))
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_D];
else if (string_ends_with_size(enum_label, "_left", enum_label_len, STRLEN_CONST("_left")))
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_L];
else if (string_ends_with_size(enum_label, "_right", enum_label_len, STRLEN_CONST("_right")))
return xmb->textures.list[XMB_TEXTURE_INPUT_DPAD_R];
else if (string_ends_with_size(enum_label, "_b", enum_label_len, STRLEN_CONST("_b")))
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_D];
else if (string_ends_with_size(enum_label, "_a", enum_label_len, STRLEN_CONST("_a")))
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_R];
else if (string_ends_with_size(enum_label, "_y", enum_label_len, STRLEN_CONST("_y")))
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_L];
else if (string_ends_with_size(enum_label, "_x", enum_label_len, STRLEN_CONST("_x")))
return xmb->textures.list[XMB_TEXTURE_INPUT_BTN_U];
else if (string_ends_with_size(enum_label, "_select", enum_label_len, STRLEN_CONST("_select")))
return xmb->textures.list[XMB_TEXTURE_INPUT_SELECT];
else if (string_ends_with_size(enum_label, "_start", enum_label_len, STRLEN_CONST("_start")))
return xmb->textures.list[XMB_TEXTURE_INPUT_START];
else if (string_ends_with_size(enum_label, "_l", enum_label_len, STRLEN_CONST("_l")))
return xmb->textures.list[XMB_TEXTURE_INPUT_LB];
else if (string_ends_with_size(enum_label, "_r", enum_label_len, STRLEN_CONST("_r")))
return xmb->textures.list[XMB_TEXTURE_INPUT_RB];
else if (string_ends_with_size(enum_label, "_l2", enum_label_len, STRLEN_CONST("_l2")))
return xmb->textures.list[XMB_TEXTURE_INPUT_LT];
else if (string_ends_with_size(enum_label, "_r2", enum_label_len, STRLEN_CONST("_r2")))
return xmb->textures.list[XMB_TEXTURE_INPUT_RT];
else if (string_ends_with_size(enum_label, "_l3", enum_label_len, STRLEN_CONST("_l3")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_P];
else if (string_ends_with_size(enum_label, "_r3", enum_label_len, STRLEN_CONST("_r3")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_P];
else if (string_ends_with_size(enum_label, "_y_minus", enum_label_len, STRLEN_CONST("_y_minus")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_U];
else if (string_ends_with_size(enum_label, "_y_plus", enum_label_len, STRLEN_CONST("_y_plus")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_D];
else if (string_ends_with_size(enum_label, "_x_minus", enum_label_len, STRLEN_CONST("_x_minus")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_L];
else if (string_ends_with_size(enum_label, "_x_plus", enum_label_len, STRLEN_CONST("_x_plus")))
return xmb->textures.list[XMB_TEXTURE_INPUT_STCK_R];
}
if ( type >= MENU_SETTINGS_REMAPPING_PORT_BEGIN
&& type <= MENU_SETTINGS_REMAPPING_PORT_END)
return xmb->textures.list[XMB_TEXTURE_INPUT_SETTINGS];
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 size_t xmb_animation_line_ticker_loop(uint64_t idx,
size_t line_len, size_t num_lines)
{
size_t line_ticks = TICKER_LINE_DISPLAY_TICKS(line_len);
size_t ticker_period = num_lines + 1;
size_t phase = (idx / line_ticks) % ticker_period;
/* In this case, line_offset is simply equal to the phase */
return phase;
}
static size_t xmb_animation_line_ticker_generic(uint64_t idx,
size_t line_len, size_t max_lines, size_t num_lines)
{
size_t line_ticks = TICKER_LINE_DISPLAY_TICKS(line_len);
/* Note: This function is only called if num_lines > max_lines */
size_t excess_lines = num_lines - max_lines;
/* Ticker will pause for one line duration when the first
* or last line is reached (this is mostly required for the
* case where num_lines == (max_lines + 1), since otherwise
* the text flicks rapidly up and down in disconcerting
* fashion...) */
size_t ticker_period = (excess_lines * 2) + 2;
size_t phase = (idx / line_ticks) % ticker_period;
/* Pause on first line */
if (phase > 0)
phase--;
/* Pause on last line */
if (phase > excess_lines)
phase--;
/* Lines scrolling upwards */
if (phase <= excess_lines)
return phase;
/* Lines scrolling downwards */
return (excess_lines * 2) - phase;
}
static bool xmb_animation_line_ticker(gfx_animation_t *p_anim, gfx_animation_ctx_line_ticker_t *line_ticker)
{
char *wrapped_str = NULL;
size_t wrapped_str_len = 0;
size_t line_ticker_str_len = 0;
size_t line_offset = 0;
size_t total_lines = 0;
bool ret = false;
bool is_active = false;
/* Sanity check */
if (!line_ticker)
return false;
if ( (!line_ticker->str || !*line_ticker->str)
|| (line_ticker->line_len < 1)
|| (line_ticker->max_lines < 1)
|| (line_ticker->len < 1))
goto end;
/* Line wrap input string */
line_ticker_str_len = strlen(line_ticker->str);
wrapped_str_len = line_ticker_str_len + 1 + 10; /* 10 bytes use for inserting '\n' */
if (!(wrapped_str = (char*)malloc(wrapped_str_len)))
goto end;
wrapped_str[0] = '\0';
word_wrap(
wrapped_str,
wrapped_str_len,
line_ticker->str,
line_ticker_str_len,
(int)line_ticker->line_len,
100, 0);
if (!wrapped_str || !*wrapped_str)
goto end;
/* Count total number of lines */
{
size_t slen = strlen(wrapped_str);
const char *p = wrapped_str;
size_t remaining = slen;
total_lines = 1;
while (remaining > 0)
{
const char *nl = (const char *)memchr(p, '\n', remaining);
if (!nl)
break;
total_lines++;
nl++;
remaining -= (size_t)(nl - p);
p = nl;
}
}
/* Check whether total number of lines fits within
* the set limit */
if (total_lines <= line_ticker->max_lines)
{
strlcpy(line_ticker->s, wrapped_str, line_ticker->len);
ret = true;
goto end;
}
/* Determine offset of first line in wrapped string */
switch (line_ticker->type_enum)
{
case TICKER_TYPE_LOOP:
line_offset = xmb_animation_line_ticker_loop(
line_ticker->idx,
line_ticker->line_len,
total_lines);
break;
case TICKER_TYPE_BOUNCE:
default:
line_offset = xmb_animation_line_ticker_generic(
line_ticker->idx,
line_ticker->line_len,
line_ticker->max_lines,
total_lines);
break;
}
/* Build output string from required lines */
{
const char *p = wrapped_str;
size_t remaining = strlen(wrapped_str);
size_t cur_line = 0;
/* Skip to the starting line */
while (cur_line < line_offset && remaining > 0)
{
const char *nl = (const char *)memchr(p, '\n', remaining);
if (!nl)
break;
nl++;
remaining -= (size_t)(nl - p);
p = nl;
cur_line++;
}
/* Find the end of the visible window */
{
const char *end_p = p;
size_t end_remaining = remaining;
size_t lines_found = 0;
while (lines_found < line_ticker->max_lines && end_remaining > 0)
{
const char *nl = (const char *)memchr(end_p, '\n', end_remaining);
if (!nl)
{
end_p = end_p + end_remaining;
break;
}
lines_found++;
if (lines_found >= line_ticker->max_lines)
{
end_p = nl;
break;
}
nl++;
end_remaining -= (size_t)(nl - end_p);
end_p = nl;
}
/* Single memcpy for the whole block */
{
size_t copy_len = (size_t)(end_p - p);
size_t max_copy = line_ticker->len - 1;
if (copy_len > max_copy)
copy_len = max_copy;
memcpy(line_ticker->s, p, copy_len);
line_ticker->s[copy_len] = '\0';
}
}
}
ret = true;
is_active = true;
p_anim->flags |= GFX_ANIM_FLAG_TICKER_IS_ACTIVE;
end:
if (wrapped_str)
{
free(wrapped_str);
wrapped_str = NULL;
}
if (!ret)
if (line_ticker->len > 0)
line_ticker->s[0] = '\0';
return is_active;
}
static void xmb_animation_set_line_smooth_fade_parameters(
bool scroll_up,
size_t scroll_ticks, size_t line_phase, size_t line_height,
size_t num_lines, size_t num_display_lines, size_t line_offset,
float y_offset,
size_t *top_fade_line_offset,
float *top_fade_y_offset, float *top_fade_alpha,
size_t *bottom_fade_line_offset,
float *bottom_fade_y_offset, float *bottom_fade_alpha)
{
/* When a line fades out, alpha transitions from
* 1 to 0 over the course of one half of the
* scrolling line height. When a line fades in,
* it's the other way around */
float fade_out_alpha = ((float)scroll_ticks - ((float)line_phase * 2.0f)) / (float)scroll_ticks;
float fade_in_alpha = -1.0f * fade_out_alpha;
if (fade_out_alpha < 0.0f)
fade_out_alpha = 0.0f;
if (fade_in_alpha < 0.0f)
fade_in_alpha = 0.0f;
*top_fade_line_offset = (line_offset > 0) ? line_offset - 1 : num_lines;
*top_fade_y_offset = y_offset - (float)line_height;
if (scroll_up)
{
*top_fade_alpha = fade_out_alpha;
*bottom_fade_alpha = fade_in_alpha;
}
else
{
*top_fade_alpha = fade_in_alpha;
*bottom_fade_alpha = fade_out_alpha;
}
*bottom_fade_line_offset = line_offset + num_display_lines;
*bottom_fade_y_offset = y_offset + (float)(line_height * num_display_lines);
}
static void xmb_animation_set_line_smooth_fade_parameters_default(
size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha,
size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha)
{
*top_fade_line_offset = 0;
*top_fade_y_offset = 0.0f;
*top_fade_alpha = 0.0f;
*bottom_fade_line_offset = 0;
*bottom_fade_y_offset = 0.0f;
*bottom_fade_alpha = 0.0f;
}
static void xmb_animation_line_ticker_smooth_generic(uint64_t idx,
bool fade_enabled, size_t line_len, size_t line_height,
size_t max_display_lines, size_t num_lines,
size_t *num_display_lines, size_t *line_offset,
float *y_offset,
bool *fade_active,
size_t *top_fade_line_offset, float *top_fade_y_offset,
float *top_fade_alpha,
size_t *bottom_fade_line_offset, float *bottom_fade_y_offset,
float *bottom_fade_alpha)
{
size_t scroll_ticks = TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len);
/* Note: This function is only called if num_lines > max_display_lines */
size_t excess_lines = num_lines - max_display_lines;
/* Ticker will pause for one line duration when the first
* or last line is reached */
size_t ticker_period = ((excess_lines * 2) + 2) * scroll_ticks;
size_t phase = idx % ticker_period;
size_t line_phase = 0;
bool pause = false;
bool scroll_up = true;
if (phase >= scroll_ticks)
phase -= scroll_ticks;
else
{
/* Pause on first line */
pause = true;
phase = 0;
}
/* Pause on last line and change direction */
if (phase >= excess_lines * scroll_ticks)
{
scroll_up = false;
if (phase < (excess_lines + 1) * scroll_ticks)
{
pause = true;
phase = 0;
}
else
phase -= (excess_lines + 1) * scroll_ticks;
}
line_phase = phase % scroll_ticks;
if (pause)
{
/* Static display of max_display_lines
* (no animation) */
*num_display_lines = max_display_lines;
*y_offset = 0.0f;
*fade_active = false;
*line_offset = scroll_up ? 0 : excess_lines;
}
else if (line_phase == 0)
{
/* Static display of max_display_lines
* (no animation) */
*num_display_lines = max_display_lines;
*y_offset = 0.0f;
*fade_active = false;
*line_offset = scroll_up ? (phase / scroll_ticks) : (excess_lines - (phase / scroll_ticks));
}
else
{
/* Scroll animation is active */
*num_display_lines = (max_display_lines > 1) ? max_display_lines - 1 : 1;
*fade_active = fade_enabled;
if (scroll_up)
{
*line_offset = (phase / scroll_ticks) + 1;
*y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks;
}
else
{
*line_offset = excess_lines - (phase / scroll_ticks);
*y_offset = (float)line_height * (1.0f - (float)(scroll_ticks - line_phase) / (float)scroll_ticks);
}
/* Set fade parameters if fade animation is active */
if (*fade_active)
xmb_animation_set_line_smooth_fade_parameters(
scroll_up, scroll_ticks, line_phase, line_height,
num_lines, *num_display_lines, *line_offset, *y_offset,
top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
}
/* Set 'default' fade parameters if fade animation
* is inactive */
if (!*fade_active)
xmb_animation_set_line_smooth_fade_parameters_default(
top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
}
static void xmb_animation_line_ticker_smooth_loop(uint64_t idx,
bool fade_enabled, size_t line_len, size_t line_height,
size_t max_display_lines, size_t num_lines,
size_t *num_display_lines, size_t *line_offset, float *y_offset,
bool *fade_active,
size_t *top_fade_line_offset, float *top_fade_y_offset, float *top_fade_alpha,
size_t *bottom_fade_line_offset, float *bottom_fade_y_offset, float *bottom_fade_alpha)
{
size_t scroll_ticks = TICKER_LINE_SMOOTH_SCROLL_TICKS(line_len);
size_t ticker_period = (num_lines + 1) * scroll_ticks;
size_t phase = idx % ticker_period;
size_t line_phase = phase % scroll_ticks;
*line_offset = phase / scroll_ticks;
if (line_phase == (scroll_ticks - 1))
{
/* Static display of max_display_lines
* (no animation) */
*num_display_lines = max_display_lines;
*fade_active = false;
}
else
{
*num_display_lines = (max_display_lines > 1) ? max_display_lines - 1 : 1;
*fade_active = fade_enabled;
}
*y_offset = (float)line_height * (float)(scroll_ticks - line_phase) / (float)scroll_ticks;
/* Set fade parameters */
if (*fade_active)
xmb_animation_set_line_smooth_fade_parameters(
true, scroll_ticks, line_phase, line_height,
num_lines, *num_display_lines, *line_offset, *y_offset,
top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
else
xmb_animation_set_line_smooth_fade_parameters_default(
top_fade_line_offset, top_fade_y_offset, top_fade_alpha,
bottom_fade_line_offset, bottom_fade_y_offset, bottom_fade_alpha);
}
static bool xmb_animation_line_ticker_smooth(gfx_animation_t *p_anim, gfx_animation_ctx_line_ticker_smooth_t *line_ticker)
{
char wrapped_str[PATH_MAX_LENGTH];
const char *wideglyph_str = NULL;
size_t line_ticker_src_len = 0;
size_t wrapped_str_len = 0;
int glyph_width = 0;
int glyph_height = 0;
size_t line_len = 0;
size_t max_display_lines = 0;
size_t num_display_lines = 0;
size_t line_offset = 0;
size_t top_fade_line_offset = 0;
size_t bottom_fade_line_offset = 0;
bool fade_active = false;
int wideglyph_width = 100;
#define XMB_LINE_TICKER_MAX_LINES 256
const char *line_starts[XMB_LINE_TICKER_MAX_LINES];
size_t line_lengths[XMB_LINE_TICKER_MAX_LINES];
size_t num_lines = 0;
size_t (*word_wrap_func)(char *dst, size_t dst_size,
const char *src, size_t src_len,
int line_width, int wideglyph_width, unsigned max_lines);
/* Sanity check */
if (!line_ticker)
return false;
if ( !line_ticker->font
|| (!line_ticker->src_str || !*line_ticker->src_str)
|| (line_ticker->field_width < 1)
|| (line_ticker->field_height < 1))
{
if (line_ticker->dst_str_len > 0)
line_ticker->dst_str[0] = '\0';
if (line_ticker->fade_enabled)
{
if (line_ticker->top_fade_str_len > 0)
line_ticker->top_fade_str[0] = '\0';
if (line_ticker->bottom_fade_str_len > 0)
line_ticker->bottom_fade_str[0] = '\0';
*line_ticker->top_fade_alpha = 0.0f;
*line_ticker->bottom_fade_alpha = 0.0f;
}
return false;
}
/* Get font dimensions */
if ((glyph_width = font_driver_get_message_width(
line_ticker->font, "a", 1, line_ticker->font_scale)) <= 0)
goto fail;
if ((wideglyph_str = msg_hash_get_wideglyph_str()))
{
int new_glyph_width = font_driver_get_message_width(
line_ticker->font, wideglyph_str, strlen(wideglyph_str),
line_ticker->font_scale);
if (new_glyph_width > 0)
wideglyph_width = new_glyph_width * 100 / glyph_width;
word_wrap_func = word_wrap_wideglyph;
}
else
word_wrap_func = word_wrap;
/* > Height */
if ((glyph_height = font_driver_get_line_height(
line_ticker->font, line_ticker->font_scale)) <= 0)
goto fail;
/* Determine line wrap parameters */
line_len = (size_t)(line_ticker->field_width / glyph_width);
max_display_lines = (size_t)(line_ticker->field_height / glyph_height);
if ((line_len < 1) || (max_display_lines < 1))
goto fail;
/* Line wrap input string */
line_ticker_src_len = strlen(line_ticker->src_str);
wrapped_str_len = sizeof(wrapped_str);
if (line_ticker_src_len + 11 > wrapped_str_len)
goto fail;
wrapped_str[0] = '\0';
(word_wrap_func)(
wrapped_str,
wrapped_str_len,
line_ticker->src_str,
line_ticker_src_len,
(int)line_len,
wideglyph_width, 0);
if (!*wrapped_str)
goto fail;
/* Split into lines using memchr */
{
const char *p = wrapped_str;
const char *end_ptr = wrapped_str + strlen(wrapped_str);
while (p < end_ptr)
{
const char *nl = (const char*)memchr(p, '\n', end_ptr - p);
if (!nl)
nl = end_ptr;
if (num_lines >= XMB_LINE_TICKER_MAX_LINES)
break;
line_starts[num_lines] = p;
line_lengths[num_lines] = (size_t)(nl - p);
num_lines++;
p = nl + 1;
}
}
if (num_lines < 1)
goto fail;
/* Check whether total number of lines fits within
* the set field limit */
if (num_lines <= max_display_lines)
{
strlcpy(line_ticker->dst_str, wrapped_str, line_ticker->dst_str_len);
*line_ticker->y_offset = 0.0f;
if (line_ticker->fade_enabled)
{
if (line_ticker->top_fade_str_len > 0)
line_ticker->top_fade_str[0] = '\0';
if (line_ticker->bottom_fade_str_len > 0)
line_ticker->bottom_fade_str[0] = '\0';
*line_ticker->top_fade_y_offset = 0.0f;
*line_ticker->bottom_fade_y_offset = 0.0f;
*line_ticker->top_fade_alpha = 0.0f;
*line_ticker->bottom_fade_alpha = 0.0f;
}
return true;
}
/* Determine which lines should be shown, along with
* y axis draw offset */
switch (line_ticker->type_enum)
{
case TICKER_TYPE_LOOP:
xmb_animation_line_ticker_smooth_loop(
line_ticker->idx,
line_ticker->fade_enabled,
line_len, (size_t)glyph_height,
max_display_lines, num_lines,
&num_display_lines, &line_offset, line_ticker->y_offset,
&fade_active,
&top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha,
&bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha);
break;
case TICKER_TYPE_BOUNCE:
default:
xmb_animation_line_ticker_smooth_generic(
line_ticker->idx,
line_ticker->fade_enabled,
line_len, (size_t)glyph_height,
max_display_lines, num_lines,
&num_display_lines, &line_offset, line_ticker->y_offset,
&fade_active,
&top_fade_line_offset, line_ticker->top_fade_y_offset, line_ticker->top_fade_alpha,
&bottom_fade_line_offset, line_ticker->bottom_fade_y_offset, line_ticker->bottom_fade_alpha);
break;
}
if (!num_display_lines)
goto fail;
/* Build output string from required lines */
{
size_t i;
size_t dst_offset = 0;
bool is_loop = (line_ticker->type_enum == TICKER_TYPE_LOOP);
for (i = 0; i < num_display_lines; i++)
{
size_t line_idx = line_offset + i;
size_t copy_len;
/* Only wrap around for loop ticker mode;
* for bounce/generic mode, clamp to valid range */
if (is_loop)
line_idx %= num_lines;
else if (line_idx >= num_lines)
break;
copy_len = line_lengths[line_idx];
if (dst_offset + copy_len + 2 > line_ticker->dst_str_len)
break;
if (i > 0)
line_ticker->dst_str[dst_offset++] = '\n';
memcpy(line_ticker->dst_str + dst_offset,
line_starts[line_idx], copy_len);
dst_offset += copy_len;
}
if (dst_offset < line_ticker->dst_str_len)
line_ticker->dst_str[dst_offset] = '\0';
else if (line_ticker->dst_str_len > 0)
line_ticker->dst_str[line_ticker->dst_str_len - 1] = '\0';
}
/* Extract top/bottom fade strings, if required */
if (fade_active)
{
size_t top_fade_line_index = top_fade_line_offset;
size_t bottom_fade_line_index = bottom_fade_line_offset;
if (line_ticker->type_enum == TICKER_TYPE_LOOP)
{
/* For the top fade, use (num_lines + 1) so that the
* sentinel value 'num_lines' (set when line_offset == 0)
* correctly maps to >= num_lines and suppresses the
* fade string during the loop gap */
top_fade_line_index %= (num_lines + 1);
/* For the bottom fade, use num_lines so that the index
* wraps to the correct next line at the loop boundary
* instead of duplicating a line already in the main
* display */
bottom_fade_line_index %= num_lines;
}
if (top_fade_line_index < num_lines
&& line_ticker->top_fade_str_len > 0)
{
size_t copy_len = line_lengths[top_fade_line_index];
if (copy_len >= line_ticker->top_fade_str_len)
copy_len = line_ticker->top_fade_str_len - 1;
memcpy(line_ticker->top_fade_str,
line_starts[top_fade_line_index], copy_len);
line_ticker->top_fade_str[copy_len] = '\0';
}
if (bottom_fade_line_index < num_lines
&& line_ticker->bottom_fade_str_len > 0)
{
size_t copy_len = line_lengths[bottom_fade_line_index];
if (copy_len >= line_ticker->bottom_fade_str_len)
copy_len = line_ticker->bottom_fade_str_len - 1;
memcpy(line_ticker->bottom_fade_str,
line_starts[bottom_fade_line_index], copy_len);
line_ticker->bottom_fade_str[copy_len] = '\0';
}
}
p_anim->flags |= GFX_ANIM_FLAG_TICKER_IS_ACTIVE;
return true;
fail:
if (line_ticker->dst_str_len > 0)
line_ticker->dst_str[0] = '\0';
if (line_ticker->fade_enabled)
{
if (line_ticker->top_fade_str_len > 0)
line_ticker->top_fade_str[0] = '\0';
if (line_ticker->bottom_fade_str_len > 0)
line_ticker->bottom_fade_str[0] = '\0';
*line_ticker->top_fade_alpha = 0.0f;
*line_ticker->bottom_fade_alpha = 0.0f;
}
return false;
}
static int xmb_draw_item(
struct menu_state *menu_st,
void *userdata,
gfx_display_t *p_disp,
gfx_animation_t *p_anim,
gfx_display_ctx_driver_t *dispctx,
settings_t *settings,
unsigned video_width,
unsigned video_height,
bool 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_icon_x, gfx_icon_y, gfx_icon_height, gfx_icon_width;
gfx_animation_ctx_ticker_t ticker;
gfx_animation_ctx_ticker_smooth_t ticker_smooth;
char tmp[NAME_MAX_LENGTH];
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 draw_text_value = false;
int extra_margins_setting_left = 0;
unsigned ticker_limit = ((xmb->use_ps3_layout) ? 37 : 37) * xmb->scale_mod[0];
unsigned line_ticker_width = ((xmb->use_ps3_layout) ? 58 : 58) * xmb->scale_mod[3];
xmb_node_t *node = (xmb_node_t*)list->list[i].userdata;
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 thumbnail_scale_factor = settings->uints.menu_xmb_thumbnail_scale_factor;
bool vertical_thumbnails = settings->bools.menu_xmb_vertical_thumbnails;
bool show_sublabels = settings->bools.menu_show_sublabels;
bool show_switch_icons = settings->bools.menu_xmb_switch_icons;
unsigned show_history_icons = settings->uints.playlist_show_history_icons;
unsigned vertical_fade_factor = settings->uints.menu_xmb_vertical_fade_factor;
bool show_icon_thumbnail = false;
/* Initial ticker configuration */
if (use_smooth_ticker)
{
ticker_smooth.idx = p_anim->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 = p_anim->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_INITIALIZE(entry);
entry.flags |= MENU_ENTRY_FLAG_PATH_ENABLED
| MENU_ENTRY_FLAG_LABEL_ENABLED
| MENU_ENTRY_FLAG_RICH_LABEL_ENABLED
| MENU_ENTRY_FLAG_VALUE_ENABLED;
if (i == current)
entry.flags |= MENU_ENTRY_FLAG_SUBLABEL_ENABLED;
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];
strlcpy(entry_path, entry.path, sizeof(entry_path));
fill_pathname(entry_path, path_basename(entry_path), "",
sizeof(entry_path));
if (*entry_path)
strlcpy(entry.path, entry_path, sizeof(entry.path));
}
/* Only show switch icons with bool type options */
if (show_switch_icons && entry.setting_type == ST_BOOL)
{
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
draw_text_value = 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
draw_text_value = true;
}
else
draw_text_value = true;
}
else
{
if (*entry.value)
{
bool found = false;
if (memcmp(entry.value, "...", 4) == 0)
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)
draw_text_value = true;
}
else
draw_text_value = true;
}
if (!texture_switch)
{
bool show_right_thumbnail =
(gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
&& xmb->show_thumbnails
&& ( (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_PENDING)));
bool show_left_thumbnail =
(gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
&& xmb->show_thumbnails
&& ( (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_PENDING)));
if ( (xmb->is_playlist || xmb->is_explore_list)
&& !xmb->is_playlist_information
&& xmb->use_ps3_layout
&& ( show_right_thumbnail
|| (show_left_thumbnail && vertical_thumbnails))
)
{
ticker_limit = 37 * xmb->scale_mod[0];
/* Can increase text length if thumbnail is downscaled */
if (thumbnail_scale_factor < 100)
{
float ticker_scale_factor =
1.0f - ((float)thumbnail_scale_factor / 100.0f);
ticker_limit +=
(unsigned)(ticker_scale_factor * 20.0f * xmb->scale_mod[1]);
line_ticker_width +=
(unsigned)(ticker_scale_factor * 10.0f * xmb->scale_mod[3]);
}
}
else if (!draw_text_value || !*entry.value)
ticker_limit = ((xmb->use_ps3_layout) ? 75 : 65) * xmb->scale_mod[2];
}
switch (entry_type)
{
case FILE_TYPE_DOWNLOAD_CORE:
/* Special margin for 'installed' tag value */
extra_margins_setting_left = ((xmb->use_ps3_layout) ? 300 : 250) * xmb->last_scale_factor * xmb->scale_mod[2];
ticker_limit = ((xmb->use_ps3_layout) ? 80 : 58) * xmb->scale_mod[2];
break;
default:
if (!xmb->use_ps3_layout)
break;
if (xmb->is_quick_menu || xmb->is_state_slot)
extra_margins_setting_left = -(100 * xmb->last_scale_factor * xmb->scale_mod[2]);
break;
}
if (!use_smooth_ticker && string_is_equal(settings->paths.path_menu_xmb_font, FILE_PATH_UNKNOWN))
{
ticker_limit *= 0.85f;
line_ticker_width *= 0.85f;
}
/* Don't update ticker limit while waiting for thumbnail status */
if ( (xmb->is_playlist || xmb->is_explore_list)
&& ( ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
&& xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_UNKNOWN)
|| ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
&& xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_UNKNOWN))
)
; /* no-op */
else
xmb->ticker_limit = ticker_limit;
if (*entry.rich_label)
ticker_str = entry.rich_label;
else
ticker_str = entry.path;
if (use_smooth_ticker)
{
ticker_smooth.selected = (i == current);
ticker_smooth.field_width = xmb->font_size * 0.5f * xmb->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.selected = (i == current);
ticker.len = xmb->ticker_limit;
ticker.str = ticker_str;
ticker.s = tmp;
if (ticker.str)
gfx_animation_ticker(&ticker);
}
label_offset = xmb->margins_label_top;
if (vertical_fade_factor)
{
float min_alpha = 0.01f;
float max_alpha = (i == current) ? xmb->items_active_alpha : xmb->items_passive_alpha;
float new_alpha = node->alpha;
float icon_space = xmb->icon_spacing_vertical;
float icon_ratio = icon_space / height / icon_space * 4;
float scr_margin = xmb->margins_screen_top + (icon_space / icon_ratio / 400);
float factor = vertical_fade_factor / 100.0f / icon_ratio;
if (!xmb->use_ps3_layout)
scr_margin -= (icon_space / 8);
/* Top */
if (i < current)
new_alpha = (node->y + scr_margin) / factor;
/* Bottom */
else if (i > current)
new_alpha = (height - node->y - scr_margin + icon_space) / factor;
/* Limits */
if (new_alpha < min_alpha)
new_alpha = min_alpha;
if (new_alpha > max_alpha)
new_alpha = max_alpha;
/* Avoid adjusting animating rows */
if (new_alpha < node->alpha || node->x == 0)
node->alpha = node->label_alpha = new_alpha;
}
if (show_sublabels)
{
if ( i == current
&& width > 320 && height > 240
&& *entry.sublabel)
{
char entry_sublabel[MENU_LABEL_MAX_LENGTH];
char entry_sublabel_top_fade[MENU_LABEL_MAX_LENGTH >> 2];
char entry_sublabel_bottom_fade[MENU_LABEL_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 = p_anim->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.5f * line_ticker_width);
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);
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;
xmb_animation_line_ticker_smooth(p_anim, &line_ticker_smooth);
}
else
{
line_ticker.type_enum = menu_ticker_type;
line_ticker.idx = p_anim->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;
xmb_animation_line_ticker(p_anim, &line_ticker);
}
label_offset = -xmb->margins_label_top;
/* Draw sublabel */
xmb_draw_text(shadows_enable, xmb, settings,
entry_sublabel,
sublabel_x,
ticker_y_offset + sublabel_y,
1, node->label_alpha * xmb->alpha_list, TEXT_ALIGN_LEFT,
width, height, xmb->font2);
/* Draw top/bottom line fade effect, if required */
if (use_smooth_ticker)
{
if ( *entry_sublabel_top_fade
&& ticker_top_fade_alpha > 0.0f)
xmb_draw_text(shadows_enable, xmb, settings,
entry_sublabel_top_fade,
sublabel_x, ticker_top_fade_y_offset + sublabel_y,
1, ticker_top_fade_alpha * node->label_alpha * xmb->alpha_list, TEXT_ALIGN_LEFT,
width, height, xmb->font2);
if ( *entry_sublabel_bottom_fade
&& ticker_bottom_fade_alpha > 0.0f)
xmb_draw_text(shadows_enable, xmb, settings,
entry_sublabel_bottom_fade,
sublabel_x, ticker_bottom_fade_y_offset + sublabel_y,
1, ticker_bottom_fade_alpha * node->label_alpha * xmb->alpha_list, TEXT_ALIGN_LEFT,
width, height, xmb->font2);
}
}
}
/* Draw entry index of current selection */
if ( i == current
&& xmb->entry_idx_enabled
&& *xmb->entry_index_str)
{
float entry_idx_margin = 12 * xmb->last_scale_factor;
float x_position = video_width - entry_idx_margin;
float y_position = video_height - entry_idx_margin;
xmb_draw_text(shadows_enable, xmb, settings,
xmb->entry_index_str, x_position, y_position,
1, vertical_thumbnails ? node->label_alpha : 1,
TEXT_ALIGN_RIGHT, width, height, xmb->font);
}
xmb_draw_text(shadows_enable, xmb, settings, 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 * xmb->alpha_list, 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 * ((xmb->use_ps3_layout) ? 40 : 26) * xmb->scale_mod[7];
ticker_smooth.src_str = entry.value;
ticker_smooth.dst_str = tmp;
ticker_smooth.dst_str_len = sizeof(tmp);
if (*entry.value)
gfx_animation_ticker_smooth(&ticker_smooth);
}
else
{
ticker.s = tmp;
ticker.len = ((xmb->use_ps3_layout) ? 40 : 26) * xmb->scale_mod[7];
ticker.selected = (i == current);
ticker.str = entry.value;
if (*entry.value)
gfx_animation_ticker(&ticker);
}
if (draw_text_value)
xmb_draw_text(shadows_enable, xmb, settings, tmp,
(float)ticker_x_offset
+ node->x
+ xmb->margins_screen_left
+ xmb->icon_spacing_horizontal
+ xmb->margins_label_left
+ xmb->margins_setting_left
+ extra_margins_setting_left,
xmb->margins_screen_top
+ node->y
+ xmb->margins_label_top,
1, node->label_alpha * xmb->alpha_list, TEXT_ALIGN_LEFT,
width, height, xmb->font);
gfx_display_set_alpha(color, MIN(node->alpha * xmb->alpha_list, xmb->alpha));
if ( (!xmb->assets_missing)
&& (color[3] != 0)
&& ( (entry.flags & MENU_ENTRY_FLAG_CHECKED)
|| !( entry_type >= MENU_SETTING_DROPDOWN_ITEM
&& entry_type <= MENU_SETTING_DROPDOWN_SETTING_UINT_ITEM_SPECIAL))
)
{
uintptr_t texture = xmb_icon_get_id(xmb, core_node, node,
entry.enum_idx, entry.path, entry.label,
entry_type, (i == current), entry.flags & MENU_ENTRY_FLAG_CHECKED);
float scale_factor = node->zoom;
if (i != current)
{
/* Differentiate the basic setting icon from the rest */
if (texture == xmb->textures.list[XMB_TEXTURE_SUBSETTING])
gfx_display_set_alpha(color, MIN(node->alpha / 2.0f, xmb->alpha));
/* Highlight active icon more by dimming all passives */
else
gfx_display_set_alpha(color, MIN(node->alpha / 1.25f, xmb->alpha));
}
/* Playlist manager icons */
if (xmb->depth == 3 && entry.enum_idx == MENU_ENUM_LABEL_PLAYLIST_MANAGER_SETTINGS)
{
if (string_is_equal(entry.rich_label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_HISTORY_TAB)))
texture = xmb->textures.list[XMB_TEXTURE_HISTORY];
else if (string_is_equal(entry.rich_label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_FAVORITES_TAB)))
texture = xmb->textures.list[XMB_TEXTURE_FAVORITES];
#ifdef HAVE_IMAGEVIEWER
else if (string_is_equal(entry.rich_label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_IMAGES_TAB)))
texture = xmb->textures.list[XMB_TEXTURE_IMAGES];
#endif
else if (string_is_equal(entry.rich_label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_MUSIC_TAB)))
texture = xmb->textures.list[XMB_TEXTURE_MUSICS];
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
else if (string_is_equal(entry.rich_label, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_TAB)))
texture = xmb->textures.list[XMB_TEXTURE_MOVIES];
#endif
else if (i < xmb->horizontal_list.size)
{
xmb_node_t *sidebar_node = NULL;
unsigned offset = 0;
/* Ignore Explore Views */
for (offset = 0; offset < xmb->horizontal_list.size; offset++)
{
char playlist_file_noext[NAME_MAX_LENGTH];
fill_pathname(playlist_file_noext, xmb->horizontal_list.list[offset].path, "",
sizeof(playlist_file_noext));
if (string_is_equal(playlist_file_noext, entry.rich_label))
break;
}
sidebar_node = (xmb_node_t*)file_list_get_userdata_at_offset(&xmb->horizontal_list, offset);
if (sidebar_node && sidebar_node->icon)
texture = sidebar_node->icon;
}
}
/* "Main Menu" playlists */
else if (xmb->depth == 2 && entry_type == FILE_TYPE_PLAYLIST_COLLECTION)
{
xmb_node_t *sidebar_node = (xmb_node_t*)
(xmb->horizontal_list.size)
? (xmb_node_t*)file_list_get_userdata_at_offset(&xmb->horizontal_list, list->list[i].entry_idx)
: NULL;
if (sidebar_node && sidebar_node->icon)
texture = sidebar_node->icon;
}
/* History/Favorite console specific content icons */
else if (entry_type == FILE_TYPE_RPL_ENTRY
&& show_history_icons != PLAYLIST_SHOW_HISTORY_ICONS_DEFAULT)
{
switch (xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr))
{
case XMB_SYSTEM_TAB_MAIN:
{
/* Special fall-through for "Main Menu" > Playlists > History/Favorites */
char title[NAME_MAX_LENGTH];
bool is_history = false;
menu_entries_get_title(title, sizeof(title));
is_history =
string_is_equal(title, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_LOAD_CONTENT_HISTORY))
|| string_is_equal(title, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES));
/* Reset unknown file icon */
if (is_history || xmb->is_quick_menu)
texture = xmb->textures.list[XMB_TEXTURE_FILE];
/* Force "Main Menu" playlists to use content icon
* inside the playlist regardless of the option,
* since playlists themselves use the main icon */
if (!is_history && (xmb->is_quick_menu || xmb->depth > 2))
show_history_icons = PLAYLIST_SHOW_HISTORY_ICONS_CONTENT;
}
case XMB_SYSTEM_TAB_HISTORY:
case XMB_SYSTEM_TAB_FAVORITES:
{
const struct playlist_entry *pl_entry = NULL;
xmb_node_t *db_node = NULL;
playlist_get_index(playlist_get_cached(), entry.entry_idx, &pl_entry);
if ( pl_entry
&& (pl_entry->db_name && *pl_entry->db_name)
&& (db_node = RHMAP_GET_STR(xmb->playlist_db_node_map, pl_entry->db_name)))
{
switch (show_history_icons)
{
case PLAYLIST_SHOW_HISTORY_ICONS_MAIN:
texture = db_node->icon;
break;
case PLAYLIST_SHOW_HISTORY_ICONS_CONTENT:
texture = db_node->content_icon;
break;
default:
break;
}
}
}
break;
default:
break;
}
}
gfx_icon_x = icon_x;
gfx_icon_y = icon_y;
gfx_icon_height = gfx_icon_width = xmb->icon_size;
show_icon_thumbnail =
xmb->is_playlist
&& gfx_thumbnail_is_enabled(&node->thumbnail_icon.thumbnail_path_data, GFX_THUMBNAIL_ICON)
&& node->thumbnail_icon.icon.status == GFX_THUMBNAIL_STATUS_AVAILABLE;
if (show_icon_thumbnail)
{
float gfx_icon_width_draw, gfx_icon_height_draw, gfx_icon_aspect;
float gfx_icon_x_end = icon_x + xmb->icon_size;
float zoom_mp = (xmb->use_ps3_layout) ? 2.0f : 1.0f;
bool zoom = (i == current);
texture = node->thumbnail_icon.icon.texture;
/* Show selected item as "current menu" icon instead */
if (zoom)
gfx_display_set_alpha(color, 0);
/* More active zoom */
if (zoom)
gfx_icon_height = (xmb->icon_size * zoom_mp);
/* More width room for non-squares */
gfx_icon_width = (gfx_icon_height * 1.5f);
gfx_icon_aspect = (gfx_icon_width / gfx_icon_height);
gfx_thumbnail_get_draw_dimensions(
&node->thumbnail_icon.icon,
gfx_icon_width, gfx_icon_height, 1.0f,
&gfx_icon_width_draw, &gfx_icon_height_draw);
gfx_icon_width_draw = ceil(gfx_icon_width_draw);
gfx_icon_height_draw = ceil(gfx_icon_height_draw);
/* Match center with normal icons */
icon_x -= (gfx_icon_width / 6.0f);
if (xmb->use_ps3_layout)
{
/* Align current to right instead of center */
if (zoom)
icon_x -= (gfx_icon_width_draw / 2.0f) + (gfx_icon_width / 24.0f);
/* Center alignments */
gfx_icon_x = icon_x + (gfx_icon_width - gfx_icon_width_draw) / 2.0f;
/* Limit current maximum right aligment */
if (zoom && gfx_icon_x + gfx_icon_width_draw > gfx_icon_x_end)
gfx_icon_x -= (gfx_icon_x_end - gfx_icon_width_draw - gfx_icon_x);
}
else
{
float icon_x_orig = gfx_icon_x;
/* Center alignments */
gfx_icon_x = icon_x + (gfx_icon_width - gfx_icon_width_draw) / 2.0f;
/* Limit current maximum left alignment */
if (zoom && gfx_icon_x < icon_x_orig)
gfx_icon_x -= gfx_icon_x - icon_x_orig;
/* Limit current maximum right aligment */
if (zoom && gfx_icon_x + gfx_icon_width_draw > gfx_icon_x_end)
{
gfx_icon_width_draw = gfx_icon_x_end - gfx_icon_x;
gfx_icon_height_draw = gfx_icon_width_draw / gfx_icon_aspect;
}
}
gfx_icon_y = icon_y - (gfx_icon_height - gfx_icon_height_draw) / 2.0f;
if (zoom && xmb->use_ps3_layout)
gfx_icon_y += (xmb->icon_size / 1.5f);
gfx_icon_width = gfx_icon_width_draw;
gfx_icon_height = gfx_icon_height_draw;
}
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
(show_icon_thumbnail) ? false : shadows_enable,
gfx_icon_width,
gfx_icon_height,
texture,
gfx_icon_x,
gfx_icon_y,
width,
height,
xmb->alpha,
0,
scale_factor,
&color[0],
xmb->shadow_offset / 2,
mymat);
}
if (texture_switch != 0 && color[3] != 0 && !xmb->assets_missing)
{
if (texture_switch == xmb->textures.list[XMB_TEXTURE_SWITCH_OFF])
gfx_display_set_alpha(color, MIN(node->alpha / 2, xmb->alpha));
else
gfx_display_set_alpha(color, MIN(node->alpha, xmb->alpha));
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
xmb->icon_size,
xmb->icon_size,
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,
mymat);
}
return 0;
}
static void xmb_draw_items(
void *userdata,
gfx_display_t *p_disp,
gfx_display_ctx_driver_t *dispctx,
gfx_animation_t *p_anim,
struct menu_state *menu_st,
settings_t *settings,
unsigned video_width,
unsigned video_height,
bool shadows_enable,
xmb_handle_t *xmb,
file_list_t *list,
size_t current,
size_t cat_selection_ptr,
float *color,
unsigned width, unsigned height,
math_matrix_4x4 *mymat)
{
size_t i;
unsigned first, last;
xmb_node_t *core_node = NULL;
size_t end = 0;
if (!list || !list->size)
return;
end = list->size;
i = menu_st->entries.begin;
first = (unsigned)i;
last = (unsigned)(end - 1);
xmb_calculate_visible_range(xmb, height, end, (unsigned)current, &first, &last);
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)));
for (i = first; i <= last; i++)
{
if (xmb_draw_item(
menu_st,
userdata,
p_disp,
p_anim,
dispctx,
settings,
video_width,
video_height,
shadows_enable,
mymat,
xmb,
core_node,
list,
color,
i, current,
width, height) == -1)
break;
}
}
static INLINE bool xmb_use_ps3_layout(unsigned menu_xmb_layout,
unsigned width, unsigned height)
{
switch (menu_xmb_layout)
{
case 1: /* PS3 */
return true;
case 2: /* PSP */
return false;
case 0:
default:
break;
}
/* Automatic
* > Use PSP layout on tiny screens */
return (width > 320) && (height > 240);
}
static INLINE float xmb_get_scale_factor(float menu_scale_factor,
bool use_ps3_layout, unsigned width)
{
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 */
if (scale_factor < 0.1f)
return 0.1f;
return scale_factor;
}
static void xmb_set_thumbnail_delay(bool on)
{
if (on)
{
gfx_thumbnail_set_stream_delay(-1.0f);
gfx_thumbnail_set_fade_duration(-1.0f);
}
else
{
gfx_thumbnail_set_stream_delay(0);
gfx_thumbnail_set_fade_duration(1);
}
}
/* 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 = XMB_EASING_ALPHA;
animation_entry.tag = alpha_tag;
animation_entry.duration = gfx_thumb_get_ptr()->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;
xmb->want_fullscreen_thumbnails = false;
xmb_set_thumbnail_delay(true);
}
/* Enables (and triggers a fade in of) the fullscreen
* thumbnail view */
static void xmb_show_fullscreen_thumbnails(
xmb_handle_t *xmb, struct menu_state *menu_st,
size_t selection)
{
gfx_animation_ctx_entry_t animation_entry;
bool animate = !xmb->want_fullscreen_thumbnails;
uintptr_t alpha_tag = (uintptr_t)&xmb->fullscreen_thumbnail_alpha;
/* Cache selected entry label
* (used as title when fullscreen thumbnails
* are shown) */
if (menu_update_fullscreen_thumbnail_label(xmb->fullscreen_thumbnail_label,
sizeof(xmb->fullscreen_thumbnail_label),
xmb->is_quick_menu,
xmb->title_name) == 0)
xmb->fullscreen_thumbnail_label[0] = '\0';
if (animate)
{
/* Configure fade in animation */
animation_entry.easing_enum = XMB_EASING_ALPHA;
animation_entry.tag = alpha_tag;
animation_entry.duration = gfx_thumb_get_ptr()->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);
}
else
xmb->fullscreen_thumbnail_alpha = 1.0f;
/* Enable fullscreen thumbnails */
xmb->fullscreen_thumbnail_selection = selection;
xmb->show_fullscreen_thumbnails = true;
xmb->want_fullscreen_thumbnails = true;
xmb_set_thumbnail_delay(false);
}
static INLINE bool xmb_fullscreen_thumbnails_available(xmb_handle_t *xmb,
struct menu_state *menu_st)
{
bool ret = xmb->fullscreen_thumbnails_available
&& ( gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT));
if (xmb->is_state_slot)
ret = true;
return ret;
}
static enum menu_action xmb_parse_menu_entry_action(
xmb_handle_t *xmb, enum menu_action action)
{
enum menu_action new_action = action;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
/* Scan user inputs */
switch (action)
{
case MENU_ACTION_LEFT:
case MENU_ACTION_RIGHT:
if (xmb->show_fullscreen_thumbnails && (xmb->is_quick_menu && !menu_is_running_quick_menu()))
return MENU_ACTION_NOOP;
/* 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();
/* Determine whether input repeat is
* currently active
* > This is always true when scroll
* acceleration is greater than zero */
size_t scroll_accel = menu_st->scroll.acceleration;
#ifdef HAVE_AUDIOMIXER
settings_t *settings = config_get_ptr();
size_t category = xmb->categories_selection_ptr;
size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end;
/* We only want the scrolling sound to play if any of the following are true:
* 1. Wraparound is enabled (since the category is guaranteed to change)
* 2. We're scrolling right, but we aren't on the last category
* 3. We're scrolling left, but we aren't on the first category */
bool fail_condition = ((action == MENU_ACTION_RIGHT)
? (category == list_size)
: (category == 0)) && !(settings->bools.menu_navigation_wraparound_enable);
if ( ((current_time - xmb->last_tab_switch_time) >= XMB_TAB_SWITCH_REPEAT_DELAY || scroll_accel <= 0)
&& !fail_condition)
audio_driver_mixer_play_scroll_sound(action == MENU_ACTION_RIGHT);
#endif
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 ( (xmb->is_state_slot)
|| (xmb->is_quick_menu && menu_is_running_quick_menu()))
break;
/* If this is a menu with thumbnails, attempt
* to show fullscreen thumbnail view */
if ( xmb_fullscreen_thumbnails_available(xmb, menu_st)
&& !xmb->show_fullscreen_thumbnails)
{
xmb_hide_fullscreen_thumbnails(xmb, false);
xmb_show_fullscreen_thumbnails(xmb, menu_st, menu_st->selection_ptr);
new_action = MENU_ACTION_NOOP;
}
else if (xmb->show_fullscreen_thumbnails
|| xmb->want_fullscreen_thumbnails)
{
xmb_hide_fullscreen_thumbnails(xmb, true);
new_action = MENU_ACTION_NOOP;
}
break;
case MENU_ACTION_SCAN:
if (xmb->is_playlist_tab)
{
size_t selection_total = menu_st->entries.list ? MENU_LIST_GET_SELECTION(menu_st->entries.list, 0)->size : 0;
size_t selection = menu_st->selection_ptr;
size_t new_selection = random_range(0, (unsigned)(selection_total - 1));
menu_entry_t entry_new;
MENU_ENTRY_INITIALIZE(entry_new);
menu_entry_get(&entry_new, 0, new_selection, NULL, false);
/* Keep randomizing until selection is a fresh playlist */
while (new_selection == selection || entry_new.type != FILE_TYPE_PLAYLIST_COLLECTION)
{
new_selection = random_range(0, (unsigned)(selection_total - 1));
menu_entry_get(&entry_new, 0, new_selection, NULL, false);
}
if (new_selection != selection)
{
menu_st->selection_ptr = new_selection;
if (menu_st->driver_ctx->navigation_set)
menu_st->driver_ctx->navigation_set(menu_st->userdata, true);
}
new_action = MENU_ACTION_NOOP;
#ifdef HAVE_AUDIOMIXER
if (new_selection != selection)
audio_driver_mixer_play_scroll_sound(true);
#endif
}
else if ((xmb->is_playlist)
|| (xmb->is_explore_list))
{
size_t selection = menu_st->selection_ptr;
size_t new_selection = menu_playlist_random_selection(selection, xmb->is_explore_list);
if (new_selection != selection)
{
menu_st->selection_ptr = new_selection;
xmb_selection_pointer_changed(xmb, true);
}
new_action = MENU_ACTION_NOOP;
#ifdef HAVE_AUDIOMIXER
if (new_selection != selection)
audio_driver_mixer_play_scroll_sound(true);
#endif
break;
}
if ( !xmb->show_fullscreen_thumbnails
&& ( (xmb->is_state_slot)
|| (xmb->is_quick_menu && *xmb->savestate_thumbnail_file_path)))
{
xmb_hide_fullscreen_thumbnails(xmb, false);
xmb_show_fullscreen_thumbnails(xmb, menu_st, menu_st->selection_ptr);
new_action = MENU_ACTION_NOOP;
}
else if (xmb->show_fullscreen_thumbnails
&& (xmb->is_state_slot || (xmb->is_quick_menu && menu_is_running_quick_menu())))
{
xmb_hide_fullscreen_thumbnails(xmb, true);
new_action = MENU_ACTION_NOOP;
}
break;
case MENU_ACTION_SEARCH:
/* Playlist thumbnail cycle */
if ( (xmb->show_fullscreen_thumbnails)
|| ((xmb->is_quick_menu) && !menu_is_running_quick_menu()))
{
xmb->skip_thumbnail_reset = false;
action_switch_thumbnail(NULL, NULL, 0, 0);
new_action = MENU_ACTION_NOOP;
}
break;
case MENU_ACTION_OK:
if (xmb->is_state_slot)
xmb->skip_thumbnail_reset = true;
/* Open fullscreen thumbnail with Ok when core is running
to prevent accidental imageviewer core launch */
if ( xmb->libretro_running
&& xmb->is_file_list
&& xmb_fullscreen_thumbnails_available(xmb, menu_st)
&& xmb_get_system_tab(xmb, (unsigned)xmb->categories_selection_ptr) == XMB_SYSTEM_TAB_MAIN)
{
/* Allow launch if already using "imageviewer" core */
if (string_is_equal(runloop_state_get_ptr()->system.info.library_name, "Image Viewer"))
break;
if (xmb->show_fullscreen_thumbnails)
xmb_hide_fullscreen_thumbnails(xmb, true);
else
xmb_show_fullscreen_thumbnails(xmb, menu_st, menu_st->selection_ptr);
return MENU_ACTION_NOOP;
}
if (xmb->show_fullscreen_thumbnails || xmb->want_fullscreen_thumbnails)
{
xmb_hide_fullscreen_thumbnails(xmb, true);
if (!xmb->is_state_slot && !xmb->is_playlist && !xmb->is_explore_list)
return MENU_ACTION_NOOP;
}
/* Make transition smoother for single-click playlist launching */
if ( config_get_ptr()->bools.input_menu_singleclick_playlists
&& (xmb->is_playlist || xmb->is_explore_list))
{
if (xmb->is_explore_list)
{
#if defined(HAVE_LIBRETRODB)
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, menu_st->selection_ptr, NULL, true);
if ( entry.type == FILE_TYPE_RDB
|| entry.type == FILE_TYPE_PLAIN
|| !menu_explore_is_content_list())
break;
#endif
}
xmb->alpha_list = 0.0f;
xmb->draw_entry_delay = MENU_DRAW_ENTRY_DELAY;
}
break;
case MENU_ACTION_CANCEL:
if (xmb->is_state_slot)
xmb->skip_thumbnail_reset = true;
if (xmb->show_fullscreen_thumbnails || xmb->want_fullscreen_thumbnails)
{
xmb->skip_thumbnail_reset = false;
xmb_hide_fullscreen_thumbnails(xmb, true);
return MENU_ACTION_NOOP;
}
/* Back up to Main Menu and first item */
if ( !menu_entries_search_get_terms()
&& MENU_LIST_GET_STACK_SIZE(menu_list, 0) == 1)
{
if (xmb_get_system_tab(xmb,
(unsigned)xmb->categories_selection_ptr) == XMB_SYSTEM_TAB_MAIN)
{
/* Jump to first item on Main Menu */
menu_st->selection_ptr = 0;
xmb_selection_pointer_changed(xmb, true);
}
else if (config_get_ptr()->bools.input_menu_allow_tabs_back)
{
/* Jump to Main Menu */
size_t i = 0;
size_t current_tab = xmb->categories_selection_ptr;
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, menu_st->selection_ptr, NULL, true);
/* Icon animations get stuck if they happen too fast,
* therefore allow it only on the last action */
xmb->allow_horizontal_animation = false;
/* Prevent dynamic wallpaper flashing during jump */
xmb->allow_dynamic_wallpaper = false;
for (i = 0; i < current_tab; i++)
{
if (i == current_tab - 1)
{
xmb->allow_horizontal_animation = true;
xmb->allow_dynamic_wallpaper = true;
}
xmb_menu_entry_action(xmb,
&entry, menu_st->selection_ptr, MENU_ACTION_LEFT);
}
}
return MENU_ACTION_NOOP;
}
break;
case MENU_ACTION_INFO:
if ( (xmb->is_playlist)
|| (xmb->is_explore_list))
{
xmb->show_thumbnails = !xmb->show_thumbnails;
new_action = MENU_ACTION_NOOP;
}
break;
case MENU_ACTION_UP:
case MENU_ACTION_DOWN:
case MENU_ACTION_SCROLL_UP:
case MENU_ACTION_SCROLL_DOWN:
if (xmb->show_fullscreen_thumbnails && xmb->is_quick_menu)
return MENU_ACTION_NOOP;
break;
default:
/* In all other cases, pass through input
* menu action without intervention */
break;
}
switch (new_action)
{
case MENU_ACTION_UP:
case MENU_ACTION_DOWN:
case MENU_ACTION_LEFT:
case MENU_ACTION_RIGHT:
case MENU_ACTION_CANCEL:
if ( config_get_ptr()->bools.input_menu_singleclick_playlists
&& xmb->alpha_list < 1.0f)
{
xmb_animation_list_alpha(xmb, true);
new_action = MENU_ACTION_NOOP;
}
break;
default:
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);
}
static void xmb_layout_common(xmb_handle_t *xmb, float scale_factor, unsigned new_font_size)
{
float margins_title = xmb->margins_title;
float margins_title_h_offset = xmb->margins_title_horizontal_offset;
xmb->categories_active_zoom = 1.0f;
xmb->categories_passive_zoom = 0.5f;
xmb->items_active_zoom = 1.0f;
xmb->items_passive_zoom = 0.5f;
xmb->categories_active_alpha = 1.0f;
xmb->categories_passive_alpha = 0.75f;
xmb->items_active_alpha = 1.0f;
xmb->items_passive_alpha = 0.75f;
xmb->shadow_offset = 4.0f * scale_factor;
if (xmb->shadow_offset < 1.0)
xmb->shadow_offset = 1.0f;
if (xmb->shadow_offset > 2.0)
xmb->shadow_offset = 2.0f;
xmb->margins_title_left = (margins_title * scale_factor)
+ (4 * scale_factor)
+ (margins_title_h_offset * scale_factor);
xmb->margins_title_top = (margins_title * scale_factor)
+ (new_font_size - (new_font_size / 6) * scale_factor);
xmb->margins_title_bottom = (margins_title * scale_factor) + (4 * scale_factor);
xmb->cursor_size = 64.0f * scale_factor;
xmb->icon_size = 128.0f * scale_factor;
/* Limit minimum font size */
xmb->font_size = (xmb->font_size < 7) ? 7.0f : xmb->font_size;
xmb->font2_size = (xmb->font2_size < 6) ? 6.0f : xmb->font2_size;
}
static void xmb_layout_ps3(xmb_handle_t *xmb, int width)
{
float scale_factor = xmb->last_scale_factor;
unsigned new_font_size = 32 * scale_factor;
xmb->above_subitem_offset = 1.5f;
xmb->above_item_offset = -1.0f;
xmb->active_item_factor = 3.0f;
xmb->under_item_offset = 5.0f;
xmb->font_size = new_font_size;
xmb->font2_size = 22.0f * scale_factor;
xmb->icon_spacing_horizontal = 192.0f * scale_factor;
xmb->icon_spacing_vertical = 64.0f * scale_factor;
xmb->margins_screen_top = (256 + 16) * scale_factor;
xmb->margins_screen_left = 336.0f * scale_factor;
xmb->margins_label_left = 85.0f * scale_factor;
xmb->margins_label_top = new_font_size / 3.0f;
xmb->margins_setting_left = 660.0f * scale_factor * xmb->scale_mod[6];
xmb->margins_dialog = new_font_size * 2.0f;
xmb->margins_slice = new_font_size / 2.0f;
xmb_layout_common(xmb, scale_factor, new_font_size);
}
static void xmb_layout_psp(xmb_handle_t *xmb, int width)
{
float scale_factor = xmb->last_scale_factor;
unsigned new_font_size = 26 * scale_factor;
xmb->above_subitem_offset = 1.5f;
xmb->above_item_offset = -0.5f;
xmb->active_item_factor = 2.0f;
xmb->under_item_offset = 3.0f;
xmb->font_size = new_font_size;
xmb->font2_size = 18.0f * scale_factor;
xmb->icon_spacing_horizontal = 192.0f * scale_factor;
xmb->icon_spacing_vertical = 82.0f * scale_factor;
xmb->margins_screen_top = 192.0f * scale_factor;
xmb->margins_screen_left = 136.0f * scale_factor;
xmb->margins_label_left = 85.0f * scale_factor;
xmb->margins_label_top = new_font_size / 3.0f;
xmb->margins_setting_left = 520.0f * scale_factor * xmb->scale_mod[6];
xmb->margins_dialog = new_font_size * 2.0f;
xmb->margins_slice = new_font_size / 2.0f;
xmb_layout_common(xmb, scale_factor, new_font_size);
}
static void xmb_init_scale_mod(float *scale_mod, float scale_value)
{
uint8_t i;
/* 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} */
{
/* 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.024 * scale_value + 3.400;
/* 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;
}
}
static void xmb_layout(xmb_handle_t *xmb)
{
unsigned i;
unsigned width = xmb->last_width;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t selection = menu_st->selection_ptr;
unsigned current = (unsigned)selection;
unsigned end = (unsigned)MENU_LIST_GET_SELECTION(menu_list, 0)->size;
xmb_init_scale_mod(xmb->scale_mod, config_get_ptr()->floats.menu_scale_factor * 100.0f);
if (xmb->use_ps3_layout)
xmb_layout_ps3(xmb, width);
else
xmb_layout_psp(xmb, width);
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*)selection_buf->list[i].userdata;
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);
}
}
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";
#ifdef HAVE_IMAGEVIEWER
case XMB_TEXTURE_IMAGES:
return "images.png";
#endif
case XMB_TEXTURE_MUSICS:
return "musics.png";
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
case XMB_TEXTURE_MOVIES:
return "movies.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_RECORDREPLAY:
return "savestate.png";
case XMB_TEXTURE_PLAYREPLAY:
return "loadstate.png";
case XMB_TEXTURE_HALTREPLAY:
return "close.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_FAVORITE:
return "favorites-content.png";
case XMB_TEXTURE_IMAGE:
return "image.png";
case XMB_TEXTURE_MUSIC:
return "music.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_NETPLAY_ALT:
return "../../../ozone/png/sidebar/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 menu_xmb_theme)
{
unsigned i;
/* Invalidate in-flight context texture loads */
xmb_ctx_icon_load_gen++;
for (i = 0; i < XMB_TEXTURE_LAST; i++)
{
char texpath[PATH_MAX_LENGTH];
const char *texture_path = xmb_texture_path(i);
#ifdef HAVE_NETWORKING
if ( (i == XMB_TEXTURE_NETPLAY)
&& (menu_xmb_theme == XMB_ICON_THEME_MONOCHROME))
{
char alt_path[PATH_MAX_LENGTH];
fill_pathname_join_special(alt_path,
iconpath, xmb_texture_path(XMB_TEXTURE_NETPLAY_ALT),
sizeof(alt_path));
if (path_is_valid(alt_path))
texture_path = xmb_texture_path(XMB_TEXTURE_NETPLAY_ALT);
}
#endif
fill_pathname_join_special(texpath,
iconpath, texture_path, sizeof(texpath));
gfx_display_reset_icon_texture(texpath,
&xmb->textures.list[i], TEXTURE_FILTER_LINEAR,
NULL, NULL);
}
xmb->main_menu_node.icon = 0;
xmb->settings_tab_node.icon = 0;
xmb->history_tab_node.icon = 0;
xmb->favorites_tab_node.icon = 0;
#ifdef HAVE_IMAGEVIEWER
xmb->images_tab_node.icon = 0;
#endif
xmb->music_tab_node.icon = 0;
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
xmb->video_tab_node.icon = 0;
#endif
xmb->add_tab_node.icon = 0;
xmb->contentless_cores_tab_node.icon = 0;
#if defined(HAVE_LIBRETRODB)
xmb->explore_tab_node.icon = 0;
#endif
#ifdef HAVE_NETWORKING
xmb->netplay_tab_node.icon = 0;
#endif
xmb->main_menu_node.alpha = xmb->categories_active_alpha;
xmb->main_menu_node.zoom = xmb->categories_active_zoom;
xmb->settings_tab_node.alpha = xmb->categories_active_alpha;
xmb->settings_tab_node.zoom = xmb->categories_active_zoom;
xmb->history_tab_node.alpha = xmb->categories_active_alpha;
xmb->history_tab_node.zoom = xmb->categories_active_zoom;
xmb->favorites_tab_node.alpha = xmb->categories_active_alpha;
xmb->favorites_tab_node.zoom = xmb->categories_active_zoom;
#ifdef HAVE_IMAGEVIEWER
xmb->images_tab_node.alpha = xmb->categories_active_alpha;
xmb->images_tab_node.zoom = xmb->categories_active_zoom;
#endif
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.alpha = xmb->categories_active_alpha;
xmb->video_tab_node.zoom = xmb->categories_active_zoom;
#endif
xmb->add_tab_node.alpha = xmb->categories_active_alpha;
xmb->add_tab_node.zoom = xmb->categories_active_zoom;
xmb->contentless_cores_tab_node.alpha = xmb->categories_active_alpha;
xmb->contentless_cores_tab_node.zoom = xmb->categories_active_zoom;
#if defined(HAVE_LIBRETRODB)
xmb->explore_tab_node.alpha = xmb->categories_active_alpha;
xmb->explore_tab_node.zoom = xmb->categories_active_zoom;
#endif
#ifdef HAVE_NETWORKING
xmb->netplay_tab_node.alpha = xmb->categories_active_alpha;
xmb->netplay_tab_node.zoom = xmb->categories_active_zoom;
#endif
xmb->current_menu_icon = 0;
/* Recolor */
if ( menu_xmb_theme == XMB_ICON_THEME_MONOCHROME_INVERTED
|| menu_xmb_theme == XMB_ICON_THEME_AUTOMATIC_INVERTED)
memcpy(xmb->item_color_base, xmb_color_black, sizeof(xmb->item_color_base));
else
memcpy(xmb->item_color_base, xmb_color_white, sizeof(xmb->item_color_base));
}
static void xmb_context_reset_internal(xmb_handle_t *xmb,
bool is_threaded, bool reinit_textures, unsigned menu_xmb_theme)
{
char iconpath[PATH_MAX_LENGTH];
char fontpath[PATH_MAX_LENGTH];
gfx_display_t *p_disp = disp_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
const char *wideglyph_str = msg_hash_get_wideglyph_str();
iconpath[0] = '\0';
fontpath[0] = '\0';
fill_pathname_application_special(iconpath, sizeof(iconpath),
APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_ICONS);
xmb_layout(xmb);
if (xmb->font)
{
font_driver_free(xmb->font);
xmb->font = NULL;
}
if (xmb->font2)
{
font_driver_free(xmb->font2);
xmb->font2 = NULL;
}
fill_pathname_application_special(
fontpath, sizeof(fontpath), APPLICATION_SPECIAL_DIRECTORY_ASSETS_XMB_FONT);
xmb->font = gfx_display_font_file(p_disp,
fontpath, xmb->font_size, is_threaded);
xmb->font2 = gfx_display_font_file(p_disp,
fontpath, xmb->font2_size, is_threaded);
xmb->wideglyph_width = 100;
if (wideglyph_str)
{
int char_width = font_driver_get_message_width(
xmb->font, "a", 1, 1.0f);
int wideglyph_width = font_driver_get_message_width(
xmb->font, wideglyph_str, strlen(wideglyph_str), 1.0f);
if (wideglyph_width > 0 && char_width > 0)
xmb->wideglyph_width = wideglyph_width * 100 / char_width;
}
if (reinit_textures)
{
gfx_display_deinit_white_texture();
gfx_display_init_white_texture();
xmb->assets_missing = false;
xmb_context_reset_textures(xmb, iconpath, menu_xmb_theme);
}
xmb->allow_horizontal_animation = true;
xmb->allow_dynamic_wallpaper = true;
xmb_context_reset_horizontal_list(xmb);
xmb_set_title(xmb);
/* Must run after xmb_set_title(): xmb_path_dynamic_wallpaper()
* reads xmb->title_name to build the wallpaper filename. The
* previous order invoked this with a stale title_name from the
* prior stack position (or empty-string on cold boot, since
* xmb_handle_t is calloc-initialized), so the wallpaper loaded
* here could briefly be the wrong one until the next
* xmb_populate_entries -> xmb_update_dynamic_wallpaper chain
* corrected it. xmb_populate_entries already uses the correct
* set_title -> update_dynamic_wallpaper order. */
xmb_update_dynamic_wallpaper(xmb, true);
menu_screensaver_context_destroy(xmb->screensaver);
/* 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(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT)
|| gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON))
{
if ( xmb->is_playlist
|| xmb->is_db_manager_list
|| xmb->is_explore_list
|| xmb->is_file_list
|| xmb->is_quick_menu)
xmb_update_thumbnail_image(xmb);
if (xmb->is_playlist)
{
/* Synchronous populate — clear any pending deferred flag so
* xmb_render does not redundantly repeat this work once input
* settles. */
xmb->pending_dynamic_icons_repopulate = false;
xmb_populate_dynamic_icons(xmb);
}
}
/* Have to reset this, otherwise savestate
* thumbnail won't update after fullscreen toggle */
gfx_thumbnail_reset(&xmb->thumbnails.savestate);
xmb_update_savestate_thumbnail_image(xmb);
}
static void xmb_render(void *data,
unsigned width, unsigned height, bool is_idle)
{
size_t i;
/* c.f. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
* On some platforms (e.g. 32-bit x86 without SSE),
* gcc can produce inconsistent floating point results
* depending upon optimisation level. This can break
* floating point variable comparisons. A workaround is
* to declare the affected variable as 'volatile', which
* disables optimisations and removes excess precision
* (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c87) */
volatile float scale_factor;
xmb_handle_t *xmb = (xmb_handle_t*)data;
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
menu_input_t *menu_input = &menu_st->input_state;
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t end = selection_buf->size;
gfx_display_t *p_disp = disp_get_ptr();
gfx_animation_t *p_anim = anim_get_ptr();
size_t selection = menu_st->selection_ptr;
playlist_t *playlist = playlist_get_cached();
unsigned first = 0;
unsigned last = (unsigned)end;
unsigned gfx_thumbnail_upscale_threshold = settings->uints.gfx_thumbnail_upscale_threshold;
bool network_on_demand_thumbnails = settings->bools.network_on_demand_thumbnails;
if (!xmb)
return;
/* Handle deferred context reset from a previous scale factor /
* layout change. We must wait two xmb_render() calls (not one)
* because xmb_render() is called from runloop_check_state()
* BEFORE video_driver_cached_frame() in the same iteration.
* The sequence per iteration is:
* xmb_render() -> video_driver_cached_frame() -> gfx_frame()
* So on the frame that detects the change (N), we set the counter
* to 2. On frame N+1 xmb_render runs first (counter becomes 1),
* then gfx_frame completes frame N's GPU work. On frame N+2
* xmb_render runs (counter becomes 0), and now the GPU has finished
* all prior command lists, so it is safe to release textures and
* fonts via xmb_context_reset_internal(). */
if (xmb->pending_context_reset > 0)
{
if (--xmb->pending_context_reset == 0)
xmb_context_reset_internal(xmb, video_driver_is_threaded(), false,
settings->uints.menu_xmb_theme);
}
/* Fire deferred dynamic-icon repopulate once input has settled.
* Set by xmb_populate_entries when it wanted to run the work but
* the user was mid-mash (menu_st->scroll.acceleration > 0) — the
* intermediate tabs blown through during held-repeat don't get
* the populate, only whichever tab the user actually stops on.
* Re-checks is_playlist and menu_icon_thumbnails at fire time:
* the list that was pushed when the flag was set may no longer
* be the list being looked at now, and the gate uses the
* *current* state. If the final list is not a playlist, the
* pending-icons unload branch from populate_entries runs here
* instead. */
if ( xmb->pending_dynamic_icons_repopulate
&& (menu_st->scroll.acceleration == 0))
{
xmb->pending_dynamic_icons_repopulate = false;
if (xmb->is_playlist)
{
if (settings->uints.menu_icon_thumbnails)
xmb_populate_dynamic_icons(xmb);
}
else if (xmb->thumbnails.pending_icons != XMB_PENDING_THUMBNAIL_NONE)
xmb_unload_icon_thumbnail_textures(xmb);
}
/* Retry current-menu-icon resolution if a previous xmb_set_title()
* (typically the one invoked from xmb_context_reset_internal() after
* a fullscreen toggle) could not resolve the icon because an
* async-loaded sidebar or db-node icon handle was still 0.
* xmb_set_title() decrements the counter on each retry and clears
* it on success, so the loop terminates either way. */
if (xmb->current_menu_icon_retry > 0)
xmb_set_title(xmb);
xmb->use_ps3_layout = xmb_use_ps3_layout(settings->uints.menu_xmb_layout, width, height);
scale_factor = xmb_get_scale_factor(settings->floats.menu_scale_factor,
xmb->use_ps3_layout, width);
if ( (xmb->use_ps3_layout != xmb->last_use_ps3_layout)
|| (xmb->margins_title != xmb->last_margins_title)
|| (xmb->margins_title_horizontal_offset != xmb->last_margins_title_horizontal_offset)
|| (scale_factor != xmb->last_scale_factor))
{
xmb->last_use_ps3_layout = xmb->use_ps3_layout;
xmb->last_margins_title = xmb->margins_title;
xmb->last_margins_title_horizontal_offset = xmb->margins_title_horizontal_offset;
xmb->last_scale_factor = scale_factor;
/* Defer context reset by 2 frames — see comment above */
xmb->pending_context_reset = 2;
}
/* This must be set every frame when using a pointer,
* otherwise touchscreen input breaks when changing
* orientation */
p_disp->framebuf_width = width;
p_disp->framebuf_height = height;
/* Read pointer state */
menu_input_get_pointer_state(&xmb->pointer);
/* Direction locking for horizontal category dragging */
if (xmb->pointer.type != MENU_POINTER_DISABLED)
{
if (xmb->pointer.flags & MENU_INP_PTR_FLG_PRESSED)
{
if (xmb->drag_mode == XMB_DRAG_DETECTING)
{
/* Wait for movement threshold before locking direction */
float dx = fabs((float)xmb->pointer.x - xmb->drag_start_x);
float dy = fabs((float)xmb->pointer.y - xmb->drag_start_y);
float threshold = 10.0f;
if (dx > threshold || dy > threshold)
{
/* Lock to dominant direction */
if (dx > dy)
xmb->drag_mode = XMB_DRAG_HORIZONTAL;
else
xmb->drag_mode = XMB_DRAG_VERTICAL;
}
}
if (xmb->drag_mode == XMB_DRAG_HORIZONTAL)
{
/* Only allow horizontal category switching at the top level (depth == 1)
* When in submenus (depth > 1), horizontal drag is disabled */
if (xmb->depth == 1)
{
/* Apply horizontal drag to categories */
size_t list_size = xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL) + xmb->system_tab_end + 1;
float current_dx = (float)xmb->pointer.x - xmb->drag_start_x;
float min_x = -xmb->icon_spacing_horizontal * (float)(list_size > 0 ? list_size - 1 : 0);
float max_x = 0.0f;
xmb->categories_x_pos = xmb->categories_drag_start_pos + current_dx;
/* Bounds checking */
if (xmb->categories_x_pos > max_x)
xmb->categories_x_pos = max_x;
if (xmb->categories_x_pos < min_x)
xmb->categories_x_pos = min_x;
/* Update selection and switch categories during drag */
{
float normalized = -xmb->categories_x_pos / xmb->icon_spacing_horizontal;
size_t nearest = (size_t)(normalized + 0.5f);
/* Clamp to valid range */
if (nearest >= list_size)
nearest = list_size > 0 ? list_size - 1 : 0;
/* If category changed, do full switch */
if (nearest != xmb->categories_selection_ptr)
{
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
/* Determine direction */
enum menu_action action = (nearest > xmb->categories_selection_ptr)
? MENU_ACTION_RIGHT
: MENU_ACTION_LEFT;
/* Call list_cache to update state (increments/decrements by 1) */
if (menu_st->driver_ctx && menu_st->driver_ctx->list_cache)
menu_st->driver_ctx->list_cache(menu_st->userdata, MENU_LIST_HORIZONTAL, action);
/* Repopulate vertical list with new category content */
menu_driver_deferred_push_content_list(selection_buf);
/* Visual update without animation during drag */
xmb_list_switch_horizontal_list(xmb, false, 0);
}
}
}
}
else if (xmb->drag_mode == XMB_DRAG_VERTICAL)
{
/* Apply vertical drag to list selection */
menu_list_t *menu_list = menu_st->entries.list;
size_t list_size = MENU_LIST_GET_SELECTION(menu_list, 0)->size;
/* Calculate how many items to move based on drag distance */
float dy = (float)xmb->pointer.y - xmb->drag_start_y;
float item_height = xmb->icon_spacing_vertical;
/* Convert drag distance to item steps with threshold */
int steps = (int)((dy + (dy > 0 ? item_height * 0.5f : -item_height * 0.5f)) / item_height);
/* Calculate new selection (negative because dragging down = scroll up) */
int new_selection = (int)xmb->drag_start_selection - steps;
/* Clamp to valid range */
if (new_selection < 0)
new_selection = 0;
if (new_selection >= (int)list_size)
new_selection = list_size > 0 ? (int)list_size - 1 : 0;
/* Update selection if changed */
if ((size_t)new_selection != menu_st->selection_ptr)
{
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
uintptr_t tag = (uintptr_t)selection_buf;
menu_st->selection_ptr = (size_t)new_selection;
/* Kill existing animations to prevent stacking during continuous drag */
gfx_animation_kill_by_tag(&tag);
/* Enable animations for smooth scrolling */
xmb_navigation_set(xmb, true);
}
}
}
}
/* If menu screensaver is active, update
* screensaver and return */
if (xmb->show_screensaver)
{
menu_screensaver_iterate(
xmb->screensaver,
p_disp, p_anim,
(enum menu_screensaver_effect)settings->uints.menu_screensaver_animation,
settings->floats.menu_screensaver_animation_speed,
XMB_SCREENSAVER_TINT,
width, height,
settings->paths.directory_assets);
GFX_ANIMATION_CLEAR_ACTIVE(p_anim);
return;
}
if (xmb->pointer.type != MENU_POINTER_DISABLED)
{
size_t selection = menu_st->selection_ptr;
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 = (unsigned)end;
if (height)
xmb_calculate_visible_range(xmb, height,
end, (unsigned)selection, &first, &last);
for (i = (size_t)first; i <= (size_t)last; i++)
{
float entry_size = (i == (unsigned)selection) ?
xmb->icon_spacing_vertical * xmb->active_item_factor : xmb->icon_spacing_vertical;
float half_entry_size = entry_size * 0.5f;
float y_curr = xmb_item_y(xmb, (int)i, selection) + xmb->margins_screen_top;
int y1 = (int)((y_curr - half_entry_size) + 0.5f);
int y2 = (int)((y_curr + half_entry_size) + 0.5f);
if ( (pointer_y > y1)
&& (pointer_y < y2))
{
menu_input->ptr = (unsigned)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
* Skip during drag operations to avoid fighting with drag scrolling */
if ( (xmb->drag_mode == XMB_DRAG_NONE)
&& ((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_INITIALIZE(entry);
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)
xmb_menu_entry_action(xmb, &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)
xmb_menu_entry_action(xmb, &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)
xmb_menu_entry_action(xmb,
&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)
xmb_menu_entry_action(xmb,
&entry, selection,
(xmb->depth == 1)
? MENU_ACTION_LEFT
: MENU_ACTION_RIGHT);
break;
default:
/* Do nothing */
break;
}
}
}
/* Handle any pending icon thumbnail load requests */
if (xmb->thumbnails.pending_icons != XMB_PENDING_THUMBNAIL_NONE)
{
/* Walk the visible range and dispatch async stream requests for
* each unresolved entry. We deliberately do NOT sync-load on
* the main thread here. Doing so blocks the main thread long
* enough (PNG/JPEG decode + GPU texture upload, repeated for
* 10–20 entries) that the runloop misses several
* gfx_animation_update ticks — the next tick advances
* delta_time by the full elapsed wall-clock and tween easing
* functions evaluate near or past their duration, snapping
* any in-flight animation to its end state. The visible
* effect: the enter-playlist or tab-switch animation jumps
* straight to the end on the load frame.
*
* Async decode + upload runs on a worker thread and lands via
* task callback over the next several frames, keeping the
* main thread responsive and animations smooth. Path
* resolution stays inline (it's a few stat syscalls per
* unresolved entry, cheap enough not to block a frame). */
if (height)
xmb_calculate_visible_range(xmb, height, end, (unsigned)selection, &first, &last);
xmb->thumbnails.pending_icons = XMB_PENDING_THUMBNAIL_NONE;
for (i = first; i <= last; i++)
{
xmb_node_t *node = (xmb_node_t*)selection_buf->list[i].userdata;
xmb_icons_t *thumbnail_icon;
if (!node)
continue;
thumbnail_icon = &node->thumbnail_icon;
/* Already resolved and dispatched — nothing to do. */
if (thumbnail_icon->icon.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
continue;
/* Lazy path resolution: if icon_path is empty (fresh populate
* or prior unload cleared it), resolve now. This is the work
* xmb_populate_dynamic_icons and xmb_selection_pointer_changed
* used to pay eagerly for every visible entry on every change,
* including on each vertical scroll step — a stat-syscall
* storm multiplied ×5 by playlist_allow_non_png's extension
* fallback chain. Doing it here, once per entry per list
* population, eliminates the repeat cost on scroll. */
if (!*thumbnail_icon->thumbnail_path_data.icon_path)
xmb_set_dynamic_icon_content(xmb, NULL, i, thumbnail_icon);
/* Dispatch async. gfx_thumbnail_request_stream tolerates an
* empty icon_path: it transitions status to MISSING on the
* next frame so we stop retrying. */
gfx_thumbnail_request_stream(
&thumbnail_icon->thumbnail_path_data,
p_anim,
GFX_THUMBNAIL_ICON,
playlist, i,
&thumbnail_icon->icon,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
/* Status will be UNKNOWN until stream_delay elapses, then
* transitions PENDING (task queued) or MISSING (no path).
* Either way it leaves UNKNOWN within a few frames. While
* UNKNOWN, raise pending_icons so we keep ticking the
* stream's delay_timer via gfx_thumbnail_request_stream
* on subsequent frames; the dispatch above is idempotent
* (it returns early once status is no longer UNKNOWN). */
if (thumbnail_icon->icon.status == GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending_icons = XMB_PENDING_THUMBNAIL_ICONS;
}
}
else if (xmb->thumbnails.icon.status == GFX_THUMBNAIL_STATUS_UNKNOWN
&& xmb->thumbnails.pending == XMB_PENDING_THUMBNAIL_NONE)
{
xmb_node_t *node = (xmb_node_t*)selection_buf->list[selection].userdata;
if (node)
{
xmb_icons_t *thumbnail_icon = &node->thumbnail_icon;
gfx_thumbnail_request_stream(
&thumbnail_icon->thumbnail_path_data,
p_anim,
GFX_THUMBNAIL_ICON,
playlist, selection,
&xmb->thumbnails.icon,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (xmb->thumbnails.icon.status == GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_ICONS;
}
}
/* Handle any pending thumbnail load requests */
if (xmb->thumbnails.pending != XMB_PENDING_THUMBNAIL_NONE)
{
size_t selection = menu_st->selection_ptr;
playlist_t *playlist = playlist_get_cached();
unsigned gfx_thumbnail_upscale_threshold = settings->uints.gfx_thumbnail_upscale_threshold;
bool network_on_demand_thumbnails = settings->bools.network_on_demand_thumbnails;
/* Explore list needs cached selection index */
if (xmb->is_explore_list)
selection = menu_st->thumbnail_path_data ? menu_st->thumbnail_path_data->playlist_index : 0;
switch (xmb->thumbnails.pending)
{
case XMB_PENDING_THUMBNAIL_BOTH:
gfx_thumbnail_request_streams(
menu_st->thumbnail_path_data,
p_anim,
playlist, selection,
&xmb->thumbnails.right,
&xmb->thumbnails.left,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if ( xmb->thumbnails.right.status != GFX_THUMBNAIL_STATUS_UNKNOWN
&& xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
break;
case XMB_PENDING_THUMBNAIL_RIGHT:
gfx_thumbnail_request_stream(
menu_st->thumbnail_path_data,
p_anim,
GFX_THUMBNAIL_RIGHT,
playlist, selection,
&xmb->thumbnails.right,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (xmb->thumbnails.right.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
break;
case XMB_PENDING_THUMBNAIL_LEFT:
gfx_thumbnail_request_stream(
menu_st->thumbnail_path_data,
p_anim,
GFX_THUMBNAIL_LEFT,
playlist, selection,
&xmb->thumbnails.left,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
break;
case XMB_PENDING_THUMBNAIL_ICONS:
gfx_thumbnail_request_stream(
menu_st->thumbnail_path_data,
p_anim,
GFX_THUMBNAIL_ICON,
playlist, selection,
&xmb->thumbnails.icon,
gfx_thumbnail_upscale_threshold,
network_on_demand_thumbnails);
if (xmb->thumbnails.icon.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
break;
default:
break;
}
if (xmb->thumbnails.left.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails_left_status_prev = xmb->thumbnails.left.status;
if (xmb->thumbnails.right.status != GFX_THUMBNAIL_STATUS_UNKNOWN)
xmb->thumbnails_right_status_prev = xmb->thumbnails.right.status;
}
i = menu_st->entries.begin;
if (i >= end)
menu_st->entries.begin = 0;
GFX_ANIMATION_CLEAR_ACTIVE(p_anim);
}
static void xmb_draw_bg(
void *userdata,
gfx_display_t *p_disp,
gfx_display_ctx_driver_t *dispctx,
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,
const char *bg_file_path,
float *coord_black,
float *coord_white)
{
gfx_display_ctx_draw_t draw;
struct video_coords coords;
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.pipeline_id = 0;
draw.pipeline_active = (menu_shader_pipeline == XMB_SHADER_PIPELINE_WALLPAPER) ? false : true;
if (!video_width || !video_height)
return;
if (dispctx->blend_begin)
dispctx->blend_begin(userdata);
/* Draw background wallpaper */
if ( draw.texture
&& ( xmb_color_theme == XMB_THEME_WALLPAPER
|| ((bg_file_path && *bg_file_path) && !strstr(bg_file_path, FILE_PATH_BACKGROUND_IMAGE))))
{
draw.color = &coord_white[0];
gfx_display_set_alpha(draw.color, coord_white[3]);
gfx_display_draw_bg(p_disp, &draw, &coords, userdata, true, menu_wallpaper_opacity);
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
/* Draw empty color theme gradient */
else
{
draw.color = xmb_gradient_ident(xmb_color_theme);
draw.texture = 0;
gfx_display_set_alpha(draw.color, coord_white[3]);
gfx_display_draw_bg(p_disp, &draw, &coords, userdata, true, alpha);
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
#ifdef HAVE_SHADERPIPELINE
/* Draw pipeline */
if (menu_shader_pipeline > XMB_SHADER_PIPELINE_WALLPAPER)
{
switch (menu_shader_pipeline)
{
default:
case XMB_SHADER_PIPELINE_WALLPAPER:
draw.pipeline_id = VIDEO_SHADER_STOCK_BLEND;
break;
case XMB_SHADER_PIPELINE_RIBBON:
draw.pipeline_id = VIDEO_SHADER_MENU;
break;
case XMB_SHADER_PIPELINE_SIMPLE_RIBBON:
draw.pipeline_id = VIDEO_SHADER_MENU_2;
break;
#if !defined(VITA)
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;
#endif
}
if (dispctx->draw_pipeline)
dispctx->draw_pipeline(&draw, p_disp,
userdata, video_width, video_height);
if (dispctx->draw)
dispctx->draw(&draw, userdata, video_width, video_height);
}
#endif
if (dispctx->blend_end)
dispctx->blend_end(userdata);
}
static void xmb_draw_dark_layer(
xmb_handle_t *xmb,
gfx_display_t *p_disp,
gfx_display_ctx_driver_t *dispctx,
void *userdata,
unsigned width,
unsigned height,
float alpha)
{
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,
};
draw.x = 0;
draw.y = 0;
draw.width = width;
draw.height = height;
draw.color = &black[0];
draw.vertex = NULL;
draw.matrix_data = NULL;
draw.tex_coord = NULL;
draw.vertex_count = 4;
draw.texture = 0;
draw.pipeline_id = 0;
draw.pipeline_active = false;
if (dispctx->blend_begin)
dispctx->blend_begin(userdata);
gfx_display_draw_bg(p_disp, &draw, &coords, userdata, true, MIN(xmb->alpha, alpha));
if (draw.height > 0 && draw.width > 0)
if (dispctx && dispctx->draw)
dispctx->draw(&draw, userdata, width, height);
if (dispctx->blend_end)
dispctx->blend_end(userdata);
}
static void xmb_draw_no_thumbnail_available(
xmb_handle_t *xmb,
gfx_display_t *p_disp,
void *userdata,
unsigned video_width,
unsigned video_height,
unsigned x_position,
unsigned y_position,
unsigned view_width,
unsigned view_height,
bool shadows_enable,
bool draw_text,
float *color,
math_matrix_4x4 *mymat)
{
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
unsigned icon_size = (unsigned)xmb->icon_size;
if (icon_size < 64)
icon_size = 64;
if (icon_size > view_width || icon_size > view_height)
icon_size = MIN(view_width, view_height);
if (draw_text)
y_position -= (icon_size / 5.0f);
if (dispctx)
{
gfx_display_set_alpha(color, xmb->alpha * 0.20f);
if (dispctx->blend_begin)
dispctx->blend_begin(userdata);
if (dispctx->draw)
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
false,
icon_size,
icon_size,
xmb->textures.list[XMB_TEXTURE_IMAGE],
x_position + ((view_width - icon_size) / 2),
y_position + ((view_height - icon_size) / 2) + icon_size,
video_width,
video_height,
xmb->alpha,
0,
1,
&color[0],
xmb->shadow_offset / 2,
mymat);
if (dispctx->blend_end)
dispctx->blend_end(userdata);
}
if (!draw_text)
return;
xmb_draw_text(shadows_enable, xmb, config_get_ptr(),
msg_hash_to_str(MSG_NO_THUMBNAIL_AVAILABLE),
x_position + (view_width / 2),
video_height - y_position - ((view_height - icon_size) / 2),
1, 1, TEXT_ALIGN_CENTER,
video_width, video_height,
xmb->font2);
}
static void xmb_draw_fullscreen_thumbnails(
xmb_handle_t *xmb,
gfx_animation_t *p_anim,
gfx_display_t *p_disp,
void *userdata,
unsigned video_width,
unsigned video_height,
bool shadows_enable,
unsigned xmb_color_theme,
float *color,
settings_t *settings, size_t selection)
{
static float right_thumbnail_draw_width_prev = 0.0f;
static float right_thumbnail_draw_height_prev = 0.0f;
static float left_thumbnail_draw_width_prev = 0.0f;
static float left_thumbnail_draw_height_prev = 0.0f;
/* Check whether fullscreen thumbnails are visible */
if (xmb->fullscreen_thumbnail_alpha > 0.0f || xmb->want_fullscreen_thumbnails)
{
int header_height;
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 = &xmb->thumbnails.left;
gfx_thumbnail_t *left_thumbnail = &xmb->thumbnails.right;
int view_width = (int)video_width;
int view_height = (int)video_height;
int thumbnail_margin = (int)xmb->icon_size / 2;
int frame_width = (int)xmb->shadow_offset * 2;
uint32_t title_color = 0xFFFFFF00;
uint8_t 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 background_alpha = 0.95f;
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,
};
float header_alpha = 1.0f;
float header_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,
};
float frame_color[16] = {
0.05f, 0.05f, 0.05f, 1.0f,
0.05f, 0.05f, 0.05f, 1.0f,
0.05f, 0.05f, 0.05f, 1.0f,
0.05f, 0.05f, 0.05f, 1.0f,
};
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;
bool show_header = *xmb->fullscreen_thumbnail_label;
bool show_right_thumbnail = false;
bool show_left_thumbnail = false;
header_height = show_header ? (int)((float)xmb->font_size * 1.5f) + (frame_width * 4) : 0;
/* Sanity check: Return immediately if the view
* width/height is < 1 */
if ((view_width < 1) || (view_height < 1))
goto error;
/* Get thumbnail pointers */
/* Get number of 'active' thumbnails */
show_right_thumbnail =
( right_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE
|| right_thumbnail->status == GFX_THUMBNAIL_STATUS_PENDING);
show_left_thumbnail =
( left_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE
|| left_thumbnail->status == GFX_THUMBNAIL_STATUS_PENDING);
if ( (xmb->is_quick_menu && *xmb->savestate_thumbnail_file_path)
|| (xmb->is_state_slot))
{
left_thumbnail = &xmb->thumbnails.savestate;
show_left_thumbnail = (left_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE);
show_right_thumbnail = false;
}
if (show_right_thumbnail)
num_thumbnails++;
if (show_left_thumbnail)
num_thumbnails++;
/* 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
&& right_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
{
gfx_thumbnail_get_draw_dimensions(right_thumbnail,
thumbnail_box_width, thumbnail_box_height, 1.0f,
&right_thumbnail_draw_width, &right_thumbnail_draw_height);
right_thumbnail_draw_width_prev = right_thumbnail_draw_width;
right_thumbnail_draw_height_prev = right_thumbnail_draw_height;
/* Sanity check */
if ( (right_thumbnail_draw_width <= 0.0f)
|| (right_thumbnail_draw_height <= 0.0f))
show_right_thumbnail = false;
}
else if (right_thumbnail->status == GFX_THUMBNAIL_STATUS_PENDING)
{
right_thumbnail_draw_width = right_thumbnail_draw_width_prev;
right_thumbnail_draw_height = right_thumbnail_draw_height_prev;
}
if ( show_left_thumbnail
&& left_thumbnail->status == GFX_THUMBNAIL_STATUS_AVAILABLE)
{
gfx_thumbnail_get_draw_dimensions(
left_thumbnail,
thumbnail_box_width, thumbnail_box_height, 1.0f,
&left_thumbnail_draw_width, &left_thumbnail_draw_height);
left_thumbnail_draw_width_prev = left_thumbnail_draw_width;
left_thumbnail_draw_height_prev = left_thumbnail_draw_height;
/* Sanity check */
if ( (left_thumbnail_draw_width <= 0.0f)
|| (left_thumbnail_draw_height <= 0.0f))
show_left_thumbnail = false;
}
else if (left_thumbnail->status == GFX_THUMBNAIL_STATUS_PENDING)
{
left_thumbnail_draw_width = left_thumbnail_draw_width_prev;
left_thumbnail_draw_height = left_thumbnail_draw_height_prev;
}
/* 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 */
gfx_display_set_alpha(frame_color, xmb->fullscreen_thumbnail_alpha);
/* Darken background */
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
0,
0,
(unsigned)view_width,
(unsigned)view_height,
(unsigned)view_width,
(unsigned)view_height,
background_color,
NULL);
/* Draw header */
if (show_header)
{
/* Background */
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
0,
0,
(unsigned)view_width,
(unsigned)header_height,
(unsigned)view_width,
(unsigned)view_height,
header_color,
NULL);
/* Title text */
if (menu_ticker_smooth)
{
char title_buf[NAME_MAX_LENGTH];
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 = p_anim->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 * 1.33f,
(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 * 1.33f,
(unsigned)view_width,
(unsigned)view_height,
title_color,
TEXT_ALIGN_CENTER,
1.0f, false, 0.0f, false);
}
/* Draw thumbnails */
/* > Configure shadow effect */
/* > Disabled for now since the images already have a background border */
if (0 && shadows_enable)
{
float shadow_offset = xmb->icon_size / 24.0f;
thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_DROP;
thumbnail_shadow.alpha = GFX_SHADOW_ALPHA;
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(
p_disp,
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,
NULL);
/* 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(
p_disp,
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,
NULL);
/* 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);
}
/* Show "No thumbnail available" */
if ( !show_right_thumbnail
&& !show_left_thumbnail)
xmb_draw_no_thumbnail_available(
xmb,
p_disp,
userdata,
video_width,
video_height,
0,
0,
video_width,
video_height,
shadows_enable,
true,
color,
NULL);
}
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;
char title_msg[128];
char msg[1024];
char title_truncated[NAME_MAX_LENGTH];
gfx_thumbnail_shadow_t thumbnail_shadow;
size_t selection = 0;
size_t percent_width = 0;
size_t title_header_max_width = 0;
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();
float thumbnail_scale_factor = (float)settings->uints.menu_xmb_thumbnail_scale_factor / 100.0f;
bool menu_core_enable = settings->bools.menu_core_enable;
bool show_title_header = settings->bools.menu_xmb_show_title_header;
bool vertical_thumbnails = settings->bools.menu_xmb_vertical_thumbnails;
unsigned vertical_fade_factor = settings->uints.menu_xmb_vertical_fade_factor;
unsigned current_menu_icon = settings->uints.menu_xmb_current_menu_icon;
void *userdata = video_info->userdata;
unsigned video_width = video_info->width;
unsigned video_height = video_info->height;
bool shadows_enable = video_info->xmb_shadows_enable;
float 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 mouse_grabbed = video_info->input_driver_grab_mouse_state;
bool menu_mouse_enable = video_info->menu_mouse_enable;
unsigned 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;
gfx_display_t *p_disp = (gfx_display_t*)video_info->disp_userdata;
gfx_animation_t *p_anim = anim_get_ptr();
gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
video_driver_state_t *video_st = video_state_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
bool input_dialog_display_kb = menu_input_dialog_get_display_kb();
bool render_background = false;
bool icon_thumbnail_drawn = false;
if (!xmb)
return;
/* Cache the per-frame size on the handle so non-render paths
* can read it without locking video_st. */
xmb->last_width = video_width;
xmb->last_height = video_height;
/* Snapshot context generation — if xmb_context_destroy()
* runs on the main thread while we are mid-render on the
* video thread, the generation will change and we must
* abandon the frame to avoid use-after-free on freed
* textures and fonts. */
{
uint32_t ctx_gen = xmb->context_generation;
/* Per-frame scratch color arrays — local to this stack frame
* to avoid data races with the GPU driver thread.
* coord_black: zero RGB, alpha set per use
* coord_white: one RGB, alpha set per use
* xmb_item_color: base color from theme (black or white), alpha set per use */
{
float coord_black[16] = {0};
float coord_white[16];
float xmb_item_color[16];
memcpy(coord_white, xmb_color_white, sizeof(coord_white));
memcpy(xmb_item_color, xmb->item_color_base, sizeof(xmb_item_color));
/* Snapshot texture handles — the main thread may write new
* handles via async icon load callbacks or context reset at
* any time. Copy the entire array once and use the snapshot
* for all rendering this frame, eliminating torn reads and
* use-after-free on handles freed by context destroy. */
{
uintptr_t tex_list[XMB_TEXTURE_LAST];
uintptr_t tex_bg = xmb->textures.bg;
memcpy(tex_list, xmb->textures.list, sizeof(tex_list));
if (tex_list[XMB_TEXTURE_MAIN_MENU])
xmb->main_menu_node.icon = tex_list[XMB_TEXTURE_MAIN_MENU];
if (tex_list[XMB_TEXTURE_SETTINGS])
xmb->settings_tab_node.icon = tex_list[XMB_TEXTURE_SETTINGS];
if (tex_list[XMB_TEXTURE_HISTORY])
xmb->history_tab_node.icon = tex_list[XMB_TEXTURE_HISTORY];
if (tex_list[XMB_TEXTURE_FAVORITES])
xmb->favorites_tab_node.icon = tex_list[XMB_TEXTURE_FAVORITES];
#ifdef HAVE_IMAGEVIEWER
if (tex_list[XMB_TEXTURE_IMAGES])
xmb->images_tab_node.icon = tex_list[XMB_TEXTURE_IMAGES];
#endif
if (tex_list[XMB_TEXTURE_MUSICS])
xmb->music_tab_node.icon = tex_list[XMB_TEXTURE_MUSICS];
#if defined(HAVE_FFMPEG) || defined(HAVE_MPV)
if (tex_list[XMB_TEXTURE_MOVIES])
xmb->video_tab_node.icon = tex_list[XMB_TEXTURE_MOVIES];
#endif
if (tex_list[XMB_TEXTURE_ADD])
xmb->add_tab_node.icon = tex_list[XMB_TEXTURE_ADD];
if (tex_list[XMB_TEXTURE_CORE])
xmb->contentless_cores_tab_node.icon = tex_list[XMB_TEXTURE_CORE];
#if defined(HAVE_LIBRETRODB)
if (tex_list[XMB_TEXTURE_RDB])
xmb->explore_tab_node.icon = tex_list[XMB_TEXTURE_RDB];
#endif
#ifdef HAVE_NETWORKING
if (tex_list[XMB_TEXTURE_NETPLAY])
xmb->netplay_tab_node.icon = tex_list[XMB_TEXTURE_NETPLAY];
#endif
msg[0] = '\0';
title_msg[0] = '\0';
title_truncated[0] = '\0';
/* If menu screensaver is active, draw
* screensaver and return */
if (xmb->show_screensaver)
{
menu_screensaver_frame(xmb->screensaver, video_info, p_disp);
return;
}
if (video_st->current_video && video_st->current_video->set_viewport)
video_st->current_video->set_viewport(
video_st->data, video_width, video_height, true, false);
pseudo_font_length = xmb->icon_spacing_horizontal * 4 - xmb->icon_size / 4.0f;
left_thumbnail_margin_width = floorf(xmb->icon_size * (xmb->use_ps3_layout ? 3.3f : 2.0f));
right_thumbnail_margin_width =
(float)video_width - (xmb->icon_size / 2.0f) -
(xmb->margins_screen_left * xmb->scale_mod[5]) -
xmb->icon_spacing_horizontal - pseudo_font_length;
thumbnail_margin_height_under = (float)video_height
- 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 / 4.0f;
right_thumbnail_margin_x = (float)video_width - left_thumbnail_margin_x
- right_thumbnail_margin_width;
xmb->margins_title = (float)settings->ints.menu_xmb_title_margin * 10.0f;
xmb->margins_title_horizontal_offset = (float)settings->ints.menu_xmb_title_margin_horizontal_offset * 10.0f;
/* Configure shadow effect */
if (shadows_enable)
{
thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_OUTLINE;
thumbnail_shadow.alpha = 0.2f;
thumbnail_shadow.outline.width = xmb->last_scale_factor;
if (thumbnail_shadow.outline.width < 1)
thumbnail_shadow.outline.width = 1;
}
else
thumbnail_shadow.type = GFX_THUMBNAIL_SHADOW_NONE;
/* Guard: bail if context was destroyed after we started */
if (ctx_gen != xmb->context_generation)
goto ctx_destroyed;
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;
/* Single-click playlist button hold delay */
if (xmb->alpha_list == 0.0f && xmb->draw_entry_delay)
{
xmb->draw_entry_delay--;
if (!xmb->draw_entry_delay)
xmb_animation_list_alpha(xmb, true);
}
/* Blank dummy core output */
if (!libretro_running)
{
gfx_display_set_alpha(coord_black, 1.0f);
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
0,
0,
video_width,
video_height,
video_width,
video_height,
coord_black,
NULL);
}
gfx_display_set_alpha(coord_black, MIN((float)alpha_factor / 100, xmb->alpha));
gfx_display_set_alpha(coord_white, xmb->alpha);
if (dispctx)
xmb_draw_bg(
userdata,
p_disp,
dispctx,
video_width,
video_height,
menu_shader_pipeline,
color_theme,
MIN(xmb->alpha, menu_wallpaper_opacity),
libretro_running,
MIN(xmb->alpha, alpha_factor / 100),
tex_bg,
xmb->bg_file_path,
coord_black,
coord_white);
/* Visual consistency during deferred context reset:
* xmb_render() set pending_context_reset on a scale-factor /
* layout change; the actual reset (which rebuilds icon_size,
* margins, fonts and textures at the new scale) won't fire
* until the counter reaches 0 — see the comment in xmb_render().
* In the interim frames, layout values and asset sizes are
* mismatched, which produces a visible flash. Skip everything
* past the background quad (icons, text, ribbon-overlay items,
* cursor, message box) until the reset completes; the gradient
* we just drew is the entire frame for those 1-2 frames. */
if (xmb->pending_context_reset > 0)
goto ctx_destroyed;
selection = menu_st->selection_ptr;
if (!p_disp->dispctx->handles_transform)
{
float cosine = 1.0f; /* cos(rad) = cos(0) = 1.0f */
float sine = 0.0f; /* sine(rad) = sine(0) = 0.0f */
gfx_display_rotate_z(p_disp, &mymat, cosine, sine, userdata);
}
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
/* List icons */
xmb_draw_items(
userdata,
p_disp,
dispctx,
p_anim,
menu_st,
settings,
video_width,
video_height,
shadows_enable,
xmb,
selection_buf,
selection,
xmb->categories_selection_ptr,
&xmb_item_color[0],
video_width,
video_height,
&mymat);
/* Current menu icon + arrow */
gfx_display_set_alpha(xmb_item_color, xmb->alpha * xmb->alpha_list);
if (current_menu_icon == XMB_CURRENT_MENU_ICON_NONE)
xmb->current_menu_icon = 0;
/* Show icon thumbnail instead if enabled */
if ( (xmb->is_quick_menu || xmb->is_playlist)
&& settings->uints.menu_icon_thumbnails)
{
gfx_thumbnail_t *icon_thumbnail = &xmb->thumbnails.icon;
bool show_icon_thumbnail =
gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_ICON)
&& icon_thumbnail->status != GFX_THUMBNAIL_STATUS_MISSING;
icon_thumbnail_drawn = show_icon_thumbnail;
if (show_icon_thumbnail)
{
size_t selection = menu_st->selection_ptr;
xmb_node_t *node = (xmb_node_t*)selection_buf->list[selection].userdata;
float gfx_icon_x_margin = left_thumbnail_margin_x * (xmb->use_ps3_layout ? 1 : 0);
float gfx_icon_x = gfx_icon_x_margin;
float gfx_icon_y = xmb->margins_screen_top + (xmb->icon_size / 1.5f);
float gfx_icon_width = left_thumbnail_margin_width;
float gfx_icon_height = xmb->icon_size * (xmb->use_ps3_layout ? 2 : 1.2f);
float gfx_icon_width_draw = 0;
float gfx_icon_height_draw = 0;
float playlist_extra_x = 0;
gfx_thumbnail_get_draw_dimensions(
icon_thumbnail,
gfx_icon_width, gfx_icon_height, 1.0f,
&gfx_icon_width_draw, &gfx_icon_height_draw);
playlist_extra_x = xmb->icon_size + fabs(gfx_icon_width - gfx_icon_width_draw) / 2;
if (xmb->is_playlist)
gfx_icon_x += playlist_extra_x;
gfx_icon_x += node->x;
if (gfx_icon_x < gfx_icon_x_margin)
gfx_icon_x = gfx_icon_x_margin;
else if (gfx_icon_x > gfx_icon_x_margin + playlist_extra_x)
gfx_icon_x = gfx_icon_x_margin + playlist_extra_x;
gfx_thumbnail_draw(
userdata,
video_width,
video_height,
icon_thumbnail,
gfx_icon_x,
gfx_icon_y,
gfx_icon_width,
gfx_icon_height,
GFX_THUMBNAIL_ALIGN_CENTRE,
1.0f,
1.0f,
&thumbnail_shadow);
}
}
if (dispctx && dispctx->blend_end)
dispctx->blend_end(userdata);
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
if ( !xmb->assets_missing
&& !(icon_thumbnail_drawn && current_menu_icon == XMB_CURRENT_MENU_ICON_NORMAL)
&& xmb->current_menu_icon
&& current_menu_icon)
{
int icon_size = xmb->icon_size;
int current_x = xmb->x
+ xmb->margins_screen_left
+ xmb->icon_spacing_horizontal
- (icon_size / 2.0f);
int current_y = xmb->margins_screen_top
+ (icon_size / 2.0f)
+ (xmb->icon_spacing_vertical * xmb->active_item_factor);
if (current_menu_icon == XMB_CURRENT_MENU_ICON_TITLE)
{
icon_size /= 3.0f;
current_x = xmb->margins_title_left - (icon_size / 6.0f);
current_y = xmb->margins_title_top + (icon_size / 4.0f);
}
if (xmb->depth > 1 || current_menu_icon == XMB_CURRENT_MENU_ICON_TITLE)
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
icon_size,
icon_size,
xmb->current_menu_icon,
current_x,
current_y,
video_width,
video_height,
xmb->alpha,
0,
1,
&xmb_item_color[0],
xmb->shadow_offset,
&mymat);
gfx_display_set_alpha(xmb_item_color, MIN(xmb->items_passive_alpha, xmb->alpha));
if (xmb->depth > 1 && current_menu_icon == XMB_CURRENT_MENU_ICON_NORMAL)
{
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
xmb->icon_size,
xmb->icon_size,
tex_list[XMB_TEXTURE_ARROW],
current_x + (xmb->use_ps3_layout ? (icon_size * 1.1f) : (icon_size * 0.70f)),
current_y,
video_width,
video_height,
xmb->alpha,
0,
0.75f,
&xmb_item_color[0],
xmb->shadow_offset,
&mymat);
}
}
if (dispctx && dispctx->blend_end)
dispctx->blend_end(userdata);
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
/* Horizontal tab icons */
if (!xmb->assets_missing)
{
unsigned horizontal_list_size = (xmb->show_playlist_tabs)
? (unsigned)xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL)
: 0;
for (i = 0; i <= xmb->system_tab_end + horizontal_list_size; i++)
{
xmb_node_t *node = xmb_get_node(xmb, i);
if (!node)
continue;
gfx_display_set_alpha(xmb_item_color, MIN(node->alpha, xmb->alpha));
if (xmb_item_color[3] != 0)
{
uintptr_t texture = node->icon;
float x;
float y;
float scale_factor;
/* Fallback for nodes whose icon hasn't resolved yet
* (e.g. .lvw nodes when CURSOR wasn't loaded during
* list build, or .lpl nodes with an in-flight async
* load). Use the shared CURSOR texture for this frame
* only — do NOT write it back into node->icon, or
* xmb_context_destroy_horizontal_list will later
* double-free it when it unloads the node's icon
* alongside tex_list[CURSOR]. */
if (!texture && tex_list[XMB_TEXTURE_CURSOR])
texture = tex_list[XMB_TEXTURE_CURSOR];
x = xmb->x + xmb->categories_x_pos
+ xmb->margins_screen_left
+ xmb->icon_spacing_horizontal * (i + 1)
- xmb->icon_size / 2.0;
y = xmb->margins_screen_top
+ xmb->icon_size / 2.0;
scale_factor = node->zoom;
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
xmb->icon_size,
xmb->icon_size,
texture,
x,
y,
video_width,
video_height,
xmb->alpha,
0,
scale_factor,
&xmb_item_color[0],
xmb->shadow_offset,
&mymat);
}
}
}
/**************************/
/* Draw thumbnails: START */
/**************************/
/* Reset thumbnails when starting/closing content */
if (xmb->libretro_running != libretro_running)
{
xmb->libretro_running = libretro_running;
if (xmb->is_quick_menu && libretro_running)
xmb->fullscreen_thumbnails_available = false;
else if (xmb->is_quick_menu && !libretro_running)
xmb->fullscreen_thumbnails_available = true;
}
/* Allow browsing playlist in fullscreen thumbnail mode */
if ( (xmb->is_playlist || xmb->is_explore_list || xmb->is_state_slot)
&& (xmb->show_fullscreen_thumbnails)
&& (menu_st->selection_ptr != xmb->fullscreen_thumbnail_selection))
xmb_show_fullscreen_thumbnails(xmb, menu_st, menu_st->selection_ptr);
else if (!xmb->show_fullscreen_thumbnails
&& xmb->want_fullscreen_thumbnails)
xmb_show_fullscreen_thumbnails(xmb, menu_st, menu_st->selection_ptr);
/* Save state thumbnail, right side in PS3 layout, left in PSP layout */
if ( (xmb->is_quick_menu || xmb->is_state_slot)
&& *xmb->savestate_thumbnail_file_path
&& (!xmb->show_fullscreen_thumbnails)
)
{
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,
};
float thumb_width = right_thumbnail_margin_width;
float thumb_height = thumb_width - (thumb_width / 4);
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 = (video_height - thumb_height) / 2.0f;
/* Make sure thumbnail stays visible with extra wide screens */
if (thumb_height > video_height)
thumb_y -= (thumb_height - video_height) / 2.0f;
if (!xmb->use_ps3_layout)
{
float height_offset = xmb->icon_size * 0.35f;
thumb_width = left_thumbnail_margin_width;
thumb_height = thumb_width - (thumb_width / 4);
scaled_thumb_width = thumb_width * thumbnail_scale_factor;
scaled_thumb_height = thumb_height * thumbnail_scale_factor;
thumb_x = (thumb_width - scaled_thumb_width) / 2.0f;
thumb_y = video_height - thumbnail_margin_height_under - height_offset;
}
if ( (xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| (xmb->thumbnails.savestate.status == GFX_THUMBNAIL_STATUS_PENDING))
{
if (settings->bools.menu_thumbnail_background_enable)
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
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);
}
else
{
/* Darken background */
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
xmb_draw_no_thumbnail_available(
xmb,
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
shadows_enable,
false,
xmb_item_color,
NULL);
}
}
else if (xmb->fullscreen_thumbnails_available && !xmb->show_fullscreen_thumbnails)
{
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,
};
bool thumbnail_background = settings->bools.menu_thumbnail_background_enable;
bool show_right_thumbnail =
(gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_RIGHT))
&& xmb->show_thumbnails
&& ( (xmb->thumbnails.right.status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| ( xmb->thumbnails.right.status < GFX_THUMBNAIL_STATUS_AVAILABLE
&& xmb->thumbnails_right_status_prev <= GFX_THUMBNAIL_STATUS_AVAILABLE
&& xmb->thumbnails_right_status_prev != GFX_THUMBNAIL_STATUS_UNKNOWN));
bool show_left_thumbnail =
(gfx_thumbnail_is_enabled(menu_st->thumbnail_path_data, GFX_THUMBNAIL_LEFT))
&& ( (xmb->thumbnails.left.status == GFX_THUMBNAIL_STATUS_AVAILABLE)
|| ( xmb->thumbnails.left.status < GFX_THUMBNAIL_STATUS_AVAILABLE
&& xmb->thumbnails_left_status_prev <= GFX_THUMBNAIL_STATUS_AVAILABLE
&& xmb->thumbnails_left_status_prev != GFX_THUMBNAIL_STATUS_UNKNOWN));
/* 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 (vertical_thumbnails)
{
if (!xmb->show_thumbnails)
show_right_thumbnail = show_left_thumbnail = false;
/* 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 / 8) + thumb_y_offset;
/* Darken background */
if (thumbnail_background)
{
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
right_thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
left_thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
}
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);
}
/* 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 / 1.5f);
if (thumbnail_background)
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
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,
(thumbnail_background) ? GFX_THUMBNAIL_ALIGN_CENTRE : 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 / 1.5f);
if (thumbnail_background)
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
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,
(thumbnail_background) ? GFX_THUMBNAIL_ALIGN_CENTRE : GFX_THUMBNAIL_ALIGN_TOP,
1.0f, 1.0f, &thumbnail_shadow);
}
/* Left thumbnail, left side */
if (show_left_thumbnail)
{
bool icon_thumbnails = settings->uints.menu_icon_thumbnails;
uint8_t current_menu_icon = settings->uints.menu_xmb_current_menu_icon;
float margins_title_min = -(xmb->icon_size / 5.0f);
float margins_title = (xmb->margins_title_bottom < margins_title_min)
? margins_title_min : xmb->margins_title_bottom;
float height_offset = (current_menu_icon != XMB_CURRENT_MENU_ICON_NORMAL && !icon_thumbnails)
? 0 : xmb->icon_size * (icon_thumbnails ? 2.05f : 1.5f);
float thumb_width = left_thumbnail_margin_width;
float thumb_height = thumbnail_margin_height_under - margins_title - height_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 + height_offset + (xmb->icon_size / 1.5f);
if (thumb_height > xmb->icon_size)
{
if (thumbnail_background)
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
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,
(thumbnail_background) ? GFX_THUMBNAIL_ALIGN_CENTRE : 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)
{
bool icon_thumbnails = settings->uints.menu_icon_thumbnails;
uint8_t current_menu_icon = settings->uints.menu_xmb_current_menu_icon;
float margins_title_min = -(xmb->icon_size / 5.0f);
float margins_title = (xmb->margins_title_bottom < margins_title_min)
? margins_title_min : xmb->margins_title_bottom;
float height_offset = (current_menu_icon != XMB_CURRENT_MENU_ICON_NORMAL && !icon_thumbnails)
? 0 : xmb->icon_size * (icon_thumbnails ? 1.25f : 1.25f);
float thumb_width = left_thumbnail_margin_width;
float thumb_height = thumbnail_margin_height_under - margins_title - height_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 + height_offset + (xmb->icon_size / 1.5f);
/* Very small thumbnails look ridiculous
* > Impose a minimum size limit */
if (thumb_height > xmb->icon_size)
{
if (thumbnail_background)
gfx_display_draw_quad(
p_disp,
userdata,
video_width,
video_height,
thumb_x,
thumb_y,
scaled_thumb_width,
scaled_thumb_height,
video_width,
video_height,
background_color,
NULL);
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,
(thumbnail_background) ? GFX_THUMBNAIL_ALIGN_CENTRE : GFX_THUMBNAIL_ALIGN_TOP,
1.0f, 1.0f, &thumbnail_shadow);
}
}
}
if (dispctx && dispctx->blend_end)
dispctx->blend_end(userdata);
/**************************/
/* Draw thumbnails: END */
/**************************/
/* Clock image */
gfx_display_set_alpha(xmb_item_color, MIN(xmb->alpha, 1.00f));
if (battery_level_enable)
{
gfx_display_ctx_powerstate_t powerstate;
char msg[12];
msg[0] = '\0';
menu_display_powerstate(&powerstate, msg, sizeof(msg));
if (powerstate.battery_enabled)
{
size_t x_pos = xmb->icon_size / 5;
if (!xmb->assets_missing)
{
float margin_offset = -(xmb->icon_size / 2) - (7 * xmb->last_scale_factor);
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
xmb->icon_size,
xmb->icon_size,
tex_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->margins_title_left + margin_offset,
xmb->icon_size + xmb->margins_title_top + margin_offset,
video_width,
video_height,
xmb->alpha,
0,
1,
&xmb_item_color[0],
xmb->shadow_offset,
&mymat);
if (dispctx && dispctx->blend_end)
dispctx->blend_end(userdata);
}
percent_width = (unsigned)
font_driver_get_message_width(
xmb->font, msg, strlen(msg), 1.0f);
title_header_max_width = x_pos;
xmb_draw_text(shadows_enable, xmb, settings, 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[256];
size_t _len = 0;
size_t x_pos = 0;
if (percent_width)
x_pos += percent_width + (xmb->icon_size / 2);
if (!xmb->assets_missing)
{
float margin_offset = -(xmb->icon_size / 2) - (7 * xmb->last_scale_factor);
if (dispctx && dispctx->blend_begin)
dispctx->blend_begin(userdata);
xmb_draw_icon(
userdata,
p_disp,
dispctx,
video_width,
video_height,
shadows_enable,
xmb->icon_size,
xmb->icon_size,
tex_list[XMB_TEXTURE_CLOCK],
video_width - xmb->margins_title_left + margin_offset - x_pos,
xmb->icon_size + xmb->margins_title_top + margin_offset,
video_width,
video_height,
xmb->alpha,
0,
1,
&xmb_item_color[0],
xmb->shadow_offset,
&mymat);
if (dispctx && dispctx->blend_end)
dispctx->blend_end(userdata);
}
datetime.time_mode = settings->uints.menu_timedate_style;
datetime.date_separator = settings->uints.menu_timedate_date_separator;
_len = menu_display_timedate(&datetime, timedate, sizeof(timedate));
title_header_max_width = x_pos + font_driver_get_message_width(
xmb->font, timedate, _len, 1.0f);
xmb_draw_text(shadows_enable, xmb, settings, 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);
}
/* Use alternative title if available */
strlcpy(title_truncated,
*xmb->title_name_alt
? xmb->title_name_alt
: xmb->title_name,
sizeof(title_truncated));
if (!vertical_fade_factor && 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 */
if (show_title_header)
{
gfx_animation_ctx_ticker_t ticker;
gfx_animation_ctx_ticker_smooth_t ticker_smooth;
char tmp[NAME_MAX_LENGTH];
size_t icon_len = (settings->uints.menu_xmb_current_menu_icon == XMB_CURRENT_MENU_ICON_TITLE)
? xmb->icon_size / 2.5f : 0;
size_t tmp_len = (video_width
- xmb->margins_title_left
- xmb->icon_size
- icon_len
- title_header_max_width);
unsigned ticker_x_offset = 0;
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;
tmp[0] = '\0';
/* Initial ticker configuration */
if (use_smooth_ticker)
{
ticker_smooth.idx = p_anim->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.selected = true;
ticker_smooth.x_offset = &ticker_x_offset;
ticker_smooth.dst_str_width = NULL;
}
else
{
ticker.idx = p_anim->ticker_idx;
ticker.type_enum = menu_ticker_type;
ticker.spacer = NULL;
ticker.selected = true;
}
if (use_smooth_ticker)
{
ticker_smooth.field_width = (unsigned)tmp_len;
ticker_smooth.src_str = title_truncated;
ticker_smooth.dst_str = tmp;
ticker_smooth.dst_str_len = sizeof(tmp);
if (ticker_smooth.src_str)
gfx_animation_ticker_smooth(&ticker_smooth);
}
else
{
unsigned font_width = (unsigned)
font_driver_get_message_width(
xmb->font, "a", 1, 1.0f);
ticker.str = title_truncated;
ticker.s = tmp;
ticker.len = (tmp_len * xmb->last_scale_factor) / font_width;
if (ticker.str)
gfx_animation_ticker(&ticker);
}
xmb_draw_text(shadows_enable, xmb, settings,
tmp,
(float)ticker_x_offset + xmb->margins_title_left + icon_len,
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(shadows_enable, xmb, settings,
title_msg,
xmb->margins_title_left,
video_height - xmb->margins_title_bottom,
1, 1, TEXT_ALIGN_LEFT,
video_width, video_height, xmb->font);
}
/* Guard: bail if context was destroyed during the draw pass */
if (ctx_gen != xmb->context_generation)
goto ctx_destroyed;
/* First-frame init done — subsequent frames are normal.
* Cleared after the last ctx-destroyed guard so that a context
* death mid-frame leaves the flag set for the retry. */
xmb->is_first_frame = false;
if (xmb->font && xmb->font->renderer && xmb->font->renderer->flush)
xmb->font->renderer->flush(video_width,
video_height, xmb->font->renderer_data);
if (xmb->font2 && xmb->font2->renderer && xmb->font2->renderer->flush)
xmb->font2->renderer->flush(video_width,
video_height, xmb->font2->renderer_data);
font_driver_bind_block(xmb->font, NULL);
font_driver_bind_block(xmb->font2, NULL);
/* Draw fullscreen thumbnails, if required */
xmb_draw_fullscreen_thumbnails(
xmb,
p_anim,
p_disp,
userdata,
video_width,
video_height,
shadows_enable,
color_theme,
xmb_item_color,
settings, selection);
if (input_dialog_display_kb)
{
const char *str = menu_input_dialog_get_buffer();
const char *label = menu_st->input_dialog_kb_label;
size_t _len = strlcpy(msg, label, sizeof(msg) - 2);
msg[ _len] = '\n';
msg[++_len] = '\0';
strlcpy(msg + _len,
str,
sizeof(msg) - _len);
render_background = true;
}
if (xmb->box_message && *xmb->box_message)
{
strlcpy(msg, xmb->box_message, sizeof(msg));
free(xmb->box_message);
xmb->box_message = NULL;
render_background = true;
}
if (render_background)
{
if (dispctx)
xmb_draw_dark_layer(xmb, p_disp, dispctx,
userdata, video_width, video_height,
(input_dialog_display_kb) ? 0.95f : 0.75f);
if (xmb->font && *msg)
xmb_render_messagebox_internal(userdata, p_disp,
dispctx,
video_width, video_height,
xmb, msg, &mymat);
}
/* Cursor image */
if (xmb->show_mouse)
{
bool cursor_visible = menu_mouse_enable
&& (video_fullscreen || mouse_grabbed);
gfx_display_set_alpha(coord_white, MIN(xmb->alpha, 1.00f));
if (cursor_visible)
gfx_display_draw_cursor(
p_disp,
userdata,
video_width,
video_height,
cursor_visible,
&coord_white[0],
xmb->cursor_size,
tex_list[XMB_TEXTURE_POINTER],
xmb->pointer.x,
xmb->pointer.y,
video_width,
video_height);
}
if (video_st->current_video && video_st->current_video->set_viewport)
video_st->current_video->set_viewport(
video_st->data, video_width, video_height, false, true);
ctx_destroyed:
; /* no-op — reached if context was destroyed mid-frame */
} /* end of texture snapshot scope */
} /* end of local color array scope */
} /* end of context generation scope */
}
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;
gfx_display_t *p_disp = disp_get_ptr();
video_coord_array_t *ca = &p_disp->dispca;
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));
/* NULL-check both callocs: the for-loop below unconditionally
* writes into ribbon_verts via xmb_ribbon_set_vertex, and the
* video_coord_array_append call at the bottom passes dummy
* as color/tex_coord/lut_tex_coord which the underlying
* append implementation would copy from (reading NULL).
* Skip ribbon init entirely on OOM - the ribbon is a
* decorative background animation; its absence is visually
* degraded but not functionally broken. The free()s at
* the bottom are NULL-safe. */
if (!dummy || !ribbon_verts)
{
free(dummy);
free(ribbon_verts);
return;
}
/* 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)
{
xmb_handle_t *xmb = NULL;
menu_handle_t *menu = menu_state_get_ptr()->driver_data;
if (menu)
if ((xmb = (xmb_handle_t*)menu->userdata))
*(ticker_pixel_increment) *= xmb->last_scale_factor;
}
static void *xmb_init(void **userdata, bool video_is_threaded)
{
int i;
unsigned width, height;
xmb_handle_t *xmb = NULL;
settings_t *settings = config_get_ptr();
gfx_animation_t *p_anim = anim_get_ptr();
gfx_display_t *p_disp = disp_get_ptr();
menu_handle_t *menu = (menu_handle_t*)calloc(1, sizeof(*menu));
struct menu_state *menu_st = menu_state_get_ptr();
if (!menu)
return NULL;
video_driver_get_output_size(&width, &height);
if (!(xmb = (xmb_handle_t*)calloc(1, sizeof(xmb_handle_t))))
{
free(menu);
return NULL;
}
/* Initialise last_{width,height} from the snapshot taken
* above; xmb_frame will refresh these every render. */
xmb->last_width = width;
xmb->last_height = height;
xmb_init_scale_mod(xmb->scale_mod, settings->floats.menu_scale_factor * 100.0f);
*userdata = xmb;
xmb->categories_active_idx = 0;
xmb->categories_active_idx_old = 0;
xmb->x = 0;
xmb->categories_x_pos = 0;
xmb->depth = 1;
xmb->old_depth = 1;
xmb->alpha = 0.0f;
xmb->alpha_list = 1.0f;
xmb->is_first_frame = true;
xmb_refresh_system_tabs_list(xmb);
for (i = 0; i < XMB_TAB_MAX_LENGTH; i++)
xmb->tab_selection[i] = 0;
menu_st->flags &= ~MENU_ST_FLAG_PREVENT_POPULATE;
/* TODO/FIXME - we don't use framebuffer at all
* for XMB, we should refactor this dependency
* away. */
p_disp->framebuf_width = width;
p_disp->framebuf_height = height;
gfx_display_init_white_texture();
xmb->horizontal_list.list = NULL;
xmb->horizontal_list.capacity = 0;
xmb->horizontal_list.size = 0;
xmb_init_horizontal_list(xmb);
xmb_init_ribbon(xmb);
/* Initialise screensaver */
if (!(xmb->screensaver = menu_screensaver_init()))
goto error;
xmb->fullscreen_thumbnails_available = false;
xmb->show_fullscreen_thumbnails = false;
xmb->skip_thumbnail_reset = false;
xmb->show_thumbnails = true;
xmb->fullscreen_thumbnail_alpha = 0.0f;
xmb->fullscreen_thumbnail_selection = 0;
xmb->fullscreen_thumbnail_label[0] = '\0';
xmb->savestate_thumbnail_file_path[0] = '\0';
xmb->thumbnails.pending = XMB_PENDING_THUMBNAIL_NONE;
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->uints.menu_xmb_layout, width, height);
xmb->last_use_ps3_layout = xmb->use_ps3_layout;
xmb->last_scale_factor = xmb_get_scale_factor(
settings->floats.menu_scale_factor,
xmb->use_ps3_layout, width);
xmb->margins_title = (float)settings->ints.menu_xmb_title_margin * 10.0f;
xmb->last_margins_title = xmb->margins_title;
xmb->margins_title_horizontal_offset = (float)settings->ints.menu_xmb_title_margin_horizontal_offset * 10.0f;
xmb->last_margins_title_horizontal_offset = xmb->margins_title_horizontal_offset;
p_anim->updatetime_cb = xmb_menu_animation_update_time;
/* set word_wrap function pointer */
xmb->word_wrap =
msg_hash_get_wideglyph_str() ? word_wrap_wideglyph : word_wrap;
return menu;
error:
free(menu);
/* See comment in xmb_refresh_horizontal_list: the map's
* values are non-owning pointers into the horizontal_list,
* so it must be freed before the nodes are. */
RHMAP_FREE(xmb->playlist_db_node_map);
xmb_free_list_nodes(&xmb->horizontal_list, false);
file_list_deinitialize(&xmb->horizontal_list);
return NULL;
}
static void xmb_free(void *data)
{
xmb_handle_t *xmb = (xmb_handle_t*)data;
if (xmb)
{
/* Invalidate any in-flight async icon loads before freeing
* the nodes they would write into */
xmb_icon_load_gen++;
xmb_ctx_icon_load_gen++;
/* See comment in xmb_refresh_horizontal_list: free the
* db_node_map before the nodes its values point into. */
RHMAP_FREE(xmb->playlist_db_node_map);
xmb_free_list_nodes(&xmb->horizontal_list, false);
file_list_deinitialize(&xmb->horizontal_list);
video_coord_array_free(&xmb->raster_block.carr);
video_coord_array_free(&xmb->raster_block2.carr);
if (xmb->box_message)
free(xmb->box_message);
if (xmb->bg_file_path)
free(xmb->bg_file_path);
menu_screensaver_free(xmb->screensaver);
}
gfx_display_deinit_white_texture();
font_driver_bind_block(NULL, NULL);
}
static void xmb_context_bg_destroy(xmb_handle_t *xmb)
{
if (!xmb)
return;
video_driver_texture_unload(&xmb->textures.bg);
gfx_display_deinit_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_load(data,
TEXTURE_FILTER_LINEAR,
&xmb->textures.bg);
gfx_display_init_white_texture();
break;
case MENU_IMAGE_NONE:
default:
xmb_context_bg_destroy(xmb);
gfx_display_init_white_texture();
break;
}
return true;
}
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,
config_get_ptr()->uints.menu_xmb_theme);
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, scroll);
}
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;
struct menu_state *menu_st = menu_state_get_ptr();
size_t selection = menu_st->selection_ptr;
if (!xmb || !list)
return;
if (!(node = (xmb_node_t*)list->list[i].userdata))
{
if (!(node = xmb_alloc_node()))
return;
}
current = (int)selection;
if (fullpath && *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;
}
list->list[i].userdata = 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_cache(void *data, enum menu_list_type type,
unsigned action)
{
size_t stack_size, list_size;
xmb_handle_t *xmb = (xmb_handle_t*)data;
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *menu_stack = MENU_LIST_GET(menu_list, 0);
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
size_t selection = menu_st->selection_ptr;
unsigned horizontal_list_size = (xmb->show_playlist_tabs)
? (unsigned)xmb_list_get_size(xmb, MENU_LIST_HORIZONTAL)
: 0;
if (!xmb)
return;
/* Check whether to enable the horizontal animation. */
if (xmb->allow_horizontal_animation)
{
unsigned first = 0, last = 0;
unsigned height = xmb->last_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->selection_ptr_old -= first;
last -= first;
first = 0;
}
else
xmb->selection_ptr_old = selection;
list_size = xmb->system_tab_end + horizontal_list_size;
switch (type)
{
case MENU_LIST_PLAIN:
break;
case MENU_LIST_HORIZONTAL:
xmb->categories_selection_ptr_old = xmb->categories_selection_ptr;
/* Remember last selection per tab */
xmb->tab_selection[xmb->categories_selection_ptr] = selection;
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;
#if defined(HAVE_LIBRETRODB)
case XMB_SYSTEM_TAB_EXPLORE:
menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_EXPLORE_TAB));
menu_stack->list[stack_size - 1].type = MENU_EXPLORE_TAB;
break;
#endif
case XMB_SYSTEM_TAB_CONTENTLESS_CORES:
menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB));
menu_stack->list[stack_size - 1].type = MENU_CONTENTLESS_CORES_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;
/* Signal the render path to stop using textures/fonts.
* Under threaded video, xmb_frame() may be mid-render on
* the video thread when this runs on the main thread. */
xmb->context_generation++;
/* Invalidate in-flight async icon loads before unloading */
xmb_icon_load_gen++;
xmb_ctx_icon_load_gen++;
for (i = 0; i < XMB_TEXTURE_LAST; i++)
video_driver_texture_unload(&xmb->textures.list[i]);
xmb_unload_thumbnail_textures(xmb);
xmb_unload_icon_thumbnail_textures(xmb);
/* The matching xmb_context_reset_internal will synchronously repopulate
* dynamic icons on a playlist. Clear any pending deferred flag so
* xmb_render does not fire between destroy and reset, which would walk
* the selection list with already-reset thumbnail state only to have
* it wiped and redone by the reset. */
xmb->pending_dynamic_icons_repopulate = false;
xmb_context_destroy_horizontal_list(xmb);
xmb_context_bg_destroy(xmb);
font_driver_free(xmb->font);
xmb->font = NULL;
font_driver_free(xmb->font2);
xmb->font2 = NULL;
menu_screensaver_context_destroy(xmb->screensaver);
}
static void xmb_fade_out(xmb_handle_t *xmb)
{
if (xmb)
xmb->alpha = 0;
}
static void xmb_fade_in(xmb_handle_t *xmb)
{
gfx_animation_ctx_entry_t anim_entry;
uintptr_t tag = 0;
if (!xmb)
return;
tag = (uintptr_t)&xmb->textures.bg;
anim_entry.duration = XMB_DELAY * 1.25f;
anim_entry.subject = &xmb->alpha;
anim_entry.easing_enum = EASING_IN_QUINT;
anim_entry.tag = tag;
anim_entry.cb = NULL;
anim_entry.target_value = xmb->items_active_alpha;
gfx_animation_push(&anim_entry);
}
static void xmb_toggle(void *userdata, bool menu_on)
{
xmb_handle_t *xmb = (xmb_handle_t*)userdata;
struct menu_state *menu_st = menu_state_get_ptr();
if (!xmb)
return;
/* Reset */
xmb->alpha_list = 1.0f;
if (!menu_on)
{
xmb_fade_out(xmb);
return;
}
/* Have to reset this, otherwise savestate
* thumbnail won't update after selecting
* 'save state' option */
if (xmb->is_quick_menu)
{
gfx_thumbnail_reset(&xmb->thumbnails.savestate);
if (xmb->libretro_running)
xmb->fullscreen_thumbnails_available = false;
}
if (MENU_ENTRIES_NEEDS_REFRESH(menu_st))
menu_st->flags &= ~MENU_ST_FLAG_PREVENT_POPULATE;
else
menu_st->flags |= MENU_ST_FLAG_PREVENT_POPULATE;
xmb_toggle_horizontal_list(xmb);
/* Skip the fade-in on the very first frame after init: at
* startup xmb_toggle(true) fires from retroarch_menu_running()
* before the first xmb_frame, and xmb_fade_in animates xmb->alpha
* from its init value of 0 up to items_active_alpha over ~200ms.
* xmb->alpha gates the entire menu including the wallpaper, so
* the first ~12 frames render as a fully black screen before the
* menu appears. The menu has no prior state to fade in from at
* startup, so jump straight to full opacity instead. Subsequent
* toggles (menu hotkey from gameplay) are unaffected. */
if (xmb->is_first_frame)
xmb->alpha = xmb->items_active_alpha;
else
xmb_fade_in(xmb);
}
static int xmb_deferred_push_content_actions(menu_displaylist_info_t *info)
{
settings_t *settings = config_get_ptr();
if (!menu_displaylist_ctl(DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, info, settings))
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)
{
switch (type)
{
case DISPLAYLIST_MENU_SETTINGS_LIST:
{
xmb_handle_t *xmb = (xmb_handle_t*)userdata;
xmb_update_dynamic_wallpaper(xmb, false);
break;
}
}
/* Use common lists for all drivers */
return -1;
}
static bool xmb_menu_init_list(void *data)
{
menu_displaylist_info_t info;
settings_t *settings = config_get_ptr();
struct menu_state *menu_st = menu_state_get_ptr();
menu_list_t *menu_list = menu_st->entries.list;
file_list_t *menu_stack = MENU_LIST_GET(menu_list, 0);
file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, 0);
menu_displaylist_info_init(&info);
info.label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU));
info.type_default = FILE_TYPE_PLAIN;
info.enum_idx = MENU_ENUM_LABEL_MAIN_MENU;
menu_entries_append(menu_stack, info.path,
info.label,
MENU_ENUM_LABEL_MAIN_MENU,
info.type, info.flags, 0, NULL);
info.list = selection_buf;
if (!menu_displaylist_ctl(DISPLAYLIST_MAIN_MENU, &info, settings))
goto error;
info.flags |= MD_FLAG_NEED_PUSH;
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_down(void *userdata,
unsigned x, unsigned y,
unsigned ptr, menu_file_list_cbs_t *cbs,
menu_entry_t *entry, unsigned action)
{
xmb_handle_t *xmb = (xmb_handle_t*)userdata;
if (!xmb)
return -1;
/* Initialize drag detection */
xmb->drag_mode = XMB_DRAG_DETECTING;
xmb->drag_start_x = (float)x;
xmb->drag_start_y = (float)y;
xmb->categories_drag_start_pos = xmb->categories_x_pos;
xmb->drag_start_selection = menu_state_get_ptr()->selection_ptr;
return 0;
}
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;
struct menu_state *menu_st = menu_state_get_ptr();
size_t selection = menu_st->selection_ptr;
menu_list_t *menu_list = menu_st->entries.list;
unsigned end = (unsigned)MENU_LIST_GET_SELECTION(menu_list, 0)->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;
}
/* Handle snap animation if we were dragging */
if (xmb->drag_mode == XMB_DRAG_HORIZONTAL || xmb->drag_mode == XMB_DRAG_VERTICAL)
{
/* Snap horizontal scrolling to final category position */
if (xmb->drag_mode == XMB_DRAG_HORIZONTAL)
{
settings_t *settings = config_get_ptr();
bool horizontal_animation = settings->bools.menu_horizontal_animation;
float target_x = xmb->icon_spacing_horizontal * -(float)xmb->categories_selection_ptr;
/* Animate to exact category position */
if (horizontal_animation)
{
gfx_animation_ctx_entry_t anim_entry;
uintptr_t cat_tag = (uintptr_t)&xmb->categories_x_pos;
anim_entry.duration = XMB_DELAY;
anim_entry.target_value = target_x;
anim_entry.subject = &xmb->categories_x_pos;
anim_entry.easing_enum = EASING_OUT_QUAD;
anim_entry.tag = cat_tag;
anim_entry.cb = NULL;
/* See xmb_list_switch for the rationale — same unique-tag
* kill-before-push pattern so drag-release doesn't stack
* a tween on top of an in-flight tab-switch tween. */
gfx_animation_kill_by_tag(&cat_tag);
if (anim_entry.subject)
gfx_animation_push(&anim_entry);
}
else
{
uintptr_t cat_tag = (uintptr_t)&xmb->categories_x_pos;
gfx_animation_kill_by_tag(&cat_tag);
xmb->categories_x_pos = target_x;
}
}
xmb->drag_mode = XMB_DRAG_NONE;
return 0;
}
width = xmb->last_width;
height = xmb->last_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:
/* Switch tabs */
if (xmb->depth == 1)
{
if ( (int)y >= margin_top - (xmb->icon_size + xmb->icon_spacing_vertical) / 2
&& (int)y <= margin_top + (xmb->icon_size + xmb->icon_spacing_vertical) / 2)
{
int current_tab_x = margin_left + xmb->icon_spacing_horizontal;
int tab_delta = floor(((int)x - current_tab_x + (xmb->icon_spacing_horizontal / 2)) / xmb->icon_spacing_horizontal);
if (tab_delta)
{
int i = 0;
menu_entry_t entry;
MENU_ENTRY_INITIALIZE(entry);
menu_entry_get(&entry, 0, menu_st->selection_ptr, NULL, true);
/* Icon animations get stuck if they happen too fast,
* therefore allow it only on the last action */
xmb->allow_horizontal_animation = false;
/* Prevent dynamic wallpaper flashing during jump */
xmb->allow_dynamic_wallpaper = false;
if (tab_delta < 0)
{
for (i = tab_delta; i < 0; i++)
xmb_menu_entry_action(xmb,
&entry, menu_st->selection_ptr, MENU_ACTION_LEFT);
}
else
{
for (i = 0; i < tab_delta; i++)
xmb_menu_entry_action(xmb,
&entry, menu_st->selection_ptr, MENU_ACTION_RIGHT);
}
xmb->allow_horizontal_animation = true;
xmb->allow_dynamic_wallpaper = true;
}
return MENU_ACTION_NOOP;
}
}
/* - 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 ((int)x < margin_left)
{
if ((int)y >= margin_top)
return xmb_menu_entry_action(xmb, entry, selection, MENU_ACTION_CANCEL);
return menu_input_dialog_start_search() ? 0 : -1;
}
else if ((int)x > margin_right)
return xmb_menu_entry_action(xmb, entry, selection, MENU_ACTION_SELECT);
else if (ptr <= (end - 1))
{
/* Perform 'select' on the pointed item */
menu_st->selection_ptr = ptr;
xmb_navigation_set(xmb, false);
return xmb_menu_entry_action(xmb, entry, ptr, MENU_ACTION_SELECT);
}
break;
case MENU_INPUT_GESTURE_LONG_PRESS:
/* 'Reset to default' action */
if ((ptr <= end - 1) && (ptr == selection))
return xmb_menu_entry_action(xmb, 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 ((int)y > margin_top)
xmb_menu_entry_action(xmb, 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 ((int)y > margin_top)
xmb_menu_entry_action(xmb, 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 ((int)x < margin_left)
xmb_menu_entry_action(xmb, entry, selection, MENU_ACTION_SCROLL_DOWN);
else if ((int)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_st->selection_ptr = (size_t)last;
xmb_navigation_set(xmb, true);
}
else
{
size_t menu_list_size = menu_st->entries.list
? MENU_LIST_GET_SELECTION(menu_st->entries.list, 0)->size : 0;
size_t new_selection = menu_list_size - 1;
menu_st->selection_ptr = new_selection;
if (menu_st->driver_ctx->navigation_set_last)
menu_st->driver_ctx->navigation_set_last(menu_st->userdata);
}
}
break;
case MENU_INPUT_GESTURE_SWIPE_DOWN:
/* Swipe down in left margin: descend alphabet */
if ((int)x < margin_left)
xmb_menu_entry_action(xmb,
entry, selection, MENU_ACTION_SCROLL_UP);
else if ((int)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 = 0;
size_t step = 0;
/* 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++;
}
if (bottom_idx >= selection)
step = bottom_idx - selection;
if (selection > step)
new_idx = selection - step;
if (new_idx > 0)
{
menu_st->selection_ptr = new_idx;
xmb_navigation_set(xmb, true);
}
else
{
bool pending_push = false;
menu_driver_ctl(MENU_NAVIGATION_CTL_CLEAR, &pending_push);
}
}
break;
default:
/* Ignore input */
break;
}
return 0;
}
menu_ctx_driver_t menu_ctx_xmb = {
NULL,
xmb_messagebox,
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_content,
gfx_display_osk_ptr_at_pos,
xmb_update_savestate_thumbnail_path,
xmb_update_savestate_thumbnail_image,
xmb_pointer_down,
xmb_pointer_up,
xmb_menu_entry_action
};