1use crate::audio::{Audio, Synth};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum UiSound {
8 Open,
10 Close,
12 Navigate,
14 Wrap,
17 Activate,
19 Edge,
21 Change,
23 Typing,
25}
26
27impl UiSound {
28 pub const ALL: [UiSound; 8] = [
30 UiSound::Open,
31 UiSound::Close,
32 UiSound::Navigate,
33 UiSound::Wrap,
34 UiSound::Activate,
35 UiSound::Edge,
36 UiSound::Change,
37 UiSound::Typing,
38 ];
39
40 pub fn name(self) -> &'static str {
42 match self {
43 UiSound::Open => "open",
44 UiSound::Close => "close",
45 UiSound::Navigate => "navigate",
46 UiSound::Wrap => "wrap",
47 UiSound::Activate => "activate",
48 UiSound::Edge => "edge",
49 UiSound::Change => "change",
50 UiSound::Typing => "typing",
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct UiSounds {
73 pub open: Option<String>,
75 pub close: Option<String>,
77 pub navigate: Option<String>,
79 pub wrap: Option<String>,
81 pub activate: Option<String>,
83 pub edge: Option<String>,
85 pub change: Option<String>,
87 pub typing: Option<String>,
89}
90
91impl Default for UiSounds {
92 fn default() -> Self {
93 Self::builtin()
94 }
95}
96
97impl UiSounds {
98 pub fn builtin() -> Self {
100 let mut sounds = Self::none();
101 for kind in UiSound::ALL {
102 if kind != UiSound::Typing {
103 sounds.set(kind, Some(format!("ui:builtin:{}", kind.name())));
104 }
105 }
106 sounds
107 }
108
109 pub fn none() -> Self {
111 Self {
112 open: None,
113 close: None,
114 navigate: None,
115 wrap: None,
116 activate: None,
117 edge: None,
118 change: None,
119 typing: None,
120 }
121 }
122
123 pub fn from_folder(folder: &str) -> Self {
128 let mut sounds = Self::none();
129 for kind in UiSound::ALL {
130 sounds.set(kind, Some(format!("{}/{}", folder.trim_end_matches('/'), kind.name())));
131 }
132 sounds
133 }
134
135 pub fn with(mut self, kind: UiSound, name: impl Into<String>) -> Self {
137 self.set(kind, Some(name.into()));
138 self
139 }
140
141 pub fn without(mut self, kind: UiSound) -> Self {
143 self.set(kind, None);
144 self
145 }
146
147 pub fn set(&mut self, kind: UiSound, name: Option<String>) {
149 *self.slot(kind) = name;
150 }
151
152 fn slot(&mut self, kind: UiSound) -> &mut Option<String> {
153 match kind {
154 UiSound::Open => &mut self.open,
155 UiSound::Close => &mut self.close,
156 UiSound::Navigate => &mut self.navigate,
157 UiSound::Wrap => &mut self.wrap,
158 UiSound::Activate => &mut self.activate,
159 UiSound::Edge => &mut self.edge,
160 UiSound::Change => &mut self.change,
161 UiSound::Typing => &mut self.typing,
162 }
163 }
164
165 pub fn get(&self, kind: UiSound) -> Option<&str> {
167 match kind {
168 UiSound::Open => self.open.as_deref(),
169 UiSound::Close => self.close.as_deref(),
170 UiSound::Navigate => self.navigate.as_deref(),
171 UiSound::Wrap => self.wrap.as_deref().or(self.navigate.as_deref()),
172 UiSound::Activate => self.activate.as_deref(),
173 UiSound::Edge => self.edge.as_deref(),
174 UiSound::Change => self.change.as_deref(),
175 UiSound::Typing => self.typing.as_deref(),
176 }
177 }
178}
179
180pub(crate) fn ensure_builtin(audio: &mut Audio, name: &str) {
182 let Some(kind) = name.strip_prefix("ui:builtin:") else {
183 return;
184 };
185 if audio.is_loaded(name) {
186 return;
187 }
188 let synth = match kind {
189 "navigate" => Synth::sine(1400.0).length(0.035).decay(120.0).volume(0.35),
190 "wrap" => Synth::sine(1400.0).length(0.03).decay(120.0).volume(0.3).then(Synth::sine(1900.0).length(0.04).decay(120.0).volume(0.3)),
191 "change" => Synth::sine(1000.0).length(0.04).decay(90.0).volume(0.35),
192 "activate" => Synth::sine(700.0).length(0.05).decay(40.0).volume(0.35).then(Synth::sine(1050.0).length(0.07).decay(40.0).volume(0.35)),
193 "edge" => Synth::sine(180.0).length(0.08).decay(45.0).volume(0.5),
194 "open" => Synth::sine(500.0).sweep_to(980.0).length(0.12).decay(15.0).volume(0.25),
195 "close" => Synth::sine(950.0).sweep_to(590.0).length(0.12).decay(15.0).volume(0.25),
196 "typing" => Synth::noise().length(0.015).decay(250.0).volume(0.2),
197 _ => return,
198 };
199 audio.register(name, synth);
200}