Skip to main content

fyroxed_base/audio/
bus.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::fyrox::{
22    core::{
23        pool::Handle, reflect::prelude::*, type_traits::prelude::*, uuid_provider,
24        visitor::prelude::*,
25    },
26    gui::{
27        border::BorderBuilder,
28        decorator::DecoratorBuilder,
29        define_widget_deref,
30        dropdown_list::{DropdownListBuilder, DropdownListMessage},
31        grid::{Column, GridBuilder, Row},
32        list_view::{ListViewBuilder, ListViewMessage},
33        message::UiMessage,
34        text::{TextBuilder, TextMessage},
35        utils::make_simple_tooltip,
36        widget::{Widget, WidgetBuilder},
37        BuildContext, Control, HorizontalAlignment, Thickness, UiNode, UserInterface,
38        VerticalAlignment,
39    },
40    scene::sound::{AudioBus, AudioBusGraph},
41};
42use fyrox::gui::dropdown_list::DropdownList;
43use fyrox::gui::list_view::ListView;
44use fyrox::gui::message::MessageData;
45use fyrox::gui::style::resource::StyleResourceExt;
46use fyrox::gui::style::Style;
47use fyrox::gui::text::Text;
48use fyrox::gui::utils::make_dropdown_list_option;
49
50#[derive(Debug, Clone, PartialEq)]
51pub enum AudioBusViewMessage {
52    ChangeParent(Handle<AudioBus>),
53    PossibleParentBuses(Vec<(Handle<AudioBus>, String)>),
54    EffectNames(Vec<String>),
55    Name(String),
56}
57impl MessageData for AudioBusViewMessage {}
58
59#[derive(Clone, Visit, Reflect, Debug, ComponentProvider)]
60#[reflect(derived_type = "UiNode")]
61pub struct AudioBusView {
62    widget: Widget,
63    pub bus: Handle<AudioBus>,
64    parent_bus_selector: Handle<DropdownList>,
65    possible_parent_buses: Vec<Handle<AudioBus>>,
66    effect_names_list: Handle<ListView>,
67    name: Handle<Text>,
68}
69
70define_widget_deref!(AudioBusView);
71
72uuid_provider!(AudioBusView = "5439e3a9-096a-4155-922c-ed57a76a46f3");
73
74impl Control for AudioBusView {
75    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
76        self.widget.handle_routed_message(ui, message);
77
78        if let Some(msg) = message.data_for::<AudioBusViewMessage>(self.handle) {
79            match msg {
80                AudioBusViewMessage::ChangeParent(_) => {
81                    // Do nothing.
82                }
83                AudioBusViewMessage::PossibleParentBuses(buses) => {
84                    self.possible_parent_buses =
85                        buses.iter().map(|(handle, _)| *handle).collect::<Vec<_>>();
86
87                    let items = make_items(buses, &mut ui.build_ctx());
88
89                    ui.send(self.parent_bus_selector, DropdownListMessage::Items(items))
90                }
91                AudioBusViewMessage::EffectNames(names) => {
92                    let items = make_effect_names(names, &mut ui.build_ctx());
93                    ui.send(self.effect_names_list, ListViewMessage::Items(items));
94                }
95                AudioBusViewMessage::Name(new_name) => {
96                    ui.send(self.name, TextMessage::Text(new_name.clone()))
97                }
98            }
99        }
100
101        if let Some(DropdownListMessage::Selection(Some(selection))) =
102            message.data_from(self.parent_bus_selector)
103        {
104            let parent = self.possible_parent_buses[*selection];
105            ui.post(self.handle, AudioBusViewMessage::ChangeParent(parent));
106        }
107    }
108}
109
110fn make_items(buses: &[(Handle<AudioBus>, String)], ctx: &mut BuildContext) -> Vec<Handle<UiNode>> {
111    buses
112        .iter()
113        .map(|(_, name)| make_dropdown_list_option(ctx, name))
114        .collect::<Vec<_>>()
115}
116
117fn make_effect_names(names: &[String], ctx: &mut BuildContext) -> Vec<Handle<UiNode>> {
118    if names.is_empty() {
119        vec![TextBuilder::new(
120            WidgetBuilder::new().with_foreground(ctx.style.property(Style::BRUSH_LIGHTER)),
121        )
122        .with_text("No Effects")
123        .with_horizontal_text_alignment(HorizontalAlignment::Center)
124        .build(ctx)
125        .to_base()]
126    } else {
127        names
128            .iter()
129            .map(|n| {
130                TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::uniform(1.0)))
131                    .with_text(n)
132                    .with_horizontal_text_alignment(HorizontalAlignment::Center)
133                    .build(ctx)
134                    .to_base()
135            })
136            .collect::<Vec<_>>()
137    }
138}
139
140pub struct AudioBusViewBuilder {
141    widget_builder: WidgetBuilder,
142    name: String,
143    effect_names: Vec<String>,
144    bus: Handle<AudioBus>,
145    parent_bus: Handle<AudioBus>,
146    possible_parent_buses: Vec<(Handle<AudioBus>, String)>,
147}
148
149impl AudioBusViewBuilder {
150    pub fn new(widget_builder: WidgetBuilder) -> Self {
151        Self {
152            widget_builder,
153            name: AudioBusGraph::PRIMARY_BUS.to_string(),
154            effect_names: Default::default(),
155            bus: Default::default(),
156            parent_bus: Default::default(),
157            possible_parent_buses: Default::default(),
158        }
159    }
160
161    pub fn with_name<S: AsRef<str>>(mut self, name: S) -> Self {
162        name.as_ref().clone_into(&mut self.name);
163        self
164    }
165
166    pub fn with_audio_bus(mut self, bus: Handle<AudioBus>) -> Self {
167        self.bus = bus;
168        self
169    }
170
171    pub fn with_effect_names(mut self, names: Vec<String>) -> Self {
172        self.effect_names = names;
173        self
174    }
175
176    pub fn with_parent_bus(mut self, parent_bus: Handle<AudioBus>) -> Self {
177        self.parent_bus = parent_bus;
178        self
179    }
180
181    pub fn with_possible_parent_buses(
182        mut self,
183        possible_parent_buses: Vec<(Handle<AudioBus>, String)>,
184    ) -> Self {
185        self.possible_parent_buses = possible_parent_buses;
186        self
187    }
188
189    pub fn build(self, ctx: &mut BuildContext) -> Handle<AudioBusView> {
190        let effect_names_list;
191        let name;
192        let parent_bus_selector;
193        let grid = GridBuilder::new(
194            WidgetBuilder::new()
195                .with_child(
196                    BorderBuilder::new(WidgetBuilder::new().on_row(0).on_column(0).with_child({
197                        name = TextBuilder::new(
198                            WidgetBuilder::new()
199                                .with_horizontal_alignment(HorizontalAlignment::Center)
200                                .with_vertical_alignment(VerticalAlignment::Center),
201                        )
202                        .with_text(self.name)
203                        .build(ctx);
204                        name
205                    }))
206                    .build(ctx),
207                )
208                .with_child(
209                    BorderBuilder::new(
210                        WidgetBuilder::new()
211                            .on_row(1)
212                            .on_column(0)
213                            .with_margin(Thickness::uniform(1.0))
214                            .with_tooltip(make_simple_tooltip(
215                                ctx,
216                                "A list of effects applied to the audio bus.",
217                            ))
218                            .with_child({
219                                effect_names_list = ListViewBuilder::new(
220                                    WidgetBuilder::new().with_margin(Thickness::uniform(1.0)),
221                                )
222                                .with_items(make_effect_names(&self.effect_names, ctx))
223                                .build(ctx);
224                                effect_names_list
225                            }),
226                    )
227                    .build(ctx),
228                )
229                .with_child({
230                    parent_bus_selector = DropdownListBuilder::new(
231                        WidgetBuilder::new()
232                            .with_visibility(self.parent_bus.is_some())
233                            .on_row(2)
234                            .on_column(0)
235                            .with_margin(Thickness::uniform(1.0))
236                            .with_tooltip(make_simple_tooltip(
237                                ctx,
238                                "A parent audio bus to which this audio bus will send its data.",
239                            )),
240                    )
241                    .with_opt_selected(
242                        self.possible_parent_buses
243                            .iter()
244                            .position(|(h, _)| *h == self.parent_bus),
245                    )
246                    .with_items(make_items(&self.possible_parent_buses, ctx))
247                    .build(ctx);
248                    parent_bus_selector
249                }),
250        )
251        .add_row(Row::strict(25.0))
252        .add_row(Row::stretch())
253        .add_row(Row::strict(25.0))
254        .add_column(Column::stretch())
255        .build(ctx);
256
257        let view = AudioBusView {
258            widget: self
259                .widget_builder
260                .with_child(
261                    DecoratorBuilder::new(BorderBuilder::new(
262                        WidgetBuilder::new().with_child(grid),
263                    ))
264                    .build(ctx),
265                )
266                .build(ctx),
267            bus: self.bus,
268            parent_bus_selector,
269            possible_parent_buses: self
270                .possible_parent_buses
271                .into_iter()
272                .map(|(handle, _)| handle)
273                .collect::<Vec<_>>(),
274            effect_names_list,
275            name,
276        };
277        ctx.add(view)
278    }
279}
280
281#[cfg(test)]
282mod test {
283    use crate::audio::bus::AudioBusViewBuilder;
284    use fyrox::{gui::test::test_widget_deletion, gui::widget::WidgetBuilder};
285
286    #[test]
287    fn test_deletion() {
288        test_widget_deletion(|ctx| AudioBusViewBuilder::new(WidgetBuilder::new()).build(ctx));
289    }
290}