diff --git a/desktop/assets/texts/de-DE/main_menu.ftl b/desktop/assets/texts/de-DE/main_menu.ftl index 8868735b3..6ebd6b8dc 100644 --- a/desktop/assets/texts/de-DE/main_menu.ftl +++ b/desktop/assets/texts/de-DE/main_menu.ftl @@ -17,6 +17,7 @@ file-menu-exit = Beenden controls-menu = Steuerung controls-menu-suspend = Aussetzen controls-menu-resume = Fortsetzen +controls-menu-volume = Lautstärke help-menu = Hilfe help-menu-join-discord = Discord beitreten help-menu-report-a-bug = Bug melden... diff --git a/desktop/assets/texts/de-DE/volume_dialog.ftl b/desktop/assets/texts/de-DE/volume_dialog.ftl new file mode 100644 index 000000000..859d25c90 --- /dev/null +++ b/desktop/assets/texts/de-DE/volume_dialog.ftl @@ -0,0 +1,3 @@ +volume-controls = Lautstärkeeinstellungen +volume-controls-mute = Stummschalten +volume-controls-volume = Lautstärke diff --git a/desktop/assets/texts/en-US/main_menu.ftl b/desktop/assets/texts/en-US/main_menu.ftl index 68bb93cbf..0068f9206 100644 --- a/desktop/assets/texts/en-US/main_menu.ftl +++ b/desktop/assets/texts/en-US/main_menu.ftl @@ -17,6 +17,7 @@ file-menu-exit = Exit controls-menu = Controls controls-menu-suspend = Suspend controls-menu-resume = Resume +controls-menu-volume = Volume controls help-menu = Help help-menu-join-discord = Join Discord diff --git a/desktop/assets/texts/en-US/volume_dialog.ftl b/desktop/assets/texts/en-US/volume_dialog.ftl new file mode 100644 index 000000000..82c0fe068 --- /dev/null +++ b/desktop/assets/texts/en-US/volume_dialog.ftl @@ -0,0 +1,3 @@ +volume-controls = Volume controls +volume-controls-mute = Mute +volume-controls-volume = Volume diff --git a/desktop/src/gui.rs b/desktop/src/gui.rs index 541f5d5b5..4317c4728 100644 --- a/desktop/src/gui.rs +++ b/desktop/src/gui.rs @@ -20,6 +20,7 @@ use ruffle_core::debug_ui::Message as DebugMessage; use ruffle_core::Player; use std::collections::HashMap; use std::fs; +use std::sync::MutexGuard; use sys_locale::get_locale; use unic_langid::LanguageIdentifier; use winit::event_loop::EventLoopProxy; @@ -64,6 +65,8 @@ pub const MENU_HEIGHT: u32 = 24; pub struct RuffleGui { event_loop: EventLoopProxy, is_about_visible: bool, + is_volume_visible: bool, + volume_controls: VolumeControls, is_open_dialog_visible: bool, context_menu: Vec, open_dialog: OpenDialog, @@ -89,6 +92,8 @@ impl RuffleGui { Self { is_about_visible: false, + is_volume_visible: false, + volume_controls: VolumeControls::new(false, default_player_options.volume * 100.0), is_open_dialog_visible: false, was_suspended_before_debug: false, @@ -148,6 +153,10 @@ impl RuffleGui { } }); } + + self.volume_window(egui_ctx, Some(player)); + } else { + self.volume_window(egui_ctx, None); } if !self.context_menu.is_empty() { @@ -164,7 +173,12 @@ impl RuffleGui { } /// Notifies the GUI that a new player was created. - fn on_player_created(&mut self, opt: PlayerOptions, movie_url: Url) { + fn on_player_created( + &mut self, + opt: PlayerOptions, + movie_url: Url, + mut player: MutexGuard, + ) { self.currently_opened = Some((movie_url.clone(), opt.clone())); // Update dialog state to reflect the newly-opened movie's options. @@ -175,6 +189,8 @@ impl RuffleGui { self.event_loop.clone(), self.locale.clone(), ); + + player.set_volume(self.volume_controls.get_real_volume()); } /// Renders the main menu bar at the top of the window. @@ -255,6 +271,9 @@ impl RuffleGui { } } }); + if Button::new(text(&self.locale, "controls-menu-volume")).ui(ui).clicked() { + self.show_volume_screen(ui); + } }); menu::menu_button(ui, text(&self.locale, "debug-menu"), |ui| { ui.add_enabled_ui(player.is_some(), |ui| { @@ -306,6 +325,7 @@ impl RuffleGui { }); } + /// Renders the About Ruffle window. fn about_window(&mut self, egui_ctx: &egui::Context) { egui::Window::new(text(&self.locale, "about-ruffle")) .collapsible(false) @@ -392,6 +412,40 @@ impl RuffleGui { }); } + /// Renders the volume controls window. + fn volume_window(&mut self, egui_ctx: &egui::Context, player: Option<&mut Player>) { + egui::Window::new(text(&self.locale, "volume-controls")) + .collapsible(false) + .resizable(false) + .anchor(Align2::CENTER_CENTER, egui::Vec2::ZERO) + .open(&mut self.is_volume_visible) + .show(egui_ctx, |ui| { + let mut changed_slider = false; + + let changed_checkbox = ui + .checkbox( + &mut self.volume_controls.is_muted, + text(&self.locale, "volume-controls-mute"), + ) + .changed(); + + ui.add_enabled_ui(!self.volume_controls.is_muted, |ui| { + ui.horizontal(|ui| { + ui.label(text(&self.locale, "volume-controls-volume")); + changed_slider = ui + .add(Slider::new(&mut self.volume_controls.volume, 0.0..=100.0)) + .changed(); + }); + }); + + if changed_checkbox || changed_slider { + if let Some(player) = player { + player.set_volume(self.volume_controls.get_real_volume()); + } + } + }); + } + /// Renders the right-click context menu. fn context_menu(&mut self, egui_ctx: &egui::Context) { let mut item_clicked = false; @@ -483,4 +537,35 @@ impl RuffleGui { self.is_about_visible = true; ui.close_menu(); } + + fn show_volume_screen(&mut self, ui: &mut egui::Ui) { + self.is_volume_visible = true; + ui.close_menu(); + } +} + +/// The volume controls of the Ruffle GUI. +pub struct VolumeControls { + is_muted: bool, + volume: f32, +} + +impl VolumeControls { + fn new(is_muted: bool, volume: f32) -> Self { + Self { is_muted, volume } + } + + /// Calculates the real volume (between 0 and 1) out of the entered volume + /// (between 0 and 100) and the mute checkbox. + /// + /// This is also necessary because human hearing is logarithmic: + /// What sounds like half as loud (and should be 50% on the scale) is actually + /// about 1/10th of the real volume. + pub fn get_real_volume(&self) -> f32 { + if !self.is_muted { + (10_f32.powf(81_f32.log10() * self.volume / 100.0) - 1.0) / 80.0 + } else { + 0.0 + } + } } diff --git a/desktop/src/gui/controller.rs b/desktop/src/gui/controller.rs index 743c8ad32..2b3e57892 100644 --- a/desktop/src/gui/controller.rs +++ b/desktop/src/gui/controller.rs @@ -182,7 +182,13 @@ impl GuiController { self.size.height, ); player.create(&opt, &movie_url, movie_view); - self.gui.on_player_created(opt, movie_url); + self.gui.on_player_created( + opt, + movie_url, + player + .get() + .expect("Player must exist after being created."), + ); } pub fn render(&mut self, mut player: Option>) { diff --git a/desktop/src/player.rs b/desktop/src/player.rs index 2247d2ad4..f16560984 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -8,7 +8,6 @@ use crate::executor::WinitAsyncExecutor; use crate::gui::MovieView; use crate::{CALLSTACK, RENDER_INFO, SWF_INFO}; use anyhow::anyhow; -use ruffle_core::backend::audio::AudioBackend; use ruffle_core::backend::navigator::{OpenURLMode, SocketMode}; use ruffle_core::config::Letterbox; use ruffle_core::{LoadBehavior, Player, PlayerBuilder, PlayerEvent, StageAlign, StageScaleMode}; @@ -98,8 +97,7 @@ impl ActivePlayer { let mut builder = PlayerBuilder::new(); match CpalAudioBackend::new() { - Ok(mut audio) => { - audio.set_volume(opt.volume); + Ok(audio) => { builder = builder.with_audio(audio); } Err(e) => {