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