Downloading prototype works

This commit is contained in:
2024-11-19 14:35:33 +02:00
parent b29caa58b4
commit fda77f6981
35 changed files with 1764 additions and 1257 deletions

View File

@@ -1,5 +1,8 @@
use egui::{RichText, Vec2};
use std::fmt::write;
use egui::{Color32, RichText, Sense, Vec2};
use song_list_nav::SearchType;
use xmpd_cache::DlStatus;
use xmpd_manifest::{song::Song, store::BaseStore};
use super::{CompGetter, CompUi};
@@ -80,34 +83,92 @@ 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(
ui.horizontal(|ui| {
let mut clicked = ui.add(
egui::Image::new(crate::data::NOTE_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
.fit_to_exact_size(Vec2::new(32.0, 32.0))
);
).clicked();
ui.vertical(|ui| {
let selected_song_id = {handle_error_ui!(SongList::get()).selected_song_id};
if selected_song_id == *sid {
let label = if selected_song_id == *sid {
ui.label(
RichText::new(song.name())
.color(theme.accent_color)
);
)
} else {
ui.label(
RichText::new(song.name())
.color(theme.text_color)
);
)
};
if label.clicked() {
clicked = true;
}
ui.monospace(
RichText::new(format!("By {}", song.author()))
.color(theme.dim_text_color)
.size(10.0)
);
});
}).response.rect;
if ui.interact(rct, format!("song_list_{sid:?}").into(), egui::Sense::click()).clicked() {
handle_error_ui!(SongList::get()).selected_song_id = sid.clone();
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
ui.add_space(3.0);
let status = {
handle_error_ui!(xmpd_cache::Cache::get()).get_cached_song_status(&sid).clone()
};
match status {
Some(DlStatus::Done(p)) => {
let img = ui.add(
egui::Image::new(crate::data::CHECK_ICON)
.tint(Color32::LIGHT_GREEN)
.sense(Sense::hover())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
img.on_hover_ui(|ui| {
ui.label(format!("Path: {p}"));
});
}
Some(DlStatus::Downloading) => {
ui.add(
egui::Spinner::new()
.color(theme.accent_color)
.size(16.0)
);
}
Some(DlStatus::Error(e)) => {
let img = ui.add(
egui::Image::new(crate::data::WARN_ICON)
.tint(Color32::LIGHT_YELLOW)
.sense(Sense::hover())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
img.on_hover_ui(|ui| {
ui.label(e);
});
}
None => {
let img = ui.add(
egui::Image::new(crate::data::DL_ICON)
.tint(theme.accent_color)
.sense(Sense::click())
.fit_to_exact_size(Vec2::new(16.0, 16.0))
);
if img.clicked() {
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
}
}
}
});
if clicked {
handle_error_ui!(SongList::get()).selected_song_id = sid.clone();
}
});
ui.separator();
}

View File

@@ -1,4 +1,7 @@
use crate::components::{CompGetter, CompUi};
use uuid::Uuid;
use xmpd_manifest::store::BaseStore;
use crate::components::{left_nav::LeftNav, CompGetter, CompUi};
#[derive(Debug, Clone)]
pub enum SearchType {
@@ -9,14 +12,16 @@ pub enum SearchType {
#[derive(Debug, Default)]
pub struct SongListNav {
text: String
text: String,
}
component_register!(SongListNav);
impl CompUi for SongListNav {
fn draw(ui: &mut egui::Ui, _: &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 pid = {LeftNav::get()?.selected_playlist_id.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))
@@ -25,6 +30,31 @@ impl CompUi for SongListNav {
{
ui.text_edit_singleline(&mut handle_error_ui!(SongListNav::get()).text);
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::RIGHT), |ui| {
let img = ui.add(
egui::Image::new(crate::data::DL_ICON)
.tint(theme.accent_color)
.sense(egui::Sense::click())
.fit_to_exact_size(egui::Vec2::new(16.0, 16.0))
);
if img.clicked() {
let songs: Vec<_>;
match pid {
Some(pid) => {
songs = state.manifest.store().get_playlist(&pid).unwrap().songs().to_vec();
}
None => {
songs = state.manifest.store().get_songs().keys().cloned().collect();
}
}
for sid in &songs {
if let Some(song) = state.manifest.store().get_song(&sid) {
handle_error_ui!(xmpd_cache::Cache::get()).download_to_cache(sid.clone(), song.clone())
}
}
}
});
});
Ok(())
}

