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