Skip to main content

danceinterpreter_rs/ui/config_window/
sidebar.rs

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