Remade the window adding machanism, added playlist add window

This commit is contained in:
Gvidas Juknevičius 2024-11-23 17:34:14 +02:00
parent 86cf5542be
commit c2c18154b3
Signed by: MCorange
GPG Key ID: 12B1346D720B7FBB
13 changed files with 295 additions and 55 deletions

View File

@ -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(())
}
}

View File

@ -1,8 +1,11 @@
use egui::{CursorIcon, RichText, Sense}; use egui::{CursorIcon, RichText, Sense, TextBuffer};
use xmpd_manifest::store::BaseStore; use xmpd_manifest::store::BaseStore;
use crate::utils::SearchType;
use super::{CompGetter, CompUi}; use super::{CompGetter, CompUi};
pub mod header;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct LeftNav { pub struct LeftNav {
pub selected_playlist_id: Option<uuid::Uuid>, pub selected_playlist_id: Option<uuid::Uuid>,
@ -26,7 +29,15 @@ impl CompUi for LeftNav {
let b = b.1.name().to_lowercase(); let b = b.1.name().to_lowercase();
a.cmp(&b) 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() { 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, add_playlist_tab(ui,
&Some(**pid), &Some(**pid),
playlist.name(), playlist.name(),
@ -43,6 +54,9 @@ impl CompUi for LeftNav {
} }
fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option<uuid::Uuid>, title: &str, author: Option<&str>, song_count: usize, width: f32) { fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option<uuid::Uuid>, 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 theme = &handle_error_ui!(xmpd_settings::Settings::get()).theme;
let wdg_rect = ui.horizontal(|ui| { let wdg_rect = ui.horizontal(|ui| {
ui.set_width(width); ui.set_width(width);
@ -91,7 +105,6 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option<uuid::Uuid>, title: &str, au
if blob.hovered() { if blob.hovered() {
ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand); ui.output_mut(|o| o.cursor_icon = CursorIcon::PointingHand);
} }
ui.separator();
} }

View File

@ -2,25 +2,20 @@ use uuid::Uuid;
use xmpd_cache::DlStatus; use xmpd_cache::DlStatus;
use xmpd_manifest::{song::Song, store::BaseStore}; 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; use super::SongList;
#[derive(Debug, Clone)]
pub enum SearchType {
Name(String),
Author(String),
Source(String),
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct SongListNav { pub struct Header {
text: String, 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<()> { fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
let theme = xmpd_settings::Settings::get()?.theme.clone(); let theme = xmpd_settings::Settings::get()?.theme.clone();
let pid = {LeftNav::get()?.selected_playlist_id.clone()}; let pid = {LeftNav::get()?.selected_playlist_id.clone()};
@ -31,7 +26,7 @@ impl CompUi for SongListNav {
.tint(theme.accent_color); .tint(theme.accent_color);
ui.add(search_icon); 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| { ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
@ -72,7 +67,7 @@ impl CompUi for SongListNav {
} }
if add_song.clicked() { if add_song.clicked() {
todo!() state.windows.toggle(&WindowId::AddSongToPl, true);
} }
}); });
}); });
@ -80,16 +75,8 @@ impl CompUi for SongListNav {
} }
} }
impl SongListNav { impl Header {
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())
}
}
fn get_songs_to_download(songs: &Vec<uuid::Uuid>) -> crate::Result<Vec<uuid::Uuid>> { fn get_songs_to_download(songs: &Vec<uuid::Uuid>) -> crate::Result<Vec<uuid::Uuid>> {
let mut songs2 = Vec::new(); let mut songs2 = Vec::new();

View File

@ -1,12 +1,11 @@
use std::borrow::Cow;
use egui::{Color32, CursorIcon, ImageSource, RichText, Sense, Vec2}; use egui::{Color32, CursorIcon, ImageSource, RichText, Sense, Vec2};
use song_list_nav::SearchType;
use xmpd_cache::DlStatus; 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}; use super::{CompGetter, CompUi};
pub mod song_list_nav; pub mod header;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct SongList { pub struct SongList {
@ -80,27 +79,28 @@ impl SongList {
fn get_songs_to_display(songs: &Vec<(uuid::Uuid, Song)>) -> crate::Result<Vec<(uuid::Uuid, Song)>>{ fn get_songs_to_display(songs: &Vec<(uuid::Uuid, Song)>) -> crate::Result<Vec<(uuid::Uuid, Song)>>{
let mut to_display = Vec::new(); 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 { for (sid, song) in songs {
let should_display = match &query { let should_display = match &query_type {
SearchType::Name(s) | SearchType::Normal |
SearchType::Author(s) | SearchType::Author |
SearchType::Source(s) if s.is_empty() => true, SearchType::Source if query_text.is_empty() => true,
SearchType::Source(s) => { SearchType::Source => {
song.source_type().to_string() song.source_type().to_string()
.to_lowercase() .to_lowercase()
.contains(s) .contains(&query_text)
}, },
SearchType::Author(s) => { SearchType::Author => {
song.author() song.author()
.to_lowercase() .to_lowercase()
.contains(s) .contains(&query_text)
}, },
SearchType::Name(s) => { SearchType::Normal => {
song.name() song.name()
.to_lowercase() .to_lowercase()
.contains(s) .contains(&query_text)
}, },
}; };

View File

@ -29,15 +29,22 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState, cache_rx: &Receiver<Messa
let song_list_width = avail.x - left_nav_width - 35.0; let song_list_width = avail.x - left_nav_width - 35.0;
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.set_height(main_height); ui.set_height(main_height);
ui.group(|ui| { ui.vertical(|ui| {
ui.set_height(main_height); ui.group(|ui| {
ui.set_max_width(left_nav_width); //ui.set_height(main_height * 0.1);
handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state)); ui.set_max_width(left_nav_width);
handle_error_ui!(crate::components::left_nav::header::Header::draw(ui, state));
});
ui.group(|ui| {
// ui.set_height(main_height * 0.9);
ui.set_max_width(left_nav_width);
handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
});
}); });
ui.vertical(|ui| { ui.vertical(|ui| {
ui.group(|ui| { ui.group(|ui| {
ui.set_width(song_list_width); ui.set_width(song_list_width);
handle_error_ui!(crate::components::song_list::song_list_nav::SongListNav::draw(ui, state)); handle_error_ui!(crate::components::song_list::header::Header::draw(ui, state));
}); });
ui.group(|ui| { ui.group(|ui| {
ui.set_width(song_list_width); ui.set_width(song_list_width);

View File

@ -1,4 +1,23 @@
#[derive(Debug, Clone)]
pub enum SearchType {
Normal,
Author,
Source,
}
impl SearchType {
pub fn from_str(s: &str) -> (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) { pub fn super_separator(ui: &mut egui::Ui, color: egui::Color32, width: f32, height: f32) {
egui::Frame::none() egui::Frame::none()
.fill(color) .fill(color)

View File

@ -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(())
}
}

View File

@ -13,6 +13,12 @@ pub struct DebugW {
} }
impl Window for 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<()> { fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
ui.group(|ui| { ui.group(|ui| {
ui.vertical(|ui| { ui.vertical(|ui| {

View File

@ -7,6 +7,12 @@ pub struct ErrorW {
} }
impl Window for 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<()> { fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
ui.label("Hello from other window!"); ui.label("Hello from other window!");
Ok(()) Ok(())

View File

@ -6,15 +6,22 @@ use crate::GuiState;
mod debug; mod debug;
mod error; mod error;
mod settings; mod settings;
mod add_song;
mod new_song;
mod new_playlist;
lazy_static::lazy_static!( lazy_static::lazy_static!(
static ref WINDOWS: Arc<Mutex<HashMap<WindowId, Box<dyn Window>>>> = Arc::new(Mutex::new(HashMap::new())); static ref WINDOWS: Arc<Mutex<HashMap<WindowId, Box<dyn Window>>>> = Arc::new(Mutex::new(HashMap::new()));
static ref OPEN_WINDOWS: Arc<Mutex<HashSet<WindowId>>> = Arc::new(Mutex::new(HashSet::new())); static ref OPEN_WINDOWS: Arc<Mutex<HashSet<WindowId>>> = Arc::new(Mutex::new(HashSet::new()));
); );
pub trait Window: std::fmt::Debug + Send { pub trait Window: std::fmt::Debug + Send {
fn draw(&mut self, ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()>; 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)] #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
@ -22,7 +29,10 @@ pub enum WindowId {
Settings, Settings,
Error, Error,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
Debug Debug,
NewPlaylist,
NewSong,
AddSongToPl,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -40,18 +50,21 @@ impl Windows {
} }
pub fn add_all_windows(&mut self) { pub fn add_all_windows(&mut self) {
self.add_new_window(WindowId::Error, "Error!", Box::<error::ErrorW>::default());
self.add_new_window(WindowId::Settings, "Settings", Box::<settings::SettingsW>::default());
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
self.add_new_window(WindowId::Debug, "Debug", Box::<debug::DebugW>::default()); self.add_new_window::<debug::DebugW>();
self.add_new_window::<error::ErrorW>();
self.add_new_window::<settings::SettingsW>();
self.add_new_window::<add_song::AddSongW>();
self.add_new_window::<new_song::NewSongW>();
self.add_new_window::<new_playlist::NewPlaylistW>();
} }
pub fn add_new_window(&mut self, id: WindowId, title: &str, cb: Box<dyn Window>) { pub fn add_new_window<WT: Window + Default + 'static>(&mut self) {
let builder = ViewportBuilder::default() let builder = ViewportBuilder::default()
.with_window_type(egui::X11WindowType::Dialog) .with_window_type(egui::X11WindowType::Dialog)
.with_title(title); .with_title(WT::default_title());
self.windows.insert(id.clone(), (ViewportId::from_hash_of(id.clone()), builder)); self.windows.insert(WT::id(), (ViewportId::from_hash_of(WT::id()), builder));
WINDOWS.lock().unwrap().insert(id, cb); WINDOWS.lock().unwrap().insert(WT::id(), Box::<WT>::default());
} }
pub fn draw_all(&mut self, ctx: &egui::Context, state: &mut GuiState) { pub fn draw_all(&mut self, ctx: &egui::Context, state: &mut GuiState) {

View File

@ -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(())
}
}

View File

@ -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(())
}
}

View File

@ -25,6 +25,12 @@ impl Default for SettingsW {
impl Window 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)] #[allow(irrefutable_let_patterns)]
fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
ui.group(|ui| { ui.group(|ui| {