mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
subtitles
This commit is contained in:
parent
1c9b502f69
commit
99463a37f8
File diff suppressed because it is too large
Load Diff
@ -151,3 +151,4 @@ hud-settings-group_only = Group only
|
||||
hud-settings-reset_chat = Reset to Defaults
|
||||
hud-settings-third_party_integrations = Third-party Integrations
|
||||
hud-settings-enable_discord_integration = Enable Discord Integration
|
||||
hud-settings-subtitles = Subtitles
|
||||
|
148
assets/voxygen/i18n/en/hud/subtitles.ftl
Normal file
148
assets/voxygen/i18n/en/hud/subtitles.ftl
Normal file
@ -0,0 +1,148 @@
|
||||
subtitle-campfire = Campfire crackling
|
||||
subtitle-bird_call = Birds singing
|
||||
subtitle-bees = Bees buzzing
|
||||
subtitle-owl = Owl hooting
|
||||
subtitle-running_water = Water bubbling
|
||||
subtitle-lightning = Thunder
|
||||
|
||||
subtitle-footsteps_grass = Walking on grass
|
||||
subtitle-footsteps_earth = Walking on dirt
|
||||
subtitle-footsteps_rock = Walking on rock
|
||||
subtitle-footsteps_snow = Walking on snow
|
||||
subtitle-pickup_item = Item picked up
|
||||
subtitle-pickup_failed = Pickup failed
|
||||
|
||||
subtitle-glider_open = Glider equipped
|
||||
subtitle-glider_close = Glider unequipped
|
||||
subtitle-glide = Gliding
|
||||
subtitle-roll = Rolling
|
||||
subtitle-swim = Swimming
|
||||
subtitle-climb = Climbing
|
||||
subtitle-damage = Damage
|
||||
subtitle-death = Death
|
||||
|
||||
subtitle-wield_bow = Bow equipped
|
||||
subtitle-unwield_bow = Bow unequipped
|
||||
subtitle-pickup_bow = Bow picked up
|
||||
|
||||
subtitle-wield_sword = Sword equipped
|
||||
subtitle-unwield_sword = Sword unequipped
|
||||
subtitle-sword_attack = Sword swung
|
||||
subtitle-pickup_sword = Sword picked up
|
||||
|
||||
subtitle-wield_axe = Axe equipped
|
||||
subtitle-unwield_axe = Axe unequipped
|
||||
subtitle-axe_attack = Axe swung
|
||||
subtitle-pickup_axe = Axe picked up
|
||||
|
||||
subtitle-wield_hammer = Hammer equipped
|
||||
subtitle-unwield_hammer = Hammer unequipped
|
||||
subtitle-hammer_attack = Hammer swung
|
||||
subtitle-pickup_hammer = Hammer picked up
|
||||
|
||||
subtitle-wield_staff = Staff equipped
|
||||
subtitle-unwield_staff = Staff unequipped
|
||||
subtitle-fire_shot = Staff fired
|
||||
subtitle-staff_attack = Staff fired
|
||||
subtitle-pickup_staff = Staff picked up
|
||||
|
||||
subtitle-wield_sceptre = Sceptre equipped
|
||||
subtitle-unwield_sceptre = Sceptre unequipped
|
||||
subtitle-sceptre_heal = Sceptre heal aura
|
||||
subtitle-pickup_sceptre = Sceptre picked up
|
||||
|
||||
subtitle-wield_dagger = Dagger equipped
|
||||
subtitle-uwield_dagger = Dagger unequipped
|
||||
subtitle-dagger_attack = Dagger swung
|
||||
subtitle-pickup_dagger = Dagger picked up
|
||||
|
||||
subtitle-wield_shield = Shield equipped
|
||||
subtitle-unwield_shield = Shield unequipped
|
||||
subtitle-shield_attack = Shield pushed
|
||||
subtitle-pickup_shield = Shield picked up
|
||||
|
||||
subtitle-pickup_pick = Pickaxe picked up
|
||||
subtitle-pickup_gemstone = Gemstone picked up
|
||||
|
||||
subtitle-instrument_organ = Organ playing
|
||||
|
||||
subtitle-wield_instrument = Instrument equipped
|
||||
subtitle-unwield_instrument = Instrument unequipped
|
||||
subtitle-instrument_double_bass = Double Bass playing
|
||||
subtitle-instrument_flute = Flute playing
|
||||
subtitle-instrument_glass_flute = Glass Flute playing
|
||||
subtitle-instrument_lyre = Lyre playing
|
||||
subtitle-instrument_icy_talharpa = Icy Talharpa playing
|
||||
subtitle-instrument_kalimba = Kalimba playing
|
||||
subtitle-instrument_melodica = Melodica playing
|
||||
subtitle-instrument_lute = Lute playing
|
||||
subtitle-instrument_sitar = Sitar playing
|
||||
subtitle-instrument_guitar = Guitar playing
|
||||
subtitle-instrument_dark_guitar = Dark Guitar playing
|
||||
subtitle-instrument_washboard = Washboard playing
|
||||
subtitle-pickup_instrument = Pickup instrument
|
||||
|
||||
subtitle-explosion = Explosion
|
||||
|
||||
subtitle-arrow_shot = Arrow released
|
||||
subtitle-arrow_miss = Arrow miss
|
||||
subtitle-arrow_hit = Arrow hit
|
||||
subtitle-skill_point = Skill Point gained
|
||||
subtitle-sceptre_beam = Sceptre beam
|
||||
subtitle-flame_thrower = Flame thrower
|
||||
subtitle-break_block = Block destroyed
|
||||
subtitle-attack_blocked = Attack blocked
|
||||
subtitle-parry = Parried
|
||||
subtitle-interrupted = Interrupted
|
||||
subtitle-stunned = Stunned
|
||||
subtitle-dazed = Dazed
|
||||
subtitle-knocked_down = Knocked down
|
||||
|
||||
subtitle-attack-ground_slam = Ground slam
|
||||
subtitle-attack-laser_beam = Laser beam
|
||||
subtitle-attack-cyclops_charge = Cyclops charge
|
||||
subtitle-giga_roar = Frost gigas roar
|
||||
subtitle-attack-flash_freeze = Flash freeze
|
||||
subtitle-attack-icy_spikes = Icy spikes
|
||||
subtitle-attack-ice_crack = Ice crack
|
||||
|
||||
subtitle-consume_potion = Drinking potion
|
||||
subtitle-consume_apple = Eating apple
|
||||
subtitle-consume_cheese = Eating cheese
|
||||
subtitle-consume_food = Eating
|
||||
subtitle-consume_liquid = Drinking
|
||||
|
||||
subtitle-utterance-alligator-angry = Alligator hissing
|
||||
subtitle-utterance-antelope-angry = Antelope snorting
|
||||
subtitle-utterance-biped_large-angry = Heavy grunting
|
||||
subtitle-utterance-bird-angry = Bird screeching
|
||||
subtitle-utterance-adlet-angry = Adlet barking
|
||||
subtitle-utterance-pig-angry = Pig grunting
|
||||
subtitle-utterance-reptile-angry = Reptile hissing
|
||||
subtitle-utterance-sea_crocodile-angry = Sea Crocodile hissing
|
||||
subtitle-utterance-saurok-angry = Saurok hissing
|
||||
subtitle-utterance-cat-calm = Cat meowing
|
||||
subtitle-utterance-cow-calm = Cow mooing
|
||||
subtitle-utterance-fungome-calm = Fungome squeaking
|
||||
subtitle-utterance-goat-calm = Goat bleating
|
||||
subtitle-utterance-pig-calm = Pig oinking
|
||||
subtitle-utterance-sheep-calm = Sheep bleating
|
||||
subtitle-utterance-truffler-calm = Truffler oinking
|
||||
subtitle-utterance-human-greeting = Greeting
|
||||
subtitle-utterance-adlet-hurt = Adlet whining
|
||||
subtitle-utterance-antelope-hurt = Antelope crying
|
||||
subtitle-utterance-biped_large-hurt = Heavy hurting
|
||||
subtitle-utterance-human-hurt = Human hurting
|
||||
subtitle-utterance-lion-hurt = Lion growling
|
||||
subtitle-utterance-mandroga-hurt = Mandroga screaming
|
||||
subtitle-utterance-maneater-hurt = Maneater burping
|
||||
subtitle-utterance-marlin-hurt = Marlin hurting
|
||||
subtitle-utterance-mindflayer-hurt = Mindflayer hurting
|
||||
subtitle-utterance-dagon-hurt = Dagon hurting
|
||||
subtitle-utterance-asp-angry = Asp hissing
|
||||
subtitle-utterance-asp-calm = Asp croaking
|
||||
subtitle-utterance-asp-hurt = Asp hurting
|
||||
subtitle-utterance-wendigo-angry = Wendigo screaming
|
||||
subtitle-utterance-wendigo-calm = Wendigo mumbling
|
||||
subtitle-utterance-wolf-angry = Wolf growling
|
||||
subtitle-utterance-wolf-hurt = Wolf whining
|
@ -14,13 +14,15 @@ use fader::Fader;
|
||||
use music::MusicTransitionManifest;
|
||||
use sfx::{SfxEvent, SfxTriggerItem};
|
||||
use soundcache::load_ogg;
|
||||
use std::time::Duration;
|
||||
use std::{collections::VecDeque, time::Duration};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use common::assets::{AssetExt, AssetHandle};
|
||||
use rodio::{source::Source, OutputStream, OutputStreamHandle, StreamError};
|
||||
use vek::*;
|
||||
|
||||
use crate::hud::Subtitle;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Listener {
|
||||
pos: Vec3<f32>,
|
||||
@ -53,12 +55,19 @@ pub struct AudioFrontend {
|
||||
music_spacing: f32,
|
||||
listener: Listener,
|
||||
|
||||
pub subtitles_enabled: bool,
|
||||
pub subtitles: VecDeque<Subtitle>,
|
||||
|
||||
mtm: AssetHandle<MusicTransitionManifest>,
|
||||
}
|
||||
|
||||
impl AudioFrontend {
|
||||
/// Construct with given device
|
||||
pub fn new(/* dev: String, */ num_sfx_channels: usize, num_ui_channels: usize) -> Self {
|
||||
pub fn new(
|
||||
/* dev: String, */ num_sfx_channels: usize,
|
||||
num_ui_channels: usize,
|
||||
subtitles: bool,
|
||||
) -> Self {
|
||||
// Commented out until audio device switcher works
|
||||
//let audio_device = get_device_raw(&dev);
|
||||
|
||||
@ -106,6 +115,8 @@ impl AudioFrontend {
|
||||
music_spacing: 1.0,
|
||||
listener: Listener::default(),
|
||||
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
|
||||
subtitles: VecDeque::new(),
|
||||
subtitles_enabled: subtitles,
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +140,8 @@ impl AudioFrontend {
|
||||
music_spacing: 1.0,
|
||||
listener: Listener::default(),
|
||||
mtm: AssetExt::load_expect("voxygen.audio.music_transition_manifest"),
|
||||
subtitles: VecDeque::new(),
|
||||
subtitles_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,9 +236,9 @@ impl AudioFrontend {
|
||||
/// Errors if no sounds are found
|
||||
fn get_sfx_file<'a>(
|
||||
trigger_item: Option<(&'a SfxEvent, &'a SfxTriggerItem)>,
|
||||
) -> Option<&'a str> {
|
||||
) -> Option<(&'a str, f32, Option<&'a str>)> {
|
||||
trigger_item.map(|(event, item)| {
|
||||
match item.files.len() {
|
||||
let file = match item.files.len() {
|
||||
0 => {
|
||||
debug!("Sfx event {:?} is missing audio file.", event);
|
||||
"voxygen.audio.sfx.placeholder"
|
||||
@ -239,7 +252,9 @@ impl AudioFrontend {
|
||||
let rand_step = rand::random::<usize>() % item.files.len();
|
||||
&item.files[rand_step]
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
(file, item.threshold, item.subtitle.as_deref())
|
||||
})
|
||||
}
|
||||
|
||||
@ -252,11 +267,19 @@ impl AudioFrontend {
|
||||
volume: Option<f32>,
|
||||
underwater: bool,
|
||||
) {
|
||||
if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: Some(position),
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Play sound in empty channel at given position
|
||||
if self.audio_stream.is_some() {
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
|
||||
let listener = self.listener.clone();
|
||||
if let Some(channel) = self.get_sfx_channel() {
|
||||
channel.set_pos(position);
|
||||
@ -286,11 +309,19 @@ impl AudioFrontend {
|
||||
freq: Option<u32>,
|
||||
underwater: bool,
|
||||
) {
|
||||
if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: Some(position),
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Play sound in empty channel at given position
|
||||
if self.audio_stream.is_some() {
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
|
||||
let listener = self.listener.clone();
|
||||
if let Some(channel) = self.get_sfx_channel() {
|
||||
channel.set_pos(position);
|
||||
@ -321,11 +352,19 @@ impl AudioFrontend {
|
||||
trigger_item: Option<(&SfxEvent, &SfxTriggerItem)>,
|
||||
volume: Option<f32>,
|
||||
) {
|
||||
if let Some(sfx_file) = Self::get_sfx_file(trigger_item) {
|
||||
if let Some((sfx_file, dur, subtitle)) = Self::get_sfx_file(trigger_item) {
|
||||
if self.subtitles_enabled {
|
||||
if let Some(subtitle) = subtitle {
|
||||
self.subtitles.push_back(Subtitle {
|
||||
localization: subtitle.to_string(),
|
||||
position: None,
|
||||
show_until: dur.max(1.5) as f64,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Play sound in empty channel
|
||||
if self.audio_stream.is_some() {
|
||||
if self.audio_stream.is_some() && self.sfx_enabled() {
|
||||
let sound = load_ogg(sfx_file).amplify(volume.unwrap_or(1.0));
|
||||
|
||||
if let Some(channel) = self.get_ui_channel() {
|
||||
channel.play(sound);
|
||||
}
|
||||
@ -504,6 +543,8 @@ impl AudioFrontend {
|
||||
|
||||
pub fn set_music_spacing(&mut self, multiplier: f32) { self.music_spacing = multiplier }
|
||||
|
||||
pub fn set_subtitles(&mut self, enabled: bool) { self.subtitles_enabled = enabled }
|
||||
|
||||
/// Updates master volume in all channels
|
||||
pub fn set_master_volume(&mut self, master_volume: f32) {
|
||||
self.master_volume = master_volume;
|
||||
|
@ -23,6 +23,7 @@ fn config_but_played_since_threshold_no_emit() {
|
||||
let trigger_item = SfxTriggerItem {
|
||||
files: vec![String::from("some.path.to.sfx.file")],
|
||||
threshold: 1.0,
|
||||
subtitle: None,
|
||||
};
|
||||
|
||||
// Triggered a 'Run' 0 seconds ago
|
||||
@ -47,6 +48,7 @@ fn config_and_not_played_since_threshold_emits() {
|
||||
let trigger_item = SfxTriggerItem {
|
||||
files: vec![String::from("some.path.to.sfx.file")],
|
||||
threshold: 0.5,
|
||||
subtitle: None,
|
||||
};
|
||||
|
||||
let previous_state = PreviousEntityState {
|
||||
@ -70,6 +72,7 @@ fn same_previous_event_elapsed_emits() {
|
||||
let trigger_item = SfxTriggerItem {
|
||||
files: vec![String::from("some.path.to.sfx.file")],
|
||||
threshold: 0.5,
|
||||
subtitle: None,
|
||||
};
|
||||
|
||||
let previous_state = PreviousEntityState {
|
||||
|
@ -328,6 +328,9 @@ pub struct SfxTriggerItem {
|
||||
pub files: Vec<String>,
|
||||
/// The time to wait before repeating this SfxEvent
|
||||
pub threshold: f32,
|
||||
|
||||
#[serde(default)]
|
||||
pub subtitle: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
@ -369,7 +372,7 @@ impl SfxMgr {
|
||||
) {
|
||||
// Checks if the SFX volume is set to zero or audio is disabled
|
||||
// This prevents us from running all the following code unnecessarily
|
||||
if !audio.sfx_enabled() {
|
||||
if !audio.sfx_enabled() && !audio.subtitles_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -401,7 +404,7 @@ impl SfxMgr {
|
||||
client: &Client,
|
||||
underwater: bool,
|
||||
) {
|
||||
if !audio.sfx_enabled() {
|
||||
if !audio.sfx_enabled() && !audio.subtitles_enabled {
|
||||
return;
|
||||
}
|
||||
let triggers = self.triggers.read();
|
||||
|
@ -9,8 +9,6 @@ mod diary;
|
||||
mod esc_menu;
|
||||
mod group;
|
||||
mod hotbar;
|
||||
pub mod img_ids;
|
||||
pub mod item_imgs;
|
||||
mod loot_scroller;
|
||||
mod map;
|
||||
mod minimap;
|
||||
@ -23,7 +21,11 @@ mod settings_window;
|
||||
mod skillbar;
|
||||
mod slots;
|
||||
mod social;
|
||||
mod subtitles;
|
||||
mod trade;
|
||||
|
||||
pub mod img_ids;
|
||||
pub mod item_imgs;
|
||||
pub mod util;
|
||||
|
||||
pub use crafting::CraftingTab;
|
||||
@ -31,6 +33,7 @@ pub use hotbar::{SlotContents as HotbarSlotContents, State as HotbarState};
|
||||
pub use item_imgs::animate_by_pulse;
|
||||
pub use loot_scroller::LootMessage;
|
||||
pub use settings_window::ScaleChange;
|
||||
pub use subtitles::Subtitle;
|
||||
|
||||
use bag::Bag;
|
||||
use buffs::BuffsBar;
|
||||
@ -54,6 +57,7 @@ use serde::{Deserialize, Serialize};
|
||||
use settings_window::{SettingsTab, SettingsWindow};
|
||||
use skillbar::Skillbar;
|
||||
use social::Social;
|
||||
use subtitles::Subtitles;
|
||||
use trade::Trade;
|
||||
|
||||
use crate::{
|
||||
@ -328,6 +332,7 @@ widget_ids! {
|
||||
settings_window,
|
||||
group_window,
|
||||
item_info,
|
||||
subtitles,
|
||||
|
||||
// Free look indicator
|
||||
free_look_txt,
|
||||
@ -1451,7 +1456,7 @@ impl Hud {
|
||||
fn update_layout(
|
||||
&mut self,
|
||||
client: &Client,
|
||||
global_state: &GlobalState,
|
||||
global_state: &mut GlobalState,
|
||||
debug_info: &Option<DebugInfo>,
|
||||
dt: Duration,
|
||||
info: HudInfo,
|
||||
@ -3343,6 +3348,13 @@ impl Hud {
|
||||
}
|
||||
}
|
||||
|
||||
if global_state.settings.audio.subtitles {
|
||||
Subtitles::new(client, &mut global_state.audio.subtitles, &self.fonts, i18n)
|
||||
.set(self.ids.subtitles, ui_widgets);
|
||||
} else {
|
||||
global_state.audio.subtitles.clear();
|
||||
}
|
||||
|
||||
self.new_messages = VecDeque::new();
|
||||
self.new_notifications = VecDeque::new();
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
hud::{img_ids::Imgs, TEXT_COLOR},
|
||||
render::RenderMode,
|
||||
session::settings_change::{Accessibility as AccessibilityChange, Accessibility::*},
|
||||
ui::{fonts::Fonts, ToggleButton},
|
||||
GlobalState, render::RenderMode,
|
||||
GlobalState,
|
||||
};
|
||||
use conrod_core::{
|
||||
color,
|
||||
@ -18,6 +19,8 @@ widget_ids! {
|
||||
flashing_lights_button,
|
||||
flashing_lights_label,
|
||||
flashing_lights_info_label,
|
||||
subtitles_button,
|
||||
subtitles_label,
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,7 +87,7 @@ impl<'a> Widget for Accessibility<'a> {
|
||||
// Get render mode
|
||||
let render_mode = &self.global_state.settings.graphics.render_mode;
|
||||
|
||||
// Disable flashing lights
|
||||
// Flashing lights
|
||||
Text::new(
|
||||
&self
|
||||
.localized_strings
|
||||
@ -97,11 +100,7 @@ impl<'a> Widget for Accessibility<'a> {
|
||||
.set(state.ids.flashing_lights_label, ui);
|
||||
|
||||
let flashing_lights_enabled = ToggleButton::new(
|
||||
self.global_state
|
||||
.settings
|
||||
.graphics
|
||||
.render_mode
|
||||
.flashing_lights_enabled,
|
||||
render_mode.flashing_lights_enabled,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
@ -129,6 +128,29 @@ impl<'a> Widget for Accessibility<'a> {
|
||||
})));
|
||||
}
|
||||
|
||||
// Subtitles
|
||||
Text::new(&self.localized_strings.get_msg("hud-settings-subtitles"))
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.down_from(state.ids.flashing_lights_label, 10.0)
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.subtitles_label, ui);
|
||||
|
||||
let subtitles_enabled = ToggleButton::new(
|
||||
self.global_state.settings.audio.subtitles,
|
||||
self.imgs.checkbox,
|
||||
self.imgs.checkbox_checked,
|
||||
)
|
||||
.w_h(18.0, 18.0)
|
||||
.right_from(state.ids.subtitles_label, 10.0)
|
||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||
.set(state.ids.subtitles_button, ui);
|
||||
|
||||
if subtitles_enabled != self.global_state.settings.audio.subtitles {
|
||||
events.push(SetSubtitles(subtitles_enabled));
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
mod accessibility;
|
||||
mod chat;
|
||||
mod controls;
|
||||
mod gameplay;
|
||||
@ -6,7 +7,6 @@ mod language;
|
||||
mod networking;
|
||||
mod sound;
|
||||
mod video;
|
||||
mod accessibility;
|
||||
|
||||
use crate::{
|
||||
hud::{img_ids::Imgs, Show, TEXT_COLOR, UI_HIGHLIGHT_0, UI_MAIN},
|
||||
@ -356,15 +356,11 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
}
|
||||
},
|
||||
SettingsTab::Accessibility => {
|
||||
for change in accessibility::Accessibility::new(
|
||||
global_state,
|
||||
imgs,
|
||||
fonts,
|
||||
localized_strings,
|
||||
)
|
||||
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
|
||||
.wh_of(state.ids.settings_content_align)
|
||||
.set(state.ids.accessibility, ui)
|
||||
for change in
|
||||
accessibility::Accessibility::new(global_state, imgs, fonts, localized_strings)
|
||||
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
|
||||
.wh_of(state.ids.settings_content_align)
|
||||
.set(state.ids.accessibility, ui)
|
||||
{
|
||||
events.push(Event::SettingsChange(change.into()));
|
||||
}
|
||||
|
191
voxygen/src/hud/subtitles.rs
Normal file
191
voxygen/src/hud/subtitles.rs
Normal file
@ -0,0 +1,191 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::ui::fonts::Fonts;
|
||||
use client::Client;
|
||||
use common::comp;
|
||||
use conrod_core::{
|
||||
widget::{self, Text},
|
||||
widget_ids, Colorable, Positionable, Widget, WidgetCommon,
|
||||
};
|
||||
use i18n::Localization;
|
||||
|
||||
use vek::{Vec2, Vec3};
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
subtitle_box,
|
||||
subtitle_box_bg,
|
||||
subtitle_message[],
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(WidgetCommon)]
|
||||
pub struct Subtitles<'a> {
|
||||
client: &'a Client,
|
||||
|
||||
fonts: &'a Fonts,
|
||||
|
||||
new_subtitles: &'a mut VecDeque<Subtitle>,
|
||||
|
||||
#[conrod(common_builder)]
|
||||
common: widget::CommonBuilder,
|
||||
|
||||
localized_strings: &'a Localization,
|
||||
}
|
||||
|
||||
impl<'a> Subtitles<'a> {
|
||||
pub fn new(
|
||||
client: &'a Client,
|
||||
new_subtitles: &'a mut VecDeque<Subtitle>,
|
||||
fonts: &'a Fonts,
|
||||
localized_strings: &'a Localization,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
fonts,
|
||||
new_subtitles,
|
||||
common: widget::CommonBuilder::default(),
|
||||
localized_strings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_SUBTITLE_DIST: f32 = 80.0;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Subtitle {
|
||||
pub localization: String,
|
||||
pub position: Option<Vec3<f32>>,
|
||||
pub show_until: f64,
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
subtitles: Vec<Subtitle>,
|
||||
ids: Ids,
|
||||
}
|
||||
|
||||
impl<'a> Widget for Subtitles<'a> {
|
||||
type Event = ();
|
||||
type State = State;
|
||||
type Style = ();
|
||||
|
||||
fn init_state(&self, id_gen: widget::id::Generator) -> Self::State {
|
||||
State {
|
||||
subtitles: Vec::new(),
|
||||
ids: Ids::new(id_gen),
|
||||
}
|
||||
}
|
||||
|
||||
fn style(&self) -> Self::Style {}
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
common_base::prof_span!("Chat::update");
|
||||
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
let time = self.client.state().get_time();
|
||||
let player_pos = self.client.position().unwrap_or_default();
|
||||
let player_dir = self
|
||||
.client
|
||||
.state()
|
||||
.read_storage::<comp::Ori>()
|
||||
.get(self.client.entity())
|
||||
.map_or(Vec3::unit_y(), |ori| ori.look_vec());
|
||||
// Empty old subtitles and add new.
|
||||
state.update(|s| {
|
||||
s.subtitles.retain(|subtitle| {
|
||||
time <= subtitle.show_until
|
||||
&& subtitle
|
||||
.position
|
||||
.map_or(true, |pos| pos.distance(player_pos) <= MAX_SUBTITLE_DIST)
|
||||
});
|
||||
for mut subtitle in self.new_subtitles.drain(..) {
|
||||
if subtitle
|
||||
.position
|
||||
.map_or(false, |pos| pos.distance(player_pos) > MAX_SUBTITLE_DIST)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let t = time + subtitle.show_until;
|
||||
if let Some(s) = s
|
||||
.subtitles
|
||||
.iter_mut()
|
||||
.find(|s| s.localization == subtitle.localization)
|
||||
{
|
||||
if t > s.show_until {
|
||||
s.show_until = t;
|
||||
s.position = subtitle.position;
|
||||
}
|
||||
} else {
|
||||
subtitle.show_until = t;
|
||||
s.subtitles.push(subtitle);
|
||||
}
|
||||
}
|
||||
s.ids
|
||||
.subtitle_message
|
||||
.resize(s.subtitles.len(), &mut ui.widget_id_generator());
|
||||
});
|
||||
|
||||
let mut subtitles = state
|
||||
.ids
|
||||
.subtitle_message
|
||||
.iter()
|
||||
.zip(state.subtitles.iter());
|
||||
|
||||
let fade_amount = |t: &Subtitle| ((t.show_until - time) * 1.5).clamp(0.0, 1.0) as f32;
|
||||
|
||||
let player_dir = player_dir.xy().try_normalized().unwrap_or(Vec2::unit_y());
|
||||
let player_right = Vec2::new(player_dir.y, -player_dir.x);
|
||||
|
||||
let message = |subtitle: &Subtitle| {
|
||||
let message = self.localized_strings.get_msg(&subtitle.localization);
|
||||
let is_right = subtitle.position.and_then(|pos| {
|
||||
let dist = pos.distance(player_pos);
|
||||
let dir = (pos - player_pos) / dist;
|
||||
|
||||
let dot = dir.xy().dot(player_dir);
|
||||
if dist < 2.0 || dot > 0.85 {
|
||||
None
|
||||
} else {
|
||||
Some(dir.xy().dot(player_right) >= 0.0)
|
||||
}
|
||||
});
|
||||
|
||||
match is_right {
|
||||
Some(true) => format!(" {message} >"),
|
||||
Some(false) => format!("< {message} "),
|
||||
None => format!(" {message} "),
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((id, subtitle)) = subtitles.next() {
|
||||
Text::new(&message(subtitle))
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.center_justify()
|
||||
.bottom_right_with_margins(40.0, 50.0)
|
||||
.color(conrod_core::Color::Rgba(
|
||||
0.9,
|
||||
1.0,
|
||||
1.0,
|
||||
fade_amount(subtitle),
|
||||
))
|
||||
.set(*id, ui);
|
||||
let mut last_id = *id;
|
||||
for (id, subtitle) in subtitles {
|
||||
Text::new(&message(subtitle))
|
||||
.font_size(self.fonts.cyri.scale(14))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.center_justify()
|
||||
.up_from(last_id, 10.0)
|
||||
.color(conrod_core::Color::Rgba(
|
||||
0.9,
|
||||
1.0,
|
||||
1.0,
|
||||
fade_amount(subtitle),
|
||||
))
|
||||
.set(*id, ui);
|
||||
last_id = *id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -139,6 +139,7 @@ fn main() {
|
||||
AudioOutput::Automatic => AudioFrontend::new(
|
||||
settings.audio.num_sfx_channels,
|
||||
settings.audio.num_ui_channels,
|
||||
settings.audio.subtitles,
|
||||
),
|
||||
// AudioOutput::Device(ref dev) => Some(dev.clone()),
|
||||
};
|
||||
|
@ -175,6 +175,7 @@ pub enum Networking {
|
||||
#[derive(Clone)]
|
||||
pub enum Accessibility {
|
||||
ChangeRenderMode(Box<RenderMode>),
|
||||
SetSubtitles(bool),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -744,7 +745,11 @@ impl SettingsChange {
|
||||
SettingsChange::Accessibility(accessibility_change) => match accessibility_change {
|
||||
Accessibility::ChangeRenderMode(new_render_mode) => {
|
||||
change_render_mode(*new_render_mode, &mut global_state.window, settings);
|
||||
}
|
||||
},
|
||||
Accessibility::SetSubtitles(enabled) => {
|
||||
global_state.settings.audio.subtitles = enabled;
|
||||
global_state.audio.set_subtitles(enabled);
|
||||
},
|
||||
},
|
||||
}
|
||||
global_state
|
||||
|
@ -47,6 +47,7 @@ pub struct AudioSettings {
|
||||
pub num_sfx_channels: usize,
|
||||
pub num_ui_channels: usize,
|
||||
pub music_spacing: f32,
|
||||
pub subtitles: bool,
|
||||
|
||||
/// Audio Device that Voxygen will use to play audio.
|
||||
pub output: AudioOutput,
|
||||
@ -63,6 +64,7 @@ impl Default for AudioSettings {
|
||||
num_sfx_channels: 60,
|
||||
num_ui_channels: 10,
|
||||
music_spacing: 1.0,
|
||||
subtitles: false,
|
||||
output: AudioOutput::Automatic,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user