mod dataloading;
mod macros;
mod ui;
use crate::dataloading::dataprovider::song_data_provider::{SongChange, SongDataEdit, SongDataProvider, SongDataSource};
use crate::dataloading::id3tagreader::read_song_info_from_filepath;
use crate::dataloading::m3uloader::load_tag_data_from_m3u;
use crate::dataloading::songinfo::SongInfo;
use crate::ui::config_window::ConfigWindow;
use crate::ui::song_window::SongWindow;
use iced::advanced::graphics::image::image_rs::ImageFormat;
use iced::keyboard::key::Named;
use iced::keyboard::{on_key_press, Key, Modifiers};
use iced::widget::horizontal_space;
use iced::window::icon::from_file_data;
use iced::{exit, window, Element, Size, Subscription, Task, Theme};
use rfd::FileDialog;
use std::path::PathBuf;
fn main() -> iced::Result {
iced::daemon(
DanceInterpreter::title,
DanceInterpreter::update,
DanceInterpreter::view,
)
.theme(DanceInterpreter::theme)
.subscription(DanceInterpreter::subscription)
.run_with(DanceInterpreter::new)
}
pub trait Window {
fn on_create(&mut self, id: window::Id);
fn on_resize(&mut self, size: Size);
}
#[derive(Default)]
struct DanceInterpreter {
config_window: ConfigWindow,
song_window: SongWindow,
data_provider: SongDataProvider,
}
#[derive(Debug, Clone)]
pub enum Message {
Noop,
WindowOpened(window::Id),
WindowResized((window::Id, Size)),
WindowClosed(window::Id),
ToggleFullscreen,
SetFullscreen(bool),
OpenPlaylist,
AddSong(SongInfo),
FileDropped(PathBuf),
SongChanged(SongChange),
SongDataEdit(usize, SongDataEdit),
SetNextSong(SongDataSource),
EnableImage(bool),
EnableNextDance(bool),
}
impl DanceInterpreter {
pub fn new() -> (Self, Task<Message>) {
let mut tasks = Vec::new();
let icon = from_file_data(match dark_light::detect().unwrap_or(dark_light::Mode::Unspecified) {
dark_light::Mode::Dark => include_bytes!(res_file!("icon_dark.png")),
_ => include_bytes!(res_file!("icon_light.png")),
}, Some(ImageFormat::Png)).ok();
let (config_window, cw_opened) = Self::open_window(window::Settings {
platform_specific: Self::get_platform_specific(),
icon: icon.clone(),
..Default::default()
});
let (song_window, sw_opened) = Self::open_window(window::Settings {
platform_specific: Self::get_platform_specific(),
icon: icon.clone(),
..Default::default()
});
let state = Self {
config_window,
song_window,
..Self::default()
};
tasks.push(cw_opened);
tasks.push(sw_opened);
(state, Task::batch(tasks))
}
fn open_window<T: Window + Default>(settings: window::Settings) -> (T, Task<Message>) {
let (id, open) = window::open(settings);
let mut window = T::default();
window.on_create(id);
(window, open.map(Message::WindowOpened))
}
fn get_platform_specific() -> window::settings::PlatformSpecific {
#[cfg(target_os = "linux")]
return window::settings::PlatformSpecific {
application_id: "danceinterpreter".to_string(),
..Default::default()
};
#[cfg(not(target_os = "linux"))]
return Default::default();
}
pub fn title(&self, window_id: window::Id) -> String {
if self.config_window.id == Some(window_id) {
"Config Window".to_string()
} else if self.song_window.id == Some(window_id) {
"Song Window".to_string()
} else {
String::new()
}
}
pub fn view(&self, window_id: window::Id) -> Element<'_, Message> {
if self.config_window.id == Some(window_id) {
self.config_window.view(self)
} else if self.song_window.id == Some(window_id) {
self.song_window.view(self)
} else {
horizontal_space().into()
}
}
pub fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::WindowOpened(_) => ().into(),
Message::WindowResized((window_id, size)) => {
if self.config_window.id == Some(window_id) {
self.config_window.on_resize(size);
} else if self.song_window.id == Some(window_id) {
self.song_window.on_resize(size);
}
().into()
}
Message::WindowClosed(window_id) => {
if self.config_window.id == Some(window_id) {
self.config_window.id = None;
match self.song_window.id {
Some(window_id) => window::close(window_id),
None => exit(),
}
} else if self.song_window.id == Some(window_id) {
self.song_window.id = None;
match self.config_window.id {
Some(window_id) => window::close(window_id),
None => exit(),
}
} else {
().into()
}
}
Message::ToggleFullscreen => {
let Some(song_window_id) = self.song_window.id else {
return ().into();
};
window::get_mode(song_window_id)
.map(|mode| Message::SetFullscreen(mode != window::Mode::Fullscreen))
}
Message::SetFullscreen(fullscreen) => {
let Some(song_window_id) = self.song_window.id else {
return ().into();
};
window::change_mode(
song_window_id,
if fullscreen {
window::Mode::Fullscreen
} else {
window::Mode::Windowed
},
)
}
Message::OpenPlaylist => {
let file = FileDialog::new()
.add_filter("Playlist", &["m3u", "m3u8"])
.add_filter("Any(*)", &["*"])
.set_title("Select playlist file")
.set_directory(
dirs::audio_dir().unwrap_or(dirs::home_dir().unwrap_or(PathBuf::from("."))),
)
.pick_file();
let Some(file) = file else {
return ().into();
};
println!("Selected file: {:?}", file);
let Ok(playlist) = load_tag_data_from_m3u(&file) else {
return ().into();
};
self.data_provider.set_vec(playlist);
().into()
}
Message::FileDropped(path) => {
if let Ok(playlist) = load_tag_data_from_m3u(&path) {
self.data_provider.set_vec(playlist);
} else if let Ok(song_info) = read_song_info_from_filepath(&path) {
self.data_provider.append_song(song_info);
}
().into()
}
Message::SongChanged(song_change) => {
self.data_provider.handle_song_change(song_change);
().into()
}
Message::SongDataEdit(i, edit) => {
self.data_provider.handle_song_data_edit(i, edit);
().into()
}
Message::AddSong(song) => {
self.data_provider.append_song(song);
().into()
}
Message::SetNextSong(i) => {
self.data_provider.set_next(i);
().into()
}
Message::EnableImage(state) => {
self.song_window.enable_image = state;
().into()
}
Message::EnableNextDance(state) => {
self.song_window.enable_next_dance = state;
().into()
}
_ => ().into(),
}
}
fn theme(&self, window_id: window::Id) -> Theme {
if self.song_window.id.is_some_and(|id| id == window_id) {
Theme::Dark
} else {
Theme::default()
}
}
pub fn subscription(&self) -> Subscription<Message> {
Subscription::batch([
window::close_events().map(Message::WindowClosed),
window::resize_events().map(Message::WindowResized),
window::events().map(|(_, event)| match event {
window::Event::FileDropped(path) => Message::FileDropped(path),
_ => Message::Noop,
}),
on_key_press(|key: Key, _modifiers: Modifiers| match key {
Key::Named(Named::ArrowRight) | Key::Named(Named::Space) => {
Some(Message::SongChanged(SongChange::Next))
}
Key::Named(Named::ArrowLeft) => Some(Message::SongChanged(SongChange::Previous)),
Key::Named(Named::End) => Some(Message::SongChanged(SongChange::StaticAbsolute(0))),
Key::Named(Named::F11) => Some(Message::ToggleFullscreen),
_ => None,
}),
])
}
}