Skip to main content

danceinterpreter_rs/ui/config_window/
playlist_view.rs

1use 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}