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 /* Constants */
24 #define VOLUME_DURATION 3000
25 
26 enum gfx_widget_volume_icon
27 {
28    ICON_MED = 0,
29    ICON_MAX,
30    ICON_MIN,
31    ICON_MUTE,
32 
33    ICON_LAST
34 };
35 
36 static const char* const ICONS_NAMES[ICON_LAST] = {
37    "menu_volume_med.png",
38    "menu_volume_max.png",
39    "menu_volume_min.png",
40    "menu_volume_mute.png",
41 };
42 
43 /* Widget state */
44 struct gfx_widget_volume_state
45 {
46    uintptr_t tag;
47    uintptr_t textures[ICON_LAST];
48 
49    unsigned widget_width;
50    unsigned widget_height;
51 
52    float bar_background[16];
53    float bar_normal[16];
54    float bar_loud[16];
55    float bar_loudest[16];
56    float alpha;
57    float text_alpha;
58    float db;
59    float percent;
60    gfx_timer_t timer;   /* float alignment */
61 
62    bool mute;
63 };
64 
65 typedef struct gfx_widget_volume_state gfx_widget_volume_state_t;
66 
67 static gfx_widget_volume_state_t p_w_volume_st = {
68    (uintptr_t) &p_w_volume_st,
69    {0},
70    0,
71    0,
72    COLOR_HEX_TO_FLOAT(0x1A1A1A, 1.0f),
73    COLOR_HEX_TO_FLOAT(0x198AC6, 1.0f),
74    COLOR_HEX_TO_FLOAT(0xF5DD19, 1.0f),
75    COLOR_HEX_TO_FLOAT(0xC23B22, 1.0f),
76    0.0f,
77    0.0f,
78    0.0f,
79    1.0f,
80    0.0f,
81    false
82 };
83 
gfx_widget_volume_frame(void * data,void * user_data)84 static void gfx_widget_volume_frame(void* data, void *user_data)
85 {
86    static float pure_white[16]             = {
87       1.00, 1.00, 1.00, 1.00,
88       1.00, 1.00, 1.00, 1.00,
89       1.00, 1.00, 1.00, 1.00,
90       1.00, 1.00, 1.00, 1.00,
91    };
92    gfx_widget_volume_state_t *state        = &p_w_volume_st;
93 
94    if (state->alpha > 0.0f)
95    {
96       char msg[255];
97       char percentage_msg[255];
98       video_frame_info_t *video_info       = (video_frame_info_t*)data;
99       dispgfx_widget_t *p_dispwidget       = (dispgfx_widget_t*)user_data;
100       gfx_widget_font_data_t *font_regular = &p_dispwidget->gfx_widget_fonts.regular;
101 
102       void *userdata                       = video_info->userdata;
103       unsigned video_width                 = video_info->width;
104       unsigned video_height                = video_info->height;
105 
106       unsigned padding                     = p_dispwidget->simple_widget_padding;
107 
108       float* backdrop_orig                 = p_dispwidget->backdrop_orig;
109 
110       uintptr_t volume_icon                = 0;
111       unsigned icon_size                   = state->textures[ICON_MED] ? state->widget_height : padding;
112       unsigned text_color                  = COLOR_TEXT_ALPHA(0xffffffff, (unsigned)(state->text_alpha*255.0f));
113       unsigned text_color_db               = COLOR_TEXT_ALPHA(TEXT_COLOR_FAINT, (unsigned)(state->text_alpha*255.0f));
114 
115       unsigned bar_x                       = icon_size;
116       unsigned bar_height                  = font_regular->line_height / 2;
117       unsigned bar_width                   = state->widget_width - bar_x - padding;
118       unsigned bar_y                       = state->widget_height / 2 + bar_height;
119 
120       float *bar_background                = NULL;
121       float *bar_foreground                = NULL;
122       float bar_percentage                 = 0.0f;
123       gfx_display_t            *p_disp     = (gfx_display_t*)video_info->disp_userdata;
124       gfx_display_ctx_driver_t *dispctx    = p_disp->dispctx;
125 
126       /* Note: Volume + percentage text has no component
127        * that extends below the baseline, so we shift
128        * the text down by the font descender to achieve
129        * better spacing */
130       unsigned volume_text_y               = (bar_y / 2.0f)
131          + font_regular->line_centre_offset
132          + font_regular->line_descender;
133 
134       msg[0]                               = '\0';
135       percentage_msg[0]                    = '\0';
136 
137       if (state->mute)
138          volume_icon                       = state->textures[ICON_MUTE];
139       else if (state->percent <= 1.0f)
140       {
141          if (state->percent <= 0.5f)
142             volume_icon                    = state->textures[ICON_MIN];
143          else
144             volume_icon                    = state->textures[ICON_MED];
145 
146          bar_background                    = state->bar_background;
147          bar_foreground                    = state->bar_normal;
148          bar_percentage                    = state->percent;
149       }
150       else if (state->percent > 1.0f && state->percent <= 2.0f)
151       {
152          volume_icon                       = state->textures[ICON_MAX];
153 
154          bar_background                    = state->bar_normal;
155          bar_foreground                    = state->bar_loud;
156          bar_percentage                    = state->percent - 1.0f;
157       }
158       else
159       {
160          volume_icon                       = state->textures[ICON_MAX];
161 
162          bar_background                    = state->bar_loud;
163          bar_foreground                    = state->bar_loudest;
164          bar_percentage                    = state->percent - 2.0f;
165       }
166 
167       if (bar_percentage > 1.0f)
168          bar_percentage                    = 1.0f;
169 
170       /* Backdrop */
171       gfx_display_set_alpha(backdrop_orig, state->alpha);
172 
173       gfx_display_draw_quad(
174             p_disp,
175             userdata,
176             video_width,
177             video_height,
178             0, 0,
179             state->widget_width,
180             state->widget_height,
181             video_width,
182             video_height,
183             backdrop_orig
184             );
185 
186       /* Icon */
187       if (volume_icon)
188       {
189          gfx_display_set_alpha(pure_white, state->text_alpha);
190 
191          if (dispctx && dispctx->blend_begin)
192             dispctx->blend_begin(userdata);
193          gfx_widgets_draw_icon(
194                userdata,
195                p_disp,
196                video_width,
197                video_height,
198                icon_size, icon_size,
199                volume_icon,
200                0, 0,
201                0, 1, pure_white
202                );
203          if (dispctx && dispctx->blend_end)
204             dispctx->blend_end(userdata);
205       }
206 
207       if (state->mute)
208       {
209          if (!state->textures[ICON_MUTE])
210          {
211             const char *text  = msg_hash_to_str(MSG_AUDIO_MUTED);
212             gfx_widgets_draw_text(font_regular,
213                   text,
214                   state->widget_width / 2,
215                   state->widget_height / 2.0f
216                   + font_regular->line_centre_offset,
217                   video_width, video_height,
218                   text_color, TEXT_ALIGN_CENTER,
219                   true);
220          }
221       }
222       else
223       {
224          /* Bar */
225          gfx_display_set_alpha(bar_background, state->text_alpha);
226          gfx_display_set_alpha(bar_foreground, state->text_alpha);
227 
228          gfx_display_draw_quad(
229                p_disp,
230                userdata,
231                video_width,
232                video_height,
233                bar_x + bar_percentage * bar_width, bar_y,
234                bar_width - bar_percentage * bar_width, bar_height,
235                video_width, video_height,
236                bar_background
237                );
238 
239          gfx_display_draw_quad(
240                p_disp,
241                userdata,
242                video_width,
243                video_height,
244                bar_x, bar_y,
245                bar_percentage * bar_width, bar_height,
246                video_width, video_height,
247                bar_foreground
248                );
249 
250          /* Text */
251          snprintf(msg, sizeof(msg), (state->db >= 0 ? "+%.1f dB" : "%.1f dB"),
252             state->db);
253 
254          snprintf(percentage_msg, sizeof(percentage_msg), "%d%%",
255             (int)(state->percent * 100.0f));
256 
257          gfx_widgets_draw_text(font_regular,
258                msg,
259                state->widget_width - padding, volume_text_y,
260                video_width, video_height,
261                text_color_db,
262                TEXT_ALIGN_RIGHT,
263                false);
264 
265          gfx_widgets_draw_text(font_regular,
266             percentage_msg,
267             icon_size, volume_text_y,
268             video_width, video_height,
269             text_color,
270             TEXT_ALIGN_LEFT,
271             false);
272       }
273    }
274 }
275 
gfx_widget_volume_timer_end(void * userdata)276 static void gfx_widget_volume_timer_end(void *userdata)
277 {
278    gfx_animation_ctx_entry_t entry;
279    gfx_widget_volume_state_t *state = &p_w_volume_st;
280 
281    entry.cb             = NULL;
282    entry.duration       = MSG_QUEUE_ANIMATION_DURATION;
283    entry.easing_enum    = EASING_OUT_QUAD;
284    entry.subject        = &state->alpha;
285    entry.tag            = state->tag;
286    entry.target_value   = 0.0f;
287    entry.userdata       = NULL;
288 
289    gfx_animation_push(&entry);
290 
291    entry.subject        = &state->text_alpha;
292 
293    gfx_animation_push(&entry);
294 }
295 
gfx_widget_volume_update_and_show(float new_volume,bool mute)296 void gfx_widget_volume_update_and_show(float new_volume, bool mute)
297 {
298    gfx_timer_ctx_entry_t entry;
299    gfx_widget_volume_state_t *state = &p_w_volume_st;
300 
301    gfx_animation_kill_by_tag(&state->tag);
302 
303    state->db         = new_volume;
304    state->percent    = pow(10, new_volume/20);
305    state->alpha      = DEFAULT_BACKDROP;
306    state->text_alpha = 1.0f;
307    state->mute       = mute;
308 
309    entry.cb          = gfx_widget_volume_timer_end;
310    entry.duration    = VOLUME_DURATION;
311    entry.userdata    = NULL;
312 
313    gfx_animation_timer_start(&state->timer, &entry);
314 }
315 
gfx_widget_volume_layout(void * data,bool is_threaded,const char * dir_assets,char * font_path)316 static void gfx_widget_volume_layout(
317       void *data,
318       bool is_threaded, const char *dir_assets, char *font_path)
319 {
320    dispgfx_widget_t *p_dispwidget       = (dispgfx_widget_t*)data;
321    gfx_widget_volume_state_t *state     = &p_w_volume_st;
322    unsigned last_video_width            = p_dispwidget->last_video_width;
323    gfx_widget_font_data_t *font_regular = &p_dispwidget->gfx_widget_fonts.regular;
324 
325    state->widget_height                 = font_regular->line_height * 4;
326    state->widget_width                  = state->widget_height * 4;
327 
328    /* Volume widget cannot exceed screen width
329     * > If it does, scale it down */
330    if (state->widget_width > last_video_width)
331    {
332       state->widget_width  = last_video_width;
333       state->widget_height = state->widget_width / 4;
334    }
335 }
336 
gfx_widget_volume_context_reset(bool is_threaded,unsigned width,unsigned height,bool fullscreen,const char * dir_assets,char * font_path,char * menu_png_path,char * widgets_png_path)337 static void gfx_widget_volume_context_reset(bool is_threaded,
338       unsigned width, unsigned height, bool fullscreen,
339       const char *dir_assets, char *font_path,
340       char* menu_png_path,
341       char* widgets_png_path)
342 {
343    size_t i;
344    gfx_widget_volume_state_t *state     = &p_w_volume_st;
345 
346    for (i = 0; i < ICON_LAST; i++)
347       gfx_display_reset_textures_list(ICONS_NAMES[i], menu_png_path, &state->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL);
348 }
349 
gfx_widget_volume_context_destroy(void)350 static void gfx_widget_volume_context_destroy(void)
351 {
352    size_t i;
353    gfx_widget_volume_state_t *state     = &p_w_volume_st;
354 
355    for (i = 0; i < ICON_LAST; i++)
356       video_driver_texture_unload(&state->textures[i]);
357 }
358 
gfx_widget_volume_free(void)359 static void gfx_widget_volume_free(void)
360 {
361    gfx_widget_volume_state_t *state     = &p_w_volume_st;
362 
363    /* Kill all running animations */
364    gfx_animation_kill_by_tag(&state->tag);
365 
366    state->alpha = 0.0f;
367 }
368 
369 const gfx_widget_t gfx_widget_volume = {
370    NULL, /* init */
371    gfx_widget_volume_free,
372    gfx_widget_volume_context_reset,
373    gfx_widget_volume_context_destroy,
374    gfx_widget_volume_layout,
375    NULL, /* iterate */
376    gfx_widget_volume_frame
377 };
378