danceinterpreter_rs/ui/config_window/
playlist_view.rs1use crate::dataloading::dataprovider::{ItemChange, ItemSource, SongDataEdit};
2use crate::dataloading::songinfo::SongInfo;
3use crate::ui::config_window::ConfigWindow;
4use crate::ui::config_window::item_row::{
5 build_item_row_container_styled, empty_folder_button_style,
6};
7use crate::ui::config_window::search_bar::data_matches_search_query;
8use crate::ui::config_window::sidebar::SidebarMessage;
9use crate::ui::widget::dynamic_text_input::DynamicTextInput;
10use crate::ui::widgets::buttons::material_symbol_message_button;
11use crate::ui::widgets::{material_symbol, material_symbol_sized};
12use crate::ui::with_tooltip;
13use crate::{DanceInterpreter, DialogMessage, DummySongMessage, Message};
14use iced::alignment::Vertical;
15use iced::widget::{
16 Column, Row, Scrollable, Space, button, column as col, container, row, scrollable, text,
17};
18use iced::{Alignment, Element, Length};
19use std::sync::LazyLock;
20
21pub static PLAYLIST_SCROLLABLE_ID: LazyLock<iced::widget::Id> =
22 LazyLock::new(iced::widget::Id::unique);
23
24pub fn build<'a>(
25 config_window: &'a ConfigWindow,
26 dance_interpreter: &'a DanceInterpreter,
27) -> Column<'a, Message> {
28 if dance_interpreter.data_provider.playlist().is_empty() {
29 return build_empty_playlist_view(dance_interpreter);
30 }
31
32 let mut header_items: Vec<Element<'_, Message>> = vec![
33 text!("#").width(Length::Fixed(24.0)).into(),
34 text!("Title").width(Length::Fill).into(),
35 text!("Artist").width(Length::Fill).into(),
36 text!("Dance").width(Length::Fill).into(),
37 Space::new()
38 .width(Length::Fill)
39 .height(Length::Shrink)
40 .into(),
41 ];
42
43 if dance_interpreter
44 .data_provider
45 .traktor_provider
46 .get_song_info()
47 .is_some()
48 {
49 header_items.push(
50 button(row![material_symbol("add", false), text("Add Traktor")].spacing(5))
51 .on_press(Message::AddTraktorSong)
52 .padding([5, 10])
53 .style(button::primary)
54 .into(),
55 );
56 } else {
57 header_items.push(
58 Space::new()
59 .width(Length::Fixed(10.0))
60 .height(Length::Shrink)
61 .into(),
62 );
63 }
64
65 let trow = container(
66 Row::with_children(header_items)
67 .spacing(5)
68 .align_y(Vertical::Bottom),
69 )
70 .padding([4, 6])
71 .width(Length::Fill);
72
73 let mut playlist_column: Column<'_, _, _, _> = col!();
74
75 for (i, item) in dance_interpreter
76 .data_provider
77 .playlist()
78 .iter()
79 .enumerate()
80 {
81 let song = &item.song;
82 let is_match = data_matches_search_query(&config_window.search_query, song.into());
83 let song_row = build_song_row(dance_interpreter, song, i);
84 let accent_color = dance_interpreter.data_provider.get_dance_color(&song.dance);
85 let sr_container = build_item_row_container_styled(song_row, is_match, i, accent_color);
86 playlist_column = playlist_column.push(sr_container);
87 }
88
89 let dummy_row = row![
90 text!("*").width(Length::Fixed(24.0)),
91 iced::widget::text_input("Title...", &config_window.dummy_song_title)
92 .width(Length::Fill)
93 .on_input(|v| Message::DummySong(DummySongMessage::UpdateTitle(v)))
94 .on_submit(Message::DummySong(DummySongMessage::Submit)),
95 iced::widget::text_input("Artist...", &config_window.dummy_song_artist)
96 .width(Length::Fill)
97 .on_input(|v| Message::DummySong(DummySongMessage::UpdateArtist(v)))
98 .on_submit(Message::DummySong(DummySongMessage::Submit)),
99 iced::widget::text_input("Dance...", &config_window.dummy_song_dance)
100 .width(Length::Fill)
101 .on_input(|v| Message::DummySong(DummySongMessage::UpdateDance(v)))
102 .on_submit(Message::DummySong(DummySongMessage::Submit)),
103 Space::new().width(Length::Fill).height(Length::Shrink),
104 Space::new()
105 .width(Length::Fixed(10.0))
106 .height(Length::Shrink),
107 ]
108 .spacing(5)
109 .align_y(Alignment::Center);
110
111 playlist_column = playlist_column.push(
112 container(row![
113 container(Space::new().width(4).height(Length::Fill)),
114 dummy_row,
115 ])
116 .padding([4, 6])
117 .width(Length::Fill),
118 );
119
120 let playlist_scrollable: Scrollable<'_, Message> = scrollable(playlist_column)
121 .width(Length::Fill)
122 .height(Length::Fill)
123 .spacing(5)
124 .id(PLAYLIST_SCROLLABLE_ID.clone());
125
126 col!(trow, playlist_scrollable).spacing(5)
127}
128
129pub fn build_empty_playlist_view(dance_interpreter: &DanceInterpreter) -> Column<'_, Message> {
130 let playlist_col = col![
131 text("Load Playlist").size(20),
132 button(
133 col![
134 material_symbol_sized("folder_open", false, 64),
135 text("Open playlist file").size(16)
136 ]
137 .align_x(Alignment::Center)
138 )
139 .style(empty_folder_button_style)
140 .on_press(Message::OpenPlaylist)
141 .padding(20)
142 ]
143 .spacing(20)
144 .align_x(Alignment::Center);
145
146 let traktor_col = if dance_interpreter
147 .data_provider
148 .traktor_provider
149 .get_song_info()
150 .is_some()
151 {
152 col![
153 text("Song Playing!").size(20),
154 button(
155 col![
156 material_symbol_sized("add", false, 64),
157 text("Add Traktor Song").size(16)
158 ]
159 .align_x(Alignment::Center)
160 )
161 .style(empty_folder_button_style)
162 .on_press(Message::AddTraktorSong)
163 .padding(20)
164 ]
165 .spacing(20)
166 .align_x(Alignment::Center)
167 } else {
168 col![
169 text("Use Traktor").size(20),
170 button(
171 col![
172 material_symbol_sized("agriculture", false, 64),
173 text("Open the sidebar").size(16)
174 ]
175 .align_x(Alignment::Center)
176 )
177 .style(empty_folder_button_style)
178 .on_press(Message::Sidebar(SidebarMessage::Toggle))
179 .padding(20)
180 ]
181 .spacing(20)
182 .align_x(Alignment::Center)
183 };
184
185 let empty_state_column = col![
186 text("No playlist loaded.").size(24),
187 row![playlist_col, traktor_col]
188 .spacing(40)
189 .align_y(Alignment::Center)
190 ]
191 .spacing(40)
192 .align_x(Alignment::Center);
193
194 col![
195 Space::new().height(Length::Fill),
196 empty_state_column,
197 Space::new().height(Length::Fill)
198 ]
199 .width(Length::Fill)
200 .height(Length::Fill)
201 .align_x(Alignment::Center)
202}
203
204pub fn build_song_row<'a>(
205 dance_interpreter: &DanceInterpreter,
206 song: &SongInfo,
207 idx: usize,
208) -> Row<'a, Message> {
209 let (is_current, is_next, is_traktor, is_played) =
210 dance_interpreter.data_provider.get_play_state(idx);
211 let icon: Element<Message> = if is_traktor {
212 material_symbol("agriculture", false)
213 .width(Length::Fixed(24.0))
214 .into()
215 } else if is_current {
216 material_symbol("play_arrow", false)
217 .width(Length::Fixed(24.0))
218 .into()
219 } else if is_next {
220 material_symbol("skip_next", false)
221 .width(Length::Fixed(24.0))
222 .into()
223 } else if is_played {
224 material_symbol("check", false)
225 .width(Length::Fixed(24.0))
226 .into()
227 } else {
228 Space::new()
229 .width(Length::Fixed(24.0))
230 .height(Length::Shrink)
231 .into()
232 };
233
234 row![
235 icon,
236 DynamicTextInput::<'_, Message>::new("Title", &song.title)
237 .width(Length::Fill)
238 .on_change(move |v| Message::SongDataEdit(idx, SongDataEdit::Title(v))),
239 DynamicTextInput::<'_, Message>::new("Artist", &song.artist)
240 .width(Length::Fill)
241 .on_change(move |v| Message::SongDataEdit(idx, SongDataEdit::Artist(v))),
242 DynamicTextInput::<'_, Message>::new("Dance", &song.dance)
243 .width(Length::Fill)
244 .on_change(move |v| Message::SongDataEdit(idx, SongDataEdit::Dance(v)))
245 .on_submit(Message::SubmitPlaylistDance(idx)),
246 row![
247 Space::new().width(Length::Fill).height(Length::Shrink),
248 with_tooltip(
249 material_symbol_message_button(
250 "smart_display",
251 false,
252 Message::ItemChanged(ItemChange::PlaylistAbsolute(idx))
253 ),
254 "Show now"
255 ),
256 with_tooltip(
257 material_symbol_message_button(
258 "queue_play_next",
259 false,
260 Message::SetNextItem(ItemSource::Playlist(idx))
261 ),
262 "Set as next song"
263 ),
264 with_tooltip(
265 material_symbol_message_button(
266 "delete",
267 false,
268 Message::Dialog(DialogMessage::RequestDelete(ItemSource::Playlist(idx)))
269 ),
270 "Delete song"
271 ),
272 ]
273 .spacing(5)
274 .align_y(Alignment::Center)
275 .width(Length::Fill),
276 ]
277 .align_y(Alignment::Center)
278}