diff --git a/gfx/gfx_widgets.c b/gfx/gfx_widgets.c index e6cff6f000..bfa1a4cc64 100644 --- a/gfx/gfx_widgets.c +++ b/gfx/gfx_widgets.c @@ -142,6 +142,12 @@ unsigned gfx_widgets_get_padding(void *data) return p_dispwidget->simple_widget_padding; } +unsigned gfx_widgets_get_divider_width(void *data) +{ + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + return p_dispwidget->divider_width_1px; +} + unsigned gfx_widgets_get_height(void *data) { dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h index 1ba3e99415..70d82fd2d4 100644 --- a/gfx/gfx_widgets.h +++ b/gfx/gfx_widgets.h @@ -275,6 +275,8 @@ float* gfx_widgets_get_pure_white(void); unsigned gfx_widgets_get_padding(void *data); +unsigned gfx_widgets_get_divider_width(void *data); + unsigned gfx_widgets_get_height(void *data); gfx_widget_font_data_t* gfx_widgets_get_font_regular(void *data); diff --git a/gfx/widgets/gfx_widget_libretro_message.c b/gfx/widgets/gfx_widget_libretro_message.c index 1d909a06ce..d3ebcc1ba3 100644 --- a/gfx/widgets/gfx_widget_libretro_message.c +++ b/gfx/widgets/gfx_widget_libretro_message.c @@ -20,21 +20,75 @@ #include "../gfx_display.h" #include "../../retroarch.h" +#define LIBRETRO_MESSAGE_FADE_DURATION MSG_QUEUE_ANIMATION_DURATION + +/* Widget state */ + +enum gfx_widget_libretro_message_status +{ + GFX_WIDGET_LIBRETRO_MESSAGE_IDLE = 0, + GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN, + GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN, + GFX_WIDGET_LIBRETRO_MESSAGE_WAIT, + GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT +}; + struct gfx_widget_libretro_message_state { - unsigned width; + unsigned bg_width; + unsigned bg_height; + unsigned text_padding; + unsigned text_color; + unsigned frame_width; + + unsigned message_duration; + gfx_timer_t timer; /* float alignment */ + + float bg_x; + float bg_y_start; + float bg_y_end; + float text_x; + float text_y_start; + float text_y_end; float alpha; + + float frame_color[16]; + + enum gfx_widget_libretro_message_status status; + char message[512]; + bool message_updated; }; typedef struct gfx_widget_libretro_message_state gfx_widget_libretro_message_state_t; static gfx_widget_libretro_message_state_t p_w_libretro_message_st = { - 0, - 0.0f, - 0.0f, - {'\0'} + + 0, /* bg_width */ + 0, /* bg_height */ + 0, /* text_padding */ + 0xE0E0E0FF, /* text_color */ + 0, /* frame_width */ + + 0, /* message_duration */ + + 0.0f, /* timer */ + + 0.0f, /* bg_x */ + 0.0f, /* bg_y_start */ + 0.0f, /* bg_y_end */ + 0.0f, /* text_x */ + 0.0f, /* text_y_start */ + 0.0f, /* text_y_end */ + 0.0f, /* alpha */ + + COLOR_HEX_TO_FLOAT(0x909090, 1.0f), /* frame_color */ + + GFX_WIDGET_LIBRETRO_MESSAGE_IDLE, /* status */ + + {'\0'}, /* message */ + false /* message_updated */ }; static gfx_widget_libretro_message_state_t* gfx_widget_libretro_message_get_state(void) @@ -42,106 +96,395 @@ static gfx_widget_libretro_message_state_t* gfx_widget_libretro_message_get_stat return &p_w_libretro_message_st; } -static void gfx_widget_libretro_message_fadeout(void *userdata) +/* Utilities */ + +static void gfx_widget_libretro_message_reset(void) { - gfx_animation_ctx_entry_t entry; - gfx_widget_libretro_message_state_t* state = gfx_widget_libretro_message_get_state(); - uintptr_t tag = (uintptr_t)&state->timer; + gfx_widget_libretro_message_state_t *state = gfx_widget_libretro_message_get_state(); + uintptr_t alpha_tag = (uintptr_t)&state->alpha; - /* Start fade out animation */ - entry.cb = NULL; - entry.duration = MSG_QUEUE_ANIMATION_DURATION; - entry.easing_enum = EASING_OUT_QUAD; - entry.subject = &state->alpha; - entry.tag = tag; - entry.target_value = 0.0f; - entry.userdata = NULL; + /* Kill any existing timers/animations */ + gfx_timer_kill(&state->timer); + gfx_animation_kill_by_tag(&alpha_tag); - gfx_animation_push(&entry); + /* Reset status */ + state->status = GFX_WIDGET_LIBRETRO_MESSAGE_IDLE; + state->message_updated = false; } +/* Callbacks */ + +static void gfx_widget_libretro_message_fade_out_cb(void *userdata) +{ + gfx_widget_libretro_message_reset(); +} + +static void gfx_widget_libretro_message_wait_cb(void *userdata) +{ + gfx_widget_libretro_message_state_t *state = (gfx_widget_libretro_message_state_t*)userdata; + uintptr_t alpha_tag = (uintptr_t)&state->alpha; + gfx_animation_ctx_entry_t animation_entry; + + /* Trigger fade out */ + state->alpha = 1.0f; + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = alpha_tag; + animation_entry.duration = LIBRETRO_MESSAGE_FADE_DURATION; + animation_entry.target_value = 0.0f; + animation_entry.subject = &state->alpha; + animation_entry.cb = gfx_widget_libretro_message_fade_out_cb; + animation_entry.userdata = NULL; + + gfx_animation_push(&animation_entry); + state->status = GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT; +} + +static void gfx_widget_libretro_message_slide_in_cb(void *userdata) +{ + gfx_widget_libretro_message_state_t *state = (gfx_widget_libretro_message_state_t*)userdata; + gfx_timer_ctx_entry_t timer; + + /* Start wait timer */ + state->alpha = 1.0f; + timer.duration = state->message_duration; + timer.cb = gfx_widget_libretro_message_wait_cb; + timer.userdata = state; + + gfx_timer_start(&state->timer, &timer); + state->status = GFX_WIDGET_LIBRETRO_MESSAGE_WAIT; +} + +/* Widget interface */ + void gfx_widget_set_libretro_message(void *data, const char *msg, unsigned duration) { - gfx_timer_ctx_entry_t timer; - gfx_widget_libretro_message_state_t* state = gfx_widget_libretro_message_get_state(); - uintptr_t tag = (uintptr_t)&state->timer; - gfx_widget_font_data_t* font_regular = gfx_widgets_get_font_regular(data); + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + gfx_widget_libretro_message_state_t *state = gfx_widget_libretro_message_get_state(); + gfx_widget_font_data_t *font_msg_queue = gfx_widgets_get_font_msg_queue(p_dispwidget); + /* Ensure we have a valid message string */ + if (string_is_empty(msg)) + return; + + /* Cache message parameters */ strlcpy(state->message, msg, sizeof(state->message)); + state->message_duration = duration; - state->alpha = DEFAULT_BACKDROP; + /* Get background width */ + state->bg_width = (state->text_padding * 2) + + font_driver_get_message_width( + font_msg_queue->font, state->message, + (unsigned)strlen(state->message), 1.0f); - /* Kill and restart the timer / animation */ - gfx_timer_kill(&state->timer); - gfx_animation_kill_by_tag(&tag); + /* If a 'slide in' animation is already in + * progress, no further action is required; + * just let animation continue with the updated + * message text */ + if (state->status == GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN) + return; - timer.cb = gfx_widget_libretro_message_fadeout; - timer.duration = duration; - timer.userdata = NULL; - - gfx_timer_start(&state->timer, &timer); - - /* Compute text width */ - state->width = font_driver_get_message_width(font_regular->font, msg, (unsigned)strlen(msg), 1) + gfx_widgets_get_padding(data) * 2; + /* Signal that message has been updated + * > Note that we have to defer the triggering + * of any animation changes until the next + * call of gfx_widget_libretro_message_iterate(). + * This is because cores will often send messages + * during initialisation - i.e. during processes + * that take a non-trivial amount of time. In these + * cases, updating the animation state here would + * result in the following: + * - Core starts initialisation/load content + * - Message is set, animation is triggered + * - Core finishes initialisation/load content, + * taking multiple 100's of ms + * - On next runloop iterate, animation status + * is checked - but because initialisation + * took so long, the animation duration has + * already elapsed + * - Animation 'finishes' immediately, and the + * user never sees it... */ + state->message_updated = true; } -static void gfx_widget_libretro_message_frame(void *data, void *user_data) +/* Widget layout() */ + +static void gfx_widget_libretro_message_layout( + void *data, + bool is_threaded, const char *dir_assets, char *font_path) { - gfx_widget_libretro_message_state_t* state = - gfx_widget_libretro_message_get_state(); + dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data; + gfx_widget_libretro_message_state_t *state = gfx_widget_libretro_message_get_state(); - if (state->alpha > 0.0f) + unsigned last_video_height = gfx_widgets_get_last_video_height(p_dispwidget); + unsigned divider_width = gfx_widgets_get_divider_width(p_dispwidget); + gfx_widget_font_data_t *font_msg_queue = gfx_widgets_get_font_msg_queue(p_dispwidget); + + /* Set values that are independent of message length */ + state->bg_height = font_msg_queue->line_height * 2; + state->text_padding = (unsigned)(((float)font_msg_queue->line_height * (2.0f / 3.0f)) + 0.5f); + state->frame_width = divider_width; + + state->bg_x = 0.0f; + state->bg_y_start = (float)last_video_height + (float)state->frame_width; + state->bg_y_end = (float)last_video_height - (float)state->bg_height; + state->text_x = (float)state->text_padding; + state->text_y_start = state->bg_y_start + ((float)state->bg_height * 0.5f) + + (float)font_msg_queue->line_centre_offset; + state->text_y_end = state->bg_y_end + ((float)state->bg_height * 0.5f) + + (float)font_msg_queue->line_centre_offset; + + /* Update values that are dependent upon message length */ + state->bg_width = state->text_padding * 2; + + if (!string_is_empty(state->message)) + state->bg_width += font_driver_get_message_width( + font_msg_queue->font, state->message, + (unsigned)strlen(state->message), 1.0f); +} + +/* Widget iterate() */ + +static void gfx_widget_libretro_message_iterate(void *user_data, + unsigned width, unsigned height, bool fullscreen, + const char *dir_assets, char *font_path, + bool is_threaded) +{ + gfx_widget_libretro_message_state_t *state = gfx_widget_libretro_message_get_state(); + + if (state->message_updated) { - video_frame_info_t* video_info = (video_frame_info_t*)data; - void* userdata = video_info->userdata; - unsigned video_width = video_info->width; - unsigned video_height = video_info->height; - unsigned height = gfx_widgets_get_generic_message_height(user_data); - float* backdrop_orign = 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(user_data); - size_t msg_queue_size = gfx_widgets_get_msg_queue_size(user_data); + enum gfx_widget_libretro_message_status current_status = state->status; + uintptr_t alpha_tag = (uintptr_t)&state->alpha; + gfx_animation_ctx_entry_t animation_entry; - gfx_display_set_alpha(backdrop_orign, state->alpha); + /* In all cases, reset any existing animation + * > Note that this sets state->message_updated + * to 'false' */ + gfx_widget_libretro_message_reset(); - gfx_display_draw_quad(userdata, - video_width, video_height, - 0, video_height - height, - state->width, height, - video_width, video_height, - backdrop_orign); + /* If an animation was already in progress, + * have to continue from the last active + * animation phase */ + switch (current_status) + { + case GFX_WIDGET_LIBRETRO_MESSAGE_IDLE: + /* Trigger 'slide in' animation */ + state->alpha = 0.0f; + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = alpha_tag; + animation_entry.duration = LIBRETRO_MESSAGE_FADE_DURATION; + animation_entry.target_value = 1.0f; + animation_entry.subject = &state->alpha; + animation_entry.cb = gfx_widget_libretro_message_slide_in_cb; + animation_entry.userdata = state; - gfx_widgets_draw_text(font_regular, state->message, - gfx_widgets_get_padding(user_data), - video_height - height/2 + font_regular->line_centre_offset, - video_width, video_height, - text_color, TEXT_ALIGN_LEFT, - false); + gfx_animation_push(&animation_entry); + state->status = GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN; + break; + case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN: + case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT: + { + /* If we are fading in or out, start + * a new fade in animation (transitioning + * from the current alpha value to 1.0) */ + unsigned fade_duration = (unsigned)(((1.0f - state->alpha) * + (float)LIBRETRO_MESSAGE_FADE_DURATION) + 0.5f); + fade_duration = (fade_duration > LIBRETRO_MESSAGE_FADE_DURATION) ? + LIBRETRO_MESSAGE_FADE_DURATION : fade_duration; - /* 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); + /* > If current and final alpha values are the + * same, or fade duration is zero, skip + * straight to the wait phase */ + if ((state->alpha >= 1.0f) || (fade_duration < 1)) + gfx_widget_libretro_message_slide_in_cb(state); + else + { + animation_entry.easing_enum = EASING_OUT_QUAD; + animation_entry.tag = alpha_tag; + animation_entry.duration = LIBRETRO_MESSAGE_FADE_DURATION; + animation_entry.target_value = 1.0f; + animation_entry.subject = &state->alpha; + /* Note that 'slide in' and 'fade in' share + * the same callback */ + animation_entry.cb = gfx_widget_libretro_message_slide_in_cb; + animation_entry.userdata = state; + + gfx_animation_push(&animation_entry); + state->status = GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN; + } + } + break; + case GFX_WIDGET_LIBRETRO_MESSAGE_WAIT: + /* If we are currently waiting, just + * 'reset' the wait timer */ + gfx_widget_libretro_message_slide_in_cb(state); + break; + default: + /* The only remaining case is + * GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN, + * which can never happen (state->message_updated + * will be false if it is, so this code will + * not be invoked). If we reach this point, an + * unknown error has occurred. We have already + * reset any existing animation, so no further + * action is required */ + break; + } } } +/* Widget frame() */ + +static void gfx_widget_libretro_message_frame(void *data, void *user_data) +{ + gfx_widget_libretro_message_state_t *state = gfx_widget_libretro_message_get_state(); + + if (state->status != GFX_WIDGET_LIBRETRO_MESSAGE_IDLE) + { + 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; + + gfx_widget_font_data_t *font_msg_queue = gfx_widgets_get_font_msg_queue(p_dispwidget); + size_t msg_queue_size = gfx_widgets_get_msg_queue_size(p_dispwidget); + + float *bg_color = gfx_widgets_get_backdrop_orig(); + float bg_alpha; + float bg_y; + + unsigned text_color; + float text_alpha; + float text_y; + + /* Determine status-dependent opacity/position + * values */ + switch (state->status) + { + case GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN: + bg_alpha = DEFAULT_BACKDROP * state->alpha; + text_alpha = state->alpha; + /* Use 'alpha' to determine the draw offset + * > Saves having to trigger two animations */ + bg_y = state->bg_y_start + (state->alpha * + (state->bg_y_end - state->bg_y_start)); + text_y = state->text_y_start + (state->alpha * + (state->text_y_end - state->text_y_start)); + break; + case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN: + case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT: + bg_alpha = DEFAULT_BACKDROP * state->alpha; + text_alpha = state->alpha; + bg_y = state->bg_y_end; + text_y = state->text_y_end; + break; + case GFX_WIDGET_LIBRETRO_MESSAGE_WAIT: + bg_alpha = DEFAULT_BACKDROP; + text_alpha = 1.0f; + bg_y = state->bg_y_end; + text_y = state->text_y_end; + break; + default: + bg_alpha = 0.0f; + text_alpha = 0.0f; + bg_y = state->bg_y_start; + text_y = state->text_y_start; + break; + } + + /* Draw background */ + if (bg_alpha > 0.0f) + { + /* Set opacity + * > Note: Background and frame have the + * same opacity */ + gfx_display_set_alpha(bg_color, bg_alpha); + gfx_display_set_alpha(state->frame_color, bg_alpha); + + /* Background */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->bg_x, + bg_y, + state->bg_width, + state->bg_height, + video_width, + video_height, + bg_color); + + /* Frame */ + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->bg_x, + bg_y - (float)state->frame_width, + state->bg_width + state->frame_width, + state->frame_width, + video_width, + video_height, + state->frame_color); + + gfx_display_draw_quad( + userdata, + video_width, + video_height, + state->bg_x + (float)state->bg_width, + bg_y, + state->frame_width, + state->bg_height, + video_width, + video_height, + state->frame_color); + } + + /* Draw text */ + if (text_alpha > 0.0f) + { + /* Set opacity */ + text_color = COLOR_TEXT_ALPHA(state->text_color, + (unsigned)(text_alpha * 255.0f)); + + /* Draw message */ + gfx_widgets_draw_text( + font_msg_queue, + state->message, + state->text_x, + text_y, + video_width, + video_height, + text_color, + TEXT_ALIGN_LEFT, + 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_msg_queue); + } + } +} + +/* Widget free() */ + static void gfx_widget_libretro_message_free(void) { - gfx_widget_libretro_message_state_t* state = gfx_widget_libretro_message_get_state(); - uintptr_t tag = (uintptr_t) &state->timer; - - state->alpha = 0.0f; - gfx_timer_kill(&state->timer); - gfx_animation_kill_by_tag(&tag); + gfx_widget_libretro_message_reset(); } +/* Widget definition */ + const gfx_widget_t gfx_widget_libretro_message = { NULL, /* init */ gfx_widget_libretro_message_free, NULL, /* context_reset*/ NULL, /* context_destroy */ - NULL, /* layout */ - NULL, /* iterate */ + gfx_widget_libretro_message_layout, + gfx_widget_libretro_message_iterate, gfx_widget_libretro_message_frame };