diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..a1c71f5 --- /dev/null +++ "b/\\" @@ -0,0 +1,31 @@ +use std::collections::HashMap; + +use super::Gui; + +mod song_edit; + +trait Window { + fn show(&mut self, gui: &mut Gui, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()>; + fn name(&self) -> &'static str; +} + +pub enum WindowIndex { + SongEdit +} + + +pub struct WindowManager { + windows: HashMap> +} + +impl WindowManager { + pub fn new() -> Self { + let mut windows = HashMap::new(); + windows.instert(WindowIndex::SongEdit, Box::::default()); + Self { + windows: HashMap::from_iter([ + (WindowIndex::SongEdit, Box::::default()) + ]) + } + } +} diff --git a/src/commands/gui/song_edit_window.rs b/src/commands/gui/song_edit_window.rs deleted file mode 100644 index 1dac2fd..0000000 --- a/src/commands/gui/song_edit_window.rs +++ /dev/null @@ -1,223 +0,0 @@ -use egui::{Color32, Label, RichText}; - - -use crate::manifest::song::{Song, SongType}; - -use super::Gui; - -#[derive(Debug, Default)] -pub struct GuiSongEditor { - pub is_open: bool, - pub song: (String, String), - pub ed_url: String, - pub ed_name: String, -} - -#[derive(Debug, Default)] -pub struct GuiNewSong { - pub is_open: bool, - ed_type: SongType, - ed_name: String, - ed_playlist: Option, - ed_url: String, -} - -#[derive(Debug, Default)] -pub struct GuiImportPlaylist { - pub is_open: bool, - ed_name: String, - ed_url: String, -} - -#[derive(Debug, Default)] -pub struct GuiError { - pub is_open: bool, - pub text: String, -} - -impl Gui { - pub fn draw_song_edit_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - let mut save = false; - - let (playlist, song_name) = self.song_edit_w.song.clone(); - - let Some(song) = self.manifest.get_song(&playlist, &song_name) else { - return; - }; - let song = song.clone(); - - egui::Window::new("Song editor") - .open(&mut self.song_edit_w.is_open) - .show(ctx, - |ui| { - - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("["); - ui.hyperlink_to("link", song.get_url().unwrap()); - ui.label("] "); - ui.colored_label(Color32::LIGHT_BLUE, &playlist); - ui.label(": "); - ui.label(&song_name) - }); - - ui.horizontal(|ui| { - ui.label("Type: "); - ui.label(&song.get_type().to_string()); - }); - - ui.horizontal(|ui| { - ui.label("Name: "); - ui.text_edit_singleline(&mut self.song_edit_w.ed_name); - }); - ui.horizontal(|ui| { - ui.label("Url: "); - ui.text_edit_singleline(&mut self.song_edit_w.ed_url); - }); - - if ui.button("Save").clicked() { - save = true; - } - }); - - if save { - { - let Some(song) = self.manifest.get_song_mut(&playlist, &song_name) else { - return; - }; - - *song.get_url_str_mut() = self.song_edit_w.ed_url.clone(); - } - - let Some(playlist) = self.manifest.get_playlist_mut(&playlist) else { - return; - }; - - - playlist.remove_song(&song_name); - playlist.add_song(self.song_edit_w.ed_name.clone(), song); - self.song_edit_w.is_open = false; - let _ = self.manifest.save(None); - } - } - - pub fn draw_new_song_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - let mut save = false; - egui::Window::new("New song") - .open(&mut self.new_song_w.is_open) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.label("Type: "); - egui::ComboBox::from_id_source("new_song_window_type") - .selected_text(format!("{:?}", self.new_song_w.ed_type)) - .show_ui(ui, |ui| { - ui.selectable_value(&mut self.new_song_w.ed_type, SongType::Youtube, "Youtube"); - ui.selectable_value(&mut self.new_song_w.ed_type, SongType::Spotify, "Spotify"); - ui.selectable_value(&mut self.new_song_w.ed_type, SongType::Soundcloud, "Soundcloud"); - } - ); - }); - - ui.horizontal(|ui| { - ui.label("Name: "); - ui.text_edit_singleline(&mut self.new_song_w.ed_name); - }); - ui.horizontal(|ui| { - ui.label("Playlist: "); - egui::ComboBox::from_id_source("new_song_window_playlist") - .selected_text(format!("{}", self.new_song_w.ed_playlist.clone().unwrap_or("".to_string()))) - .show_ui(ui, |ui| { - for p in self.manifest.get_playlists().keys() { - ui.selectable_value(&mut self.new_song_w.ed_playlist, Option::Some(p.clone()), p.as_str()); - } - } - ); - }); - ui.horizontal(|ui| { - ui.label("Url: "); - ui.text_edit_singleline(&mut self.new_song_w.ed_url); - }); - - if ui.button("Save").clicked() { - save = true; - } - }); - - if save { - let Some(playlist) = self.manifest.get_playlist_mut(&self.new_song_w.ed_playlist.clone().unwrap()) else { - panic!("couldnt find playlist from a preset playlist list????????????"); - }; - - playlist.add_song( - self.new_song_w.ed_name.clone(), - Song::from_url_str(self.new_song_w.ed_url.clone()).unwrap().set_type(self.new_song_w.ed_type.clone()).clone() - ); - - - let _ = self.manifest.save(None); - self.new_song_w.is_open = false; - } - } - - pub fn draw_import_playlist_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - let mut save = false; - egui::Window::new("Import Playlist") - .open(&mut self.import_playlist_w.is_open) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.label("Type: Youtube"); - }); - - ui.horizontal(|ui| { - ui.label("Name: "); - ui.text_edit_singleline(&mut self.import_playlist_w.ed_name); - }); - ui.horizontal(|ui| { - ui.label("Url: "); - ui.text_edit_singleline(&mut self.import_playlist_w.ed_url); - }); - - if ui.button("Import").clicked() { - save = true; - } - }); - - if save { - let name = self.import_playlist_w.ed_name.clone(); - let url = self.import_playlist_w.ed_url.clone(); - - if self.manifest.get_playlist(&name).is_some() { - log::error!("Playlist {name} already exists"); - self.throw_error(format!("Playlist {name} already exists")); - } - - let songs = self.downloader.download_playlist_nb(&self.cfg, &url, &name, &self.manifest.get_format()).unwrap(); - self.manifest.add_playlist(name.clone()); - - let playlist = self.manifest.get_playlist_mut(&name).expect("Unreachable"); - - for (sname, song) in songs { - log::info!("Added: {sname}"); - playlist.add_song(sname, song); - } - let _ = self.manifest.save(None); - self.import_playlist_w.is_open = false; - } - } - - pub fn draw_error_window(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { - egui::Window::new("ERROR!!!! D:") - .open(&mut self.error_w.is_open) - .show(ctx, |ui| { - ui.vertical(|ui| { - ui.label(RichText::new("Error:").size(30.0).color(Color32::RED)); - ui.horizontal(|ui| { - ui.add(Label::new(self.error_w.text.clone()).wrap(true)); - }) - }) - }); - - } - - -} diff --git a/src/downloader.rs b/src/downloader.rs index c039778..30dfd6a 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -18,7 +18,7 @@ lazy_static!( static ref PROCESSES: Mutex>> = Mutex::new(RwLock::new(HashMap::new())); ); -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Downloader { count: usize, nb_initial_song_count: usize, diff --git a/src/main.rs b/src/main.rs index 5d65f30..e2f71d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(downcast_unchecked)] + use config::ConfigWrapper; @@ -6,11 +8,11 @@ mod manifest; mod logger; mod downloader; mod util; -mod commands; mod prompt; mod config; mod constants; mod process_manager; +mod ui; #[tokio::main] async fn main() { @@ -27,5 +29,5 @@ async fn main() { }; - let _ = commands::command_run(&cfg, &mut manifest).await; + let _ = ui::cli::command_run(&cfg, &mut manifest).await; } diff --git a/src/commands/add.rs b/src/ui/cli/add.rs similarity index 100% rename from src/commands/add.rs rename to src/ui/cli/add.rs diff --git a/src/commands/mod.rs b/src/ui/cli/mod.rs similarity index 94% rename from src/commands/mod.rs rename to src/ui/cli/mod.rs index 4101391..6c975ad 100644 --- a/src/commands/mod.rs +++ b/src/ui/cli/mod.rs @@ -1,7 +1,6 @@ mod add; -pub mod gui; -use crate::{config::{cli::CliCommand, ConfigWrapper}, downloader::Downloader, manifest::Manifest}; +use crate::{config::{cli::CliCommand, ConfigWrapper}, downloader::Downloader, manifest::Manifest, ui::gui}; diff --git a/src/ui/gui/components/mod.rs b/src/ui/gui/components/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/commands/gui/mod.rs b/src/ui/gui/mod.rs similarity index 90% rename from src/commands/gui/mod.rs rename to src/ui/gui/mod.rs index 2c2dee7..53c9232 100644 --- a/src/commands/gui/mod.rs +++ b/src/ui/gui/mod.rs @@ -1,21 +1,18 @@ mod nav_bar; -mod song_edit_window; +mod windows; +mod components; use egui::{Color32, RichText}; use egui_extras::{Column, TableBuilder}; -use song_edit_window::{GuiError, GuiImportPlaylist, GuiNewSong}; +use windows::{State, WindowIndex, WindowManager}; use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::{Song, SongType}, Manifest}}; -use self::song_edit_window::GuiSongEditor; #[derive(Debug, Default)] pub struct Gui { + windows: WindowManager, manifest: Manifest, - song_edit_w: GuiSongEditor, - new_song_w: GuiNewSong, - import_playlist_w: GuiImportPlaylist, - error_w: GuiError, filter: String, downloader: Downloader, cfg: ConfigWrapper, @@ -28,6 +25,7 @@ impl Gui { manifest, downloader, cfg, + windows: windows::WindowManager::new(), ..Default::default() } } @@ -56,18 +54,26 @@ impl Gui { Ok(()) } pub fn throw_error(&mut self, text: impl ToString) { - self.error_w.is_open = true; - self.error_w.text = text.to_string(); + let w = self.windows.get_window::(WindowIndex::Error); + w.set_error_message(text); + self.windows.open(WindowIndex::Error, true); } } impl eframe::App for Gui { fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { self.draw_nav(ctx, frame); - self.draw_song_edit_window(ctx, frame); - self.draw_new_song_window(ctx, frame); - self.draw_import_playlist_window(ctx, frame); - self.draw_error_window(ctx, frame); + { + let mut state = State { + cfg: self.cfg.clone(), + downloader: self.downloader.clone(), + manifest: self.manifest.clone(), + }; + self.windows.ui(&mut state, ctx).unwrap(); + self.cfg = state.cfg; + self.downloader = state.downloader; + self.manifest = state.manifest; + } egui::CentralPanel::default().show(ctx, |ui| { // The central panel the region left after adding TopPanel's and SidePanel's @@ -186,13 +192,9 @@ impl eframe::App for Gui { fn context_menu(this: &mut Gui, ui: &mut egui::Ui, pname: &String, sname: &String, song: &Song) { if ui.button("Edit").clicked() { - this.song_edit_w.song = ( - pname.clone(), - sname.clone(), - ); - this.song_edit_w.is_open = true; - this.song_edit_w.ed_name = sname.clone(); - this.song_edit_w.ed_url = song.get_url_str().clone(); + let w = this.windows.get_window::(WindowIndex::SongEdit); + w.set_active_song(pname, sname, song.get_url_str()); + this.windows.open(WindowIndex::SongEdit, true); ui.close_menu() } diff --git a/src/commands/gui/nav_bar.rs b/src/ui/gui/nav_bar.rs similarity index 93% rename from src/commands/gui/nav_bar.rs rename to src/ui/gui/nav_bar.rs index 822a32a..3275597 100644 --- a/src/commands/gui/nav_bar.rs +++ b/src/ui/gui/nav_bar.rs @@ -1,4 +1,4 @@ -use super::Gui; +use super::{windows::WindowIndex, Gui}; impl Gui { @@ -22,13 +22,13 @@ impl Gui { ui.menu_button("Song", |ui| { if ui.button("Add New").clicked() { - self.new_song_w.is_open = true; + self.windows.open(WindowIndex::SongNew, true); } }); ui.menu_button("Playlist", |ui| { if ui.button("Import").clicked() { - self.import_playlist_w.is_open = true; + self.windows.open(WindowIndex::ImportPlaylist, true); } }); diff --git a/src/ui/gui/windows/error.rs b/src/ui/gui/windows/error.rs new file mode 100644 index 0000000..5c7d166 --- /dev/null +++ b/src/ui/gui/windows/error.rs @@ -0,0 +1,32 @@ +use egui::{Color32, Label, RichText}; + +use super::{State, Window}; + + +#[derive(Debug, Default)] +pub struct GuiError { + text: String, +} + + +impl Window for GuiError { + fn ui(&mut self, _: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> { + egui::Window::new("ERROR!!!! D:") + .open(open) + .show(ctx, |ui| { + ui.vertical(|ui| { + ui.label(RichText::new("Error:").size(30.0).color(Color32::RED)); + ui.horizontal(|ui| { + ui.add(Label::new(self.text.clone()).wrap(true)); + }) + }) + }); + Ok(()) + } +} + +impl GuiError { + pub fn set_error_message(&mut self, text: S) { + self.text = text.to_string(); + } +} diff --git a/src/ui/gui/windows/import_playlist.rs b/src/ui/gui/windows/import_playlist.rs new file mode 100644 index 0000000..7170b4e --- /dev/null +++ b/src/ui/gui/windows/import_playlist.rs @@ -0,0 +1,59 @@ +use super::{State, Window}; + + + +#[derive(Debug, Default)] +pub struct GuiImportPlaylist { + ed_name: String, + ed_url: String, +} + + +impl Window for GuiImportPlaylist { + fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> { + let mut save = false; + egui::Window::new("Import Playlist") + .open(open) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Type: Youtube"); + }); + + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.ed_name); + }); + ui.horizontal(|ui| { + ui.label("Url: "); + ui.text_edit_singleline(&mut self.ed_url); + }); + + if ui.button("Import").clicked() { + save = true; + } + }); + + if save { + let name = self.ed_name.clone(); + let url = self.ed_url.clone(); + + if state.manifest.get_playlist(&name).is_some() { + log::error!("Playlist {name} already exists"); + } + + let songs = state.downloader.download_playlist_nb(&state.cfg, &url, &name, &state.manifest.get_format()).unwrap(); + state.manifest.add_playlist(name.clone()); + + let playlist = state.manifest.get_playlist_mut(&name).expect("Unreachable"); + + for (sname, song) in songs { + log::info!("Added: {sname}"); + playlist.add_song(sname, song); + } + let _ = state.manifest.save(None); + *open = false; + } + + Ok(()) + } +} diff --git a/src/ui/gui/windows/mod.rs b/src/ui/gui/windows/mod.rs new file mode 100644 index 0000000..a6cbcd3 --- /dev/null +++ b/src/ui/gui/windows/mod.rs @@ -0,0 +1,76 @@ +use std::collections::HashMap; +use crate::{config::ConfigWrapper, downloader::Downloader, manifest::Manifest}; + +pub mod song_edit; +pub mod error; +pub mod import_playlist; +pub mod song_new; + +pub trait Window: std::fmt::Debug { + fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()>; +} + +#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] +pub enum WindowIndex { + Error, + ImportPlaylist, + SongEdit, + SongNew, +} + + +#[derive(Debug,Default)] +pub struct WindowManager { + opened: HashMap, + windows: HashMap> +} + +pub struct State { + pub downloader: Downloader, + pub manifest: Manifest, + pub cfg: ConfigWrapper, +} + +impl WindowManager { + pub fn new() -> Self { + let mut windows: HashMap> = HashMap::new(); + windows.insert(WindowIndex::Error, Box::::default()); + windows.insert(WindowIndex::ImportPlaylist, Box::::default()); + windows.insert(WindowIndex::SongEdit, Box::::default()); + windows.insert(WindowIndex::SongNew, Box::::default()); + Self { + windows, + ..Default::default() + } + } + + #[allow(dead_code)] + pub fn is_open(&self, id: &WindowIndex) -> bool { + *self.opened.get(id).unwrap() + } + + pub fn open(&mut self, id: WindowIndex, open: bool) { + self.opened.insert(id, open); + } + + pub fn ui(&mut self, state: &mut State, ctx: &egui::Context) -> anyhow::Result<()> { + for (id, window) in &mut self.windows { + if !self.opened.contains_key(&id) { + self.opened.insert(*id, false); + } + let open = self.opened.get_mut(id).unwrap(); + if let Err(e) = window.ui(state, ctx, open) { + log::error!("Window {id:?} errored: {e}"); + } + } + + Ok(()) + } + + pub fn get_window(&mut self, id: WindowIndex) -> &mut Box { + let w = self.windows.get_mut(&id).unwrap(); + unsafe { + crate::util::as_any_mut(w).downcast_mut_unchecked() + } + } +} diff --git a/src/ui/gui/windows/song_edit.rs b/src/ui/gui/windows/song_edit.rs new file mode 100644 index 0000000..9fa6a48 --- /dev/null +++ b/src/ui/gui/windows/song_edit.rs @@ -0,0 +1,95 @@ +use anyhow::{Result, bail}; +use egui::Color32; +use crate::ui::gui::Gui; + +use super::{State, Window}; + + +#[derive(Debug, Default)] +pub struct GuiSongEditor { + song: (String, String), + ed_url: String, + ed_name: String, +} + + +impl Window for GuiSongEditor { + fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> { + let mut save = false; + let (playlist, song_name) = self.song.clone(); + + if playlist.is_empty() { + return Ok(()); + } + + let Some(song) = state.manifest.get_song(&playlist, &song_name) else { + bail!("Failed to get song (1)"); + }; + let song = song.clone(); + + egui::Window::new("Song editor") + .open(open) + .show(ctx, + |ui| { + + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("["); + ui.hyperlink_to("link", song.get_url().unwrap()); + ui.label("] "); + ui.colored_label(Color32::LIGHT_BLUE, &playlist); + ui.label(": "); + ui.label(&song_name) + }); + + ui.horizontal(|ui| { + ui.label("Type: "); + ui.label(&song.get_type().to_string()); + }); + + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.ed_name); + }); + ui.horizontal(|ui| { + ui.label("Url: "); + ui.text_edit_singleline(&mut self.ed_url); + }); + + if ui.button("Save").clicked() { + save = true; + } + }); + + if save { + { + let Some(song) = state.manifest.get_song_mut(&playlist, &song_name) else { + bail!("Failed to get song (2)"); + }; + + *song.get_url_str_mut() = self.ed_url.clone(); + } + + let Some(playlist) = state.manifest.get_playlist_mut(&playlist) else { + bail!("Failed to get playlist"); + }; + + + playlist.remove_song(&song_name); + playlist.add_song(self.ed_name.clone(), song); + *open = false; + let _ = state.manifest.save(None); + } + + Ok(()) + } +} + +impl GuiSongEditor { + pub fn set_active_song(&mut self, pname: &String, sname: &String, url: &String) { + self.song.0 = pname.clone(); + self.song.1 = sname.clone(); + self.ed_name = sname.clone(); + self.ed_url = url.clone(); + } +} diff --git a/src/ui/gui/windows/song_new.rs b/src/ui/gui/windows/song_new.rs new file mode 100644 index 0000000..8728bfc --- /dev/null +++ b/src/ui/gui/windows/song_new.rs @@ -0,0 +1,72 @@ +use crate::manifest::song::{Song, SongType}; +use super::{State, Window}; + +#[derive(Debug, Default)] +pub struct GuiNewSong { + ed_type: SongType, + ed_name: String, + ed_playlist: Option, + ed_url: String, +} + +impl Window for GuiNewSong { + fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> { + let mut save = false; + egui::Window::new("New song") + .open(open) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Type: "); + egui::ComboBox::from_id_source("new_song_window_type") + .selected_text(format!("{:?}", self.ed_type)) + .show_ui(ui, |ui| { + ui.selectable_value(&mut self.ed_type, SongType::Youtube, "Youtube"); + ui.selectable_value(&mut self.ed_type, SongType::Spotify, "Spotify"); + ui.selectable_value(&mut self.ed_type, SongType::Soundcloud, "Soundcloud"); + } + ); + }); + + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.ed_name); + }); + ui.horizontal(|ui| { + ui.label("Playlist: "); + egui::ComboBox::from_id_source("new_song_window_playlist") + .selected_text(format!("{}", self.ed_playlist.clone().unwrap_or("".to_string()))) + .show_ui(ui, |ui| { + for p in state.manifest.get_playlists().keys() { + ui.selectable_value(&mut self.ed_playlist, Option::Some(p.clone()), p.as_str()); + } + } + ); + }); + ui.horizontal(|ui| { + ui.label("Url: "); + ui.text_edit_singleline(&mut self.ed_url); + }); + + if ui.button("Save").clicked() { + save = true; + } + }); + + if save { + let Some(playlist) = state.manifest.get_playlist_mut(&self.ed_playlist.clone().unwrap()) else { + panic!("couldnt find playlist from a preset playlist list????????????"); + }; + + playlist.add_song( + self.ed_name.clone(), + Song::from_url_str(self.ed_url.clone()).unwrap().set_type(self.ed_type.clone()).clone() + ); + + + let _ = state.manifest.save(None); + *open = false; + } + + Ok(()) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..91b629a --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,2 @@ +pub mod gui; +pub mod cli;