1use crate::plugins::inspector::editors::resource::{ResourceFieldBuilder, ResourceFieldMessage};
22use crate::{
23 audio::bus::{AudioBusView, AudioBusViewBuilder, AudioBusViewMessage},
24 command::CommandGroup,
25 fyrox::{
26 core::pool::Handle,
27 engine::Engine,
28 graph::BaseSceneGraph,
29 gui::{
30 button::{ButtonBuilder, ButtonMessage},
31 dropdown_list::{DropdownListBuilder, DropdownListMessage},
32 grid::{Column, Row},
33 list_view::{ListView, ListViewBuilder, ListViewMessage},
34 message::UiMessage,
35 stack_panel::StackPanelBuilder,
36 text::TextBuilder,
37 utils::make_simple_tooltip,
38 widget::{WidgetBuilder, WidgetMessage},
39 window::{WindowBuilder, WindowTitle},
40 Orientation, Thickness, UiNode, VerticalAlignment,
41 },
42 scene::sound::{AudioBus, AudioBusGraph, DistanceModel, HrirSphereResourceData, Renderer},
43 },
44 message::MessageSender,
45 scene::{
46 commands::{
47 effect::{AddAudioBusCommand, LinkAudioBuses, RemoveAudioBusCommand},
48 sound_context::{
49 SetDistanceModelCommand, SetHrtfRendererHrirSphereResource, SetRendererCommand,
50 },
51 },
52 SelectionContainer,
53 },
54 send_sync_message,
55 utils::window_content,
56 ChangeSelectionCommand, Command, GameScene, GridBuilder, MessageDirection, Mode, Selection,
57 UserInterface,
58};
59use fyrox::gui::utils::make_dropdown_list_option;
60use std::cmp::Ordering;
61use strum::VariantNames;
62
63mod bus;
64pub mod preview;
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct AudioBusSelection {
68 pub buses: Vec<Handle<AudioBus>>,
69}
70
71impl SelectionContainer for AudioBusSelection {
72 fn len(&self) -> usize {
73 self.buses.len()
74 }
75}
76
77pub struct AudioPanel {
78 pub window: Handle<UiNode>,
79 add_bus: Handle<UiNode>,
80 remove_bus: Handle<UiNode>,
81 audio_buses: Handle<UiNode>,
82 distance_model: Handle<UiNode>,
83 renderer: Handle<UiNode>,
84 hrir_resource: Handle<UiNode>,
85}
86
87fn item_bus(item: Handle<UiNode>, ui: &UserInterface) -> Handle<AudioBus> {
88 ui.node(item).query_component::<AudioBusView>().unwrap().bus
89}
90
91fn fetch_possible_parent_buses(
92 bus: Handle<AudioBus>,
93 graph: &AudioBusGraph,
94) -> Vec<(Handle<AudioBus>, String)> {
95 let mut stack = vec![graph.primary_bus_handle()];
96 let mut result = Vec::new();
97 while let Some(other_bus) = stack.pop() {
98 let other_bus_ref = graph.try_get_bus_ref(other_bus).expect("Malformed graph!");
99 if other_bus != bus {
100 result.push((other_bus, other_bus_ref.name().to_owned()));
101 stack.extend_from_slice(other_bus_ref.children());
102 }
103 }
104 result
105}
106
107fn audio_bus_effect_names(audio_bus: &AudioBus) -> Vec<String> {
108 audio_bus
109 .effects()
110 .map(|e| AsRef::<str>::as_ref(e).to_owned())
111 .collect::<Vec<_>>()
112}
113
114impl AudioPanel {
115 pub fn new(engine: &mut Engine, sender: MessageSender) -> Self {
116 let ctx = &mut engine.user_interfaces.first_mut().build_ctx();
117
118 let add_bus;
119 let remove_bus;
120 let buses;
121 let distance_model;
122 let renderer;
123 let hrir_resource;
124 let window = WindowBuilder::new(WidgetBuilder::new().with_name("AudioPanel"))
125 .with_content(
126 GridBuilder::new(
127 WidgetBuilder::new()
128 .with_child(
129 StackPanelBuilder::new(
130 WidgetBuilder::new()
131 .on_row(0)
132 .with_child(
133 TextBuilder::new(
134 WidgetBuilder::new()
135 .with_margin(Thickness::uniform(1.0)),
136 )
137 .with_vertical_text_alignment(VerticalAlignment::Center)
138 .with_text("DM")
139 .build(ctx),
140 )
141 .with_child({
142 distance_model = DropdownListBuilder::new(
143 WidgetBuilder::new()
144 .with_tab_index(Some(0))
145 .with_margin(Thickness::uniform(1.0))
146 .with_width(130.0)
147 .with_tooltip(make_simple_tooltip(
148 ctx,
149 "Distance Model. Defines the method of \
150 calculating distance attenuation for sound \
151 sources.",
152 )),
153 )
154 .with_items(
155 DistanceModel::VARIANTS
156 .iter()
157 .map(|v| make_dropdown_list_option(ctx, v))
158 .collect::<Vec<_>>(),
159 )
160 .build(ctx);
161 distance_model
162 })
163 .with_child(
164 TextBuilder::new(
165 WidgetBuilder::new()
166 .with_margin(Thickness::uniform(1.0)),
167 )
168 .with_vertical_text_alignment(VerticalAlignment::Center)
169 .with_text("Renderer")
170 .build(ctx),
171 )
172 .with_child({
173 renderer = DropdownListBuilder::new(
174 WidgetBuilder::new()
175 .with_tab_index(Some(1))
176 .with_margin(Thickness::uniform(1.0))
177 .with_width(100.0)
178 .with_tooltip(make_simple_tooltip(ctx, "Renderer")),
179 )
180 .with_items(
181 Renderer::VARIANTS
182 .iter()
183 .map(|v| make_dropdown_list_option(ctx, v))
184 .collect::<Vec<_>>(),
185 )
186 .build(ctx);
187 renderer
188 })
189 .with_child({
190 hrir_resource =
191 ResourceFieldBuilder::<HrirSphereResourceData>::new(
192 WidgetBuilder::new().with_tab_index(Some(2)),
193 sender,
194 )
195 .build(ctx, engine.resource_manager.clone());
196 hrir_resource
197 }),
198 )
199 .with_orientation(Orientation::Horizontal)
200 .build(ctx),
201 )
202 .with_child({
203 buses = ListViewBuilder::new(
204 WidgetBuilder::new().on_row(1).with_tab_index(Some(3)),
205 )
206 .with_items_panel(
207 StackPanelBuilder::new(WidgetBuilder::new())
208 .with_orientation(Orientation::Horizontal)
209 .build(ctx),
210 )
211 .build(ctx);
212 buses
213 })
214 .with_child(
215 StackPanelBuilder::new(
216 WidgetBuilder::new()
217 .on_row(2)
218 .with_child({
219 add_bus = ButtonBuilder::new(
220 WidgetBuilder::new()
221 .with_tab_index(Some(4))
222 .with_width(100.0)
223 .with_margin(Thickness::uniform(1.0)),
224 )
225 .with_text("Add Bus")
226 .build(ctx);
227 add_bus
228 })
229 .with_child({
230 remove_bus = ButtonBuilder::new(
231 WidgetBuilder::new()
232 .with_tab_index(Some(5))
233 .with_width(100.0)
234 .with_enabled(false)
235 .with_margin(Thickness::uniform(1.0)),
236 )
237 .with_text("Remove Bus")
238 .build(ctx);
239 remove_bus
240 }),
241 )
242 .with_orientation(Orientation::Horizontal)
243 .build(ctx),
244 ),
245 )
246 .add_column(Column::stretch())
247 .add_row(Row::strict(25.0))
248 .add_row(Row::stretch())
249 .add_row(Row::strict(25.0))
250 .build(ctx),
251 )
252 .with_title(WindowTitle::text("Audio Context"))
253 .build(ctx);
254
255 Self {
256 window,
257 audio_buses: buses,
258 distance_model,
259 add_bus,
260 remove_bus,
261 renderer,
262 hrir_resource,
263 }
264 }
265
266 pub fn handle_ui_message(
267 &mut self,
268 message: &UiMessage,
269 editor_selection: &Selection,
270 sender: &MessageSender,
271 engine: &Engine,
272 ) {
273 if let Some(ButtonMessage::Click) = message.data() {
274 if message.destination() == self.add_bus {
275 sender.do_command(AddAudioBusCommand::new(AudioBus::new(
276 "AudioBus".to_string(),
277 )))
278 } else if message.destination() == self.remove_bus {
279 if let Some(selection) = editor_selection.as_audio_bus() {
280 let mut commands = vec![Command::new(ChangeSelectionCommand::new(
281 Selection::new_empty(),
282 ))];
283
284 for &bus in &selection.buses {
285 commands.push(Command::new(RemoveAudioBusCommand::new(bus)));
286 }
287
288 sender.do_command(CommandGroup::from(commands));
289 }
290 }
291 } else if let Some(ListViewMessage::SelectionChanged(selected_indices)) = message.data() {
292 if message.destination() == self.audio_buses
293 && message.direction() == MessageDirection::FromWidget
294 {
295 let ui = &engine.user_interfaces.first();
296
297 let mut selection = Vec::new();
298
299 for bus_index in selected_indices {
300 let bus = item_bus(
301 ui.node(self.audio_buses)
302 .cast::<ListView>()
303 .expect("Must be ListView")
304 .items()[*bus_index],
305 ui,
306 );
307
308 selection.push(bus);
309 }
310
311 sender.do_command(ChangeSelectionCommand::new(Selection::new(
312 AudioBusSelection { buses: selection },
313 )))
314 }
315 } else if let Some(AudioBusViewMessage::ChangeParent(new_parent)) = message.data() {
316 if message.direction() == MessageDirection::FromWidget {
317 let audio_bus_view_ref = engine
318 .user_interfaces
319 .first()
320 .node(message.destination())
321 .query_component::<AudioBusView>()
322 .unwrap();
323
324 let child = audio_bus_view_ref.bus;
325
326 sender.do_command(LinkAudioBuses {
327 child,
328 parent: *new_parent,
329 });
330 }
331 } else if let Some(DropdownListMessage::SelectionChanged(Some(index))) = message.data() {
332 if message.direction() == MessageDirection::FromWidget {
333 if message.destination() == self.renderer {
334 let renderer = match index {
335 0 => Renderer::Default,
336 1 => Renderer::HrtfRenderer(Default::default()),
337 _ => unreachable!(),
338 };
339
340 sender.do_command(SetRendererCommand::new(renderer));
341 } else if message.destination() == self.distance_model {
342 let distance_model = match index {
343 0 => DistanceModel::None,
344 1 => DistanceModel::InverseDistance,
345 2 => DistanceModel::LinearDistance,
346 3 => DistanceModel::ExponentDistance,
347 _ => unreachable!(),
348 };
349
350 sender.do_command(SetDistanceModelCommand::new(distance_model));
351 }
352 }
353 } else if let Some(ResourceFieldMessage::Value(resource)) =
354 message.data::<ResourceFieldMessage<HrirSphereResourceData>>()
355 {
356 if message.destination() == self.hrir_resource
357 && message.direction() == MessageDirection::FromWidget
358 {
359 sender.do_command(SetHrtfRendererHrirSphereResource::new(resource.clone()));
360 }
361 }
362 }
363
364 pub fn sync_to_model(
365 &mut self,
366 editor_selection: &Selection,
367 game_scene: &GameScene,
368 engine: &mut Engine,
369 ) {
370 let context_state = engine.scenes[game_scene.scene].graph.sound_context.state();
371 let ui = &mut engine.user_interfaces.first_mut();
372
373 let items = ui
374 .node(self.audio_buses)
375 .cast::<ListView>()
376 .expect("Must be ListView!")
377 .items()
378 .to_vec();
379
380 match (context_state.bus_graph_ref().len()).cmp(&items.len()) {
381 Ordering::Less => {
382 for &item in &items {
383 let bus_handle = item_bus(item, ui);
384 if context_state
385 .bus_graph_ref()
386 .buses_pair_iter()
387 .all(|(other_bus_handle, _)| other_bus_handle != bus_handle)
388 {
389 send_sync_message(
390 ui,
391 ListViewMessage::remove_item(
392 self.audio_buses,
393 MessageDirection::ToWidget,
394 item,
395 ),
396 );
397 }
398 }
399 }
400 Ordering::Greater => {
401 for (audio_bus_handle, audio_bus) in context_state.bus_graph_ref().buses_pair_iter()
402 {
403 if items.iter().all(|i| item_bus(*i, ui) != audio_bus_handle) {
404 let item = AudioBusViewBuilder::new(
405 WidgetBuilder::new()
406 .with_width(100.0)
407 .with_margin(Thickness::uniform(1.0)),
408 )
409 .with_name(audio_bus.name())
410 .with_effect_names(audio_bus_effect_names(audio_bus))
411 .with_parent_bus(audio_bus.parent())
412 .with_possible_parent_buses(fetch_possible_parent_buses(
413 audio_bus_handle,
414 context_state.bus_graph_ref(),
415 ))
416 .with_audio_bus(audio_bus_handle)
417 .build(&mut ui.build_ctx());
418
419 send_sync_message(
420 ui,
421 ListViewMessage::add_item(
422 self.audio_buses,
423 MessageDirection::ToWidget,
424 item,
425 ),
426 );
427 }
428 }
429 }
430 _ => (),
431 }
432
433 let mut selected_buses = Vec::new();
434 let mut is_primary_bus_selected = false;
435
436 if let Some(selection) = editor_selection.as_audio_bus() {
437 for (index, item) in items.into_iter().enumerate() {
438 let bus_handle = item_bus(item, ui);
439
440 if selection.buses.contains(&bus_handle) {
441 selected_buses.push(index);
442
443 if context_state.bus_graph_ref().primary_bus_handle() == bus_handle {
444 is_primary_bus_selected = true;
445 }
446
447 break;
448 }
449 }
450 }
451
452 send_sync_message(
453 ui,
454 WidgetMessage::enabled(
455 self.remove_bus,
456 MessageDirection::ToWidget,
457 !selected_buses.is_empty() && !is_primary_bus_selected,
458 ),
459 );
460
461 send_sync_message(
462 ui,
463 ListViewMessage::selection(
464 self.audio_buses,
465 MessageDirection::ToWidget,
466 selected_buses,
467 ),
468 );
469
470 for audio_bus_view in ui
471 .node(self.audio_buses)
472 .cast::<ListView>()
473 .expect("Must be ListView!")
474 .items()
475 {
476 let audio_bus_view_ref = ui
477 .node(*audio_bus_view)
478 .query_component::<AudioBusView>()
479 .unwrap();
480 send_sync_message(
481 ui,
482 AudioBusViewMessage::possible_parent_buses(
483 *audio_bus_view,
484 MessageDirection::ToWidget,
485 fetch_possible_parent_buses(
486 audio_bus_view_ref.bus,
487 context_state.bus_graph_ref(),
488 ),
489 ),
490 );
491 if let Some(audio_bus_ref) = context_state
492 .bus_graph_ref()
493 .try_get_bus_ref(audio_bus_view_ref.bus)
494 {
495 send_sync_message(
496 ui,
497 AudioBusViewMessage::effect_names(
498 *audio_bus_view,
499 MessageDirection::ToWidget,
500 audio_bus_effect_names(audio_bus_ref),
501 ),
502 );
503 send_sync_message(
504 ui,
505 AudioBusViewMessage::name(
506 *audio_bus_view,
507 MessageDirection::ToWidget,
508 audio_bus_ref.name().to_owned(),
509 ),
510 );
511 }
512 }
513
514 send_sync_message(
515 ui,
516 DropdownListMessage::selection(
517 self.distance_model,
518 MessageDirection::ToWidget,
519 Some(context_state.distance_model() as usize),
520 ),
521 );
522
523 send_sync_message(
524 ui,
525 DropdownListMessage::selection(
526 self.renderer,
527 MessageDirection::ToWidget,
528 Some(match context_state.renderer_ref() {
529 Renderer::Default => 0,
530 Renderer::HrtfRenderer(_) => 1,
531 }),
532 ),
533 );
534
535 if let Renderer::HrtfRenderer(hrtf) = context_state.renderer_ref() {
536 send_sync_message(
537 ui,
538 WidgetMessage::visibility(self.hrir_resource, MessageDirection::ToWidget, true),
539 );
540
541 send_sync_message(
542 ui,
543 ResourceFieldMessage::value(
544 self.hrir_resource,
545 MessageDirection::ToWidget,
546 hrtf.hrir_sphere_resource(),
547 ),
548 );
549 } else {
550 send_sync_message(
551 ui,
552 WidgetMessage::visibility(self.hrir_resource, MessageDirection::ToWidget, false),
553 );
554 }
555 }
556
557 pub fn on_mode_changed(&mut self, ui: &UserInterface, mode: &Mode) {
558 ui.send_message(WidgetMessage::enabled(
559 window_content(self.window, ui),
560 MessageDirection::ToWidget,
561 mode.is_edit(),
562 ));
563 }
564}