diff --git a/Makefile.common b/Makefile.common index c0ba4415d0..6ca80f040e 100644 --- a/Makefile.common +++ b/Makefile.common @@ -870,7 +870,8 @@ ifeq ($(HAVE_GFX_WIDGETS), 1) gfx/widgets/gfx_widget_screenshot.o \ gfx/widgets/gfx_widget_volume.o \ gfx/widgets/gfx_widget_generic_message.o \ - gfx/widgets/gfx_widget_libretro_message.o + gfx/widgets/gfx_widget_libretro_message.o \ + gfx/widgets/gfx_widget_progress_message.o ifeq ($(HAVE_CHEEVOS), 1) OBJ += gfx/widgets/gfx_widget_achievement_popup.o endif diff --git a/cores/libretro-ffmpeg/ffmpeg_core.c b/cores/libretro-ffmpeg/ffmpeg_core.c index 49b562bffd..27753d6a28 100644 --- a/cores/libretro-ffmpeg/ffmpeg_core.c +++ b/cores/libretro-ffmpeg/ffmpeg_core.c @@ -193,11 +193,20 @@ static struct { unsigned width; unsigned height; - + double interpolate_fps; unsigned sample_rate; float aspect; + + struct + { + double time; + unsigned hours; + unsigned minutes; + unsigned seconds; + } duration; + } media; #ifdef HAVE_SSA @@ -473,7 +482,13 @@ static void check_variables(bool firststart) static void seek_frame(int seek_frames) { char msg[256]; - struct retro_message msg_obj = {0}; + struct retro_message_ext msg_obj = {0}; + unsigned seek_hours = 0; + unsigned seek_minutes = 0; + unsigned seek_seconds = 0; + int8_t seek_progress = -1; + + msg[0] = '\0'; if ((seek_frames < 0 && (unsigned)-seek_frames > frame_cnt) || reset_triggered) frame_cnt = 0; @@ -484,10 +499,34 @@ static void seek_frame(int seek_frames) do_seek = true; seek_time = frame_cnt / media.interpolate_fps; - snprintf(msg, sizeof(msg), "Seek: %u s.", (unsigned)seek_time); - msg_obj.msg = msg; - msg_obj.frames = 180; - CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE, &msg_obj); + /* Convert seek time to a printable format */ + seek_seconds = (unsigned)seek_time; + seek_minutes = seek_seconds / 60; + seek_seconds %= 60; + seek_hours = seek_minutes / 60; + seek_minutes %= 60; + + snprintf(msg, sizeof(msg), "%02d:%02d:%02d / %02d:%02d:%02d", + seek_hours, seek_minutes, seek_seconds, + media.duration.hours, media.duration.minutes, media.duration.seconds); + + /* Get current progress */ + if (media.duration.time > 0.0) + { + seek_progress = (int8_t)((100.0 * seek_time / media.duration.time) + 0.5); + seek_progress = (seek_progress < -1) ? -1 : seek_progress; + seek_progress = (seek_progress > 100) ? 100 : seek_progress; + } + + /* Send message to frontend */ + msg_obj.msg = msg; + msg_obj.duration = 2000; + msg_obj.priority = 3; + msg_obj.level = RETRO_LOG_INFO; + msg_obj.target = RETRO_MESSAGE_TARGET_OSD; + msg_obj.type = RETRO_MESSAGE_TYPE_PROGRESS; + msg_obj.progress = seek_progress; + CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg_obj); if (seek_frames < 0) { @@ -579,7 +618,9 @@ void CORE_PREFIX(retro_run)(void) if (l && !last_l && audio_streams_num > 0) { char msg[256]; - struct retro_message msg_obj = {0}; + struct retro_message_ext msg_obj = {0}; + + msg[0] = '\0'; slock_lock(decode_thread_lock); audio_streams_ptr = (audio_streams_ptr + 1) % audio_streams_num; @@ -587,23 +628,36 @@ void CORE_PREFIX(retro_run)(void) snprintf(msg, sizeof(msg), "Audio Track #%d.", audio_streams_ptr); - msg_obj.msg = msg; - msg_obj.frames = 180; - CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE, &msg_obj); + msg_obj.msg = msg; + msg_obj.duration = 3000; + msg_obj.priority = 1; + msg_obj.level = RETRO_LOG_INFO; + msg_obj.target = RETRO_MESSAGE_TARGET_ALL; + msg_obj.type = RETRO_MESSAGE_TYPE_NOTIFICATION; + msg_obj.progress = -1; + CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg_obj); } else if (r && !last_r && subtitle_streams_num > 0) { char msg[256]; - struct retro_message msg_obj = {0}; + struct retro_message_ext msg_obj = {0}; + + msg[0] = '\0'; slock_lock(decode_thread_lock); subtitle_streams_ptr = (subtitle_streams_ptr + 1) % subtitle_streams_num; slock_unlock(decode_thread_lock); snprintf(msg, sizeof(msg), "Subtitle Track #%d.", subtitle_streams_ptr); - msg_obj.msg = msg; - msg_obj.frames = 180; - CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE, &msg_obj); + + msg_obj.msg = msg; + msg_obj.duration = 3000; + msg_obj.priority = 1; + msg_obj.level = RETRO_LOG_INFO; + msg_obj.target = RETRO_MESSAGE_TARGET_ALL; + msg_obj.type = RETRO_MESSAGE_TYPE_NOTIFICATION; + msg_obj.progress = -1; + CORE_PREFIX(environ_cb)(RETRO_ENVIRONMENT_SET_MESSAGE_EXT, &msg_obj); } last_left = left; @@ -1192,6 +1246,28 @@ static bool init_media_info(void) av_q2d(vctx->sample_aspect_ratio) / vctx->height; } + if (fctx) + { + if (fctx->duration != AV_NOPTS_VALUE) + { + int64_t duration = fctx->duration + (fctx->duration <= INT64_MAX - 5000 ? 5000 : 0); + media.duration.time = (double)(duration / AV_TIME_BASE); + media.duration.seconds = (unsigned)media.duration.time; + media.duration.minutes = media.duration.seconds / 60; + media.duration.seconds %= 60; + media.duration.hours = media.duration.minutes / 60; + media.duration.minutes %= 60; + } + else + { + media.duration.time = 0.0; + media.duration.hours = 0; + media.duration.minutes = 0; + media.duration.seconds = 0; + log_cb(RETRO_LOG_ERROR, "[FFMPEG] Could not determine media duration\n"); + } + } + #ifdef HAVE_SSA if (sctx[0]) { diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index c0a6128576..98507c7e87 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -202,7 +202,8 @@ const static gfx_widget_t* const widgets[] = { &gfx_widget_achievement_popup, #endif &gfx_widget_generic_message, - &gfx_widget_libretro_message + &gfx_widget_libretro_message, + &gfx_widget_progress_message }; static void msg_widget_msg_transition_animation_done(void *userdata) diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index f4154e0657..cef780f583 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -389,6 +389,11 @@ void gfx_widget_set_libretro_message( void *data, const char *message, unsigned duration); +/* Warning: not thread safe! */ +void gfx_widget_set_progress_message(void *data, + const char *message, unsigned duration, + unsigned priority, int8_t progress); + /* All the functions below should be called in * the video driver - once they are all added, set * enable_menu_widgets to true for that driver */ @@ -404,6 +409,7 @@ extern const gfx_widget_t gfx_widget_screenshot; extern const gfx_widget_t gfx_widget_volume; extern const gfx_widget_t gfx_widget_generic_message; extern const gfx_widget_t gfx_widget_libretro_message; +extern const gfx_widget_t gfx_widget_progress_message; #ifdef HAVE_CHEEVOS extern const gfx_widget_t gfx_widget_achievement_popup; diff --git a/gfx/widgets/gfx_widget_progress_message.c b/gfx/widgets/gfx_widget_progress_message.c new file mode 100644 index 0000000000..1616370643 --- /dev/null +++ b/gfx/widgets/gfx_widget_progress_message.c @@ -0,0 +1,342 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2014-2017 - Jean-André Santoni + * Copyright (C) 2015-2018 - Andre Leiradella + * Copyright (C) 2018-2020 - natinusala + * + * RetroArch is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with RetroArch. + * If not, see . + */ + +#include "../gfx_widgets.h" +#include "../gfx_animation.h" +#include "../gfx_display.h" +#include "../../retroarch.h" + +/* Widget state */ + +struct gfx_widget_progress_message_state +{ + gfx_timer_t timer; + float alpha; + bool active; + + unsigned widget_width; + unsigned widget_height; + float widget_x; + float widget_y; + + unsigned text_width; + float text_x; + float text_y; + + unsigned bar_bg_width; + unsigned bar_bg_height; + float bar_bg_x; + float bar_bg_y; + + unsigned bar_max_width; + unsigned bar_height; + float bar_x; + float bar_y; + + unsigned priority; + int8_t progress; + char message[256]; + + float bar_bg_color[16]; + float bar_color[16]; + float bar_disabled_color[16]; +}; + +typedef struct gfx_widget_progress_message_state gfx_widget_progress_message_state_t; + +static gfx_widget_progress_message_state_t p_w_progress_message_st = { + + 0.0f, /* timer */ + 0.0f, /* alpha */ + false, /* active */ + + 0, /* widget_width */ + 0, /* widget_height */ + 0.0f, /* widget_x */ + 0.0f, /* widget_y */ + + 0, /* text_width */ + 0.0f, /* text_x */ + 0.0f, /* text_y */ + + 0, /* bar_bg_width */ + 0, /* bar_bg_height */ + 0.0f, /* float bar_bg_x */ + 0.0f, /* float bar_bg_y */ + + 0, /* bar_max_width */ + 0, /* bar_height */ + 0.0f, /* bar_x */ + 0.0f, /* bar_y */ + + 0, /* priority */ + -1, /* progress */ + {'\0'}, /* message */ + + COLOR_HEX_TO_FLOAT(0x3A3A3A, 1.0f), /* bar_bg_color */ + COLOR_HEX_TO_FLOAT(0x198AC6, 1.0f), /* bar_color */ + COLOR_HEX_TO_FLOAT(0x000000, 1.0f), /* bar_disabled_color */ +}; + +gfx_widget_progress_message_state_t *gfx_widget_progress_message_get_ptr(void) +{ + return &p_w_progress_message_st; +} + +/* Callbacks */ + +static void gfx_widget_progress_message_fadeout_cb(void *userdata) +{ + gfx_widget_progress_message_state_t *state = (gfx_widget_progress_message_state_t*)userdata; + + /* Deactivate widget */ + state->active = false; +} + +static void gfx_widget_progress_message_fadeout(void *userdata) +{ + gfx_animation_ctx_entry_t animation_entry; + gfx_widget_progress_message_state_t *state = (gfx_widget_progress_message_state_t*)userdata; + uintptr_t alpha_tag = (uintptr_t)&state->alpha; + + /* Trigger fade out animation */ + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = alpha_tag; + animation_entry.duration = MSG_QUEUE_ANIMATION_DURATION; + animation_entry.target_value = 0.0f; + animation_entry.subject = &state->alpha; + animation_entry.cb = gfx_widget_progress_message_fadeout_cb; + animation_entry.userdata = state; + + gfx_animation_push(&animation_entry); +} + +/* Widget interface */ + +void gfx_widget_set_progress_message(void *data, + const char *message, unsigned duration, + unsigned priority, int8_t progress) +{ + gfx_timer_ctx_entry_t timer; + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + gfx_widget_progress_message_state_t *state = gfx_widget_progress_message_get_ptr(); + gfx_widget_font_data_t *font_regular = gfx_widgets_get_font_regular(p_dispwidget); + uintptr_t alpha_tag = (uintptr_t)&state->alpha; + + /* Ensure we have a valid message string */ + if (string_is_empty(message)) + return; + + /* If widget is currently active, ignore new + * message if it has a lower priority */ + if (state->active && (state->priority > priority)) + return; + + /* Cache message parameters */ + strlcpy(state->message, message, sizeof(state->message)); + state->priority = priority; + state->progress = progress; + + /* Cache text width */ + state->text_width = font_driver_get_message_width( + font_regular->font, + state->message, + (unsigned)strlen(state->message), + 1.0f); + + /* Kill any existing timer/animation */ + gfx_timer_kill(&state->timer); + gfx_animation_kill_by_tag(&alpha_tag); + + /* Start new message timer */ + timer.duration = duration; + timer.cb = gfx_widget_progress_message_fadeout; + timer.userdata = state; + + gfx_timer_start(&state->timer, &timer); + + /* Set initial widget opacity */ + state->alpha = 1.0f; + + /* Set 'active' flag */ + state->active = true; +} + +/* Widget layout() */ + +static void gfx_widget_progress_message_layout( + void *data, + bool is_threaded, const char *dir_assets, char *font_path) +{ + float bar_padding; + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + gfx_widget_progress_message_state_t *state = gfx_widget_progress_message_get_ptr(); + unsigned last_video_width = gfx_widgets_get_last_video_width(p_dispwidget); + unsigned last_video_height = gfx_widgets_get_last_video_height(p_dispwidget); + unsigned widget_padding = gfx_widgets_get_padding(p_dispwidget); + gfx_widget_font_data_t *font_regular = gfx_widgets_get_font_regular(p_dispwidget); + + /* Base widget layout */ + state->widget_width = last_video_width; + state->widget_height = (unsigned)(((float)font_regular->line_height * 3.3f) + 0.5f); + state->widget_x = 0.0f; + state->widget_y = (float)(last_video_height - state->widget_height); + + /* Text layout */ + state->text_x = (float)last_video_width / 2.0f; + state->text_y = (float)(last_video_height - + font_regular->line_height + font_regular->line_centre_offset); + + /* Progress bar layout */ + state->bar_bg_width = last_video_width - (2 * widget_padding); + state->bar_bg_height = (unsigned)(((float)font_regular->line_height * 0.7f) + 0.5f); + state->bar_bg_x = (float)widget_padding; + state->bar_bg_y = (float)last_video_height - + (float)state->widget_height + + (((float)state->widget_height - (font_regular->line_height * 1.5f)) * 0.5f) - + ((float)state->bar_bg_height * 0.5f); + + state->bar_height = (unsigned)(((float)font_regular->line_height * 0.5f) + 0.5f); + bar_padding = (float)(state->bar_bg_height - state->bar_height) * 0.5f; + state->bar_max_width = state->bar_bg_width - (unsigned)((bar_padding * 2.0f) + 0.5f); + state->bar_x = state->bar_bg_x + bar_padding; + state->bar_y = state->bar_bg_y + bar_padding; +} + +/* Widget frame() */ + +static void gfx_widget_progress_message_frame(void *data, void *user_data) +{ + gfx_widget_progress_message_state_t *state = gfx_widget_progress_message_get_ptr(); + + if (state->active) + { + video_frame_info_t *video_info = (video_frame_info_t*)data; + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)user_data; + + unsigned video_width = video_info->width; + unsigned video_height = video_info->height; + void *userdata = video_info->userdata; + + float *backdrop_color = gfx_widgets_get_backdrop_orig(); + unsigned text_color = COLOR_TEXT_ALPHA(0xFFFFFFFF, (unsigned)(state->alpha * 255.0f)); + + gfx_widget_font_data_t *font_regular = gfx_widgets_get_font_regular(p_dispwidget); + size_t msg_queue_size = gfx_widgets_get_msg_queue_size(p_dispwidget); + + unsigned bar_width = state->bar_max_width; + float *bar_color = state->bar_disabled_color; + + /* Draw backdrop */ + gfx_display_set_alpha(backdrop_color, state->alpha * DEFAULT_BACKDROP); + + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->widget_x, + state->widget_y, + state->widget_width, + state->widget_height, + video_width, + video_height, + backdrop_color); + + /* Draw progress bar background */ + gfx_display_set_alpha(state->bar_bg_color, state->alpha); + + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->bar_bg_x, + state->bar_bg_y, + state->bar_bg_width, + state->bar_bg_height, + video_width, + video_height, + state->bar_bg_color); + + /* Draw progress bar */ + if (state->progress >= 0) + { + bar_width = (unsigned)((((float)state->progress / 100.0f) * (float)state->bar_max_width) + 0.5f); + bar_width = (bar_width > state->bar_max_width) ? state->bar_max_width : bar_width; + + bar_color = state->bar_color; + } + + gfx_display_set_alpha(bar_color, state->alpha); + + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->bar_x, + state->bar_y, + bar_width, + state->bar_height, + video_width, + video_height, + bar_color); + + /* Draw message text */ + gfx_widgets_draw_text( + font_regular, + state->message, + state->text_x, + state->text_y, + video_width, + video_height, + text_color, + TEXT_ALIGN_CENTER, + true); + + /* If the message queue is active, must flush the + * text here to avoid overlaps */ + if (msg_queue_size > 0) + gfx_widgets_flush_text(video_width, video_height, font_regular); + } +} + +/* Widget free() */ + +static void gfx_widget_progress_message_free(void) +{ + gfx_widget_progress_message_state_t *state = gfx_widget_progress_message_get_ptr(); + uintptr_t alpha_tag = (uintptr_t)&state->alpha; + + /* Kill any existing timer / animation */ + gfx_timer_kill(&state->timer); + gfx_animation_kill_by_tag(&alpha_tag); + + /* Deactivate widget */ + state->alpha = 0.0f; + state->active = false; +} + +/* Widget definition */ + +const gfx_widget_t gfx_widget_progress_message = { + NULL, /* init */ + gfx_widget_progress_message_free, + NULL, /* context_reset*/ + NULL, /* context_destroy */ + gfx_widget_progress_message_layout, + NULL, /* iterate */ + gfx_widget_progress_message_frame +}; diff --git a/griffin/griffin.c b/griffin/griffin.c index c9a4b615ca..b8fa22d218 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1288,6 +1288,7 @@ MENU #include "../gfx/widgets/gfx_widget_volume.c" #include "../gfx/widgets/gfx_widget_generic_message.c" #include "../gfx/widgets/gfx_widget_libretro_message.c" +#include "../gfx/widgets/gfx_widget_progress_message.c" #ifdef HAVE_CHEEVOS #include "../gfx/widgets/gfx_widget_achievement_popup.c" #endif diff --git a/retroarch.c b/retroarch.c index ed948ec071..b8c1c293ec 100644 --- a/retroarch.c +++ b/retroarch.c @@ -18634,16 +18634,12 @@ static bool rarch_environment_cb(unsigned cmd, void *data) break; - /* Handle 'progress' messages - * TODO/FIXME: At present, we also display messages - * of type RETRO_MESSAGE_TYPE_PROGRESS via - * gfx_widget_set_libretro_message(). We need to - * implement a separate 'progress bar' widget to - * handle these correctly */ + /* Handle 'progress' messages */ case RETRO_MESSAGE_TYPE_PROGRESS: if (p_rarch->widgets_active) - gfx_widget_set_libretro_message(&p_rarch->dispwidget_st, - msg->msg, msg->duration); + gfx_widget_set_progress_message(&p_rarch->dispwidget_st, + msg->msg, msg->duration, + msg->priority, msg->progress); else runloop_core_msg_queue_push(p_rarch, msg);