diff --git a/.cargo/config.toml b/.cargo/config.toml index b807e57..d7d8b34 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,8 @@ [target.aarch64-unknown-linux-gnu] linker="aarch64-linux-gnu-gcc" +[env] +XMPD_MANIFEST_PATH="./manifest.json" +XMPD_SETTINGS_PATH="./settings.toml" +XMPD_CACHE_PATH="./cache" + diff --git a/Cargo.lock b/Cargo.lock index 8f91ecd..1517f5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1032,6 +1032,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -2406,6 +2427,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "orbclient" version = "0.3.48" @@ -2687,6 +2714,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.11.1" @@ -2920,6 +2958,15 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3255,11 +3302,26 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -3279,6 +3341,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.6.20", ] @@ -4322,19 +4386,13 @@ dependencies = [ "anyhow", "camino", "clap", + "dirs", "env_logger", "log", "xmpd-cli", "xmpd-gui", "xmpd-manifest", -] - -[[package]] -name = "xmpd-derive" -version = "2.0.0" -dependencies = [ - "quote", - "syn 2.0.87", + "xmpd-settings", ] [[package]] @@ -4354,8 +4412,8 @@ dependencies = [ "log", "tokio", "uuid", - "xmpd-derive", "xmpd-manifest", + "xmpd-settings", ] [[package]] @@ -4369,6 +4427,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "xmpd-settings" +version = "2.0.0" +dependencies = [ + "anyhow", + "egui 0.27.2", + "lazy_static", + "serde", + "toml", +] + [[package]] name = "zbus" version = "3.15.2" diff --git a/Cargo.toml b/Cargo.toml index 176ce37..9b4eb7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,9 @@ members=[ "xmpd-core", "xmpd-manifest", "xmpd-gui", - "xmpd-cli", "xmpd-dl", "xmpd-derive", + #"xmpd-cli", + "xmpd-dl", + "xmpd-settings", # "xmpd-tui" ] @@ -25,7 +27,7 @@ bitflags = { version = "2.6.0", features = ["serde"] } camino = "1.1.6" clap = { version = "4.5.4", features = ["derive"] } eframe = "0.27.2" -egui = { version = "0.27.2", features = ["color-hex"] } +egui = { version = "0.27.2", features = ["color-hex", "serde"] } egui_extras = { version = "0.27.2", features = ["all_loaders"] } env_logger = "0.11.3" futures = "0.3.30" @@ -44,4 +46,5 @@ tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread", "process" url = { version = "2.5.0", features = ["serde"] } uuid = { version = "1.11.0", features = ["serde", "v4"] } windows = { version = "0.56.0", features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_Console"] } -zip-extensions = "0.6.2" +zip-extensions = "0.6.20" +dirs="5.0.1" diff --git a/xmpd-core/Cargo.toml b/xmpd-core/Cargo.toml index 4131251..b28266b 100644 --- a/xmpd-core/Cargo.toml +++ b/xmpd-core/Cargo.toml @@ -21,11 +21,13 @@ name="xmpd" path="src/main.rs" [dependencies] -xmpd-cli={ path="../xmpd-cli" } -xmpd-gui={ path="../xmpd-gui" } -xmpd-manifest={ path="../xmpd-manifest" } +xmpd-cli.path="../xmpd-cli" +xmpd-gui.path="../xmpd-gui" +xmpd-manifest.path="../xmpd-manifest" +xmpd-settings.path = "../xmpd-settings" clap.workspace=true camino.workspace = true anyhow.workspace = true log.workspace = true env_logger.workspace = true +dirs.workspace = true diff --git a/xmpd-core/src/cli.rs b/xmpd-core/src/cli.rs index 1d56691..af74445 100644 --- a/xmpd-core/src/cli.rs +++ b/xmpd-core/src/cli.rs @@ -1,18 +1,75 @@ -use std::path::{Path, PathBuf}; +use std::{path::PathBuf, str::FromStr}; #[derive(Debug, clap::Parser)] pub struct CliArgs { /// Manifest path - #[arg(long, short)] - manifest: Option, + #[arg(long, short, default_value_t=get_default_manifest_path())] + manifest: camino::Utf8PathBuf, + /// settings file path + #[arg(long, short, default_value_t=get_default_settings_path())] + settings: camino::Utf8PathBuf, + /// Cache dir path + #[arg(long, short, default_value_t=get_default_cache_path())] + cache: camino::Utf8PathBuf, /// Debug mode #[arg(long, short)] pub debug: bool, } impl CliArgs { - pub fn manifest_path(&self) -> Option { - Some(self.manifest.clone()?.into_std_path_buf()) + pub fn manifest_path(&self) -> PathBuf { + self.manifest.clone().into_std_path_buf() + } + pub fn settings_path(&self) -> PathBuf { + self.settings.clone().into_std_path_buf() + } + pub fn cache_path(&self) -> PathBuf { + self.cache.clone().into_std_path_buf() } } + +#[allow(irrefutable_let_patterns)] // Broken? +fn get_default_settings_path() -> camino::Utf8PathBuf { + if let Ok(p) = std::env::var("XMPD_SETTINGS_PATH") { + if let Ok(p) = camino::Utf8PathBuf::from_str(&p) { + return p; + } + } + + if let Some(mut p) = dirs::config_dir() { + p.push("xmpd"); + p.push("config.toml"); + return camino::Utf8PathBuf::from_path_buf(p).expect("Invalid os path"); + } + unreachable!() +} + +#[allow(irrefutable_let_patterns)] // Broken? +fn get_default_manifest_path() -> camino::Utf8PathBuf { + if let Ok(p) = std::env::var("XMPD_MANIFEST_PATH") { + if let Ok(p) = camino::Utf8PathBuf::from_str(&p) { + return p; + } + } + if let Some(mut p) = dirs::config_dir() { + p.push("xmpd"); + p.push("manifest.json"); + return camino::Utf8PathBuf::from_path_buf(p).expect("Invalid os path"); + } + unreachable!() +} + +#[allow(irrefutable_let_patterns)] // Broken? +fn get_default_cache_path() -> camino::Utf8PathBuf { + if let Ok(p) = std::env::var("XMPD_CACHE_PATH") { + if let Ok(p) = camino::Utf8PathBuf::from_str(&p) { + return p; + } + } + if let Some(mut p) = dirs::cache_dir() { + p.push("xmpd"); + return camino::Utf8PathBuf::from_path_buf(p).expect("Invalid os path"); + } + unreachable!() +} diff --git a/xmpd-core/src/logger.rs b/xmpd-core/src/logger.rs index 4447813..5347cf2 100644 --- a/xmpd-core/src/logger.rs +++ b/xmpd-core/src/logger.rs @@ -8,10 +8,11 @@ pub fn init(cliargs: &CliArgs) { let level = if cliargs.debug { LevelFilter::Debug } else { LevelFilter::Info }; env_logger::builder() .format_timestamp(None) - .filter(Some("xmpd_core"), level) + .filter(Some("xmpd"), level) .filter(Some("xmpd_cli"), level) .filter(Some("xmpd_gui"), level) .filter(Some("xmpd_manifest"), level) + .filter(Some("xmpd_config"), level) .filter(Some("xmpd_dl"), level) .init(); } diff --git a/xmpd-core/src/main.rs b/xmpd-core/src/main.rs index c054608..6cd9278 100644 --- a/xmpd-core/src/main.rs +++ b/xmpd-core/src/main.rs @@ -1,5 +1,3 @@ -use std::path::{Path, PathBuf}; - use clap::Parser; mod cli; @@ -10,12 +8,8 @@ type Result = anyhow::Result; fn main() -> Result<()> { let cliargs = cli::CliArgs::parse(); logger::init(&cliargs); - let manifest_path; - if let Some(mp) = cliargs.manifest_path() { - manifest_path = mp; - } else { - manifest_path = PathBuf::from("manifest.json"); - }; - xmpd_gui::start(manifest_path)?; + log::debug!("Cli: {cliargs:?}"); + xmpd_settings::Settings::get()?.load(Some(cliargs.settings_path()))?; + xmpd_gui::start(cliargs.manifest_path())?; Ok(()) } diff --git a/xmpd-derive/src/lib.rs b/xmpd-derive/src/lib.rs deleted file mode 100644 index 9319f66..0000000 --- a/xmpd-derive/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -use proc_macro::TokenStream; - -#[macro_use] -extern crate quote; -#[macro_use] -extern crate syn; -extern crate proc_macro; - - -#[proc_macro_derive(UiComponent)] -pub fn ui_comp_impl(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); - - let name = &ast.ident; - let vis = &ast.vis; - let attr = &ast.attrs; - let data = &ast.data; - let generics = &ast.generics; - - let gen = quote! { - impl xmp - - }; - - gen.into() -} diff --git a/xmpd-dl/src/lib.rs b/xmpd-dl/src/lib.rs index e7a11a9..e69de29 100644 --- a/xmpd-dl/src/lib.rs +++ b/xmpd-dl/src/lib.rs @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/xmpd-gui/Cargo.toml b/xmpd-gui/Cargo.toml index 3fa8ef9..45b00d6 100644 --- a/xmpd-gui/Cargo.toml +++ b/xmpd-gui/Cargo.toml @@ -19,7 +19,7 @@ bench = false [dependencies] xmpd-manifest.path = "../xmpd-manifest" -xmpd-derive.path = "../xmpd-derive" +xmpd-settings.path = "../xmpd-settings" egui.workspace = true eframe.workspace = true tokio.workspace = true diff --git a/xmpd-gui/src/components/left_nav.rs b/xmpd-gui/src/components/left_nav.rs index e7aa40a..1c3c7e5 100644 --- a/xmpd-gui/src/components/left_nav.rs +++ b/xmpd-gui/src/components/left_nav.rs @@ -43,12 +43,13 @@ impl CompUi for LeftNav { } fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option, title: &str, author: Option<&str>, song_count: usize, width: f32) { + let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone(); let wdg_rect = ui.horizontal(|ui| { ui.set_width(width); ui.add_space(5.0); ui.add( egui::Image::new(crate::data::NOTE_ICON) - .tint(crate::data::C_ACCENT) + .tint(theme.accent_color) .fit_to_exact_size(egui::Vec2::new(32.0, 32.0)) ); ui.vertical(|ui| { @@ -57,7 +58,7 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option, title: &str, au ui.label( RichText::new(title) .size(10.0) - .color(crate::data::C_ACCENT) + .color(theme.accent_color) ); } else { ui.label( @@ -69,13 +70,13 @@ fn add_playlist_tab(ui: &mut egui::Ui, pid: &Option, title: &str, au if let Some(author) = author { ui.monospace( RichText::new(format!("By {author}")) - .color(crate::data::C_TEXT_DIM) + .color(theme.dim_text_color) .size(8.0) ); } ui.monospace( RichText::new(format!("{song_count} songs")) - .color(crate::data::C_TEXT_DIM) + .color(theme.dim_text_color) .size(8.0) ); }); diff --git a/xmpd-gui/src/components/mod.rs b/xmpd-gui/src/components/mod.rs index ea33e3e..3d75592 100644 --- a/xmpd-gui/src/components/mod.rs +++ b/xmpd-gui/src/components/mod.rs @@ -1,4 +1,4 @@ -use std::sync::{MutexGuard, PoisonError}; +use std::sync::MutexGuard; use crate::GuiState; diff --git a/xmpd-gui/src/components/player.rs b/xmpd-gui/src/components/player.rs index a36005b..b3832ac 100644 --- a/xmpd-gui/src/components/player.rs +++ b/xmpd-gui/src/components/player.rs @@ -13,7 +13,8 @@ component_register!(Player); impl CompUi for Player { - fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { + fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { + let theme = xmpd_settings::Settings::get()?.theme.clone(); let avail = ui.available_size(); ui.vertical_centered_justified(|ui| { ui.add_space(3.0); @@ -25,7 +26,7 @@ impl CompUi for Player { .show_value(false); ui.style_mut().spacing.slider_width = avail.x * 0.90; let s = Stroke { - color: crate::data::C_ACCENT, + color: theme.accent_color, width: 2.0 }; ui.style_mut().visuals.widgets.inactive.fg_stroke = s; @@ -45,15 +46,15 @@ impl CompUi for Player { let prev = egui::Image::new(crate::data::PREV_ICON) - .tint(crate::data::C_ACCENT) + .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(16.0, 16.0)); let pp = egui::Image::new(pp) - .tint(crate::data::C_ACCENT) + .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(16.0, 16.0)); let next = egui::Image::new(crate::data::NEXT_ICON) - .tint(crate::data::C_ACCENT) + .tint(theme.accent_color) .sense(Sense::click()) .max_size(Vec2::new(16.0, 16.0)); if ui.add(prev).clicked() {} diff --git a/xmpd-gui/src/components/song_list/mod.rs b/xmpd-gui/src/components/song_list/mod.rs index 0f0a8e2..7a03587 100644 --- a/xmpd-gui/src/components/song_list/mod.rs +++ b/xmpd-gui/src/components/song_list/mod.rs @@ -1,6 +1,6 @@ -use egui::{Color32, RichText, Vec2}; +use egui::{RichText, Vec2}; use song_list_nav::SearchType; -use xmpd_manifest::{query::QueryType, song::Song, store::BaseStore}; +use xmpd_manifest::{song::Song, store::BaseStore}; use super::{CompGetter, CompUi}; pub mod song_list_nav; @@ -79,23 +79,23 @@ impl CompUi for SongList { } fn display_song_tab(ui: &mut egui::Ui, sid: &uuid::Uuid, song: &Song) { - + let theme = handle_error_ui!(xmpd_settings::Settings::get()).theme.clone(); let rct = ui.horizontal(|ui| { ui.add( egui::Image::new(crate::data::NOTE_ICON) - .tint(crate::data::C_ACCENT) + .tint(theme.accent_color) .fit_to_exact_size(Vec2::new(32.0, 32.0)) ); ui.vertical(|ui| { let selected_song_id = {handle_error_ui!(SongList::get()).selected_song_id}; if selected_song_id == *sid { - ui.label(RichText::new(song.name()).color(crate::data::C_ACCENT)); + ui.label(RichText::new(song.name()).color(theme.accent_color)); } else { ui.label(song.name()); }; ui.monospace( RichText::new(format!("By {}", song.author())) - .color(crate::data::C_TEXT_DIM) + .color(theme.dim_text_color) .size(10.0) ); }); diff --git a/xmpd-gui/src/components/song_list/song_list_nav.rs b/xmpd-gui/src/components/song_list/song_list_nav.rs index ee8c992..a6a159f 100644 --- a/xmpd-gui/src/components/song_list/song_list_nav.rs +++ b/xmpd-gui/src/components/song_list/song_list_nav.rs @@ -16,10 +16,11 @@ component_register!(SongListNav); impl CompUi for SongListNav { fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { + let theme = xmpd_settings::Settings::get()?.theme.clone(); 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(crate::data::C_ACCENT); + .tint(theme.accent_color); ui.add(search_icon); { ui.text_edit_singleline(&mut handle_error_ui!(SongListNav::get()).text); diff --git a/xmpd-gui/src/components/top_nav.rs b/xmpd-gui/src/components/top_nav.rs index c60dd50..4ac30d4 100644 --- a/xmpd-gui/src/components/top_nav.rs +++ b/xmpd-gui/src/components/top_nav.rs @@ -1,12 +1,8 @@ use super::CompUi; - - #[derive(Debug, Default)] pub struct TopNav; -component_register!(TopNav); - impl CompUi for TopNav { fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { ui.add_space(3.0); @@ -14,7 +10,15 @@ impl CompUi for TopNav { ui.add_space(3.0); egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { - // TBD + if ui.button("Settings").clicked() { + + + } + }); + ui.menu_button("Manifest", |ui| { + if ui.button("Save").clicked() { + handle_error_ui!(state.manifest.save()); + } }); ui.menu_button("Help", |ui| { if ui.button("Source").clicked() { @@ -22,12 +26,7 @@ impl CompUi for TopNav { } }); - ui.menu_button("Manifest", |ui| { - if ui.button("Save").clicked() { - handle_error_ui!(state.manifest.save()); - } - - }); + }); }); diff --git a/xmpd-gui/src/data.rs b/xmpd-gui/src/data.rs index dc80a46..8c86217 100644 --- a/xmpd-gui/src/data.rs +++ b/xmpd-gui/src/data.rs @@ -1,18 +1,8 @@ // pub const APP_ICON: egui::ImageSource = egui::include_image!("../../assets/app_icon.png"); -pub const APP_ICON_BYTES: &[u8] = include_bytes!("../../assets/app_icon.png"); +// pub const APP_ICON_BYTES: &[u8] = include_bytes!("../../assets/app_icon.png"); pub const NOTE_ICON: egui::ImageSource = egui::include_image!("../../assets/note.svg"); pub const SEARCH_ICON: egui::ImageSource = egui::include_image!("../../assets/search.svg"); pub const PREV_ICON: egui::ImageSource = egui::include_image!("../../assets/prev.svg"); pub const NEXT_ICON: egui::ImageSource = egui::include_image!("../../assets/next.svg"); pub const PLAY_ICON: egui::ImageSource = egui::include_image!("../../assets/play.svg"); pub const PAUSE_ICON: egui::ImageSource = egui::include_image!("../../assets/pause.svg"); - - -// TODO: Replace this with config for theming -// pub const C_ACCENT: egui::Color32 = egui::Color32::from_rgb(51, 51, 119); -pub const C_ACCENT: egui::Color32 = egui::Color32::from_rgb(5, 102, 146); // #0566F6 -pub const C_PRIM_BG: egui::Color32 = egui::Color32::from_rgb(31, 34, 40); // #1F2228 -pub const C_SEC_BG: egui::Color32 = egui::Color32::from_rgb(47, 53, 61); // #2f353d -pub const C_TEXT: egui::Color32 = egui::Color32::from_rgb(223, 223, 223); // #dfdfdf -pub const C_TEXT_DIM: egui::Color32 = egui::Color32::from_rgb(175, 175, 175 ); // #afafaf - diff --git a/xmpd-gui/src/lib.rs b/xmpd-gui/src/lib.rs index 8f95455..731c335 100644 --- a/xmpd-gui/src/lib.rs +++ b/xmpd-gui/src/lib.rs @@ -1,8 +1,6 @@ #![feature(async_closure)] use std::{path::{Path, PathBuf}, time::Duration}; -use egui::TextStyle; -use windows::WindowId; use xmpd_manifest::{store::JsonStore, Manifest}; #[macro_use] @@ -20,21 +18,21 @@ type Result = anyhow::Result; pub fn start(manifest_path: PathBuf) -> Result<()> { let options = eframe::NativeOptions::default(); let mut state = GuiState::new(&manifest_path)?; - + let theme = xmpd_settings::Settings::get()?.theme.clone(); let res = eframe::run_simple_native(W_NAME, options, move |ctx, _frame| { egui_extras::install_image_loaders(ctx); state.windows.clone().draw_all(ctx, &mut state); egui::CentralPanel::default() .frame( egui::Frame::none() - .fill(data::C_PRIM_BG) + .fill(theme.primary_bg_color) .stroke(egui::Stroke::new( 1.0, - data::C_SEC_BG, + theme.secondary_bg_color, )), ) .show(ctx, |ui| { - ui.style_mut().visuals.override_text_color = Some(crate::data::C_TEXT); + ui.style_mut().visuals.override_text_color = Some(theme.text_color); main_window::draw(ui, &mut state) }); ctx.request_repaint_after(Duration::from_millis(500)); diff --git a/xmpd-gui/src/main_window.rs b/xmpd-gui/src/main_window.rs index bcedf6d..220131d 100644 --- a/xmpd-gui/src/main_window.rs +++ b/xmpd-gui/src/main_window.rs @@ -1,17 +1,13 @@ -use egui::ViewportId; -use xmpd_manifest::store::JsonStore; - -use crate::{components::{CompGetter, CompUi}, GuiState}; - - +use crate::{components::CompUi, GuiState}; pub fn draw(ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()> { // The central panel the region left after adding TopPanel's and SidePanel's // ui.heading(format!("Songs ({})", self.manifest.get_song_count())); + let theme = xmpd_settings::Settings::get()?.theme.clone(); let avail = ui.available_size(); ui.vertical(|ui| { handle_error_ui!(crate::components::top_nav::TopNav::draw(ui, state)); - crate::utils::super_separator(ui, crate::data::C_ACCENT, avail.x, 2.0); + crate::utils::super_separator(ui, theme.accent_color, avail.x, 2.0); let avail = ui.available_size(); let main_height = avail.y * 0.91; @@ -35,15 +31,15 @@ pub fn draw(ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()> { egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "player") .frame( egui::Frame::none() - .fill(crate::data::C_PRIM_BG) + .fill(theme.primary_bg_color) .stroke(egui::Stroke::new( 1.0, - crate::data::C_SEC_BG, + theme.secondary_bg_color, )), ) .show(ui.ctx(), |ui| { - ui.style_mut().visuals.override_text_color = Some(crate::data::C_TEXT); + ui.style_mut().visuals.override_text_color = Some(theme.accent_color); handle_error_ui!(crate::components::player::Player::draw(ui, state)); }); }); diff --git a/xmpd-gui/src/utils.rs b/xmpd-gui/src/utils.rs index b023854..c772aaa 100644 --- a/xmpd-gui/src/utils.rs +++ b/xmpd-gui/src/utils.rs @@ -2,7 +2,6 @@ pub fn super_separator(ui: &mut egui::Ui, color: egui::Color32, width: f32, height: f32) { egui::Frame::none() .fill(color) - // .stroke(egui::Stroke { color: crate::data::C_ACCENT, width: 1.0 }) .show(ui, |ui| { ui.set_width(width); ui.set_height(height); diff --git a/xmpd-gui/src/windows/error.rs b/xmpd-gui/src/windows/error.rs index 0ff7ef1..5992f74 100644 --- a/xmpd-gui/src/windows/error.rs +++ b/xmpd-gui/src/windows/error.rs @@ -7,7 +7,7 @@ pub struct ErrorW { } impl Window for ErrorW { - fn draw(&self, ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> { + fn draw(&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 5d9610c..c6cfd8d 100644 --- a/xmpd-gui/src/windows/mod.rs +++ b/xmpd-gui/src/windows/mod.rs @@ -1,8 +1,9 @@ -use std::{collections::{HashMap, HashSet}, ops::Deref, sync::{Arc, Mutex}}; +use std::{collections::{HashMap, HashSet}, sync::{Arc, Mutex}}; use egui::{ViewportBuilder, ViewportId}; use crate::GuiState; mod error; +mod settings; lazy_static::lazy_static!( static ref WINDOWS: Arc>>> = Arc::new(Mutex::new(HashMap::new())); @@ -16,6 +17,7 @@ pub trait Window: std::fmt::Debug + Send { #[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] pub enum WindowId { + Settings, Error } @@ -49,7 +51,6 @@ impl Windows { if self.is_open(&win_id) { ctx.show_viewport_immediate(vp_id.clone(), builder.clone(), |ctx, _vp_class| { ctx.input(|inp| { - // println!("CLose requested: {}",inp.viewport().close_requested() ); self.toggle(win_id, !inp.viewport().close_requested()); }); egui::CentralPanel::default().show(ctx, |ui| { diff --git a/xmpd-gui/src/windows/settings.rs b/xmpd-gui/src/windows/settings.rs new file mode 100644 index 0000000..8baf21e --- /dev/null +++ b/xmpd-gui/src/windows/settings.rs @@ -0,0 +1,14 @@ +use super::Window; + + +#[derive(Debug, Clone)] +pub struct SettingsW { + +} + + +impl Window for SettingsW { + fn draw(&self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> { + Ok(()) + } +} diff --git a/xmpd-derive/Cargo.toml b/xmpd-settings/Cargo.toml similarity index 51% rename from xmpd-derive/Cargo.toml rename to xmpd-settings/Cargo.toml index 241c8e1..ec744d6 100644 --- a/xmpd-derive/Cargo.toml +++ b/xmpd-settings/Cargo.toml @@ -1,15 +1,14 @@ [package] -name = "xmpd-derive" +name = "xmpd-settings" edition = "2021" version.workspace = true repository.workspace = true license.workspace = true authors.workspace = true -[lib] -proc-macro=true - [dependencies] -quote = "1.0.37" -syn = "2.0.87" -# xmpd-gui.path = "../xmpd-gui" +anyhow.workspace = true +egui.workspace = true +lazy_static.workspace = true +serde.workspace = true +toml = "0.8.19" diff --git a/xmpd-settings/src/lib.rs b/xmpd-settings/src/lib.rs new file mode 100644 index 0000000..46729a9 --- /dev/null +++ b/xmpd-settings/src/lib.rs @@ -0,0 +1,49 @@ +use std::{path::PathBuf, sync::{Arc, Mutex, MutexGuard}}; +use serde::{Deserialize, Serialize}; +use theme::Theme; + +mod theme; + +lazy_static::lazy_static!( + static ref SETTINGS: Arc> = Arc::new(Mutex::new(Settings::default())); +); + +pub type Result = anyhow::Result; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Settings { + #[serde(skip)] + settings_path: PathBuf, + pub theme: Theme, +} + +impl Settings { + pub fn get() -> crate::Result> { + match SETTINGS.lock() { + Ok(l) => Ok(l), + Err(e) => Err(anyhow::anyhow!(format!("{e:?}"))), + } + } + pub fn load(&mut self, path: Option) -> Result<()> { + let path = path.unwrap_or(self.settings_path.clone()); + if !path.exists() { + std::fs::write(&path, "[theme]")?; + self.save(Some(path.clone()))?; + } + let data = std::fs::read_to_string(&path)?; + let data: Self = toml::from_str(&data)?; + *self = data; + self.settings_path = path; + Ok(()) + } + + pub fn save(&mut self, path: Option) -> Result<()> { + let path = path.unwrap_or(self.settings_path.clone()); + let data = toml::to_string_pretty(&self)?; + std::fs::write(&path, data)?; + self.settings_path = path; + Ok(()) + } +} + + diff --git a/xmpd-settings/src/theme.rs b/xmpd-settings/src/theme.rs new file mode 100644 index 0000000..3894829 --- /dev/null +++ b/xmpd-settings/src/theme.rs @@ -0,0 +1,47 @@ +use serde::{Deserialize, Serialize}; + + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Theme { + #[serde(default="Theme::default_accent_color")] + pub accent_color: egui::Color32, + #[serde(default="Theme::default_primary_bg_color")] + pub primary_bg_color: egui::Color32, + #[serde(default="Theme::default_secondary_bg_color")] + pub secondary_bg_color: egui::Color32, + #[serde(default="Theme::default_text_color")] + pub text_color: egui::Color32, + #[serde(default="Theme::default_dim_text_color")] + pub dim_text_color: egui::Color32, +} + +impl Default for Theme { + fn default() -> Self { + Self { + accent_color: Self::default_accent_color(), + primary_bg_color: Self::default_primary_bg_color(), + secondary_bg_color: Self::default_secondary_bg_color(), + text_color: Self::default_text_color(), + dim_text_color: Self::default_dim_text_color(), + } + } +} + +impl Theme { + fn default_accent_color() -> egui::Color32 { + egui::Color32::from_rgb(5, 102, 146) // #0566F6 + } + fn default_primary_bg_color() -> egui::Color32 { + egui::Color32::from_rgb(31, 34, 40) // #1F2228 + } + fn default_secondary_bg_color() -> egui::Color32 { + egui::Color32::from_rgb(47, 53, 61) // #2f353d + } + fn default_text_color() -> egui::Color32 { + egui::Color32::from_rgb(223, 223, 223) // #dfdfdf + } + fn default_dim_text_color() -> egui::Color32 { + egui::Color32::from_rgb(175, 175, 175 ) // #afafaf + } + +}