Rewrite the structure of the gui and cli interfaces
Added a more modular way to add windows
This commit is contained in:
55
src/ui/cli/add.rs
Normal file
55
src/ui/cli/add.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::bail;
|
||||
|
||||
use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::Song, Manifest}, util::is_supported_host};
|
||||
|
||||
|
||||
|
||||
pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String, playlist: &String) -> anyhow::Result<()> {
|
||||
|
||||
let mut playlists = manifest.get_playlists().keys().map(|f| f.clone()).collect::<Vec<String>>();
|
||||
|
||||
playlists.sort();
|
||||
|
||||
if !is_supported_host(url::Url::from_str(&url)?) {
|
||||
log::error!("Invalid or unsupported host name");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
|
||||
let song = Song::from_url_str(url.clone())?;
|
||||
manifest.add_song(playlist, name.clone(), song.clone());
|
||||
manifest.save(None)?;
|
||||
|
||||
let should_download = crate::prompt::prompt_bool("Download song now?", Some(false));
|
||||
|
||||
if should_download {
|
||||
downloader.download_song(cfg, &name, &song, &playlist, manifest.get_format())?;
|
||||
crate::process_manager::wait_for_procs_untill(0)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_playlist(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String) -> anyhow::Result<()> {
|
||||
let songs = downloader.download_playlist_nb(cfg, url, name, manifest.get_format())?;
|
||||
|
||||
if manifest.get_playlist(name).is_some() {
|
||||
log::error!("Playlist {name} already exists");
|
||||
bail!("")
|
||||
}
|
||||
|
||||
manifest.add_playlist(name.clone());
|
||||
|
||||
let playlist = manifest.get_playlist_mut(name).expect("Unreachable");
|
||||
|
||||
for (sname, song) in songs {
|
||||
playlist.add_song(sname, song);
|
||||
}
|
||||
manifest.save(None)?;
|
||||
|
||||
while downloader.download_all_nb_poll(cfg)?.is_some() {};
|
||||
Ok(())
|
||||
}
|
||||
46
src/ui/cli/mod.rs
Normal file
46
src/ui/cli/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
mod add;
|
||||
|
||||
use crate::{config::{cli::CliCommand, ConfigWrapper}, downloader::Downloader, manifest::Manifest, ui::gui};
|
||||
|
||||
|
||||
|
||||
pub async fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow::Result<()> {
|
||||
log::info!("Is in term: {}", cfg.isatty);
|
||||
//std::fs::write("./isatty", format!("{}\n", cfg.isatty))?;
|
||||
|
||||
let mut downloader = Downloader::new();
|
||||
match (&cfg.cli.command, cfg.isatty) {
|
||||
(None | Some(CliCommand::Download), true) => {
|
||||
match downloader.download_all(manifest, &cfg).await {
|
||||
Ok(count) => log::info!("Downloaded {count} songs"),
|
||||
Err(e) => {
|
||||
log::error!("Failed to download songs: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
},
|
||||
(Some(c), _) => {
|
||||
match c {
|
||||
CliCommand::Download => unreachable!(),
|
||||
CliCommand::AddPlaylist { url, name } => {
|
||||
if let Err(e) = add::add_playlist(cfg, manifest, &mut downloader, url, name).await {
|
||||
log::error!("Failed to run 'add-playlist' commmand: {e}");
|
||||
}
|
||||
}
|
||||
CliCommand::Add { url, name, playlist } => {
|
||||
if let Err(e) = add::add(cfg, manifest, &mut downloader, url, name, playlist).await {
|
||||
log::error!("Failed to run 'add' command: {e}");
|
||||
}
|
||||
}
|
||||
CliCommand::Gui => {
|
||||
gui::Gui::start(manifest.clone(), downloader, cfg.clone())?;
|
||||
},
|
||||
}
|
||||
}
|
||||
(None, false) => {
|
||||
gui::Gui::start(manifest.clone(), downloader, cfg.clone())?;
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
0
src/ui/gui/components/mod.rs
Normal file
0
src/ui/gui/components/mod.rs
Normal file
253
src/ui/gui/mod.rs
Normal file
253
src/ui/gui/mod.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
mod nav_bar;
|
||||
mod windows;
|
||||
mod components;
|
||||
|
||||
use egui::{Color32, RichText};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use windows::{State, WindowIndex, WindowManager};
|
||||
|
||||
use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::{Song, SongType}, Manifest}};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Gui {
|
||||
windows: WindowManager,
|
||||
manifest: Manifest,
|
||||
filter: String,
|
||||
downloader: Downloader,
|
||||
cfg: ConfigWrapper,
|
||||
downloading: bool,
|
||||
}
|
||||
|
||||
impl Gui {
|
||||
fn new(_: &eframe::CreationContext<'_>, manifest: Manifest, downloader: Downloader, cfg: ConfigWrapper) -> Self {
|
||||
Self {
|
||||
manifest,
|
||||
downloader,
|
||||
cfg,
|
||||
windows: windows::WindowManager::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(manifest: Manifest, downloader: Downloader, cfg: ConfigWrapper) -> anyhow::Result<()> {
|
||||
let native_options = eframe::NativeOptions {
|
||||
viewport: egui::ViewportBuilder::default()
|
||||
.with_inner_size([400.0, 300.0])
|
||||
.with_min_inner_size([300.0, 220.0]),
|
||||
// .with_icon(
|
||||
// // NOTE: Adding an icon is optional
|
||||
// eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..])
|
||||
// .expect("Failed to load icon"),
|
||||
// ),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Err(e) = eframe::run_native(
|
||||
"eframe template",
|
||||
native_options,
|
||||
Box::new(|cc| Box::new(Gui::new(cc, manifest, downloader, cfg))),
|
||||
) {
|
||||
log::error!("Failed to create window: {e}");
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn throw_error(&mut self, text: impl ToString) {
|
||||
let w = self.windows.get_window::<windows::error::GuiError>(WindowIndex::Error);
|
||||
w.set_error_message(text);
|
||||
self.windows.open(WindowIndex::Error, true);
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Gui {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
self.draw_nav(ctx, frame);
|
||||
{
|
||||
let mut state = State {
|
||||
cfg: self.cfg.clone(),
|
||||
downloader: self.downloader.clone(),
|
||||
manifest: self.manifest.clone(),
|
||||
};
|
||||
self.windows.ui(&mut state, ctx).unwrap();
|
||||
self.cfg = state.cfg;
|
||||
self.downloader = state.downloader;
|
||||
self.manifest = state.manifest;
|
||||
}
|
||||
|
||||
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::<windows::song_edit::GuiSongEditor>(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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
|
||||
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
|
||||
egui::warn_if_debug_build(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
62
src/ui/gui/nav_bar.rs
Normal file
62
src/ui/gui/nav_bar.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use super::{windows::WindowIndex, Gui};
|
||||
|
||||
impl Gui {
|
||||
|
||||
pub fn draw_nav(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
|
||||
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| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Source").clicked() {
|
||||
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) {
|
||||
log::error!("Failed to save manifest: {e}");
|
||||
}
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Song", |ui| {
|
||||
if ui.button("Add New").clicked() {
|
||||
self.windows.open(WindowIndex::SongNew, true);
|
||||
}
|
||||
});
|
||||
|
||||
ui.menu_button("Playlist", |ui| {
|
||||
if ui.button("Import").clicked() {
|
||||
self.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) {
|
||||
log::error!("Err: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
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 {
|
||||
let _ = notify_rust::Notification::new()
|
||||
.summary("Done downloading")
|
||||
.body("Your music has been downloaded")
|
||||
.show();
|
||||
self.downloading = false;
|
||||
}
|
||||
let _ = self.downloader.download_all_nb_poll(&self.cfg);
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
32
src/ui/gui/windows/error.rs
Normal file
32
src/ui/gui/windows/error.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use egui::{Color32, Label, RichText};
|
||||
|
||||
use super::{State, Window};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiError {
|
||||
text: String,
|
||||
}
|
||||
|
||||
|
||||
impl Window for GuiError {
|
||||
fn ui(&mut self, _: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> {
|
||||
egui::Window::new("ERROR!!!! D:")
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(RichText::new("Error:").size(30.0).color(Color32::RED));
|
||||
ui.horizontal(|ui| {
|
||||
ui.add(Label::new(self.text.clone()).wrap(true));
|
||||
})
|
||||
})
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuiError {
|
||||
pub fn set_error_message<S: ToString>(&mut self, text: S) {
|
||||
self.text = text.to_string();
|
||||
}
|
||||
}
|
||||
59
src/ui/gui/windows/import_playlist.rs
Normal file
59
src/ui/gui/windows/import_playlist.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use super::{State, Window};
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiImportPlaylist {
|
||||
ed_name: String,
|
||||
ed_url: String,
|
||||
}
|
||||
|
||||
|
||||
impl Window for GuiImportPlaylist {
|
||||
fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> {
|
||||
let mut save = false;
|
||||
egui::Window::new("Import Playlist")
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type: Youtube");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name: ");
|
||||
ui.text_edit_singleline(&mut self.ed_name);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Url: ");
|
||||
ui.text_edit_singleline(&mut self.ed_url);
|
||||
});
|
||||
|
||||
if ui.button("Import").clicked() {
|
||||
save = true;
|
||||
}
|
||||
});
|
||||
|
||||
if save {
|
||||
let name = self.ed_name.clone();
|
||||
let url = self.ed_url.clone();
|
||||
|
||||
if state.manifest.get_playlist(&name).is_some() {
|
||||
log::error!("Playlist {name} already exists");
|
||||
}
|
||||
|
||||
let songs = state.downloader.download_playlist_nb(&state.cfg, &url, &name, &state.manifest.get_format()).unwrap();
|
||||
state.manifest.add_playlist(name.clone());
|
||||
|
||||
let playlist = state.manifest.get_playlist_mut(&name).expect("Unreachable");
|
||||
|
||||
for (sname, song) in songs {
|
||||
log::info!("Added: {sname}");
|
||||
playlist.add_song(sname, song);
|
||||
}
|
||||
let _ = state.manifest.save(None);
|
||||
*open = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
76
src/ui/gui/windows/mod.rs
Normal file
76
src/ui/gui/windows/mod.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::{config::ConfigWrapper, downloader::Downloader, manifest::Manifest};
|
||||
|
||||
pub mod song_edit;
|
||||
pub mod error;
|
||||
pub mod import_playlist;
|
||||
pub mod song_new;
|
||||
|
||||
pub trait Window: std::fmt::Debug {
|
||||
fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
|
||||
pub enum WindowIndex {
|
||||
Error,
|
||||
ImportPlaylist,
|
||||
SongEdit,
|
||||
SongNew,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug,Default)]
|
||||
pub struct WindowManager {
|
||||
opened: HashMap<WindowIndex, bool>,
|
||||
windows: HashMap<WindowIndex, Box<dyn Window>>
|
||||
}
|
||||
|
||||
pub struct State {
|
||||
pub downloader: Downloader,
|
||||
pub manifest: Manifest,
|
||||
pub cfg: ConfigWrapper,
|
||||
}
|
||||
|
||||
impl WindowManager {
|
||||
pub fn new() -> Self {
|
||||
let mut windows: HashMap<WindowIndex, Box<dyn Window>> = HashMap::new();
|
||||
windows.insert(WindowIndex::Error, Box::<error::GuiError>::default());
|
||||
windows.insert(WindowIndex::ImportPlaylist, Box::<import_playlist::GuiImportPlaylist>::default());
|
||||
windows.insert(WindowIndex::SongEdit, Box::<song_edit::GuiSongEditor>::default());
|
||||
windows.insert(WindowIndex::SongNew, Box::<song_new::GuiNewSong>::default());
|
||||
Self {
|
||||
windows,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_open(&self, id: &WindowIndex) -> bool {
|
||||
*self.opened.get(id).unwrap()
|
||||
}
|
||||
|
||||
pub fn open(&mut self, id: WindowIndex, open: bool) {
|
||||
self.opened.insert(id, open);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, state: &mut State, ctx: &egui::Context) -> anyhow::Result<()> {
|
||||
for (id, window) in &mut self.windows {
|
||||
if !self.opened.contains_key(&id) {
|
||||
self.opened.insert(*id, false);
|
||||
}
|
||||
let open = self.opened.get_mut(id).unwrap();
|
||||
if let Err(e) = window.ui(state, ctx, open) {
|
||||
log::error!("Window {id:?} errored: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_window<T: Window + 'static>(&mut self, id: WindowIndex) -> &mut Box<T> {
|
||||
let w = self.windows.get_mut(&id).unwrap();
|
||||
unsafe {
|
||||
crate::util::as_any_mut(w).downcast_mut_unchecked()
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/ui/gui/windows/song_edit.rs
Normal file
95
src/ui/gui/windows/song_edit.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use anyhow::{Result, bail};
|
||||
use egui::Color32;
|
||||
use crate::ui::gui::Gui;
|
||||
|
||||
use super::{State, Window};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiSongEditor {
|
||||
song: (String, String),
|
||||
ed_url: String,
|
||||
ed_name: String,
|
||||
}
|
||||
|
||||
|
||||
impl Window for GuiSongEditor {
|
||||
fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> {
|
||||
let mut save = false;
|
||||
let (playlist, song_name) = self.song.clone();
|
||||
|
||||
if playlist.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(song) = state.manifest.get_song(&playlist, &song_name) else {
|
||||
bail!("Failed to get song (1)");
|
||||
};
|
||||
let song = song.clone();
|
||||
|
||||
egui::Window::new("Song editor")
|
||||
.open(open)
|
||||
.show(ctx,
|
||||
|ui| {
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("[");
|
||||
ui.hyperlink_to("link", song.get_url().unwrap());
|
||||
ui.label("] ");
|
||||
ui.colored_label(Color32::LIGHT_BLUE, &playlist);
|
||||
ui.label(": ");
|
||||
ui.label(&song_name)
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type: ");
|
||||
ui.label(&song.get_type().to_string());
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name: ");
|
||||
ui.text_edit_singleline(&mut self.ed_name);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Url: ");
|
||||
ui.text_edit_singleline(&mut self.ed_url);
|
||||
});
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
save = true;
|
||||
}
|
||||
});
|
||||
|
||||
if save {
|
||||
{
|
||||
let Some(song) = state.manifest.get_song_mut(&playlist, &song_name) else {
|
||||
bail!("Failed to get song (2)");
|
||||
};
|
||||
|
||||
*song.get_url_str_mut() = self.ed_url.clone();
|
||||
}
|
||||
|
||||
let Some(playlist) = state.manifest.get_playlist_mut(&playlist) else {
|
||||
bail!("Failed to get playlist");
|
||||
};
|
||||
|
||||
|
||||
playlist.remove_song(&song_name);
|
||||
playlist.add_song(self.ed_name.clone(), song);
|
||||
*open = false;
|
||||
let _ = state.manifest.save(None);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuiSongEditor {
|
||||
pub fn set_active_song(&mut self, pname: &String, sname: &String, url: &String) {
|
||||
self.song.0 = pname.clone();
|
||||
self.song.1 = sname.clone();
|
||||
self.ed_name = sname.clone();
|
||||
self.ed_url = url.clone();
|
||||
}
|
||||
}
|
||||
72
src/ui/gui/windows/song_new.rs
Normal file
72
src/ui/gui/windows/song_new.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use crate::manifest::song::{Song, SongType};
|
||||
use super::{State, Window};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GuiNewSong {
|
||||
ed_type: SongType,
|
||||
ed_name: String,
|
||||
ed_playlist: Option<String>,
|
||||
ed_url: String,
|
||||
}
|
||||
|
||||
impl Window for GuiNewSong {
|
||||
fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> {
|
||||
let mut save = false;
|
||||
egui::Window::new("New song")
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type: ");
|
||||
egui::ComboBox::from_id_source("new_song_window_type")
|
||||
.selected_text(format!("{:?}", self.ed_type))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Youtube, "Youtube");
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Spotify, "Spotify");
|
||||
ui.selectable_value(&mut self.ed_type, SongType::Soundcloud, "Soundcloud");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name: ");
|
||||
ui.text_edit_singleline(&mut self.ed_name);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Playlist: ");
|
||||
egui::ComboBox::from_id_source("new_song_window_playlist")
|
||||
.selected_text(format!("{}", self.ed_playlist.clone().unwrap_or("".to_string())))
|
||||
.show_ui(ui, |ui| {
|
||||
for p in state.manifest.get_playlists().keys() {
|
||||
ui.selectable_value(&mut self.ed_playlist, Option::Some(p.clone()), p.as_str());
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Url: ");
|
||||
ui.text_edit_singleline(&mut self.ed_url);
|
||||
});
|
||||
|
||||
if ui.button("Save").clicked() {
|
||||
save = true;
|
||||
}
|
||||
});
|
||||
|
||||
if save {
|
||||
let Some(playlist) = state.manifest.get_playlist_mut(&self.ed_playlist.clone().unwrap()) else {
|
||||
panic!("couldnt find playlist from a preset playlist list????????????");
|
||||
};
|
||||
|
||||
playlist.add_song(
|
||||
self.ed_name.clone(),
|
||||
Song::from_url_str(self.ed_url.clone()).unwrap().set_type(self.ed_type.clone()).clone()
|
||||
);
|
||||
|
||||
|
||||
let _ = state.manifest.save(None);
|
||||
*open = false;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
2
src/ui/mod.rs
Normal file
2
src/ui/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod gui;
|
||||
pub mod cli;
|
||||
Reference in New Issue
Block a user