desktop: Add desktop volume controls
The desktop version of Ruffle now has a volume controls window. It can be accessed through the menu bar (Controls > Volume controls). It contains a mute button and a slider from 0 to 100. The volume settings set in the GUI are saved in a new VolumeControls struct, which is also used to calculate the real volume (adapted for logarithmic hearing) out of the entered volume and the mute checkbox. As soon as the volume is changed in the GUI, the real volume will be set in the player (if the player exists). The player doesn't set its volume level according to the PlayerOptions after its creation anymore. Instead, RuffleGui::on_player_created now gets the player and sets its volume to the real volume set in the GUI. The volume in the GUI itself defaults to the PlayerOptions value. This also fixes the issue that the PlayerOptions volume has previously not been adapted for logarithmic hearing. The existing ftl files have been adapted (and new ones have been created) to include the new multilingual text in the menu bar and the volume controls window.
This commit is contained in:
parent
d195da59af
commit
a550a998c3
|
@ -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...
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
volume-controls = Lautstärkeeinstellungen
|
||||
volume-controls-mute = Stummschalten
|
||||
volume-controls-volume = Lautstärke
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
volume-controls = Volume controls
|
||||
volume-controls-mute = Mute
|
||||
volume-controls-volume = Volume
|
|
@ -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<RuffleEvent>,
|
||||
is_about_visible: bool,
|
||||
is_volume_visible: bool,
|
||||
volume_controls: VolumeControls,
|
||||
is_open_dialog_visible: bool,
|
||||
context_menu: Vec<ruffle_core::ContextMenuItem>,
|
||||
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<Player>,
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<MutexGuard<Player>>) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in New Issue