View File

@@ -14,16 +14,19 @@ impl CompUi for TopNav {
ui.menu_button("File", |ui| {
if ui.button("Settings").clicked() {
state.windows.toggle(&WindowId::Settings, true);
ui.close_menu();
}
});
ui.menu_button("Manifest", |ui| {
if ui.button("Save").clicked() {
handle_error_ui!(state.manifest.save());
ui.close_menu();
}
});
ui.menu_button("Help", |ui| {
if ui.button("Source").clicked() {
ui.ctx().open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
ui.close_menu();
}
});

View File

@@ -6,3 +6,6 @@ pub const PREV_ICON: egui::ImageSource = egui::include_image!("../../assets/pre
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");
pub const CHECK_ICON: egui::ImageSource = egui::include_image!("../../assets/check.svg");
pub const DL_ICON: egui::ImageSource = egui::include_image!("../../assets/download.svg");
pub const WARN_ICON: egui::ImageSource = egui::include_image!("../../assets/warning.svg");

View File

@@ -1,6 +1,6 @@
#![feature(async_closure)]
use std::{path::{Path, PathBuf}, time::Duration};
use std::time::Duration;
use xmpd_manifest::{store::JsonStore, Manifest};
#[macro_use]
@@ -15,10 +15,10 @@ const W_NAME: &str = "xmpd v2.0.0a";
type Result<T> = anyhow::Result<T>;
pub fn start(manifest_path: PathBuf) -> Result<()> {
pub fn start() -> Result<()> {
xmpd_cache::Cache::get()?.init();
let options = eframe::NativeOptions::default();
let mut state = GuiState::new(&manifest_path)?;
let theme = xmpd_settings::Settings::get()?.theme.clone();
let mut state = GuiState::new()?;
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);
@@ -31,20 +31,15 @@ pub fn start(manifest_path: PathBuf) -> Result<()> {
Ok(())
}
pub enum Message {
}
pub struct GuiState {
pub manifest: Manifest<JsonStore>,
pub windows: windows::Windows
pub windows: windows::Windows,
}
impl GuiState {
pub fn new(manifest_path: &Path) -> Result<Self> {
pub fn new() -> Result<Self> {
Ok(Self {
manifest: Manifest::new(manifest_path)?,
manifest: Manifest::new(&xmpd_cliargs::CLIARGS.manifest_path())?,
windows: windows::Windows::new(),
})
}

View File

@@ -1,6 +1,6 @@
use xmpd_settings::theme::Theme;
use crate::{components::CompUi, GuiState};
use crate::{components::{song_list, CompUi}, GuiState};
pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
// The central panel the region left after adding TopPanel's and SidePanel's
@@ -22,19 +22,22 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
let avail = ui.available_size();
let main_height = avail.y * 0.91;
let left_nav_width = (avail.x * 0.25).clamp(0.0, 200.0);
let song_list_width = (avail.x - left_nav_width - 35.0);
ui.horizontal(|ui| {
ui.set_height(main_height);
ui.group(|ui| {
ui.set_height(main_height);
ui.set_max_width((avail.x * 0.25).clamp(0.0, 200.0));
ui.set_max_width(left_nav_width);
handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
});
ui.vertical(|ui| {
ui.group(|ui| {
ui.set_width(avail.x * 0.75);
ui.set_width(song_list_width);
handle_error_ui!(crate::components::song_list::song_list_nav::SongListNav::draw(ui, state));
});
ui.group(|ui| {
ui.set_width(song_list_width);
handle_error_ui!(crate::components::song_list::SongList::draw(ui, state));
});
});
@@ -58,7 +61,7 @@ fn get_themed_frame(theme: &Theme) -> egui::Frame {
egui::Frame::none()
.fill(theme.primary_bg_color)
.stroke(egui::Stroke::new(
1.0,
5.0,
theme.secondary_bg_color,
))
}

View File

@@ -1,40 +1,80 @@
use std::str::FromStr;
use super::Window;
#[derive(Debug, Clone)]
pub struct SettingsW {
accent_color: egui::Color32,
primary_bg_color: egui::Color32,
secondary_bg_color: egui::Color32,
text_color: egui::Color32,
dim_text_color: egui::Color32,
ytdlp_p: String,
spotdl_p: String,
ffmpeg_p: String,
song_fmt: String,
}
impl Default for SettingsW {
fn default() -> Self {
let def = xmpd_settings::theme::Theme::default();
Self {
accent_color: def.accent_color,
primary_bg_color: def.primary_bg_color,
secondary_bg_color: def.secondary_bg_color,
text_color: def.text_color,
dim_text_color: def.dim_text_color
let tooling = xmpd_settings::Settings::get().unwrap().tooling.clone();
Self {
ytdlp_p: tooling.ytdlp_path.to_string(),
spotdl_p: tooling.spotdl_path.to_string(),
ffmpeg_p: tooling.ffmpeg_path.to_string(),
song_fmt: tooling.song_format
}
}
}
impl Window for SettingsW {
#[allow(irrefutable_let_patterns)]
fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
let theme = &mut xmpd_settings::Settings::get()?.theme;
ui.group(|ui| {
ui.vertical(|ui| {
ui.heading("Theme");
Self::add_theme_button(&mut theme.accent_color, ui, "Accent");
Self::add_theme_button(&mut theme.primary_bg_color, ui, "Primary BG");
Self::add_theme_button(&mut theme.secondary_bg_color, ui, "Secondary BG");
Self::add_theme_button(&mut theme.text_color, ui, "Text");
Self::add_theme_button(&mut theme.dim_text_color, ui, "Dim Text");
ui.horizontal(|ui| {
{
let theme = &mut handle_error_ui!(xmpd_settings::Settings::get()).theme;
ui.group(|ui| {
ui.vertical(|ui| {
ui.heading("Theme");
Self::add_theme_button(&mut theme.accent_color, ui, "Accent");
Self::add_theme_button(&mut theme.primary_bg_color, ui, "Primary BG");
Self::add_theme_button(&mut theme.secondary_bg_color, ui, "Secondary BG");
Self::add_theme_button(&mut theme.text_color, ui, "Text");
Self::add_theme_button(&mut theme.dim_text_color, ui, "Dim Text");
if ui.button("Reset").clicked() {
*theme = xmpd_settings::theme::Theme::default();
}
});
});
}
{
ui.group(|ui| {
ui.vertical(|ui| {
ui.heading("Tooling paths");
Self::add_tooling_input(&mut self.ytdlp_p, ui, "stdlp");
Self::add_tooling_input(&mut self.spotdl_p, ui, "spotdl");
Self::add_tooling_input(&mut self.ffmpeg_p, ui, "ffmpeg");
Self::add_tooling_input(&mut self.song_fmt, ui, "Format");
});
});
}
});
ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
if ui.button("Save").clicked() {
let mut settings = handle_error_ui!(xmpd_settings::Settings::get());
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ytdlp_p) {
settings.tooling.ytdlp_path = p;
}
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.spotdl_p) {
settings.tooling.spotdl_path = p;
}
if let Ok(p) = camino::Utf8PathBuf::from_str(&self.ffmpeg_p) {
settings.tooling.ffmpeg_path = p;
}
settings.tooling.song_format.clone_from(&self.song_fmt);
handle_error_ui!(settings.save(None));
}
});
});
});
Ok(())
@@ -48,4 +88,10 @@ impl SettingsW {
ui.color_edit_button_srgba(rf);
});
}
fn add_tooling_input(inp: &mut String, ui: &mut egui::Ui, name: &str) {
ui.horizontal(|ui|{
ui.label(format!("{name}: "));
ui.text_edit_singleline(inp);
});
}
}