gfx_widgets.c 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179
  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. #include <retro_miscellaneous.h>
  18. #include <retro_inline.h>
  19. #ifdef HAVE_CONFIG_H
  20. #include "../config.h"
  21. #endif
  22. #include <queues/fifo_queue.h>
  23. #include <file/file_path.h>
  24. #include <streams/file_stream.h>
  25. #include <string/stdstring.h>
  26. #include <retro_math.h>
  27. #include "gfx_display.h"
  28. #include "gfx_widgets.h"
  29. #include "font_driver.h"
  30. #include "../configuration.h"
  31. #include "../file_path_special.h"
  32. #include "../msg_hash.h"
  33. #include "../tasks/task_content.h"
  34. #include "../tasks/tasks_internal.h"
  35. #define BASE_FONT_SIZE 32.0f
  36. #define MSG_QUEUE_FONT_SIZE (BASE_FONT_SIZE * 0.69f)
  37. /* Icons */
  38. static const char
  39. *gfx_widgets_icons_names[MENU_WIDGETS_ICON_LAST] = {
  40. "menu_pause.png",
  41. "menu_frameskip.png",
  42. "menu_rewind.png",
  43. "resume.png",
  44. "menu_hourglass.png",
  45. "menu_check.png",
  46. "menu_info.png",
  47. "menu_achievements.png"
  48. };
  49. static dispgfx_widget_t dispwidget_st = {0}; /* uint64_t alignment */
  50. static void INLINE gfx_widgets_font_free(gfx_widget_font_data_t *font_data)
  51. {
  52. if (font_data->font)
  53. gfx_display_font_free(font_data->font);
  54. font_data->font = NULL;
  55. font_data->usage_count = 0;
  56. }
  57. /* Widgets list */
  58. const static gfx_widget_t* const widgets[] = {
  59. #ifdef HAVE_NETWORKING
  60. &gfx_widget_netplay_chat,
  61. &gfx_widget_netplay_ping,
  62. #endif
  63. #ifdef HAVE_SCREENSHOTS
  64. &gfx_widget_screenshot,
  65. #endif
  66. &gfx_widget_volume,
  67. #ifdef HAVE_CHEEVOS
  68. &gfx_widget_achievement_popup,
  69. &gfx_widget_leaderboard_display,
  70. #endif
  71. &gfx_widget_generic_message,
  72. &gfx_widget_libretro_message,
  73. &gfx_widget_progress_message,
  74. &gfx_widget_load_content_animation
  75. };
  76. #if defined(HAVE_MENU) && defined(HAVE_XMB)
  77. static float gfx_display_get_widget_pixel_scale(
  78. gfx_display_t *p_disp,
  79. settings_t *settings,
  80. unsigned width, unsigned height, bool fullscreen)
  81. {
  82. static unsigned last_width = 0;
  83. static unsigned last_height = 0;
  84. static float scale = 0.0f;
  85. static bool scale_cached = false;
  86. bool scale_updated = false;
  87. static float last_menu_scale_factor = 0.0f;
  88. static enum menu_driver_id_type last_menu_driver_id = MENU_DRIVER_ID_UNKNOWN;
  89. static float adjusted_scale = 1.0f;
  90. bool gfx_widget_scale_auto = settings->bools.menu_widget_scale_auto;
  91. #if (defined(RARCH_CONSOLE) || defined(RARCH_MOBILE))
  92. float menu_widget_scale_factor = settings->floats.menu_widget_scale_factor;
  93. #else
  94. float menu_widget_scale_factor_fullscreen = settings->floats.menu_widget_scale_factor;
  95. float menu_widget_scale_factor_windowed = settings->floats.menu_widget_scale_factor_windowed;
  96. float menu_widget_scale_factor = fullscreen ?
  97. menu_widget_scale_factor_fullscreen : menu_widget_scale_factor_windowed;
  98. #endif
  99. float menu_scale_factor = menu_widget_scale_factor;
  100. if (gfx_widget_scale_auto)
  101. {
  102. #ifdef HAVE_RGUI
  103. /* When using RGUI, _menu_scale_factor
  104. * is ignored
  105. * > If we are not using a widget scale factor override,
  106. * just set menu_scale_factor to 1.0 */
  107. if (p_disp->menu_driver_id == MENU_DRIVER_ID_RGUI)
  108. menu_scale_factor = 1.0f;
  109. else
  110. #endif
  111. {
  112. float _menu_scale_factor =
  113. settings->floats.menu_scale_factor;
  114. menu_scale_factor = _menu_scale_factor;
  115. }
  116. }
  117. /* We need to perform a square root here, which
  118. * can be slow on some platforms (not *slow*, but
  119. * it involves enough work that it's worth trying
  120. * to optimise). We therefore cache the pixel scale,
  121. * and only update on first run or when the video
  122. * size changes */
  123. if (!scale_cached ||
  124. (width != last_width) ||
  125. (height != last_height))
  126. {
  127. /* Baseline reference is a 1080p display */
  128. scale = (float)(
  129. sqrt((double)((width * width) + (height * height))) /
  130. DIAGONAL_PIXELS_1080P);
  131. scale_cached = true;
  132. scale_updated = true;
  133. last_width = width;
  134. last_height = height;
  135. }
  136. /* Adjusted scale calculation may also be slow, so
  137. * only update if something changes */
  138. if (scale_updated ||
  139. (menu_scale_factor != last_menu_scale_factor) ||
  140. (p_disp->menu_driver_id != last_menu_driver_id))
  141. {
  142. adjusted_scale = gfx_display_get_adjusted_scale(
  143. p_disp,
  144. scale, menu_scale_factor, width);
  145. last_menu_scale_factor = menu_scale_factor;
  146. last_menu_driver_id = p_disp->menu_driver_id;
  147. }
  148. return adjusted_scale;
  149. }
  150. #endif
  151. static void msg_widget_msg_transition_animation_done(void *userdata)
  152. {
  153. disp_widget_msg_t *msg = (disp_widget_msg_t*)userdata;
  154. if (msg->msg)
  155. free(msg->msg);
  156. msg->msg = NULL;
  157. if (msg->msg_new)
  158. msg->msg = strdup(msg->msg_new);
  159. msg->msg_transition_animation = 0.0f;
  160. }
  161. void gfx_widgets_msg_queue_push(
  162. retro_task_t *task,
  163. const char *msg,
  164. unsigned duration,
  165. char *title,
  166. enum message_queue_icon icon,
  167. enum message_queue_category category,
  168. unsigned prio, bool flush,
  169. bool menu_is_alive)
  170. {
  171. disp_widget_msg_t *msg_widget = NULL;
  172. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  173. if (FIFO_WRITE_AVAIL_NONPTR(p_dispwidget->msg_queue) > 0)
  174. {
  175. /* Get current msg if it exists */
  176. if (task && task->frontend_userdata)
  177. {
  178. msg_widget = (disp_widget_msg_t*)task->frontend_userdata;
  179. /* msg_widgets can be passed between tasks */
  180. msg_widget->task_ptr = task;
  181. }
  182. /* Spawn a new notification */
  183. if (!msg_widget)
  184. {
  185. const char *title = msg;
  186. msg_widget = (disp_widget_msg_t*)malloc(sizeof(*msg_widget));
  187. msg_widget->msg = NULL;
  188. msg_widget->msg_new = NULL;
  189. msg_widget->msg_transition_animation = 0.0f;
  190. msg_widget->msg_len = 0;
  191. msg_widget->duration = duration;
  192. msg_widget->text_height = 0;
  193. msg_widget->offset_y = 0;
  194. msg_widget->alpha = 1.0f;
  195. msg_widget->width = 0;
  196. msg_widget->expiration_timer = 0;
  197. msg_widget->task_ptr = task;
  198. msg_widget->task_count = 0;
  199. msg_widget->task_progress = 0;
  200. msg_widget->task_ident = 0;
  201. msg_widget->unfold = 0.0f;
  202. msg_widget->hourglass_rotation = 0.0f;
  203. msg_widget->hourglass_timer = 0.0f;
  204. msg_widget->flags = 0;
  205. if (!(p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS))
  206. {
  207. msg_widget->flags |= DISPWIDG_FLAG_UNFOLDED;
  208. msg_widget->flags &= ~DISPWIDG_FLAG_UNFOLDING;
  209. msg_widget->unfold = 1.0f;
  210. }
  211. if (task)
  212. {
  213. title = msg_widget->msg = strdup(task->title);
  214. msg_widget->msg_new = strdup(title);
  215. msg_widget->msg_len = strlen(title);
  216. if (!string_is_empty(task->error))
  217. msg_widget->flags |= DISPWIDG_FLAG_TASK_ERROR;
  218. if (task->cancelled)
  219. msg_widget->flags |= DISPWIDG_FLAG_TASK_CANCELLED;
  220. if (task->finished)
  221. msg_widget->flags |= DISPWIDG_FLAG_TASK_FINISHED;
  222. msg_widget->task_progress = task->progress;
  223. msg_widget->task_ident = task->ident;
  224. msg_widget->task_count = 1;
  225. msg_widget->flags |= DISPWIDG_FLAG_UNFOLDED;
  226. msg_widget->width = font_driver_get_message_width(
  227. p_dispwidget->gfx_widget_fonts.msg_queue.font,
  228. title,
  229. msg_widget->msg_len, 1.0f) +
  230. p_dispwidget->simple_widget_padding / 2;
  231. task->frontend_userdata = msg_widget;
  232. msg_widget->hourglass_rotation = 0;
  233. }
  234. else
  235. {
  236. /* Compute rect width, wrap if necessary */
  237. /* Single line text > two lines text > two lines
  238. * text with expanded width */
  239. size_t title_length = strlen(title);
  240. char *msg = NULL;
  241. size_t msg_len = 0;
  242. unsigned width = menu_is_alive
  243. ? p_dispwidget->msg_queue_default_rect_width_menu_alive
  244. : p_dispwidget->msg_queue_default_rect_width;
  245. unsigned text_width = font_driver_get_message_width(
  246. p_dispwidget->gfx_widget_fonts.msg_queue.font,
  247. title,
  248. title_length,
  249. 1.0f);
  250. msg_widget->text_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height;
  251. /* 1 byte uses for inserting '\n' */
  252. msg_len = title_length + 1 + 1;
  253. if (!(msg = (char *)malloc(msg_len)))
  254. return;
  255. msg[0] = '\0';
  256. /* Text is too wide, split it into two lines */
  257. if (text_width > width)
  258. {
  259. /* If the second line is too short, the widget may
  260. * look unappealing - ensure that second line is at
  261. * least 25% of the total width */
  262. if ((text_width - (text_width >> 2)) < width)
  263. width = text_width - (text_width >> 2);
  264. word_wrap(msg, msg_len, title, title_length,
  265. (int)((title_length * width) / text_width),
  266. 100, 2);
  267. msg_widget->text_height *= 2;
  268. }
  269. else
  270. {
  271. width = text_width;
  272. strlcpy(msg, title, msg_len);
  273. }
  274. msg_widget->msg = msg;
  275. msg_widget->msg_len = strlen(msg);
  276. msg_widget->width = width +
  277. p_dispwidget->simple_widget_padding / 2;
  278. }
  279. fifo_write(&p_dispwidget->msg_queue,
  280. &msg_widget, sizeof(msg_widget));
  281. }
  282. /* Update task info */
  283. else
  284. {
  285. if (msg_widget->flags & DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED)
  286. {
  287. uintptr_t _tag = (uintptr_t)&msg_widget->expiration_timer;
  288. gfx_animation_kill_by_tag(&_tag);
  289. msg_widget->flags &= ~DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED;
  290. }
  291. if (!string_is_equal(task->title, msg_widget->msg_new))
  292. {
  293. size_t len;
  294. unsigned new_width;
  295. if (msg_widget->msg_new)
  296. {
  297. free(msg_widget->msg_new);
  298. msg_widget->msg_new = NULL;
  299. }
  300. title = msg_widget->msg_new = strdup(task->title);
  301. len = strlen(title);
  302. new_width = font_driver_get_message_width(
  303. p_dispwidget->gfx_widget_fonts.msg_queue.font,
  304. title,
  305. len,
  306. 1.0f);
  307. msg_widget->msg_len = len;
  308. msg_widget->msg_transition_animation = 0;
  309. if (!task->alternative_look)
  310. {
  311. gfx_animation_ctx_entry_t entry;
  312. entry.easing_enum = EASING_OUT_QUAD;
  313. entry.tag = (uintptr_t)msg_widget;
  314. entry.duration = MSG_QUEUE_ANIMATION_DURATION*2;
  315. entry.target_value = p_dispwidget->msg_queue_height / 2.0f;
  316. entry.subject = &msg_widget->msg_transition_animation;
  317. entry.cb = msg_widget_msg_transition_animation_done;
  318. entry.userdata = msg_widget;
  319. gfx_animation_push(&entry);
  320. }
  321. else
  322. msg_widget_msg_transition_animation_done(msg_widget);
  323. msg_widget->task_count++;
  324. msg_widget->width = new_width;
  325. }
  326. if (!string_is_empty(task->error))
  327. msg_widget->flags |= DISPWIDG_FLAG_TASK_ERROR;
  328. if (task->cancelled)
  329. msg_widget->flags |= DISPWIDG_FLAG_TASK_CANCELLED;
  330. if (task->finished)
  331. msg_widget->flags |= DISPWIDG_FLAG_TASK_FINISHED;
  332. msg_widget->task_progress = task->progress;
  333. }
  334. }
  335. }
  336. static void gfx_widgets_unfold_end(void *userdata)
  337. {
  338. disp_widget_msg_t *unfold = (disp_widget_msg_t*)userdata;
  339. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  340. unfold->flags &= ~DISPWIDG_FLAG_UNFOLDING;
  341. p_dispwidget->flags &= ~DISPGFX_WIDGET_FLAG_MOVING;
  342. }
  343. static void gfx_widgets_move_end(void *userdata)
  344. {
  345. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  346. if (userdata)
  347. {
  348. gfx_animation_ctx_entry_t entry;
  349. disp_widget_msg_t *unfold = (disp_widget_msg_t*)userdata;
  350. entry.cb = gfx_widgets_unfold_end;
  351. entry.duration = MSG_QUEUE_ANIMATION_DURATION;
  352. entry.easing_enum = EASING_OUT_QUAD;
  353. entry.subject = &unfold->unfold;
  354. entry.tag = (uintptr_t)unfold;
  355. entry.target_value = 1.0f;
  356. entry.userdata = unfold;
  357. gfx_animation_push(&entry);
  358. unfold->flags |= DISPWIDG_FLAG_UNFOLDED
  359. | DISPWIDG_FLAG_UNFOLDING;
  360. }
  361. else
  362. p_dispwidget->flags &= ~DISPGFX_WIDGET_FLAG_MOVING;
  363. }
  364. static void gfx_widgets_msg_queue_expired(void *userdata)
  365. {
  366. disp_widget_msg_t *msg = (disp_widget_msg_t *)userdata;
  367. if (msg && !(msg->flags & DISPWIDG_FLAG_EXPIRED))
  368. msg->flags |= DISPWIDG_FLAG_EXPIRED;
  369. }
  370. static void gfx_widgets_msg_queue_move(dispgfx_widget_t *p_dispwidget)
  371. {
  372. int i;
  373. float y = 0;
  374. /* there should always be one and only one unfolded message */
  375. disp_widget_msg_t *unfold = NULL;
  376. #ifdef HAVE_THREADS
  377. slock_lock(p_dispwidget->current_msgs_lock);
  378. #endif
  379. for (i = (int)(p_dispwidget->current_msgs_size - 1); i >= 0; i--)
  380. {
  381. disp_widget_msg_t* msg = p_dispwidget->current_msgs[i];
  382. if (!msg || (msg->flags & DISPWIDG_FLAG_DYING))
  383. continue;
  384. y += p_dispwidget->msg_queue_height
  385. / (msg->task_ptr ? 2 : 1) + p_dispwidget->msg_queue_spacing;
  386. if (!(msg->flags & DISPWIDG_FLAG_UNFOLDED))
  387. unfold = msg;
  388. if (msg->offset_y != y)
  389. {
  390. gfx_animation_ctx_entry_t entry;
  391. entry.cb = (i == 0) ? gfx_widgets_move_end : NULL;
  392. entry.duration = MSG_QUEUE_ANIMATION_DURATION;
  393. entry.easing_enum = EASING_OUT_QUAD;
  394. entry.subject = &msg->offset_y;
  395. entry.tag = (uintptr_t)msg;
  396. entry.target_value = y;
  397. entry.userdata = unfold;
  398. gfx_animation_push(&entry);
  399. p_dispwidget->flags |= DISPGFX_WIDGET_FLAG_MOVING;
  400. }
  401. }
  402. #ifdef HAVE_THREADS
  403. slock_unlock(p_dispwidget->current_msgs_lock);
  404. #endif
  405. }
  406. static void gfx_widgets_msg_queue_free(
  407. dispgfx_widget_t *p_dispwidget,
  408. disp_widget_msg_t *msg)
  409. {
  410. uintptr_t tag = (uintptr_t)msg;
  411. uintptr_t hourglass_timer_tag = (uintptr_t)&msg->hourglass_timer;
  412. if (msg->task_ptr)
  413. {
  414. /* remove the reference the task has of ourself
  415. only if the task is not finished already
  416. (finished tasks are freed before the widget) */
  417. if ( !(msg->flags & DISPWIDG_FLAG_TASK_FINISHED)
  418. && !(msg->flags & DISPWIDG_FLAG_TASK_ERROR)
  419. && !(msg->flags & DISPWIDG_FLAG_TASK_CANCELLED))
  420. msg->task_ptr->frontend_userdata = NULL;
  421. /* update tasks count */
  422. p_dispwidget->msg_queue_tasks_count--;
  423. }
  424. /* Kill all animations */
  425. gfx_animation_kill_by_tag(&hourglass_timer_tag);
  426. gfx_animation_kill_by_tag(&tag);
  427. /* Kill all timers */
  428. if (msg->flags & DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED)
  429. {
  430. uintptr_t _tag = (uintptr_t)&msg->expiration_timer;
  431. gfx_animation_kill_by_tag(&_tag);
  432. }
  433. /* Free it */
  434. if (msg->msg)
  435. free(msg->msg);
  436. if (msg->msg_new)
  437. free(msg->msg_new);
  438. p_dispwidget->flags &= ~DISPGFX_WIDGET_FLAG_MOVING;
  439. }
  440. static void gfx_widgets_msg_queue_kill_end(void *userdata)
  441. {
  442. disp_widget_msg_t* msg;
  443. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  444. #ifdef HAVE_THREADS
  445. slock_lock(p_dispwidget->current_msgs_lock);
  446. #endif
  447. if ((msg = p_dispwidget->current_msgs[p_dispwidget->msg_queue_kill]))
  448. {
  449. int i;
  450. /* Remove it from the list */
  451. for (i = p_dispwidget->msg_queue_kill; i < (int)(p_dispwidget->current_msgs_size - 1); i++)
  452. p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i + 1];
  453. p_dispwidget->current_msgs_size--;
  454. p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = NULL;
  455. /* clean up the item */
  456. gfx_widgets_msg_queue_free(p_dispwidget, msg);
  457. /* free the associated memory */
  458. free(msg);
  459. }
  460. #ifdef HAVE_THREADS
  461. slock_unlock(p_dispwidget->current_msgs_lock);
  462. #endif
  463. }
  464. static void gfx_widgets_msg_queue_kill(
  465. dispgfx_widget_t *p_dispwidget,
  466. unsigned idx)
  467. {
  468. gfx_animation_ctx_entry_t entry;
  469. disp_widget_msg_t *msg = p_dispwidget->current_msgs[idx];
  470. if (!msg)
  471. return;
  472. p_dispwidget->flags |= DISPGFX_WIDGET_FLAG_MOVING;
  473. msg->flags |= DISPWIDG_FLAG_DYING;
  474. p_dispwidget->msg_queue_kill = idx;
  475. /* Drop down */
  476. entry.cb = NULL;
  477. entry.duration = MSG_QUEUE_ANIMATION_DURATION;
  478. entry.easing_enum = EASING_OUT_QUAD;
  479. entry.tag = (uintptr_t)msg;
  480. entry.userdata = NULL;
  481. entry.subject = &msg->offset_y;
  482. entry.target_value = msg->offset_y -
  483. p_dispwidget->msg_queue_height / 4;
  484. gfx_animation_push(&entry);
  485. /* Fade out */
  486. entry.cb = gfx_widgets_msg_queue_kill_end;
  487. entry.subject = &msg->alpha;
  488. entry.target_value = 0.0f;
  489. gfx_animation_push(&entry);
  490. /* Move all messages back to their correct position */
  491. if (p_dispwidget->current_msgs_size != 0)
  492. gfx_widgets_msg_queue_move(p_dispwidget);
  493. }
  494. void gfx_widgets_draw_icon(
  495. void *userdata,
  496. void *data_disp,
  497. unsigned video_width,
  498. unsigned video_height,
  499. unsigned icon_width,
  500. unsigned icon_height,
  501. uintptr_t texture,
  502. float x, float y,
  503. float radians,
  504. float cosine,
  505. float sine,
  506. float *color)
  507. {
  508. gfx_display_ctx_draw_t draw;
  509. struct video_coords coords;
  510. math_matrix_4x4 mymat;
  511. gfx_display_t *p_disp = (gfx_display_t*)data_disp;
  512. gfx_display_ctx_driver_t *dispctx = p_disp->dispctx;
  513. if (!texture)
  514. return;
  515. if (!p_disp->dispctx->handles_transform)
  516. gfx_display_rotate_z(p_disp, &mymat, cosine, sine, userdata);
  517. coords.vertices = 4;
  518. coords.vertex = NULL;
  519. coords.tex_coord = NULL;
  520. coords.lut_tex_coord = NULL;
  521. coords.color = color;
  522. draw.x = x;
  523. draw.y = video_height - y - icon_height;
  524. draw.width = icon_width;
  525. draw.height = icon_height;
  526. draw.scale_factor = 1.0f;
  527. draw.rotation = radians;
  528. draw.coords = &coords;
  529. draw.matrix_data = &mymat;
  530. draw.texture = texture;
  531. draw.prim_type = GFX_DISPLAY_PRIM_TRIANGLESTRIP;
  532. draw.pipeline_id = 0;
  533. if (draw.height > 0 && draw.width > 0)
  534. if (dispctx->draw)
  535. dispctx->draw(&draw, userdata, video_width, video_height);
  536. }
  537. void gfx_widgets_draw_text(
  538. gfx_widget_font_data_t* font_data,
  539. const char *text,
  540. float x, float y,
  541. int width, int height,
  542. uint32_t color,
  543. enum text_alignment text_align,
  544. bool draw_outside)
  545. {
  546. if (!font_data || string_is_empty(text))
  547. return;
  548. gfx_display_draw_text(
  549. font_data->font,
  550. text,
  551. x, y,
  552. width, height,
  553. color,
  554. text_align,
  555. 1.0f,
  556. false,
  557. 0.0f,
  558. draw_outside);
  559. font_data->usage_count++;
  560. }
  561. void gfx_widgets_flush_text(
  562. unsigned video_width, unsigned video_height,
  563. gfx_widget_font_data_t* font_data)
  564. {
  565. /* Flushing is slow - only do it if font
  566. * has actually been used */
  567. if (!font_data || (font_data->usage_count == 0))
  568. return;
  569. font_driver_flush(video_width, video_height, font_data->font);
  570. font_data->raster_block.carr.coords.vertices = 0;
  571. font_data->usage_count = 0;
  572. }
  573. float gfx_widgets_get_thumbnail_scale_factor(
  574. const float dst_width, const float dst_height,
  575. const float image_width, const float image_height)
  576. {
  577. float dst_ratio = dst_width / dst_height;
  578. float image_ratio = image_width / image_height;
  579. if (dst_ratio > image_ratio)
  580. return (dst_height / image_height);
  581. return (dst_width / image_width);
  582. }
  583. static void gfx_widgets_start_msg_expiration_timer(
  584. disp_widget_msg_t *msg_widget, unsigned duration)
  585. {
  586. gfx_timer_ctx_entry_t timer;
  587. timer.cb = gfx_widgets_msg_queue_expired;
  588. timer.duration = duration;
  589. timer.userdata = msg_widget;
  590. gfx_animation_timer_start(&msg_widget->expiration_timer, &timer);
  591. msg_widget->flags |=
  592. DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED;
  593. }
  594. static void gfx_widgets_hourglass_tick(void *userdata);
  595. static void gfx_widgets_hourglass_end(void *userdata)
  596. {
  597. gfx_timer_ctx_entry_t timer;
  598. disp_widget_msg_t *msg = (disp_widget_msg_t*)userdata;
  599. msg->hourglass_rotation = 0.0f;
  600. timer.cb = gfx_widgets_hourglass_tick;
  601. timer.duration = HOURGLASS_INTERVAL;
  602. timer.userdata = msg;
  603. gfx_animation_timer_start(&msg->hourglass_timer, &timer);
  604. }
  605. static void gfx_widgets_hourglass_tick(void *userdata)
  606. {
  607. gfx_animation_ctx_entry_t entry;
  608. disp_widget_msg_t *msg = (disp_widget_msg_t*)userdata;
  609. uintptr_t tag = (uintptr_t)msg;
  610. entry.easing_enum = EASING_OUT_QUAD;
  611. entry.tag = tag;
  612. entry.duration = HOURGLASS_DURATION;
  613. entry.target_value = -(2 * M_PI);
  614. entry.subject = &msg->hourglass_rotation;
  615. entry.cb = gfx_widgets_hourglass_end;
  616. entry.userdata = msg;
  617. gfx_animation_push(&entry);
  618. }
  619. static void gfx_widgets_font_init(
  620. gfx_display_t *p_disp,
  621. dispgfx_widget_t *p_dispwidget,
  622. gfx_widget_font_data_t *font_data,
  623. bool is_threaded, char *font_path, float font_size)
  624. {
  625. int glyph_width = 0;
  626. float scaled_size = font_size *
  627. p_dispwidget->last_scale_factor;
  628. /* Free existing font */
  629. if (font_data->font)
  630. {
  631. gfx_display_font_free(font_data->font);
  632. font_data->font = NULL;
  633. }
  634. /* Get approximate glyph width */
  635. font_data->glyph_width = scaled_size * (3.0f / 4.0f);
  636. /* Create font */
  637. font_data->font = gfx_display_font_file(p_disp,
  638. font_path, scaled_size, is_threaded);
  639. /* Get font metadata */
  640. glyph_width = font_driver_get_message_width(font_data->font, "a", 1, 1.0f);
  641. if (glyph_width > 0)
  642. font_data->glyph_width = (float)glyph_width;
  643. font_data->line_height = (float)font_driver_get_line_height(font_data->font, 1.0f);
  644. font_data->line_ascender = (float)font_driver_get_line_ascender(font_data->font, 1.0f);
  645. font_data->line_descender = (float)font_driver_get_line_descender(font_data->font, 1.0f);
  646. font_data->line_centre_offset = (float)font_driver_get_line_centre_offset(font_data->font, 1.0f);
  647. font_data->usage_count = 0;
  648. }
  649. static void gfx_widgets_layout(
  650. gfx_display_t *p_disp,
  651. dispgfx_widget_t *p_dispwidget,
  652. bool is_threaded, const char *dir_assets, char *font_path)
  653. {
  654. size_t i;
  655. /* Initialise fonts */
  656. if (string_is_empty(font_path))
  657. {
  658. char font_file[PATH_MAX_LENGTH];
  659. /* Create regular font */
  660. gfx_widgets_font_init(p_disp, p_dispwidget,
  661. &p_dispwidget->gfx_widget_fonts.regular,
  662. is_threaded, p_dispwidget->ozone_regular_font_path, BASE_FONT_SIZE);
  663. /* Create bold font */
  664. gfx_widgets_font_init(p_disp, p_dispwidget,
  665. &p_dispwidget->gfx_widget_fonts.bold,
  666. is_threaded, p_dispwidget->ozone_bold_font_path, BASE_FONT_SIZE);
  667. /* Create msg_queue font */
  668. switch (*msg_hash_get_uint(MSG_HASH_USER_LANGUAGE))
  669. {
  670. case RETRO_LANGUAGE_ARABIC:
  671. case RETRO_LANGUAGE_PERSIAN:
  672. fill_pathname_join_special(font_file, p_dispwidget->assets_pkg_dir, "fallback-font.ttf", sizeof(font_file));
  673. break;
  674. case RETRO_LANGUAGE_CHINESE_SIMPLIFIED:
  675. case RETRO_LANGUAGE_CHINESE_TRADITIONAL:
  676. fill_pathname_join_special(font_file, p_dispwidget->assets_pkg_dir, "chinese-fallback-font.ttf", sizeof(font_file));
  677. break;
  678. case RETRO_LANGUAGE_KOREAN:
  679. fill_pathname_join_special(font_file, p_dispwidget->assets_pkg_dir, "korean-fallback-font.ttf", sizeof(font_file));
  680. break;
  681. default:
  682. strlcpy(font_file, p_dispwidget->ozone_regular_font_path, sizeof(font_file));
  683. break;
  684. }
  685. gfx_widgets_font_init(p_disp, p_dispwidget,
  686. &p_dispwidget->gfx_widget_fonts.msg_queue,
  687. is_threaded, font_file, MSG_QUEUE_FONT_SIZE);
  688. }
  689. else
  690. {
  691. /* Load fonts from user-supplied path */
  692. gfx_widgets_font_init(p_disp, p_dispwidget,
  693. &p_dispwidget->gfx_widget_fonts.regular,
  694. is_threaded, font_path, BASE_FONT_SIZE);
  695. gfx_widgets_font_init(p_disp, p_dispwidget,
  696. &p_dispwidget->gfx_widget_fonts.bold,
  697. is_threaded, font_path, BASE_FONT_SIZE);
  698. gfx_widgets_font_init(p_disp, p_dispwidget,
  699. &p_dispwidget->gfx_widget_fonts.msg_queue,
  700. is_threaded, font_path, MSG_QUEUE_FONT_SIZE);
  701. }
  702. /* Calculate dimensions */
  703. p_dispwidget->simple_widget_padding = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f/3.0f;
  704. p_dispwidget->simple_widget_height = p_dispwidget->gfx_widget_fonts.regular.line_height + p_dispwidget->simple_widget_padding;
  705. p_dispwidget->msg_queue_height = p_dispwidget->gfx_widget_fonts.msg_queue.line_height * 2.5f * (BASE_FONT_SIZE / MSG_QUEUE_FONT_SIZE);
  706. if (p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS)
  707. {
  708. #if 0
  709. p_dispwidget->msg_queue_icon_size_y = p_dispwidget->msg_queue_height
  710. * 1.2347826087f; /* original image is 280x284 */
  711. p_dispwidget->msg_queue_icon_size_x = 0.98591549295f * p_dispwidget->msg_queue_icon_size_y;
  712. #else
  713. p_dispwidget->msg_queue_icon_size_y = p_dispwidget->msg_queue_height * 1.2f;
  714. p_dispwidget->msg_queue_icon_size_x = p_dispwidget->msg_queue_icon_size_y;
  715. #endif
  716. }
  717. else
  718. {
  719. p_dispwidget->msg_queue_icon_size_x = p_dispwidget->simple_widget_padding * 1.5f;
  720. p_dispwidget->msg_queue_icon_size_y = 0;
  721. }
  722. p_dispwidget->msg_queue_spacing = p_dispwidget->msg_queue_height / 3.3f;
  723. p_dispwidget->msg_queue_rect_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x;
  724. p_dispwidget->msg_queue_internal_icon_size = p_dispwidget->msg_queue_icon_size_y;
  725. p_dispwidget->msg_queue_internal_icon_offset = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_internal_icon_size) / 2;
  726. p_dispwidget->msg_queue_icon_offset_y = (p_dispwidget->msg_queue_icon_size_y - p_dispwidget->msg_queue_height) / 2;
  727. p_dispwidget->msg_queue_scissor_start_x = p_dispwidget->msg_queue_spacing + p_dispwidget->msg_queue_icon_size_x - (p_dispwidget->msg_queue_icon_size_x * 0.28928571428f);
  728. if (p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS)
  729. p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding / 2;
  730. else
  731. p_dispwidget->msg_queue_regular_padding_x = p_dispwidget->simple_widget_padding;
  732. p_dispwidget->msg_queue_task_rect_start_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;
  733. p_dispwidget->msg_queue_task_text_start_x = p_dispwidget->msg_queue_task_rect_start_x + p_dispwidget->msg_queue_height / 2;
  734. if (!p_dispwidget->gfx_widgets_icons_textures[MENU_WIDGETS_ICON_HOURGLASS])
  735. p_dispwidget->msg_queue_task_text_start_x -=
  736. p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width * 2.0f;
  737. p_dispwidget->msg_queue_regular_text_start = p_dispwidget->msg_queue_rect_start_x;
  738. p_dispwidget->msg_queue_task_hourglass_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;
  739. p_dispwidget->generic_message_height = p_dispwidget->gfx_widget_fonts.regular.line_height * 2.0f;
  740. p_dispwidget->msg_queue_default_rect_width_menu_alive = p_dispwidget
  741. ->gfx_widget_fonts.msg_queue.glyph_width * 40.0f;
  742. p_dispwidget->msg_queue_default_rect_width = p_dispwidget->last_video_width
  743. - p_dispwidget->msg_queue_regular_text_start - (2 * p_dispwidget->simple_widget_padding);
  744. p_dispwidget->divider_width_1px = 1;
  745. if (p_dispwidget->last_scale_factor > 1.0f)
  746. p_dispwidget->divider_width_1px = (unsigned)(p_dispwidget->last_scale_factor + 0.5f);
  747. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  748. {
  749. const gfx_widget_t* widget = widgets[i];
  750. if (widget->layout)
  751. widget->layout(p_dispwidget,
  752. is_threaded, dir_assets, font_path);
  753. }
  754. }
  755. void gfx_widgets_iterate(
  756. void *data_disp,
  757. void *settings_data,
  758. unsigned width, unsigned height, bool fullscreen,
  759. const char *dir_assets, char *font_path,
  760. bool is_threaded)
  761. {
  762. size_t i;
  763. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  764. /* c.f. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323
  765. * On some platforms (e.g. 32-bit x86 without SSE),
  766. * gcc can produce inconsistent floating point results
  767. * depending upon optimisation level. This can break
  768. * floating point variable comparisons. A workaround is
  769. * to declare the affected variable as 'volatile', which
  770. * disables optimisations and removes excess precision
  771. * (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c87) */
  772. volatile float scale_factor = 0.0f;
  773. gfx_display_t *p_disp = (gfx_display_t*)data_disp;
  774. settings_t *settings = (settings_t*)settings_data;
  775. #ifdef HAVE_XMB
  776. enum menu_driver_id_type type = p_disp->menu_driver_id;
  777. if (type == MENU_DRIVER_ID_XMB)
  778. scale_factor = gfx_display_get_widget_pixel_scale(p_disp, settings, width, height, fullscreen);
  779. else
  780. #endif
  781. scale_factor = gfx_display_get_dpi_scale(
  782. p_disp,
  783. settings, width, height, fullscreen, true);
  784. /* Check whether screen dimensions or menu scale
  785. * factor have changed */
  786. if ((scale_factor != p_dispwidget->last_scale_factor) ||
  787. (width != p_dispwidget->last_video_width) ||
  788. (height != p_dispwidget->last_video_height))
  789. {
  790. p_dispwidget->last_scale_factor = scale_factor;
  791. p_dispwidget->last_video_width = width;
  792. p_dispwidget->last_video_height = height;
  793. /* Note: We don't need a full context reset here
  794. * > Just rescale layout, and reset frame time counter */
  795. gfx_widgets_layout(p_disp, p_dispwidget,
  796. is_threaded, dir_assets, font_path);
  797. video_driver_monitor_reset();
  798. }
  799. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  800. {
  801. const gfx_widget_t* widget = widgets[i];
  802. if (widget->iterate)
  803. widget->iterate(p_dispwidget,
  804. width, height, fullscreen,
  805. dir_assets, font_path, is_threaded);
  806. }
  807. /* Messages queue */
  808. /* Consume one message if available */
  809. if ((FIFO_READ_AVAIL_NONPTR(p_dispwidget->msg_queue) > 0)
  810. && !(p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MOVING)
  811. && (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs)))
  812. {
  813. disp_widget_msg_t *msg_widget = NULL;
  814. #ifdef HAVE_THREADS
  815. slock_lock(p_dispwidget->current_msgs_lock);
  816. #endif
  817. if (p_dispwidget->current_msgs_size < ARRAY_SIZE(p_dispwidget->current_msgs))
  818. {
  819. if (FIFO_READ_AVAIL_NONPTR(p_dispwidget->msg_queue) > 0)
  820. fifo_read(&p_dispwidget->msg_queue,
  821. &msg_widget, sizeof(msg_widget));
  822. if (msg_widget)
  823. {
  824. /* Task messages always appear from the bottom of the screen, append it */
  825. if (p_dispwidget->msg_queue_tasks_count == 0 || msg_widget->task_ptr)
  826. p_dispwidget->current_msgs[p_dispwidget->current_msgs_size] = msg_widget;
  827. /* Regular messages are always above tasks, make room and insert it */
  828. else
  829. {
  830. unsigned idx = (unsigned)(p_dispwidget->current_msgs_size -
  831. p_dispwidget->msg_queue_tasks_count);
  832. for (i = p_dispwidget->current_msgs_size; i > idx; i--)
  833. p_dispwidget->current_msgs[i] = p_dispwidget->current_msgs[i - 1];
  834. p_dispwidget->current_msgs[idx] = msg_widget;
  835. }
  836. p_dispwidget->current_msgs_size++;
  837. }
  838. }
  839. #ifdef HAVE_THREADS
  840. slock_unlock(p_dispwidget->current_msgs_lock);
  841. #endif
  842. if (msg_widget)
  843. {
  844. /* Start expiration timer if not associated to a task */
  845. if (!msg_widget->task_ptr)
  846. {
  847. if (!(msg_widget->flags & DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED))
  848. gfx_widgets_start_msg_expiration_timer(
  849. msg_widget, MSG_QUEUE_ANIMATION_DURATION * 2
  850. + msg_widget->duration);
  851. }
  852. /* Else, start hourglass animation timer */
  853. else
  854. {
  855. p_dispwidget->msg_queue_tasks_count++;
  856. gfx_widgets_hourglass_end(msg_widget);
  857. }
  858. if (p_dispwidget->current_msgs_size != 0)
  859. gfx_widgets_msg_queue_move(p_dispwidget);
  860. }
  861. }
  862. /* Kill first expired message */
  863. /* Start expiration timer of dead tasks */
  864. for (i = 0; i < p_dispwidget->current_msgs_size; i++)
  865. {
  866. disp_widget_msg_t *msg_widget = p_dispwidget->current_msgs[i];
  867. if (!msg_widget)
  868. continue;
  869. if (msg_widget->task_ptr
  870. && ((msg_widget->flags & DISPWIDG_FLAG_TASK_FINISHED)
  871. || (msg_widget->flags & DISPWIDG_FLAG_TASK_CANCELLED)))
  872. if (!(msg_widget->flags & DISPWIDG_FLAG_EXPIRATION_TIMER_STARTED))
  873. gfx_widgets_start_msg_expiration_timer(msg_widget, TASK_FINISHED_DURATION);
  874. if ( (msg_widget->flags & DISPWIDG_FLAG_EXPIRED)
  875. && !(p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MOVING))
  876. {
  877. gfx_widgets_msg_queue_kill(p_dispwidget,
  878. (unsigned)i);
  879. break;
  880. }
  881. }
  882. }
  883. static int gfx_widgets_draw_indicator(
  884. dispgfx_widget_t *p_dispwidget,
  885. gfx_display_t *p_disp,
  886. gfx_display_ctx_driver_t *dispctx,
  887. void *userdata,
  888. unsigned video_width,
  889. unsigned video_height,
  890. uintptr_t icon, int y, int top_right_x_advance,
  891. enum msg_hash_enums msg)
  892. {
  893. unsigned width;
  894. gfx_display_set_alpha(p_dispwidget->backdrop_orig, DEFAULT_BACKDROP);
  895. if (icon)
  896. {
  897. unsigned height = p_dispwidget->simple_widget_height * 2;
  898. width = height;
  899. gfx_display_draw_quad(
  900. p_disp,
  901. userdata,
  902. video_width, video_height,
  903. top_right_x_advance - width, y,
  904. width, height,
  905. video_width, video_height,
  906. p_dispwidget->backdrop_orig,
  907. NULL
  908. );
  909. gfx_display_set_alpha(p_dispwidget->pure_white, 1.0f);
  910. if (dispctx && dispctx->blend_begin)
  911. dispctx->blend_begin(userdata);
  912. gfx_widgets_draw_icon(
  913. userdata,
  914. p_disp,
  915. video_width,
  916. video_height,
  917. width,
  918. height,
  919. icon,
  920. top_right_x_advance - width, y,
  921. 0.0f, /* rad */
  922. 1.0f, /* cos(rad) = cos(0) = 1.0f */
  923. 0.0f, /* sine(rad) = sine(0) = 0.0f */
  924. p_dispwidget->pure_white
  925. );
  926. if (dispctx && dispctx->blend_end)
  927. dispctx->blend_end(userdata);
  928. }
  929. else
  930. {
  931. unsigned height = p_dispwidget->simple_widget_height;
  932. const char *txt = msg_hash_to_str(msg);
  933. width = font_driver_get_message_width(
  934. p_dispwidget->gfx_widget_fonts.regular.font,
  935. txt,
  936. strlen(txt), 1.0f)
  937. + p_dispwidget->simple_widget_padding * 2;
  938. gfx_display_draw_quad(
  939. p_disp,
  940. userdata,
  941. video_width, video_height,
  942. top_right_x_advance - width, y,
  943. width, height,
  944. video_width, video_height,
  945. p_dispwidget->backdrop_orig,
  946. NULL
  947. );
  948. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular,
  949. txt,
  950. top_right_x_advance - width
  951. + p_dispwidget->simple_widget_padding,
  952. y + (height / 2.0f) +
  953. p_dispwidget->gfx_widget_fonts.regular.line_centre_offset,
  954. video_width, video_height,
  955. 0xFFFFFFFF, TEXT_ALIGN_LEFT,
  956. false);
  957. }
  958. return width;
  959. }
  960. static void gfx_widgets_draw_task_msg(
  961. dispgfx_widget_t *p_dispwidget,
  962. gfx_display_t *p_disp,
  963. gfx_display_ctx_driver_t *dispctx,
  964. disp_widget_msg_t *msg,
  965. void *userdata,
  966. unsigned video_width,
  967. unsigned video_height)
  968. {
  969. /* Color of first progress bar in a task message */
  970. static float msg_queue_task_progress_1[16] =
  971. COLOR_HEX_TO_FLOAT(0x397869, 1.0f);
  972. /* Color of second progress bar in a task message
  973. * (for multiple tasks with same message) */
  974. static float msg_queue_task_progress_2[16] =
  975. COLOR_HEX_TO_FLOAT(0x317198, 1.0f);
  976. unsigned text_color;
  977. unsigned bar_width;
  978. unsigned rect_x;
  979. unsigned rect_y;
  980. unsigned rect_width;
  981. unsigned rect_height;
  982. float text_y_base;
  983. float *msg_queue_current_background;
  984. float *msg_queue_current_bar;
  985. char task_percentage[256];
  986. bool draw_msg_new = false;
  987. unsigned task_percentage_offset = 0;
  988. if (msg->msg_new)
  989. draw_msg_new = !string_is_equal(msg->msg_new, msg->msg);
  990. task_percentage_offset =
  991. p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width
  992. * ((msg->flags & DISPWIDG_FLAG_TASK_ERROR) ? 12 : 5)
  993. + p_dispwidget->simple_widget_padding * 1.25f; /*11 = STRLEN_CONST("Task failed") + 1 */
  994. if (msg->flags & DISPWIDG_FLAG_TASK_FINISHED)
  995. {
  996. if (msg->flags & DISPWIDG_FLAG_TASK_ERROR) /* TODO/FIXME - localize */
  997. strlcpy(task_percentage, "Task failed", sizeof(task_percentage));
  998. else
  999. {
  1000. task_percentage[0] = ' ';
  1001. task_percentage[1] = '\0';
  1002. }
  1003. }
  1004. else if (msg->task_progress >= 0 && msg->task_progress <= 100)
  1005. {
  1006. task_percentage[0] = '\0';
  1007. snprintf(task_percentage, sizeof(task_percentage),
  1008. "%i%%", msg->task_progress);
  1009. }
  1010. rect_width = p_dispwidget->simple_widget_padding
  1011. + msg->width
  1012. + task_percentage_offset;
  1013. bar_width = rect_width * msg->task_progress/100.0f;
  1014. text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha*255.0f));
  1015. /* Rect */
  1016. if (msg->flags & DISPWIDG_FLAG_TASK_FINISHED)
  1017. if (msg->task_count == 1)
  1018. msg_queue_current_background = msg_queue_task_progress_1;
  1019. else
  1020. msg_queue_current_background = msg_queue_task_progress_2;
  1021. else
  1022. if (msg->task_count == 1)
  1023. msg_queue_current_background = p_dispwidget->msg_queue_bg;
  1024. else
  1025. msg_queue_current_background = msg_queue_task_progress_1;
  1026. rect_x = p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x;
  1027. rect_y = video_height - msg->offset_y;
  1028. rect_height = p_dispwidget->msg_queue_height / 2;
  1029. gfx_display_set_alpha(msg_queue_current_background, msg->alpha);
  1030. gfx_display_draw_quad(
  1031. p_disp,
  1032. userdata,
  1033. video_width, video_height,
  1034. rect_x, rect_y,
  1035. rect_width, rect_height,
  1036. video_width, video_height,
  1037. msg_queue_current_background,
  1038. NULL
  1039. );
  1040. /* Progress bar */
  1041. if ( !(msg->flags & DISPWIDG_FLAG_TASK_FINISHED)
  1042. && (msg->task_progress >= 0)
  1043. && (msg->task_progress <= 100))
  1044. {
  1045. if (msg->task_count == 1)
  1046. msg_queue_current_bar = msg_queue_task_progress_1;
  1047. else
  1048. msg_queue_current_bar = msg_queue_task_progress_2;
  1049. gfx_display_set_alpha(msg_queue_current_bar, 1.0f);
  1050. gfx_display_draw_quad(
  1051. p_disp,
  1052. userdata,
  1053. video_width, video_height,
  1054. p_dispwidget->msg_queue_task_rect_start_x, video_height - msg->offset_y,
  1055. bar_width, rect_height,
  1056. video_width, video_height,
  1057. msg_queue_current_bar,
  1058. NULL
  1059. );
  1060. }
  1061. /* Icon */
  1062. gfx_display_set_alpha(p_dispwidget->pure_white, msg->alpha);
  1063. if (dispctx && dispctx->blend_begin)
  1064. dispctx->blend_begin(userdata);
  1065. {
  1066. float radians = 0.0f; /* rad */
  1067. float cosine = 1.0f; /* cos(rad) = cos(0) = 1.0f */
  1068. float sine = 0.0f; /* sine(rad) = sine(0) = 0.0f */
  1069. if (!(msg->flags & DISPWIDG_FLAG_TASK_FINISHED))
  1070. radians = msg->hourglass_rotation;
  1071. gfx_widgets_draw_icon(
  1072. userdata,
  1073. p_disp,
  1074. video_width,
  1075. video_height,
  1076. p_dispwidget->msg_queue_height / 2,
  1077. p_dispwidget->msg_queue_height / 2,
  1078. p_dispwidget->gfx_widgets_icons_textures[
  1079. (msg->flags & DISPWIDG_FLAG_TASK_FINISHED)
  1080. ? MENU_WIDGETS_ICON_CHECK
  1081. : MENU_WIDGETS_ICON_HOURGLASS],
  1082. p_dispwidget->msg_queue_task_hourglass_x,
  1083. video_height - msg->offset_y,
  1084. radians,
  1085. cosine,
  1086. sine,
  1087. p_dispwidget->pure_white);
  1088. }
  1089. if (dispctx && dispctx->blend_end)
  1090. dispctx->blend_end(userdata);
  1091. /* Text */
  1092. text_y_base = video_height
  1093. - msg->offset_y
  1094. + p_dispwidget->msg_queue_height / 4.0f
  1095. + p_dispwidget->gfx_widget_fonts.msg_queue.line_centre_offset;
  1096. if (draw_msg_new)
  1097. {
  1098. gfx_widgets_flush_text(video_width, video_height,
  1099. &p_dispwidget->gfx_widget_fonts.msg_queue);
  1100. gfx_display_scissor_begin(p_disp,
  1101. userdata,
  1102. video_width, video_height,
  1103. rect_x, rect_y, rect_width, rect_height);
  1104. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
  1105. msg->msg_new,
  1106. p_dispwidget->msg_queue_task_text_start_x,
  1107. text_y_base
  1108. - p_dispwidget->msg_queue_height / 2.0f
  1109. + msg->msg_transition_animation,
  1110. video_width, video_height,
  1111. text_color,
  1112. TEXT_ALIGN_LEFT,
  1113. true);
  1114. }
  1115. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
  1116. msg->msg,
  1117. p_dispwidget->msg_queue_task_text_start_x,
  1118. text_y_base + msg->msg_transition_animation,
  1119. video_width, video_height,
  1120. text_color,
  1121. TEXT_ALIGN_LEFT,
  1122. true);
  1123. if (draw_msg_new)
  1124. {
  1125. gfx_widgets_flush_text(video_width, video_height,
  1126. &p_dispwidget->gfx_widget_fonts.msg_queue);
  1127. if (dispctx && dispctx->scissor_end)
  1128. dispctx->scissor_end(userdata,
  1129. video_width, video_height);
  1130. }
  1131. /* Progress text */
  1132. text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha/2*255.0f));
  1133. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
  1134. task_percentage,
  1135. p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x + rect_width -
  1136. p_dispwidget->gfx_widget_fonts.msg_queue.glyph_width,
  1137. text_y_base,
  1138. video_width, video_height,
  1139. text_color,
  1140. TEXT_ALIGN_RIGHT,
  1141. true);
  1142. }
  1143. static void gfx_widgets_draw_regular_msg(
  1144. dispgfx_widget_t *p_dispwidget,
  1145. gfx_display_t *p_disp,
  1146. gfx_display_ctx_driver_t *dispctx,
  1147. disp_widget_msg_t *msg,
  1148. void *userdata,
  1149. unsigned video_width,
  1150. unsigned video_height)
  1151. {
  1152. static float msg_queue_info[16] = COLOR_HEX_TO_FLOAT(0x12ACF8, 1.0f);
  1153. static float msg_queue_bar[16] = COLOR_HEX_TO_FLOAT(0xDDDDDD, 1.0f);
  1154. unsigned bar_width;
  1155. unsigned bar_margin;
  1156. unsigned text_color;
  1157. static float last_alpha = 0.0f;
  1158. msg->flags &= ~DISPWIDG_FLAG_UNFOLDING;
  1159. msg->flags |= DISPWIDG_FLAG_UNFOLDED;
  1160. if (last_alpha != msg->alpha)
  1161. {
  1162. /* Icon */
  1163. gfx_display_set_alpha(msg_queue_info, msg->alpha);
  1164. gfx_display_set_alpha(p_dispwidget->pure_white, msg->alpha);
  1165. gfx_display_set_alpha(p_dispwidget->msg_queue_bg, msg->alpha);
  1166. last_alpha = msg->alpha;
  1167. }
  1168. if ( !(msg->flags & DISPWIDG_FLAG_UNFOLDED)
  1169. || (msg->flags & DISPWIDG_FLAG_UNFOLDING))
  1170. {
  1171. gfx_widgets_flush_text(video_width, video_height,
  1172. &p_dispwidget->gfx_widget_fonts.regular);
  1173. gfx_widgets_flush_text(video_width, video_height,
  1174. &p_dispwidget->gfx_widget_fonts.bold);
  1175. gfx_widgets_flush_text(video_width, video_height,
  1176. &p_dispwidget->gfx_widget_fonts.msg_queue);
  1177. gfx_display_scissor_begin(p_disp,
  1178. userdata,
  1179. video_width, video_height,
  1180. p_dispwidget->msg_queue_scissor_start_x, 0,
  1181. (p_dispwidget->msg_queue_scissor_start_x + msg->width -
  1182. p_dispwidget->simple_widget_padding * 2)
  1183. * msg->unfold, video_height);
  1184. }
  1185. /* Background */
  1186. bar_width = p_dispwidget->simple_widget_padding + msg->width + p_dispwidget->msg_queue_icon_size_x;
  1187. bar_margin = p_dispwidget->simple_widget_padding * 0.15f;
  1188. gfx_display_draw_quad(
  1189. p_disp,
  1190. userdata,
  1191. video_width,
  1192. video_height,
  1193. p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x,
  1194. video_height - msg->offset_y,
  1195. bar_width - bar_margin,
  1196. p_dispwidget->msg_queue_height,
  1197. video_width,
  1198. video_height,
  1199. p_dispwidget->msg_queue_bg,
  1200. NULL
  1201. );
  1202. gfx_display_draw_quad(
  1203. p_disp,
  1204. userdata,
  1205. video_width,
  1206. video_height,
  1207. p_dispwidget->msg_queue_rect_start_x - p_dispwidget->msg_queue_icon_size_x - bar_margin,
  1208. video_height - msg->offset_y,
  1209. bar_margin,
  1210. p_dispwidget->msg_queue_height,
  1211. video_width,
  1212. video_height,
  1213. msg_queue_bar,
  1214. NULL
  1215. );
  1216. /* Text */
  1217. text_color = COLOR_TEXT_ALPHA(0xFFFFFF00, (unsigned)(msg->alpha*255.0f));
  1218. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
  1219. msg->msg,
  1220. p_dispwidget->msg_queue_regular_text_start,
  1221. video_height - msg->offset_y + (p_dispwidget->msg_queue_height - msg->text_height)/2.0f + p_dispwidget->gfx_widget_fonts.msg_queue.line_ascender,
  1222. video_width, video_height,
  1223. text_color,
  1224. TEXT_ALIGN_LEFT,
  1225. true);
  1226. if ( !(msg->flags & DISPWIDG_FLAG_UNFOLDED)
  1227. || (msg->flags & DISPWIDG_FLAG_UNFOLDING))
  1228. {
  1229. gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.regular);
  1230. gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.bold);
  1231. gfx_widgets_flush_text(video_width, video_height, &p_dispwidget->gfx_widget_fonts.msg_queue);
  1232. if (dispctx && dispctx->scissor_end)
  1233. dispctx->scissor_end(userdata,
  1234. video_width, video_height);
  1235. }
  1236. if (p_dispwidget->flags & DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS)
  1237. {
  1238. if (dispctx && dispctx->blend_begin)
  1239. dispctx->blend_begin(userdata);
  1240. gfx_widgets_draw_icon(
  1241. userdata,
  1242. p_disp,
  1243. video_width,
  1244. video_height,
  1245. p_dispwidget->msg_queue_icon_size_x,
  1246. p_dispwidget->msg_queue_icon_size_y,
  1247. p_dispwidget->gfx_widgets_icons_textures[MENU_WIDGETS_ICON_INFO],
  1248. p_dispwidget->msg_queue_spacing,
  1249. video_height - msg->offset_y - p_dispwidget->msg_queue_icon_offset_y,
  1250. 0.0f, /* rad */
  1251. 1.0f, /* cos(rad) = cos(0) = 1.0f */
  1252. 0.0f, /* sine(rad) = sine(0) = 0.0f */
  1253. msg_queue_info);
  1254. if (dispctx && dispctx->blend_end)
  1255. dispctx->blend_end(userdata);
  1256. }
  1257. }
  1258. static void INLINE gfx_widgets_font_bind(gfx_widget_font_data_t *font_data)
  1259. {
  1260. font_driver_bind_block(font_data->font, &font_data->raster_block);
  1261. font_data->raster_block.carr.coords.vertices = 0;
  1262. font_data->usage_count = 0;
  1263. }
  1264. static void INLINE gfx_widgets_font_unbind(gfx_widget_font_data_t *font_data)
  1265. {
  1266. font_driver_bind_block(font_data->font, NULL);
  1267. }
  1268. void gfx_widgets_frame(void *data)
  1269. {
  1270. size_t i;
  1271. video_frame_info_t *video_info = (video_frame_info_t*)data;
  1272. gfx_display_t *p_disp = (gfx_display_t*)video_info->disp_userdata;
  1273. gfx_display_ctx_driver_t *dispctx= p_disp->dispctx;
  1274. dispgfx_widget_t *p_dispwidget = (dispgfx_widget_t*)video_info->widgets_userdata;
  1275. bool fps_show = video_info->fps_show;
  1276. bool framecount_show = video_info->framecount_show;
  1277. bool memory_show = video_info->memory_show;
  1278. bool core_status_msg_show = video_info->core_status_msg_show;
  1279. void *userdata = video_info->userdata;
  1280. unsigned video_width = video_info->width;
  1281. unsigned video_height = video_info->height;
  1282. bool widgets_is_paused = video_info->widgets_is_paused;
  1283. bool widgets_is_fastforwarding = video_info->widgets_is_fast_forwarding;
  1284. bool widgets_is_rewinding = video_info->widgets_is_rewinding;
  1285. bool runloop_is_slowmotion = video_info->runloop_is_slowmotion;
  1286. bool menu_screensaver_active = video_info->menu_screensaver_active;
  1287. bool notifications_hidden = video_info->notifications_hidden ||
  1288. video_info->msg_queue_delay;
  1289. int top_right_x_advance = video_width;
  1290. p_dispwidget->gfx_widgets_frame_count++;
  1291. /* If menu screensaver is active or notifications are hidden, draw nothing */
  1292. if (menu_screensaver_active || notifications_hidden)
  1293. return;
  1294. video_driver_set_viewport(video_width, video_height, true, false);
  1295. /* Font setup */
  1296. gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.regular);
  1297. gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.bold);
  1298. gfx_widgets_font_bind(&p_dispwidget->gfx_widget_fonts.msg_queue);
  1299. #ifdef HAVE_TRANSLATE
  1300. /* AI Service overlay */
  1301. if (p_dispwidget->ai_service_overlay_state > 0)
  1302. {
  1303. float outline_color[16] = {
  1304. 0.00, 1.00, 0.00, 1.00,
  1305. 0.00, 1.00, 0.00, 1.00,
  1306. 0.00, 1.00, 0.00, 1.00,
  1307. 0.00, 1.00, 0.00, 1.00,
  1308. };
  1309. gfx_display_set_alpha(p_dispwidget->pure_white, 1.0f);
  1310. if (p_dispwidget->ai_service_overlay_texture)
  1311. {
  1312. if (dispctx->blend_begin)
  1313. dispctx->blend_begin(userdata);
  1314. gfx_widgets_draw_icon(
  1315. userdata,
  1316. p_disp,
  1317. video_width,
  1318. video_height,
  1319. video_width,
  1320. video_height,
  1321. p_dispwidget->ai_service_overlay_texture,
  1322. 0,
  1323. 0,
  1324. 0.0f, /* rad */
  1325. 1.0f, /* cos(rad) = cos(0) = 1.0f */
  1326. 0.0f, /* sine(rad) = sine(0) = 0.0f */
  1327. p_dispwidget->pure_white
  1328. );
  1329. if (dispctx->blend_end)
  1330. dispctx->blend_end(userdata);
  1331. }
  1332. /* top line */
  1333. gfx_display_draw_quad(
  1334. p_disp,
  1335. userdata,
  1336. video_width, video_height,
  1337. 0, 0,
  1338. video_width,
  1339. p_dispwidget->divider_width_1px,
  1340. video_width,
  1341. video_height,
  1342. outline_color,
  1343. NULL
  1344. );
  1345. /* bottom line */
  1346. gfx_display_draw_quad(
  1347. p_disp,
  1348. userdata,
  1349. video_width, video_height,
  1350. 0,
  1351. video_height - p_dispwidget->divider_width_1px,
  1352. video_width,
  1353. p_dispwidget->divider_width_1px,
  1354. video_width,
  1355. video_height,
  1356. outline_color,
  1357. NULL
  1358. );
  1359. /* left line */
  1360. gfx_display_draw_quad(
  1361. p_disp,
  1362. userdata,
  1363. video_width,
  1364. video_height,
  1365. 0,
  1366. 0,
  1367. p_dispwidget->divider_width_1px,
  1368. video_height,
  1369. video_width,
  1370. video_height,
  1371. outline_color,
  1372. NULL
  1373. );
  1374. /* right line */
  1375. gfx_display_draw_quad(
  1376. p_disp,
  1377. userdata,
  1378. video_width, video_height,
  1379. video_width - p_dispwidget->divider_width_1px,
  1380. 0,
  1381. p_dispwidget->divider_width_1px,
  1382. video_height,
  1383. video_width,
  1384. video_height,
  1385. outline_color,
  1386. NULL
  1387. );
  1388. if (p_dispwidget->ai_service_overlay_state == 2)
  1389. p_dispwidget->ai_service_overlay_state = 3;
  1390. }
  1391. #endif
  1392. /* Status Text (fps, framecount, memory, core status message) */
  1393. if ( fps_show
  1394. || framecount_show
  1395. || memory_show
  1396. || core_status_msg_show
  1397. )
  1398. {
  1399. const char *text = *p_dispwidget->gfx_widgets_status_text == '\0'
  1400. ? msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NOT_AVAILABLE)
  1401. : p_dispwidget->gfx_widgets_status_text;
  1402. int text_width = font_driver_get_message_width(
  1403. p_dispwidget->gfx_widget_fonts.regular.font,
  1404. text,
  1405. strlen(text), 1.0f);
  1406. int total_width = text_width
  1407. + p_dispwidget->simple_widget_padding * 2;
  1408. int status_text_x = top_right_x_advance
  1409. - p_dispwidget->simple_widget_padding - text_width;
  1410. /* Ensure that left hand side of text does
  1411. * not bleed off the edge of the screen */
  1412. if (status_text_x < 0)
  1413. status_text_x = 0;
  1414. gfx_display_set_alpha(p_dispwidget->backdrop_orig, DEFAULT_BACKDROP);
  1415. gfx_display_draw_quad(
  1416. p_disp,
  1417. userdata,
  1418. video_width,
  1419. video_height,
  1420. top_right_x_advance - total_width, 0,
  1421. total_width,
  1422. p_dispwidget->simple_widget_height,
  1423. video_width,
  1424. video_height,
  1425. p_dispwidget->backdrop_orig,
  1426. NULL
  1427. );
  1428. gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.regular,
  1429. text,
  1430. status_text_x,
  1431. p_dispwidget->simple_widget_height / 2.0f
  1432. + p_dispwidget->gfx_widget_fonts.regular.line_centre_offset,
  1433. video_width, video_height,
  1434. 0xFFFFFFFF,
  1435. TEXT_ALIGN_LEFT,
  1436. true);
  1437. }
  1438. /* Indicators */
  1439. if (widgets_is_paused)
  1440. top_right_x_advance -= gfx_widgets_draw_indicator(
  1441. p_dispwidget,
  1442. p_disp,
  1443. dispctx,
  1444. userdata,
  1445. video_width,
  1446. video_height,
  1447. p_dispwidget->gfx_widgets_icons_textures[
  1448. MENU_WIDGETS_ICON_PAUSED],
  1449. (fps_show
  1450. ? p_dispwidget->simple_widget_height
  1451. : 0),
  1452. top_right_x_advance,
  1453. MSG_PAUSED);
  1454. if (widgets_is_fastforwarding)
  1455. top_right_x_advance -= gfx_widgets_draw_indicator(
  1456. p_dispwidget,
  1457. p_disp,
  1458. dispctx,
  1459. userdata,
  1460. video_width,
  1461. video_height,
  1462. p_dispwidget->gfx_widgets_icons_textures[
  1463. MENU_WIDGETS_ICON_FAST_FORWARD],
  1464. (fps_show ? p_dispwidget->simple_widget_height : 0),
  1465. top_right_x_advance,
  1466. MSG_FAST_FORWARD);
  1467. if (widgets_is_rewinding)
  1468. top_right_x_advance -= gfx_widgets_draw_indicator(
  1469. p_dispwidget,
  1470. p_disp,
  1471. dispctx,
  1472. userdata,
  1473. video_width,
  1474. video_height,
  1475. p_dispwidget->gfx_widgets_icons_textures[
  1476. MENU_WIDGETS_ICON_REWIND],
  1477. (fps_show ? p_dispwidget->simple_widget_height : 0),
  1478. top_right_x_advance,
  1479. MSG_REWINDING);
  1480. if (runloop_is_slowmotion)
  1481. {
  1482. top_right_x_advance -= gfx_widgets_draw_indicator(
  1483. p_dispwidget,
  1484. p_disp,
  1485. dispctx,
  1486. userdata,
  1487. video_width,
  1488. video_height,
  1489. p_dispwidget->gfx_widgets_icons_textures[
  1490. MENU_WIDGETS_ICON_SLOW_MOTION],
  1491. (fps_show ? p_dispwidget->simple_widget_height : 0),
  1492. top_right_x_advance,
  1493. MSG_SLOW_MOTION);
  1494. (void)top_right_x_advance;
  1495. }
  1496. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  1497. {
  1498. const gfx_widget_t* widget = widgets[i];
  1499. if (widget->frame)
  1500. widget->frame(data, p_dispwidget);
  1501. }
  1502. /* Draw all messages */
  1503. if (p_dispwidget->current_msgs_size)
  1504. {
  1505. #ifdef HAVE_THREADS
  1506. slock_lock(p_dispwidget->current_msgs_lock);
  1507. #endif
  1508. for (i = 0; i < p_dispwidget->current_msgs_size; i++)
  1509. {
  1510. disp_widget_msg_t* msg = p_dispwidget->current_msgs[i];
  1511. if (!msg)
  1512. continue;
  1513. if (msg->task_ptr)
  1514. gfx_widgets_draw_task_msg(
  1515. p_dispwidget,
  1516. p_disp,
  1517. dispctx,
  1518. msg, userdata,
  1519. video_width, video_height);
  1520. else
  1521. gfx_widgets_draw_regular_msg(
  1522. p_dispwidget,
  1523. p_disp,
  1524. dispctx,
  1525. msg, userdata,
  1526. video_width, video_height);
  1527. }
  1528. #ifdef HAVE_THREADS
  1529. slock_unlock(p_dispwidget->current_msgs_lock);
  1530. #endif
  1531. }
  1532. /* Ensure all text is flushed */
  1533. gfx_widgets_flush_text(video_width, video_height,
  1534. &p_dispwidget->gfx_widget_fonts.regular);
  1535. gfx_widgets_flush_text(video_width, video_height,
  1536. &p_dispwidget->gfx_widget_fonts.bold);
  1537. gfx_widgets_flush_text(video_width, video_height,
  1538. &p_dispwidget->gfx_widget_fonts.msg_queue);
  1539. /* Unbind fonts */
  1540. gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.regular);
  1541. gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.bold);
  1542. gfx_widgets_font_unbind(&p_dispwidget->gfx_widget_fonts.msg_queue);
  1543. video_driver_set_viewport(video_width, video_height, false, true);
  1544. }
  1545. static void gfx_widgets_free(dispgfx_widget_t *p_dispwidget)
  1546. {
  1547. size_t i;
  1548. p_dispwidget->flags &= ~DISPGFX_WIDGET_FLAG_INITED;
  1549. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  1550. {
  1551. const gfx_widget_t* widget = widgets[i];
  1552. if (widget->free)
  1553. widget->free();
  1554. }
  1555. /* Kill all running animations */
  1556. gfx_animation_kill_by_tag(
  1557. &p_dispwidget->gfx_widgets_generic_tag);
  1558. /* Purge everything from the fifo */
  1559. while (FIFO_READ_AVAIL_NONPTR(p_dispwidget->msg_queue) > 0)
  1560. {
  1561. disp_widget_msg_t *msg_widget;
  1562. fifo_read(&p_dispwidget->msg_queue,
  1563. &msg_widget, sizeof(msg_widget));
  1564. /* Note: gfx_widgets_free() is only called when
  1565. * main_exit() is invoked. At this stage, we cannot
  1566. * guarantee that any task pointers are valid (the
  1567. * task may have been free()'d, but we can't know
  1568. * that here) - so all we can do is unset the task
  1569. * pointer associated with each message
  1570. * > If we don't do this, gfx_widgets_msg_queue_free()
  1571. * will generate heap-use-after-free errors */
  1572. msg_widget->task_ptr = NULL;
  1573. gfx_widgets_msg_queue_free(p_dispwidget, msg_widget);
  1574. free(msg_widget);
  1575. }
  1576. fifo_deinitialize(&p_dispwidget->msg_queue);
  1577. /* Purge everything from the list */
  1578. #ifdef HAVE_THREADS
  1579. slock_lock(p_dispwidget->current_msgs_lock);
  1580. #endif
  1581. p_dispwidget->current_msgs_size = 0;
  1582. for (i = 0; i < ARRAY_SIZE(p_dispwidget->current_msgs); i++)
  1583. {
  1584. disp_widget_msg_t *msg = p_dispwidget->current_msgs[i];
  1585. if (!msg)
  1586. continue;
  1587. /* Note: gfx_widgets_free() is only called when
  1588. * main_exit() is invoked. At this stage, we cannot
  1589. * guarantee that any task pointers are valid (the
  1590. * task may have been free()'d, but we can't know
  1591. * that here) - so all we can do is unset the task
  1592. * pointer associated with each message
  1593. * > If we don't do this, gfx_widgets_msg_queue_free()
  1594. * will generate heap-use-after-free errors */
  1595. msg->task_ptr = NULL;
  1596. gfx_widgets_msg_queue_free(p_dispwidget, msg);
  1597. }
  1598. #ifdef HAVE_THREADS
  1599. slock_unlock(p_dispwidget->current_msgs_lock);
  1600. slock_free(p_dispwidget->current_msgs_lock);
  1601. p_dispwidget->current_msgs_lock = NULL;
  1602. #endif
  1603. p_dispwidget->msg_queue_tasks_count = 0;
  1604. /* Font */
  1605. video_coord_array_free(
  1606. &p_dispwidget->gfx_widget_fonts.regular.raster_block.carr);
  1607. video_coord_array_free(
  1608. &p_dispwidget->gfx_widget_fonts.bold.raster_block.carr);
  1609. video_coord_array_free(
  1610. &p_dispwidget->gfx_widget_fonts.msg_queue.raster_block.carr);
  1611. font_driver_bind_block(NULL, NULL);
  1612. }
  1613. static void gfx_widgets_context_reset(
  1614. dispgfx_widget_t *p_dispwidget,
  1615. gfx_display_t *p_disp,
  1616. settings_t *settings,
  1617. bool is_threaded,
  1618. unsigned width, unsigned height, bool fullscreen,
  1619. const char *dir_assets, char *font_path)
  1620. {
  1621. size_t i;
  1622. /* Load textures */
  1623. /* Icons */
  1624. for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++)
  1625. {
  1626. gfx_display_reset_textures_list(
  1627. gfx_widgets_icons_names[i],
  1628. p_dispwidget->monochrome_png_path,
  1629. &p_dispwidget->gfx_widgets_icons_textures[i],
  1630. TEXTURE_FILTER_MIPMAP_LINEAR,
  1631. NULL,
  1632. NULL);
  1633. }
  1634. /* Message queue */
  1635. gfx_display_reset_textures_list(
  1636. "msg_queue_icon.png",
  1637. p_dispwidget->gfx_widgets_path,
  1638. &p_dispwidget->msg_queue_icon,
  1639. TEXTURE_FILTER_LINEAR,
  1640. NULL,
  1641. NULL);
  1642. gfx_display_reset_textures_list(
  1643. "msg_queue_icon_outline.png",
  1644. p_dispwidget->gfx_widgets_path,
  1645. &p_dispwidget->msg_queue_icon_outline,
  1646. TEXTURE_FILTER_LINEAR,
  1647. NULL,
  1648. NULL);
  1649. gfx_display_reset_textures_list(
  1650. "msg_queue_icon_rect.png",
  1651. p_dispwidget->gfx_widgets_path,
  1652. &p_dispwidget->msg_queue_icon_rect,
  1653. TEXTURE_FILTER_NEAREST,
  1654. NULL,
  1655. NULL);
  1656. if ( p_dispwidget->msg_queue_icon
  1657. && p_dispwidget->msg_queue_icon_outline
  1658. && p_dispwidget->msg_queue_icon_rect)
  1659. p_dispwidget->flags |= DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS;
  1660. else
  1661. p_dispwidget->flags &= ~DISPGFX_WIDGET_FLAG_MSG_QUEUE_HAS_ICONS;
  1662. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  1663. {
  1664. const gfx_widget_t* widget = widgets[i];
  1665. if (widget->context_reset)
  1666. widget->context_reset(is_threaded, width, height,
  1667. fullscreen, dir_assets, font_path,
  1668. p_dispwidget->monochrome_png_path,
  1669. p_dispwidget->gfx_widgets_path);
  1670. }
  1671. /* Update scaling/dimensions */
  1672. p_dispwidget->last_video_width = width;
  1673. p_dispwidget->last_video_height = height;
  1674. #ifdef HAVE_XMB
  1675. if (p_disp->menu_driver_id == MENU_DRIVER_ID_XMB)
  1676. p_dispwidget->last_scale_factor = gfx_display_get_widget_pixel_scale(
  1677. p_disp, settings,
  1678. p_dispwidget->last_video_width,
  1679. p_dispwidget->last_video_height, fullscreen);
  1680. else
  1681. #endif
  1682. p_dispwidget->last_scale_factor = gfx_display_get_dpi_scale(
  1683. p_disp, settings,
  1684. p_dispwidget->last_video_width,
  1685. p_dispwidget->last_video_height,
  1686. fullscreen, true);
  1687. gfx_widgets_layout(p_disp, p_dispwidget,
  1688. is_threaded, dir_assets, font_path);
  1689. video_driver_monitor_reset();
  1690. }
  1691. bool gfx_widgets_init(
  1692. void *data_disp,
  1693. void *data_anim,
  1694. void *settings_data,
  1695. uintptr_t widgets_active_ptr,
  1696. bool video_is_threaded,
  1697. unsigned width, unsigned height, bool fullscreen,
  1698. const char *dir_assets, char *font_path)
  1699. {
  1700. size_t i;
  1701. unsigned color = 0x1A1A1A;
  1702. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  1703. gfx_display_t *p_disp = (gfx_display_t*)data_disp;
  1704. gfx_animation_t *p_anim = (gfx_animation_t*)data_anim;
  1705. settings_t *settings = (settings_t*)settings_data;
  1706. p_dispwidget->divider_width_1px = 1;
  1707. p_dispwidget->gfx_widgets_generic_tag = (uintptr_t)widgets_active_ptr;
  1708. if (!gfx_display_init_first_driver(p_disp, video_is_threaded))
  1709. goto error;
  1710. gfx_display_set_alpha(p_dispwidget->backdrop_orig, 0.75f);
  1711. for (i = 0; i < 16; i++)
  1712. p_dispwidget->pure_white[i] = 1.00f;
  1713. p_dispwidget->msg_queue_bg[0] = HEX_R(color);
  1714. p_dispwidget->msg_queue_bg[1] = HEX_G(color);
  1715. p_dispwidget->msg_queue_bg[2] = HEX_B(color);
  1716. p_dispwidget->msg_queue_bg[3] = 1.0f;
  1717. p_dispwidget->msg_queue_bg[4] = HEX_R(color);
  1718. p_dispwidget->msg_queue_bg[5] = HEX_G(color);
  1719. p_dispwidget->msg_queue_bg[6] = HEX_B(color);
  1720. p_dispwidget->msg_queue_bg[7] = 1.0f;
  1721. p_dispwidget->msg_queue_bg[8] = HEX_R(color);
  1722. p_dispwidget->msg_queue_bg[9] = HEX_G(color);
  1723. p_dispwidget->msg_queue_bg[10] = HEX_B(color);
  1724. p_dispwidget->msg_queue_bg[11] = 1.0f;
  1725. p_dispwidget->msg_queue_bg[12] = HEX_R(color);
  1726. p_dispwidget->msg_queue_bg[13] = HEX_G(color);
  1727. p_dispwidget->msg_queue_bg[14] = HEX_B(color);
  1728. p_dispwidget->msg_queue_bg[15] = 1.0f;
  1729. if (!(p_dispwidget->flags & DISPGFX_WIDGET_FLAG_INITED))
  1730. {
  1731. char theme_path[PATH_MAX_LENGTH];
  1732. p_dispwidget->gfx_widgets_frame_count = 0;
  1733. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  1734. {
  1735. const gfx_widget_t* widget = widgets[i];
  1736. if (widget->init)
  1737. widget->init(p_disp, p_anim, video_is_threaded, fullscreen);
  1738. }
  1739. if (!fifo_initialize(&p_dispwidget->msg_queue,
  1740. MSG_QUEUE_PENDING_MAX * sizeof(disp_widget_msg_t*)))
  1741. goto error;
  1742. memset(&p_dispwidget->current_msgs[0], 0, sizeof(p_dispwidget->current_msgs));
  1743. p_dispwidget->current_msgs_size = 0;
  1744. #ifdef HAVE_THREADS
  1745. p_dispwidget->current_msgs_lock = slock_new();
  1746. #endif
  1747. fill_pathname_join_special(
  1748. p_dispwidget->gfx_widgets_path,
  1749. dir_assets,
  1750. "menu_widgets",
  1751. sizeof(p_dispwidget->gfx_widgets_path)
  1752. );
  1753. fill_pathname_join_special(
  1754. p_dispwidget->xmb_path,
  1755. dir_assets,
  1756. "xmb",
  1757. sizeof(p_dispwidget->xmb_path)
  1758. );
  1759. /* Base path */
  1760. fill_pathname_join_special(p_dispwidget->ozone_path,
  1761. dir_assets,
  1762. "ozone",
  1763. sizeof(p_dispwidget->ozone_path));
  1764. fill_pathname_join_special(p_dispwidget->ozone_regular_font_path,
  1765. p_dispwidget->ozone_path, "regular.ttf",
  1766. sizeof(p_dispwidget->ozone_regular_font_path));
  1767. fill_pathname_join_special(p_dispwidget->ozone_bold_font_path,
  1768. p_dispwidget->ozone_path, "bold.ttf",
  1769. sizeof(p_dispwidget->ozone_bold_font_path));
  1770. fill_pathname_join_special(
  1771. theme_path,
  1772. p_dispwidget->xmb_path,
  1773. "monochrome",
  1774. sizeof(theme_path)
  1775. );
  1776. fill_pathname_join_special(
  1777. p_dispwidget->monochrome_png_path,
  1778. theme_path,
  1779. "png",
  1780. sizeof(p_dispwidget->monochrome_png_path)
  1781. );
  1782. fill_pathname_join_special(p_dispwidget->assets_pkg_dir,
  1783. settings->paths.directory_assets, "pkg",
  1784. sizeof(p_dispwidget->assets_pkg_dir));
  1785. p_dispwidget->flags |= DISPGFX_WIDGET_FLAG_INITED;
  1786. }
  1787. gfx_widgets_context_reset(
  1788. p_dispwidget,
  1789. p_disp,
  1790. settings,
  1791. video_is_threaded,
  1792. width, height, fullscreen,
  1793. dir_assets, font_path);
  1794. return true;
  1795. error:
  1796. gfx_widgets_free(p_dispwidget);
  1797. return false;
  1798. }
  1799. static void gfx_widgets_context_destroy(dispgfx_widget_t *p_dispwidget)
  1800. {
  1801. size_t i;
  1802. for (i = 0; i < ARRAY_SIZE(widgets); i++)
  1803. {
  1804. const gfx_widget_t* widget = widgets[i];
  1805. if (widget->context_destroy)
  1806. widget->context_destroy();
  1807. }
  1808. /* TODO: Dismiss onscreen notifications that have been freed */
  1809. /* Textures */
  1810. for (i = 0; i < MENU_WIDGETS_ICON_LAST; i++)
  1811. video_driver_texture_unload(&p_dispwidget->gfx_widgets_icons_textures[i]);
  1812. video_driver_texture_unload(&p_dispwidget->msg_queue_icon);
  1813. video_driver_texture_unload(&p_dispwidget->msg_queue_icon_outline);
  1814. video_driver_texture_unload(&p_dispwidget->msg_queue_icon_rect);
  1815. p_dispwidget->msg_queue_icon = 0;
  1816. p_dispwidget->msg_queue_icon_outline = 0;
  1817. p_dispwidget->msg_queue_icon_rect = 0;
  1818. /* Fonts */
  1819. gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.regular);
  1820. gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.bold);
  1821. gfx_widgets_font_free(&p_dispwidget->gfx_widget_fonts.msg_queue);
  1822. }
  1823. void gfx_widgets_deinit(bool widgets_persisting)
  1824. {
  1825. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  1826. gfx_widgets_context_destroy(p_dispwidget);
  1827. if (!widgets_persisting)
  1828. gfx_widgets_free(p_dispwidget);
  1829. }
  1830. #ifdef HAVE_TRANSLATE
  1831. bool gfx_widgets_ai_service_overlay_load(
  1832. char* buffer, unsigned buffer_len,
  1833. enum image_type_enum image_type)
  1834. {
  1835. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  1836. if (p_dispwidget->ai_service_overlay_state == 0)
  1837. {
  1838. if (!gfx_display_reset_textures_list_buffer(
  1839. &p_dispwidget->ai_service_overlay_texture,
  1840. TEXTURE_FILTER_MIPMAP_LINEAR,
  1841. (void *) buffer, buffer_len, image_type,
  1842. &p_dispwidget->ai_service_overlay_width,
  1843. &p_dispwidget->ai_service_overlay_height))
  1844. return false;
  1845. p_dispwidget->ai_service_overlay_state = 1;
  1846. }
  1847. return true;
  1848. }
  1849. void gfx_widgets_ai_service_overlay_unload(void)
  1850. {
  1851. dispgfx_widget_t *p_dispwidget = &dispwidget_st;
  1852. if (p_dispwidget->ai_service_overlay_state == 1)
  1853. {
  1854. video_driver_texture_unload(&p_dispwidget->ai_service_overlay_texture);
  1855. p_dispwidget->ai_service_overlay_texture = 0;
  1856. p_dispwidget->ai_service_overlay_state = 0;
  1857. }
  1858. }
  1859. #endif
  1860. dispgfx_widget_t *dispwidget_get_ptr(void)
  1861. {
  1862. return &dispwidget_st;
  1863. }
  1864. bool gfx_widgets_ready(void)
  1865. {
  1866. #ifdef HAVE_GFX_WIDGETS
  1867. return dispwidget_st.active;
  1868. #else
  1869. return false;
  1870. #endif
  1871. }