Skip to main content

danceinterpreter_rs/ui/config_window/
sidebar.rs

1use crate::dataloading::dataprovider::DataProvider;
2use crate::traktor_api::{
3    ConnectionState, TRAKTOR_SERVER_DEFAULT_ADDR, TraktorMessage, TraktorNextMode, TraktorSyncMode,
4};
5use crate::ui::widget::canvas_toggle::CanvasToggle;
6use crate::ui::widget::suggestion_text_input::SuggestionTextInput;
7use crate::ui::widget::{power_button, restart_button, suggestion_text_input};
8use crate::ui::widgets::labeled_message_toggler;
9use crate::ui::with_tooltip;
10use crate::{DanceInterpreter, Message};
11use iced::alignment::Vertical;
12use iced::widget::{
13    Column, Container, Space, canvas, column as col, container, pick_list, row, text,
14};
15use iced::{Alignment, Animation, Length, animation, border};
16use network_interface::Addr::V4;
17use network_interface::{NetworkInterface, NetworkInterfaceConfig};
18use std::time::Duration;
19
20pub struct Sidebar {
21    pub state: Animation<bool>,
22    pub power_button_cache: canvas::Cache,
23    pub restart_button_cache: canvas::Cache,
24    server_address_presets: suggestion_text_input::State<String>,
25    pub server_address_text: String,
26}
27
28#[derive(Debug, Clone)]
29pub enum SidebarMessage {
30    Toggle,
31    UpdateAddressPresets,
32}
33
34impl Sidebar {
35    pub fn new() -> Self {
36        Self {
37            state: Animation::new(false)
38                .duration(Duration::from_millis(100))
39                .easing(animation::Easing::EaseInOut),
40            power_button_cache: canvas::Cache::default(),
41            restart_button_cache: canvas::Cache::default(),
42            server_address_presets: suggestion_text_input::State::default(),
43            server_address_text: String::new(),
44        }
45    }
46
47    pub fn build<'a>(&'a self, dance_interpreter: &'a DanceInterpreter) -> Container<'a, Message> {
48        let sync_options = vec![
49            TraktorSyncMode::Relative,
50            TraktorSyncMode::AbsoluteByNumber,
51            TraktorSyncMode::AbsoluteByName,
52        ];
53
54        let next_options = vec![
55            TraktorNextMode::DeckByPosition,
56            TraktorNextMode::DeckByNumber,
57            TraktorNextMode::PlaylistByNumber,
58            TraktorNextMode::PlaylistByName,
59        ];
60
61        container(
62            col![
63                text("Server Settings").size(24),
64                row![
65                    col![
66                        with_tooltip(
67                            CanvasToggle::new(
68                                dance_interpreter.data_provider.traktor_provider.enabled(),
69                                &self.power_button_cache
70                            )
71                            .on_toggle(|b| Message::Traktor(TraktorMessage::EnableServer(b)))
72                            .on_draw(power_button::draw),
73                            "Toggle Traktor Server"
74                        ),
75                        text("Enable Server")
76                    ]
77                    .align_x(Alignment::Center),
78                    col![
79                        with_tooltip(
80                            CanvasToggle::new(
81                                dance_interpreter.data_provider.traktor_provider.enabled(),
82                                &self.restart_button_cache
83                            )
84                            .on_toggle(|_| Message::Traktor(TraktorMessage::Reconnect))
85                            .on_draw(restart_button::draw),
86                            "Restart Traktor Server"
87                        ),
88                        text("Restart Server")
89                    ]
90                    .align_x(Alignment::Center)
91                ]
92                .spacing(10),
93                Self::build_client_status(dance_interpreter),
94                col![
95                    text("Server Address: "),
96                    self.build_network_interface_combo_box(dance_interpreter)
97                ],
98                labeled_message_toggler(
99                    "Enable Debug Logging",
100                    dance_interpreter
101                        .data_provider
102                        .traktor_provider
103                        .debug_logging,
104                    |b| Message::Traktor(TraktorMessage::EnableDebugLogging(b))
105                ),
106                labeled_message_toggler(
107                    "Enable Sync",
108                    dance_interpreter.data_provider.traktor_provider.sync,
109                    |b| Message::Traktor(TraktorMessage::EnableSync(b)),
110                ),
111                col![
112                    text("Sync Mode"),
113                    pick_list(
114                        sync_options.clone(),
115                        Some(dance_interpreter.data_provider.traktor_provider.sync_mode),
116                        |m| Message::Traktor(TraktorMessage::SetSyncMode(m))
117                    )
118                    .width(Length::Fill)
119                ]
120                .align_x(Alignment::Center),
121                col![
122                    text("Next Song Mode"),
123                    pick_list(
124                        next_options.clone(),
125                        Some(dance_interpreter.data_provider.traktor_provider.next_mode),
126                        |m| Message::Traktor(TraktorMessage::SetNextMode(m))
127                    )
128                    .width(Length::Fill)
129                ]
130                .align_x(Alignment::Center),
131                col![
132                    text("Next Song Mode (Fallback)"),
133                    pick_list(
134                        next_options.clone(),
135                        Some(
136                            dance_interpreter
137                                .data_provider
138                                .traktor_provider
139                                .next_mode_fallback
140                        ),
141                        |m| Message::Traktor(TraktorMessage::SetNextModeFallback(m))
142                    )
143                    .width(Length::Fill)
144                ]
145                .align_x(Alignment::Center)
146            ]
147            .align_x(Alignment::Center)
148            .spacing(10)
149            .padding(10),
150        )
151        .height(Length::Fill)
152        .style(|t| {
153            container::Style::default().background(t.extended_palette().background.weakest.color)
154        })
155        .align_y(Vertical::Top)
156    }
157
158    pub fn update_network_interface_selection(&mut self, data_provider: &DataProvider) {
159        let mut detected_interfaces: Vec<String> = get_formatted_network_interfaces(data_provider)
160            .into_iter()
161            .map(|(_, _, formatted)| formatted)
162            .collect();
163        detected_interfaces.push(TRAKTOR_SERVER_DEFAULT_ADDR.to_owned());
164        detected_interfaces.sort();
165
166        self.server_address_presets = suggestion_text_input::State::with_selection(
167            detected_interfaces,
168            Some(&data_provider.traktor_provider.address.clone()),
169        );
170    }
171
172    fn build_network_interface_combo_box(
173        &'_ self,
174        dance_interpreter: &DanceInterpreter,
175    ) -> SuggestionTextInput<'_, String, Message> {
176        SuggestionTextInput::new(
177            &self.server_address_presets,
178            if !self.server_address_text.is_empty() {
179                self.server_address_text.as_ref()
180            } else {
181                TRAKTOR_SERVER_DEFAULT_ADDR
182            },
183            Some(&dance_interpreter.data_provider.traktor_provider.address),
184            |s| Message::Traktor(TraktorMessage::ChangeAndSubmitAddress(s)),
185        )
186        .on_open(Message::Sidebar(SidebarMessage::UpdateAddressPresets))
187        .on_option_hovered(|s| Message::Traktor(TraktorMessage::ChangeAddress(s)))
188        .on_input(|s| Message::Traktor(TraktorMessage::ChangeAddress(s)))
189        .on_close(Message::Traktor(TraktorMessage::SubmitAddress))
190    }
191
192    /// A small LED + label showing whether a Traktor client is
193    /// connected, listing client's IP available.
194    fn build_client_status<'a>(dance_interpreter: &DanceInterpreter) -> Column<'a, Message> {
195        let client_connection_state = dance_interpreter
196            .data_provider
197            .traktor_provider
198            .get_connection_state();
199
200        let led = container(Space::new())
201            .width(Length::Fixed(12.0))
202            .height(Length::Fixed(12.0))
203            .style(move |t: &iced::Theme| {
204                let palette = t.extended_palette();
205                let color = match client_connection_state {
206                    ConnectionState::Connected => palette.success.base.color,
207                    ConnectionState::CoverLoader | ConnectionState::Traktor => {
208                        palette.warning.base.color
209                    }
210                    ConnectionState::Disconnected => palette.background.strong.color,
211                };
212                container::Style {
213                    background: Some(color.into()),
214                    border: border::rounded(6.0),
215                    ..container::Style::default()
216                }
217            });
218
219        let summary = match client_connection_state {
220            ConnectionState::Connected => "Traktor & Cover Loader connected",
221            ConnectionState::Traktor => "Traktor connected",
222            ConnectionState::CoverLoader => "Cover Loader connected",
223            ConnectionState::Disconnected => "No client connected",
224        };
225
226        let mut column = col![
227            row![led, text(summary)]
228                .spacing(8)
229                .align_y(Alignment::Center)
230        ]
231        .spacing(4)
232        .width(Length::Fill)
233        .align_x(Alignment::Center);
234
235        let label = dance_interpreter
236            .data_provider
237            .traktor_provider
238            .cover_loader_addr
239            .map(|addr| addr.ip().to_string());
240
241        if let Some(label) = label {
242            column = column.push(text(label).size(12));
243        }
244
245        column
246    }
247}
248
249fn get_network_interfaces() -> Vec<(String, String)> {
250    let mut interfaces = vec![("any".to_owned(), "0.0.0.0".to_owned())];
251
252    if let Ok(network_interfaces) = NetworkInterface::show() {
253        for i in network_interfaces {
254            for addr in i.addr {
255                let V4(ipv4_addr) = addr else {
256                    continue;
257                };
258
259                interfaces.push((i.name.clone(), ipv4_addr.ip.to_string()));
260            }
261        }
262    }
263
264    interfaces
265}
266
267fn get_formatted_network_interfaces(
268    data_provider: &'_ DataProvider,
269) -> Vec<(String, String, String)> {
270    let interfaces = get_network_interfaces();
271
272    let original_addr = data_provider
273        .traktor_provider
274        .get_socket_addr()
275        .unwrap_or(TRAKTOR_SERVER_DEFAULT_ADDR.parse().unwrap());
276    let original_port = original_addr.port();
277
278    interfaces
279        .into_iter()
280        .map(|(name, addr)| (name, addr.clone(), format!("{}:{}", addr, original_port)))
281        .collect()
282}