1 /* RetroArch - A frontend for libretro.
2 * Copyright (C) 2014-2017 - Jean-André Santoni
3 * Copyright (C) 2015-2018 - Andre Leiradella
4 * Copyright (C) 2018-2020 - natinusala
5 *
6 * RetroArch is free software: you can redistribute it and/or modify it under the terms
7 * of the GNU General Public License as published by the Free Software Found-
8 * ation, either version 3 of the License, or (at your option) any later version.
9 *
10 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12 * PURPOSE. See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along with RetroArch.
15 * If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "../gfx_widgets.h"
19 #include "../gfx_animation.h"
20 #include "../gfx_display.h"
21 #include "../../retroarch.h"
22
23 #define LIBRETRO_MESSAGE_FADE_DURATION MSG_QUEUE_ANIMATION_DURATION
24
25 /* Widget state */
26
27 enum gfx_widget_libretro_message_status
28 {
29 GFX_WIDGET_LIBRETRO_MESSAGE_IDLE = 0,
30 GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN,
31 GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN,
32 GFX_WIDGET_LIBRETRO_MESSAGE_WAIT,
33 GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT
34 };
35
36 struct gfx_widget_libretro_message_state
37 {
38 unsigned bg_width;
39 unsigned bg_height;
40 unsigned text_padding;
41 unsigned text_color;
42 unsigned frame_width;
43
44 unsigned message_duration;
45
46 gfx_timer_t timer; /* float alignment */
47
48 float bg_x;
49 float bg_y_start;
50 float bg_y_end;
51 float text_x;
52 float text_y_start;
53 float text_y_end;
54 float alpha;
55
56 float frame_color[16];
57
58 enum gfx_widget_libretro_message_status status;
59
60 char message[512];
61 bool message_updated;
62 };
63
64 typedef struct gfx_widget_libretro_message_state gfx_widget_libretro_message_state_t;
65
66 static gfx_widget_libretro_message_state_t p_w_libretro_message_st = {
67
68 0, /* bg_width */
69 0, /* bg_height */
70 0, /* text_padding */
71 0xE0E0E0FF, /* text_color */
72 0, /* frame_width */
73
74 0, /* message_duration */
75
76 0.0f, /* timer */
77
78 0.0f, /* bg_x */
79 0.0f, /* bg_y_start */
80 0.0f, /* bg_y_end */
81 0.0f, /* text_x */
82 0.0f, /* text_y_start */
83 0.0f, /* text_y_end */
84 0.0f, /* alpha */
85
86 COLOR_HEX_TO_FLOAT(0x909090, 1.0f), /* frame_color */
87
88 GFX_WIDGET_LIBRETRO_MESSAGE_IDLE, /* status */
89
90 {'\0'}, /* message */
91 false /* message_updated */
92 };
93
94 /* Utilities */
95
gfx_widget_libretro_message_reset(bool cancel_pending)96 static void gfx_widget_libretro_message_reset(bool cancel_pending)
97 {
98 gfx_widget_libretro_message_state_t *state = &p_w_libretro_message_st;
99 uintptr_t alpha_tag = (uintptr_t)&state->alpha;
100 uintptr_t timer_tag = (uintptr_t)&state->timer;
101
102 /* Kill any existing timers/animations */
103 gfx_animation_kill_by_tag(&timer_tag);
104 gfx_animation_kill_by_tag(&alpha_tag);
105
106 /* Reset status */
107 state->status = GFX_WIDGET_LIBRETRO_MESSAGE_IDLE;
108 if (cancel_pending)
109 state->message_updated = false;
110 }
111
112 /* Callbacks */
113
gfx_widget_libretro_message_fade_out_cb(void * userdata)114 static void gfx_widget_libretro_message_fade_out_cb(void *userdata)
115 {
116 gfx_widget_libretro_message_reset(false);
117 }
118
gfx_widget_libretro_message_wait_cb(void * userdata)119 static void gfx_widget_libretro_message_wait_cb(void *userdata)
120 {
121 gfx_widget_libretro_message_state_t *state = (gfx_widget_libretro_message_state_t*)userdata;
122 uintptr_t alpha_tag = (uintptr_t)&state->alpha;
123 gfx_animation_ctx_entry_t animation_entry;
124
125 /* Trigger fade out */
126 state->alpha = 1.0f;
127 animation_entry.easing_enum = EASING_OUT_QUAD;
128 animation_entry.tag = alpha_tag;
129 animation_entry.duration = LIBRETRO_MESSAGE_FADE_DURATION;
130 animation_entry.target_value = 0.0f;
131 animation_entry.subject = &state->alpha;
132 animation_entry.cb = gfx_widget_libretro_message_fade_out_cb;
133 animation_entry.userdata = NULL;
134
135 gfx_animation_push(&animation_entry);
136 state->status = GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT;
137 }
138
gfx_widget_libretro_message_slide_in_cb(void * userdata)139 static void gfx_widget_libretro_message_slide_in_cb(void *userdata)
140 {
141 gfx_widget_libretro_message_state_t *state = (gfx_widget_libretro_message_state_t*)userdata;
142 gfx_timer_ctx_entry_t timer;
143
144 /* Start wait timer */
145 state->alpha = 1.0f;
146 timer.duration = state->message_duration;
147 timer.cb = gfx_widget_libretro_message_wait_cb;
148 timer.userdata = state;
149
150 gfx_animation_timer_start(&state->timer, &timer);
151 state->status = GFX_WIDGET_LIBRETRO_MESSAGE_WAIT;
152 }
153
154 /* Widget interface */
155
gfx_widget_set_libretro_message(void * data,const char * msg,unsigned duration)156 void gfx_widget_set_libretro_message(void *data,
157 const char *msg, unsigned duration)
158 {
159 dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data;
160 gfx_widget_libretro_message_state_t *state = &p_w_libretro_message_st;
161 gfx_widget_font_data_t *font_msg_queue = &p_dispwidget->gfx_widget_fonts.msg_queue;
162
163 /* Ensure we have a valid message string */
164 if (string_is_empty(msg))
165 return;
166
167 /* Cache message parameters */
168 strlcpy(state->message, msg, sizeof(state->message));
169 state->message_duration = duration;
170
171 /* Get background width */
172 state->bg_width = (state->text_padding * 2) +
173 font_driver_get_message_width(
174 font_msg_queue->font, state->message,
175 (unsigned)strlen(state->message), 1.0f);
176
177 /* If a 'slide in' animation is already in
178 * progress, no further action is required;
179 * just let animation continue with the updated
180 * message text */
181 if (state->status == GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN)
182 return;
183
184 /* Signal that message has been updated
185 * > Note that we have to defer the triggering
186 * of any animation changes until the next
187 * call of gfx_widget_libretro_message_iterate().
188 * This is because cores will often send messages
189 * during initialisation - i.e. during processes
190 * that take a non-trivial amount of time. In these
191 * cases, updating the animation state here would
192 * result in the following:
193 * - Core starts initialisation/load content
194 * - Message is set, animation is triggered
195 * - Core finishes initialisation/load content,
196 * taking multiple 100's of ms
197 * - On next runloop iterate, animation status
198 * is checked - but because initialisation
199 * took so long, the animation duration has
200 * already elapsed
201 * - Animation 'finishes' immediately, and the
202 * user never sees it... */
203 state->message_updated = true;
204 }
205
206 /* Widget layout() */
207
gfx_widget_libretro_message_layout(void * data,bool is_threaded,const char * dir_assets,char * font_path)208 static void gfx_widget_libretro_message_layout(
209 void *data,
210 bool is_threaded, const char *dir_assets, char *font_path)
211 {
212 dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)data;
213 gfx_widget_libretro_message_state_t *state = &p_w_libretro_message_st;
214
215 unsigned last_video_height = p_dispwidget->last_video_height;
216 unsigned divider_width = p_dispwidget->divider_width_1px;
217 gfx_widget_font_data_t *font_msg_queue = &p_dispwidget->gfx_widget_fonts.msg_queue;
218
219 /* Set values that are independent of message length */
220 state->bg_height = font_msg_queue->line_height * 2;
221 state->text_padding = (unsigned)(((float)font_msg_queue->line_height * (2.0f / 3.0f)) + 0.5f);
222 state->frame_width = divider_width;
223
224 state->bg_x = 0.0f;
225 state->bg_y_start = (float)last_video_height + (float)state->frame_width;
226 state->bg_y_end = (float)last_video_height - (float)state->bg_height;
227 state->text_x = (float)state->text_padding;
228 state->text_y_start = state->bg_y_start + ((float)state->bg_height * 0.5f) +
229 (float)font_msg_queue->line_centre_offset;
230 state->text_y_end = state->bg_y_end + ((float)state->bg_height * 0.5f) +
231 (float)font_msg_queue->line_centre_offset;
232
233 /* Update values that are dependent upon message length */
234 state->bg_width = state->text_padding * 2;
235
236 if (!string_is_empty(state->message))
237 state->bg_width += font_driver_get_message_width(
238 font_msg_queue->font, state->message,
239 (unsigned)strlen(state->message), 1.0f);
240 }
241
242 /* Widget iterate() */
243
gfx_widget_libretro_message_iterate(void * user_data,unsigned width,unsigned height,bool fullscreen,const char * dir_assets,char * font_path,bool is_threaded)244 static void gfx_widget_libretro_message_iterate(void *user_data,
245 unsigned width, unsigned height, bool fullscreen,
246 const char *dir_assets, char *font_path,
247 bool is_threaded)
248 {
249 gfx_widget_libretro_message_state_t *state = &p_w_libretro_message_st;
250
251 if (state->message_updated)
252 {
253 enum gfx_widget_libretro_message_status current_status = state->status;
254 uintptr_t alpha_tag = (uintptr_t)&state->alpha;
255 gfx_animation_ctx_entry_t animation_entry;
256
257 /* In all cases, reset any existing animation */
258 gfx_widget_libretro_message_reset(false);
259
260 /* If an animation was already in progress,
261 * have to continue from the last active
262 * animation phase */
263 switch (current_status)
264 {
265 case GFX_WIDGET_LIBRETRO_MESSAGE_IDLE:
266 /* Trigger 'slide in' animation */
267 state->alpha = 0.0f;
268 animation_entry.easing_enum = EASING_OUT_QUAD;
269 animation_entry.tag = alpha_tag;
270 animation_entry.duration = LIBRETRO_MESSAGE_FADE_DURATION;
271 animation_entry.target_value = 1.0f;
272 animation_entry.subject = &state->alpha;
273 animation_entry.cb = gfx_widget_libretro_message_slide_in_cb;
274 animation_entry.userdata = state;
275
276 gfx_animation_push(&animation_entry);
277 state->status = GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN;
278 break;
279 case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN:
280 case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT:
281 {
282 /* If we are fading in or out, start
283 * a new fade in animation (transitioning
284 * from the current alpha value to 1.0) */
285 unsigned fade_duration = (unsigned)(((1.0f - state->alpha) *
286 (float)LIBRETRO_MESSAGE_FADE_DURATION) + 0.5f);
287 if (fade_duration > LIBRETRO_MESSAGE_FADE_DURATION)
288 fade_duration = LIBRETRO_MESSAGE_FADE_DURATION;
289
290 /* > If current and final alpha values are the
291 * same, or fade duration is zero, skip
292 * straight to the wait phase */
293 if ((state->alpha >= 1.0f) || (fade_duration < 1))
294 gfx_widget_libretro_message_slide_in_cb(state);
295 else
296 {
297 animation_entry.easing_enum = EASING_OUT_QUAD;
298 animation_entry.tag = alpha_tag;
299 animation_entry.duration = fade_duration;
300 animation_entry.target_value = 1.0f;
301 animation_entry.subject = &state->alpha;
302 /* Note that 'slide in' and 'fade in' share
303 * the same callback */
304 animation_entry.cb = gfx_widget_libretro_message_slide_in_cb;
305 animation_entry.userdata = state;
306
307 gfx_animation_push(&animation_entry);
308 state->status = GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN;
309 }
310 }
311 break;
312 case GFX_WIDGET_LIBRETRO_MESSAGE_WAIT:
313 /* If we are currently waiting, just
314 * 'reset' the wait timer */
315 gfx_widget_libretro_message_slide_in_cb(state);
316 break;
317 default:
318 /* The only remaining case is
319 * GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN,
320 * which can never happen (state->message_updated
321 * will be false if it is, so this code will
322 * not be invoked). If we reach this point, an
323 * unknown error has occurred. We have already
324 * reset any existing animation, so no further
325 * action is required */
326 break;
327 }
328
329 state->message_updated = false;
330 }
331 }
332
333 /* Widget frame() */
334
gfx_widget_libretro_message_frame(void * data,void * user_data)335 static void gfx_widget_libretro_message_frame(void *data, void *user_data)
336 {
337 gfx_widget_libretro_message_state_t *state = &p_w_libretro_message_st;
338
339 if (state->status != GFX_WIDGET_LIBRETRO_MESSAGE_IDLE)
340 {
341 video_frame_info_t *video_info = (video_frame_info_t*)data;
342 gfx_display_t *p_disp = (gfx_display_t*)video_info->disp_userdata;
343 dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)user_data;
344
345 unsigned video_width = video_info->width;
346 unsigned video_height = video_info->height;
347 void *userdata = video_info->userdata;
348
349 gfx_widget_font_data_t *font_msg_queue = &p_dispwidget->gfx_widget_fonts.msg_queue;
350 size_t msg_queue_size = p_dispwidget->current_msgs_size;
351 float *bg_color = p_dispwidget->backdrop_orig;
352 float bg_alpha;
353 float bg_y;
354
355 unsigned text_color;
356 float text_alpha;
357 float text_y;
358
359 /* Determine status-dependent opacity/position
360 * values */
361 switch (state->status)
362 {
363 case GFX_WIDGET_LIBRETRO_MESSAGE_SLIDE_IN:
364 bg_alpha = DEFAULT_BACKDROP * state->alpha;
365 text_alpha = state->alpha;
366 /* Use 'alpha' to determine the draw offset
367 * > Saves having to trigger two animations */
368 bg_y = state->bg_y_start + (state->alpha *
369 (state->bg_y_end - state->bg_y_start));
370 text_y = state->text_y_start + (state->alpha *
371 (state->text_y_end - state->text_y_start));
372 break;
373 case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_IN:
374 case GFX_WIDGET_LIBRETRO_MESSAGE_FADE_OUT:
375 bg_alpha = DEFAULT_BACKDROP * state->alpha;
376 text_alpha = state->alpha;
377 bg_y = state->bg_y_end;
378 text_y = state->text_y_end;
379 break;
380 case GFX_WIDGET_LIBRETRO_MESSAGE_WAIT:
381 bg_alpha = DEFAULT_BACKDROP;
382 text_alpha = 1.0f;
383 bg_y = state->bg_y_end;
384 text_y = state->text_y_end;
385 break;
386 default:
387 bg_alpha = 0.0f;
388 text_alpha = 0.0f;
389 bg_y = state->bg_y_start;
390 text_y = state->text_y_start;
391 break;
392 }
393
394 /* Draw background */
395 if (bg_alpha > 0.0f)
396 {
397 /* Set opacity
398 * > Note: Background and frame have the
399 * same opacity */
400 gfx_display_set_alpha(bg_color, bg_alpha);
401 gfx_display_set_alpha(state->frame_color, bg_alpha);
402
403 /* Background */
404 gfx_display_draw_quad(
405 p_disp,
406 userdata,
407 video_width,
408 video_height,
409 state->bg_x,
410 bg_y,
411 state->bg_width,
412 state->bg_height,
413 video_width,
414 video_height,
415 bg_color);
416
417 /* Frame */
418 gfx_display_draw_quad(
419 p_disp,
420 userdata,
421 video_width,
422 video_height,
423 state->bg_x,
424 bg_y - (float)state->frame_width,
425 state->bg_width + state->frame_width,
426 state->frame_width,
427 video_width,
428 video_height,
429 state->frame_color);
430
431 gfx_display_draw_quad(
432 p_disp,
433 userdata,
434 video_width,
435 video_height,
436 state->bg_x + (float)state->bg_width,
437 bg_y,
438 state->frame_width,
439 state->bg_height,
440 video_width,
441 video_height,
442 state->frame_color);
443 }
444
445 /* Draw text */
446 if (text_alpha > 0.0f)
447 {
448 /* Set opacity */
449 text_color = COLOR_TEXT_ALPHA(state->text_color,
450 (unsigned)(text_alpha * 255.0f));
451
452 /* Draw message */
453 gfx_widgets_draw_text(
454 font_msg_queue,
455 state->message,
456 state->text_x,
457 text_y,
458 video_width,
459 video_height,
460 text_color,
461 TEXT_ALIGN_LEFT,
462 true);
463
464 /* If the message queue is active, must flush the
465 * text here to avoid overlaps */
466 if (msg_queue_size > 0)
467 gfx_widgets_flush_text(video_width, video_height,
468 font_msg_queue);
469 }
470 }
471 }
472
473 /* Widget free() */
474
gfx_widget_libretro_message_free(void)475 static void gfx_widget_libretro_message_free(void)
476 {
477 gfx_widget_libretro_message_reset(true);
478 }
479
480 /* Widget definition */
481
482 const gfx_widget_t gfx_widget_libretro_message = {
483 NULL, /* init */
484 gfx_widget_libretro_message_free,
485 NULL, /* context_reset*/
486 NULL, /* context_destroy */
487 gfx_widget_libretro_message_layout,
488 gfx_widget_libretro_message_iterate,
489 gfx_widget_libretro_message_frame
490 };
491