diff --git a/xmpd-gui/src/components/left_nav/header.rs b/xmpd-gui/src/components/left_nav/header.rs new file mode 100644 index 0000000..2869481 --- /dev/null +++ b/xmpd-gui/src/components/left_nav/header.rs @@ -0,0 +1,41 @@ +use uuid::Uuid; +use crate::{components::{CompGetter, CompUi}, windows::WindowId}; + + +#[derive(Debug, Default)] +pub struct Header { + pub search_text: String, +} + +component_register!(Header); + +impl CompUi for Header { + fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { + let theme = xmpd_settings::Settings::get()?.theme.clone(); + + ui.vertical(|ui| { + ui.horizontal(|ui| { + let search_icon = egui::Image::new(crate::data::SEARCH_ICON) + .fit_to_exact_size(egui::Vec2::new(16.0, 16.0)) + .tint(theme.accent_color); + ui.add(search_icon); + { + ui.text_edit_singleline(&mut handle_error_ui!(Header::get()).search_text); + } + }); + //ui.with_layout(egui::Layout::top_down(egui::Align::), add_contents) + ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { + let add_song = ui.add( + egui::Image::new(crate::data::PLUS_ICON) + .tint(theme.accent_color) + .sense(egui::Sense::click()) + .fit_to_exact_size(egui::Vec2::new(16.0, 16.0)) + ); + if add_song.clicked() { + state.windows.toggle(&WindowId::NewPlaylist, true); + } + }); + }); + Ok(()) + } +} diff --git a/xmpd-gui/src/components/left_nav.rs b/xmpd-gui/src/components/left_nav/mod.rs similarity index 82% rename from xmpd-gui/src/components/left_nav.rs rename to xmpd-gui/src/components/left_nav/mod.rs index 824c13e..f9774c5 100644 --- a/xmpd-gui/src/components/left_nav.rs +++ b/xmpd-gui/src/components/left_nav/mod.rs @@ -1,8 +1,11 @@ -use egui::{CursorIcon, RichText, Sense}; +use egui::{CursorIcon, RichText, Sense, TextBuffer}; use xmpd_manifest::store::BaseStore; +use crate::utils::SearchType; use super::{CompGetter, CompUi}; +pub mod header; + #[derive(Debug, Default)] pub struct LeftNav { pub selected_playlist_id: Option, @@ -26,7 +29,15 @@ impl CompUi for LeftNav { let b = b.1.name().to_lowercase(); a.cmp(&b) }); + let search_text = handle_error_ui!(header::Header::get()).search_text.clone(); + let (qtyp, qtxt) = crate::utils::SearchType::from_str(&search_text); for (pid, playlist) in playlists.iter() { + match qtyp { + _ if qtxt.is_empty() => (), + SearchType::Normal if playlist.name().to_lowercase().contains(&qtxt) => (), + SearchType::Author if playlist.author().to_lowercase().contains(&qtxt) => (), + _ => continue + } add_playlist_tab(ui, &Some(**pid), playlist.name(), @@ -43,6 +54,9 @@ impl CompUi for LeftNav { } fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option, title: &str, author: Option<&str>, song_count: usize, width: f32) { + if pid.is_some() { + ui.separator(); + } let theme = &handle_error_ui!(xmpd_settings::Settings::get()).theme; let wdg_rect = ui.horizontal(|ui| { ui.set_width(width); @@ -91,7 +105,6 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option, title: &str, au if blob.hovered() { ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand); } - ui.separator(); } diff --git a/xmpd-gui/src/components/song_list/song_list_nav.rs b/xmpd-gui/src/components/song_list/header.rs similarity index 76% rename from xmpd-gui/src/components/song_list/song_list_nav.rs rename to xmpd-gui/src/components/song_list/header.rs index ef332cd..65a643a 100644 --- a/xmpd-gui/src/components/song_list/song_list_nav.rs +++ b/xmpd-gui/src/components/song_list/header.rs @@ -2,25 +2,20 @@ use uuid::Uuid; use xmpd_cache::DlStatus; use xmpd_manifest::{song::Song, store::BaseStore}; -use crate::components::{left_nav::LeftNav, toast::ToastType, CompGetter, CompUi}; +use crate::{components::{left_nav::LeftNav, toast::ToastType, CompGetter, CompUi}, windows::WindowId}; use super::SongList; -#[derive(Debug, Clone)] -pub enum SearchType { - Name(String), - Author(String), - Source(String), -} + #[derive(Debug, Default)] -pub struct SongListNav { - text: String, +pub struct Header { + pub search_text: String, } -component_register!(SongListNav); +component_register!(Header); -impl CompUi for SongListNav { +impl CompUi for Header { fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { let theme = xmpd_settings::Settings::get()?.theme.clone(); let pid = {LeftNav::get()?.selected_playlist_id.clone()}; @@ -31,7 +26,7 @@ impl CompUi for SongListNav { .tint(theme.accent_color); ui.add(search_icon); { - ui.text_edit_singleline(&mut handle_error_ui!(SongListNav::get()).text); + ui.text_edit_singleline(&mut handle_error_ui!(Header::get()).search_text); } ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| { @@ -72,7 +67,7 @@ impl CompUi for SongListNav { } if add_song.clicked() { - todo!() + state.windows.toggle(&WindowId::AddSongToPl, true); } }); }); @@ -80,16 +75,8 @@ impl CompUi for SongListNav { } } -impl SongListNav { - pub fn parse_search(&self) -> SearchType { - match &self.text { - i @ _ if i.starts_with("source:") => - SearchType::Source(i.strip_prefix("source:").unwrap_or("").to_string().to_lowercase()), - i @ _ if i.starts_with("author:") => - SearchType::Author(i.strip_prefix("author:").unwrap_or("").to_string().to_lowercase()), - i @ _ => SearchType::Name(i.to_string().to_lowercase()) - } - } +impl Header { + fn get_songs_to_download(songs: &Vec) -> crate::Result> { let mut songs2 = Vec::new(); diff --git a/xmpd-gui/src/components/song_list/mod.rs b/xmpd-gui/src/components/song_list/mod.rs index 6012015..bd9395c 100644 --- a/xmpd-gui/src/components/song_list/mod.rs +++ b/xmpd-gui/src/components/song_list/mod.rs @@ -1,12 +1,11 @@ -use std::borrow::Cow; - use egui::{Color32, CursorIcon, ImageSource, RichText, Sense, Vec2}; -use song_list_nav::SearchType; use xmpd_cache::DlStatus; -use xmpd_manifest::{song::Song, store::BaseStore}; +use xmpd_manifest::{query, song::Song, store::BaseStore}; +use crate::utils::SearchType; + use super::{CompGetter, CompUi}; -pub mod song_list_nav; +pub mod header; #[derive(Debug, Default)] pub struct SongList { @@ -80,27 +79,28 @@ impl SongList { fn get_songs_to_display(songs: &Vec<(uuid::Uuid, Song)>) -> crate::Result>{ let mut to_display = Vec::new(); - let query = {song_list_nav::SongListNav::get()?.parse_search()}.clone(); + let query = {header::Header::get()?.search_text.clone()}; + let (query_type, query_text) = crate::utils::SearchType::from_str(&query); for (sid, song) in songs { - let should_display = match &query { - SearchType::Name(s) | - SearchType::Author(s) | - SearchType::Source(s) if s.is_empty() => true, + let should_display = match &query_type { + SearchType::Normal | + SearchType::Author | + SearchType::Source if query_text.is_empty() => true, - SearchType::Source(s) => { + SearchType::Source => { song.source_type().to_string() .to_lowercase() - .contains(s) + .contains(&query_text) }, - SearchType::Author(s) => { + SearchType::Author => { song.author() .to_lowercase() - .contains(s) + .contains(&query_text) }, - SearchType::Name(s) => { + SearchType::Normal => { song.name() .to_lowercase() - .contains(s) + .contains(&query_text) }, }; diff --git a/xmpd-gui/src/main_window.rs b/xmpd-gui/src/main_window.rs index 6e90e0c..885b0ce 100644 --- a/xmpd-gui/src/main_window.rs +++ b/xmpd-gui/src/main_window.rs @@ -29,15 +29,22 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState, cache_rx: &Receiver (Self, String) { + match s { + i @ _ if i.starts_with("source:") => + (Self::Source, i.strip_prefix("source:").unwrap_or("").to_string().to_lowercase()), + i @ _ if i.starts_with("author:") => + (Self::Author, i.strip_prefix("author:").unwrap_or("").to_string().to_lowercase()), + i @ _ => (Self::Normal, i.to_string().to_lowercase()) + } + } +} + pub fn super_separator(ui: &mut egui::Ui, color: egui::Color32, width: f32, height: f32) { egui::Frame::none() .fill(color) diff --git a/xmpd-gui/src/windows/add_song.rs b/xmpd-gui/src/windows/add_song.rs new file mode 100644 index 0000000..938e370 --- /dev/null +++ b/xmpd-gui/src/windows/add_song.rs @@ -0,0 +1,20 @@ +use super::Window; + + +#[derive(Debug, Default)] +pub struct AddSongW { + +} + +impl Window for AddSongW { + fn id() -> super::WindowId where Self: Sized { + super::WindowId::AddSongToPl + } + fn default_title() -> &'static str where Self: Sized { + "Add Song to Playlist" + } + fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { + ui.label("Hello from other window!"); + Ok(()) + } +} diff --git a/xmpd-gui/src/windows/debug.rs b/xmpd-gui/src/windows/debug.rs index 84a3e21..064673b 100644 --- a/xmpd-gui/src/windows/debug.rs +++ b/xmpd-gui/src/windows/debug.rs @@ -13,6 +13,12 @@ pub struct DebugW { } impl Window for DebugW { + fn id() -> super::WindowId where Self: Sized { + super::WindowId::Debug + } + fn default_title() -> &'static str where Self: Sized { + "DEBUG WINDOW" + } fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { ui.group(|ui| { ui.vertical(|ui| { diff --git a/xmpd-gui/src/windows/error.rs b/xmpd-gui/src/windows/error.rs index 3c9b055..1fcdd31 100644 --- a/xmpd-gui/src/windows/error.rs +++ b/xmpd-gui/src/windows/error.rs @@ -7,6 +7,12 @@ pub struct ErrorW { } impl Window for ErrorW { + fn id() -> super::WindowId where Self: Sized { + super::WindowId::Error + } + fn default_title() -> &'static str where Self: Sized { + "Error!" + } fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { ui.label("Hello from other window!"); Ok(()) diff --git a/xmpd-gui/src/windows/mod.rs b/xmpd-gui/src/windows/mod.rs index bb747af..82a1a80 100644 --- a/xmpd-gui/src/windows/mod.rs +++ b/xmpd-gui/src/windows/mod.rs @@ -6,15 +6,22 @@ use crate::GuiState; mod debug; mod error; mod settings; +mod add_song; +mod new_song; +mod new_playlist; lazy_static::lazy_static!( static ref WINDOWS: Arc>>> = Arc::new(Mutex::new(HashMap::new())); static ref OPEN_WINDOWS: Arc>> = Arc::new(Mutex::new(HashSet::new())); - ); pub trait Window: std::fmt::Debug + Send { fn draw(&mut self, ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()>; + fn id() -> WindowId where Self: Sized; + fn default_title() -> &'static str where Self: Sized; + fn close(&self) where Self: Sized{ + OPEN_WINDOWS.lock().unwrap().remove(&Self::id()); + } } #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] @@ -22,7 +29,10 @@ pub enum WindowId { Settings, Error, #[cfg(debug_assertions)] - Debug + Debug, + NewPlaylist, + NewSong, + AddSongToPl, } #[derive(Debug, Clone)] @@ -40,18 +50,21 @@ impl Windows { } pub fn add_all_windows(&mut self) { - self.add_new_window(WindowId::Error, "Error!", Box::::default()); - self.add_new_window(WindowId::Settings, "Settings", Box::::default()); #[cfg(debug_assertions)] - self.add_new_window(WindowId::Debug, "Debug", Box::::default()); + self.add_new_window::(); + self.add_new_window::(); + self.add_new_window::(); + self.add_new_window::(); + self.add_new_window::(); + self.add_new_window::(); } - pub fn add_new_window(&mut self, id: WindowId, title: &str, cb: Box) { + pub fn add_new_window(&mut self) { let builder = ViewportBuilder::default() .with_window_type(egui::X11WindowType::Dialog) - .with_title(title); - self.windows.insert(id.clone(), (ViewportId::from_hash_of(id.clone()), builder)); - WINDOWS.lock().unwrap().insert(id, cb); + .with_title(WT::default_title()); + self.windows.insert(WT::id(), (ViewportId::from_hash_of(WT::id()), builder)); + WINDOWS.lock().unwrap().insert(WT::id(), Box::::default()); } pub fn draw_all(&mut self, ctx: &egui::Context, state: &mut GuiState) { diff --git a/xmpd-gui/src/windows/new_playlist.rs b/xmpd-gui/src/windows/new_playlist.rs new file mode 100644 index 0000000..7414a29 --- /dev/null +++ b/xmpd-gui/src/windows/new_playlist.rs @@ -0,0 +1,102 @@ +use std::str::FromStr; + +use egui::{Sense, Vec2}; +use xmpd_manifest::{playlist::{self, Playlist}, store::BaseStore}; + +use super::{Window, WindowId}; + + +#[derive(Debug)] +pub struct NewPlaylistW { + name: String, + author: String, +} + +impl Default for NewPlaylistW { + fn default() -> Self { + Self { + name: String::from("New Playlist"), + author: String::from("Unknown"), + } + } +} + +impl Window for NewPlaylistW { + fn id() -> WindowId where Self: Sized { + WindowId::NewPlaylist + } + fn default_title() -> &'static str where Self: Sized { + "New Playlist" + } + fn draw(&mut self, ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { + let theme = xmpd_settings::Settings::get()?.theme.clone(); + let img_size = 64.0; + let img_spacing = 10.0; + ui.vertical(|ui| { + ui.horizontal(|ui| { + let mut rect = egui::Rect::ZERO; + rect.set_width(img_size); + rect.set_height(img_size); + rect.set_top(img_spacing); + rect.set_left(img_spacing); + let rect_int = ui.interact(rect, "new_playlist_w".into(), Sense::click()); + if rect_int.hovered() { + ui.allocate_ui_at_rect(rect, |ui| { + ui.group(|ui| { + let img = egui::Image::new(crate::data::PLUS_ICON) + .tint(theme.accent_color) + .fit_to_exact_size(Vec2::new(img_size, img_size)); + //.paint_at(ui, rect); + ui.add(img); + }); + }); + + } else { + ui.allocate_ui_at_rect(rect, |ui| { + ui.group(|ui| { + let img = egui::Image::new(crate::data::NOTE_ICON) + .tint(theme.accent_color) + .fit_to_exact_size(Vec2::new(img_size, img_size)); + //.paint_at(ui, rect); + ui.add(img); + }); + }); + } + if rect_int.clicked() { + // TODO: Add a way to add custom icons + } + ui.vertical(|ui| { + ui.add_space(img_spacing); + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.name); + }); + ui.horizontal(|ui| { + ui.label("Author: "); + ui.text_edit_singleline(&mut self.author); + }); + }); + }); + ui.with_layout(egui::Layout::bottom_up(egui::Align::Max), |ui| { + ui.add_space(3.0); + ui.horizontal(|ui| { + ui.add_space(3.0); + if ui.button("Cancel").clicked() { + self.author = String::from("New Playlist"); + self.name = String::from("Unknown"); + state.windows.toggle(&WindowId::NewPlaylist, false); + } + if ui.button("Add").clicked() { + let mut playlist = Playlist::default(); + playlist.set_name(&self.name); + playlist.set_author(&self.author); + let playlists = state.manifest.store_mut().get_playlists_mut(); + playlists.insert(uuid::Uuid::new_v4(), playlist); + state.windows.toggle(&WindowId::NewPlaylist, false); + } + }); + }); + }); + Ok(()) + } +} diff --git a/xmpd-gui/src/windows/new_song.rs b/xmpd-gui/src/windows/new_song.rs new file mode 100644 index 0000000..b0d27b7 --- /dev/null +++ b/xmpd-gui/src/windows/new_song.rs @@ -0,0 +1,20 @@ +use super::Window; + + +#[derive(Debug, Default)] +pub struct NewSongW { + +} + +impl Window for NewSongW { + fn id() -> super::WindowId where Self: Sized { + super::WindowId::NewSong + } + fn default_title() -> &'static str where Self: Sized { + "New Song" + } + fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { + ui.label("Hello from other window!"); + Ok(()) + } +} diff --git a/xmpd-gui/src/windows/settings.rs b/xmpd-gui/src/windows/settings.rs index 922e1aa..72d096d 100644 --- a/xmpd-gui/src/windows/settings.rs +++ b/xmpd-gui/src/windows/settings.rs @@ -25,6 +25,12 @@ impl Default for SettingsW { impl Window for SettingsW { + fn id() -> super::WindowId where Self: Sized { + super::WindowId::Settings + } + fn default_title() -> &'static str where Self: Sized { + "Settings" + } #[allow(irrefutable_let_patterns)] fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { ui.group(|ui| {