diff --git a/src/ui/gui/components/context_menu.rs b/src/ui/gui/components/context_menu.rs new file mode 100644 index 0000000..abea285 --- /dev/null +++ b/src/ui/gui/components/context_menu.rs @@ -0,0 +1,57 @@ +use egui::{Color32, RichText}; +use crate::{manifest::song::Song, ui::gui::windows::{song_edit::GuiSongEditor, WindowIndex}}; + +pub struct ContextMenu; + +// NOTE: This should be a component but theres no easy way to do that, so we just make it folow the +// trait manually, ish, more like a convention +impl /* ComponentUi for */ ContextMenu { + pub fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui, pname: &String, sname: &String, song: &Song) { + if ui.button("Edit").clicked() { + let w = gui.windows.get_window::(WindowIndex::SongEdit); + w.set_active_song(pname, sname, song.get_url_str()); + gui.windows.open(WindowIndex::SongEdit, true); + ui.close_menu() + } + + if ui.button("Download").clicked() { + if let Err(e) = gui.downloader.download_song_nb(&gui.cfg, pname, sname, song, gui.manifest.get_format()) { + log::error!("{e}"); + gui.throw_error(format!("Failed to download song {sname}: {e}")); + } + ui.close_menu() + } + + if ui.button("Open Source").clicked() { + if let Err(e) = open::that(song.get_url_str()) { + log::error!("{e}"); + gui.throw_error(format!("Failed to open song source: {e}")); + } + ui.close_menu() + } + if ui.button("Play").clicked() { + let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format()); + + if !p.exists() { + gui.throw_error(format!("Song does not exist on disk")); + } else if let Err(e) = open::that(p) { + log::error!("{e}"); + gui.throw_error(format!("Failed to play song: {e}")); + } + ui.close_menu() + } + if ui.button("Delete from disk").clicked() { + let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format()); + if p.exists() { + if let Err(e) = std::fs::remove_file(p) { + gui.throw_error(format!("Failed to delete file: {e}")); + } + } + ui.close_menu(); + } + if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() { + gui.throw_error("TODO"); + ui.close_menu() + } + } +} diff --git a/src/ui/gui/components/mod.rs b/src/ui/gui/components/mod.rs index e69de29..89aba2f 100644 --- a/src/ui/gui/components/mod.rs +++ b/src/ui/gui/components/mod.rs @@ -0,0 +1,14 @@ +use super::Gui; + + +pub mod nav; +pub mod song_list; +pub mod context_menu; + +pub trait Component { + fn ui(gui: &mut Gui, ctx: &egui::Context); +} + +pub trait ComponentUi { + fn ui(gui: &mut Gui, ui: &mut egui::Ui); +} diff --git a/src/ui/gui/nav_bar.rs b/src/ui/gui/components/nav.rs similarity index 68% rename from src/ui/gui/nav_bar.rs rename to src/ui/gui/components/nav.rs index 3275597..88d46a6 100644 --- a/src/ui/gui/nav_bar.rs +++ b/src/ui/gui/components/nav.rs @@ -1,8 +1,13 @@ -use super::{windows::WindowIndex, Gui}; +use crate::ui::gui::{windows::WindowIndex, Gui}; -impl Gui { +use super::Component; - pub fn draw_nav(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { + + +pub struct NavBar; + +impl Component for NavBar { + fn ui(gui: &mut Gui, ctx: &egui::Context) { egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { // The top panel is often a good place for a menu bar: egui::menu::bar(ui, |ui| { @@ -11,7 +16,7 @@ impl Gui { ctx.open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music")); } if ui.button("Save").clicked() { - if let Err(e) = self.manifest.save(None) { + if let Err(e) = gui.manifest.save(None) { log::error!("Failed to save manifest: {e}"); } } @@ -22,19 +27,19 @@ impl Gui { ui.menu_button("Song", |ui| { if ui.button("Add New").clicked() { - self.windows.open(WindowIndex::SongNew, true); + gui.windows.open(WindowIndex::SongNew, true); } }); ui.menu_button("Playlist", |ui| { if ui.button("Import").clicked() { - self.windows.open(WindowIndex::ImportPlaylist, true); + gui.windows.open(WindowIndex::ImportPlaylist, true); } }); ui.menu_button("Downloader", |ui| { if ui.button("Download All").clicked() { - if let Err(e) = self.downloader.download_all_nb(&self.manifest, &self.cfg) { + if let Err(e) = gui.downloader.download_all_nb(&gui.manifest, &gui.cfg) { log::error!("Err: {e}"); } } @@ -42,21 +47,22 @@ impl Gui { ui.add_space(16.0); ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| { ui.horizontal(|ui| { - if self.downloader.get_songs_left_nb() > 0 { - self.downloading = true; - ui.label(format!("Downloading: {}/{}", self.downloader.get_songs_left_nb(), self.downloader.get_initial_song_count_nb())); - } else if self.downloading { + if gui.downloader.get_songs_left_nb() > 0 { + gui.downloading = true; + ui.label(format!("Downloading: {}/{}", gui.downloader.get_songs_left_nb(), gui.downloader.get_initial_song_count_nb())); + } else if gui.downloading { let _ = notify_rust::Notification::new() .summary("Done downloading") .body("Your music has been downloaded") .show(); - self.downloading = false; + gui.downloading = false; } - let _ = self.downloader.download_all_nb_poll(&self.cfg); + let _ = gui.downloader.download_all_nb_poll(&gui.cfg); egui::widgets::global_dark_light_mode_buttons(ui); }); }); }); }); - } + + } } diff --git a/src/ui/gui/components/song_list.rs b/src/ui/gui/components/song_list.rs new file mode 100644 index 0000000..5b62b29 --- /dev/null +++ b/src/ui/gui/components/song_list.rs @@ -0,0 +1,130 @@ +use egui::{Color32, RichText}; +use egui_extras::{Column, TableBuilder}; + +use crate::manifest::song::SongType; + +use super::{context_menu::ContextMenu, ComponentUi}; + + +pub struct SongList; + +impl ComponentUi for SongList { + fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui) { + let fltr_by; + let filter_clean; + if gui.filter.starts_with("playlist:") { + fltr_by = "playlist"; + filter_clean = gui.filter.strip_prefix("playlist:").unwrap_or("").to_string().to_lowercase(); + } else if gui.filter.starts_with("source:") { + fltr_by = "source"; + filter_clean = gui.filter.strip_prefix("source:").unwrap_or("").to_string().to_lowercase(); + } else if gui.filter.starts_with("url:") { + fltr_by = "url"; + filter_clean = gui.filter.strip_prefix("url:").unwrap_or("").to_string(); + } else { + fltr_by = ""; + filter_clean = gui.filter.clone(); + } + + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.colored_label(Color32::from_hex("#4444aa").unwrap(), "Filter: "); + ui.text_edit_singleline(&mut gui.filter); + }); + }); + + ui.vertical(|ui| { + let available_height = ui.available_height(); + let table = TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .resizable(true) + //.column(Column::auto()) + .column(Column::auto()) + //.column( + // Column::remainder() + // .at_least(40.0) + // .clip(true) + // .resizable(true), + //) + .column(Column::auto()) + .column(Column::remainder()) + //.column(Column::remainder()) + .min_scrolled_height(0.0) + .max_scroll_height(available_height) + .sense(egui::Sense::click()); + + let playlists = gui.manifest.get_playlists().clone(); + + let songs = { + let mut songs = Vec::new(); + for (pname, p) in playlists { + for (sname, s) in p { + songs.push((pname.clone(), sname, s)) + } + } + songs + }; + + table.header(20.0, |mut header| { + // header.col(|_|{}); + header.col(|ui| { + ui.strong("Playlist"); + }); + header.col(|ui| { + ui.strong("Source"); + }); + header.col(|ui| { + ui.strong("Name"); + }); + }).body(|mut body| { + for (pname, sname, s) in songs { + if fltr_by == "playlist" && !filter_clean.is_empty() { + if !pname.to_lowercase().contains(&filter_clean) { + continue; + } + } else if fltr_by == "type" && !filter_clean.is_empty(){ + if !s.get_type().to_string().to_lowercase().contains(&filter_clean) { + continue; + } + } else if fltr_by == "url" && !filter_clean.is_empty(){ + if !s.get_url_str().contains(&filter_clean) { + continue; + } + } else if !filter_clean.is_empty() { + if !sname.to_lowercase().contains(&filter_clean) { + continue; + } + } + body.row(18.0, |mut row| { + + row.col(|ui| { + ui.label(pname.clone()) + .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s)); + }); + row.col(|ui| { + let color = + match s.get_type() { + SongType::Youtube => Color32::from_hex("#FF0000").unwrap(), + SongType::Spotify => Color32::from_hex("#1db954").unwrap(), + SongType::Soundcloud => Color32::from_hex("#F26F23").unwrap() + }; + + ui.colored_label(color, s.get_type().to_string()) + .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s)); + }); + row.col(|ui| { + ui.hyperlink_to(sname.clone(), s.get_url_str()) + .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s)); + }); + + row.response() + .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s)); + + }) + } + }); + }); + + } +} diff --git a/src/ui/gui/mod.rs b/src/ui/gui/mod.rs index 53c9232..1ef225c 100644 --- a/src/ui/gui/mod.rs +++ b/src/ui/gui/mod.rs @@ -1,12 +1,9 @@ -mod nav_bar; mod windows; mod components; -use egui::{Color32, RichText}; -use egui_extras::{Column, TableBuilder}; +use components::{Component, ComponentUi}; use windows::{State, WindowIndex, WindowManager}; - -use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::{Song, SongType}, Manifest}}; +use crate::{config::ConfigWrapper, downloader::Downloader, manifest::Manifest}; #[derive(Debug, Default)] @@ -61,8 +58,8 @@ impl Gui { } impl eframe::App for Gui { - fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { - self.draw_nav(ctx, frame); + fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) { + components::nav::NavBar::ui(self, ctx); { let mut state = State { cfg: self.cfg.clone(), @@ -78,173 +75,9 @@ impl eframe::App for Gui { egui::CentralPanel::default().show(ctx, |ui| { // The central panel the region left after adding TopPanel's and SidePanel's //ui.heading(format!("Songs ({})", self.manifest.get_song_count())); - - let fltr_by; - let filter_clean; - if self.filter.starts_with("playlist:") { - fltr_by = "playlist"; - filter_clean = self.filter.strip_prefix("playlist:").unwrap_or("").to_string().to_lowercase(); - } else if self.filter.starts_with("source:") { - fltr_by = "source"; - filter_clean = self.filter.strip_prefix("source:").unwrap_or("").to_string().to_lowercase(); - } else if self.filter.starts_with("url:") { - fltr_by = "url"; - filter_clean = self.filter.strip_prefix("url:").unwrap_or("").to_string(); - } else { - fltr_by = ""; - filter_clean = self.filter.clone(); - } - - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.colored_label(Color32::from_hex("#4444aa").unwrap(), "Filter: "); - ui.text_edit_singleline(&mut self.filter); - }); - }); - - ui.vertical(|ui| { - let available_height = ui.available_height(); - let table = TableBuilder::new(ui) - .striped(true) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .resizable(true) - //.column(Column::auto()) - .column(Column::auto()) - //.column( - // Column::remainder() - // .at_least(40.0) - // .clip(true) - // .resizable(true), - //) - .column(Column::auto()) - .column(Column::remainder()) - //.column(Column::remainder()) - .min_scrolled_height(0.0) - .max_scroll_height(available_height) - .sense(egui::Sense::click()); - - let playlists = self.manifest.get_playlists().clone(); - - let songs = { - let mut songs = Vec::new(); - for (pname, p) in playlists { - for (sname, s) in p { - songs.push((pname.clone(), sname, s)) - } - } - songs - }; - - table.header(20.0, |mut header| { - // header.col(|_|{}); - header.col(|ui| { - ui.strong("Playlist"); - }); - header.col(|ui| { - ui.strong("Source"); - }); - header.col(|ui| { - ui.strong("Name"); - }); - }).body(|mut body| { - for (pname, sname, s) in songs { - if fltr_by == "playlist" && !filter_clean.is_empty() { - if !pname.to_lowercase().contains(&filter_clean) { - continue; - } - } else if fltr_by == "type" && !filter_clean.is_empty(){ - if !s.get_type().to_string().to_lowercase().contains(&filter_clean) { - continue; - } - } else if fltr_by == "url" && !filter_clean.is_empty(){ - if !s.get_url_str().contains(&filter_clean) { - continue; - } - } else if !filter_clean.is_empty() { - if !sname.to_lowercase().contains(&filter_clean) { - continue; - } - } - body.row(18.0, |mut row| { - - row.col(|ui| { - ui.label(pname.clone()) - .context_menu(|ui| context_menu(self, ui, &pname, &sname, &s)); - }); - row.col(|ui| { - let color = - match s.get_type() { - SongType::Youtube => Color32::from_hex("#FF0000").unwrap(), - SongType::Spotify => Color32::from_hex("#1db954").unwrap(), - SongType::Soundcloud => Color32::from_hex("#F26F23").unwrap() - }; - - ui.colored_label(color, s.get_type().to_string()) - .context_menu(|ui| context_menu(self, ui, &pname, &sname, &s)); - }); - row.col(|ui| { - ui.hyperlink_to(sname.clone(), s.get_url_str()) - .context_menu(|ui| context_menu(self, ui, &pname, &sname, &s)); - }); - - row.response() - .context_menu(|ui| context_menu(self, ui, &pname, &sname, &s)); - - fn context_menu(this: &mut Gui, ui: &mut egui::Ui, pname: &String, sname: &String, song: &Song) { - if ui.button("Edit").clicked() { - 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() - } - - if ui.button("Download").clicked() { - if let Err(e) = this.downloader.download_song_nb(&this.cfg, pname, sname, song, this.manifest.get_format()) { - log::error!("{e}"); - this.throw_error(format!("Failed to download song {sname}: {e}")); - } - ui.close_menu() - } - - if ui.button("Open Source").clicked() { - if let Err(e) = open::that(song.get_url_str()) { - log::error!("{e}"); - this.throw_error(format!("Failed to open song source: {e}")); - } - ui.close_menu() - } - if ui.button("Play").clicked() { - let p = crate::util::get_song_path(pname, sname, this.manifest.get_format()); - - if !p.exists() { - this.throw_error(format!("Song does not exist on disk")); - } else if let Err(e) = open::that(p) { - log::error!("{e}"); - this.throw_error(format!("Failed to play song: {e}")); - } - ui.close_menu() - } - if ui.button("Delete from disk").clicked() { - let p = crate::util::get_song_path(pname, sname, this.manifest.get_format()); - if p.exists() { - if let Err(e) = std::fs::remove_file(p) { - this.throw_error(format!("Failed to delete file: {e}")); - } - } - ui.close_menu(); - } - if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() { - this.throw_error("TODO"); - ui.close_menu() - } - } - }) - } - }); - }); + components::song_list::SongList::ui(self, ui); ui.separator(); - ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { egui::warn_if_debug_build(ui); });