danceinterpreter_rs/ui/config_window/
sidebar.rs1use 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 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}