Base gui, player, side, top nav, song list

Super happy with this
This commit is contained in:
2024-11-14 00:56:02 +02:00
parent 4dcd36c3d8
commit 9bcfcb9209
22 changed files with 859 additions and 25 deletions

View File

@@ -19,9 +19,12 @@ bench = false
[dependencies]
xmpd-manifest.path = "../xmpd-manifest"
xmpd-derive.path = "../xmpd-derive"
egui.workspace = true
eframe.workspace = true
tokio.workspace = true
anyhow.workspace = true
lazy_static.workspace = true
log.workspace = true
egui_extras.workspace = true
egui-aesthetix = "0.2.4"

View File

@@ -0,0 +1,40 @@
use xmpd_manifest::store::BaseStore;
use super::CompUi;
#[derive(Debug, Default)]
pub struct LeftNav;
component_register!(LeftNav);
impl CompUi for LeftNav {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
let height = ui.available_height();
egui::ScrollArea::vertical().id_source("left_nav").show(ui, |ui| {
//ui.horizontal(|ui| {
ui.vertical(|ui| {
let playlists = state.manifest.store().get_playlists();
for (_pid, playlist) in playlists.iter() {
ui.horizontal(|ui| {
ui.add_space(5.0);
ui.add(
egui::Image::new(crate::data::NOTE_ICON)
.tint(crate::data::C_ACCENT)
.fit_to_exact_size(egui::Vec2::new(32.0, 32.0))
);
ui.label(playlist.name());
ui.with_layout(egui::Layout::bottom_up(egui::Align::RIGHT), |ui| {
ui.label(format!("{}", playlist.songs().len()));
});
});
ui.separator();
}
});
// });
});
Ok(())
}
}

View File

@@ -0,0 +1,16 @@
use std::sync::{MutexGuard, PoisonError};
use crate::GuiState;
pub mod left_nav;
pub mod song_list;
pub mod top_nav;
pub mod player;
pub trait CompUi {
fn draw(ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()>;
}
pub trait CompGetter {
fn get() -> crate::Result<MutexGuard<'static, Self>>;
}

View File

@@ -0,0 +1,55 @@
use egui::{Stroke, Vec2};
use super::{CompGetter, CompUi};
#[derive(Debug, Default)]
pub struct Player {
slider_progress: usize
}
component_register!(Player);
impl CompUi for Player {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
let avail = ui.available_size();
ui.vertical_centered_justified(|ui| {
ui.add_space(3.0);
ui.horizontal(|ui| {
{
ui.add_space(avail.x * 0.05 / 2.0);
let mut slf = handle_error_ui!(Player::get());
let slider = egui::Slider::new(&mut slf.slider_progress, 0..=100)
.show_value(false);
ui.style_mut().spacing.slider_width = avail.x * 0.90;
let s = Stroke {
color: crate::data::C_ACCENT,
width: 2.0
};
ui.style_mut().visuals.widgets.inactive.fg_stroke = s;
ui.style_mut().visuals.widgets.active.fg_stroke = s;
ui.add(slider);
ui.label("00:00");
}
});
ui.horizontal(|ui| {
ui.add_space((avail.x / 2.0) - 16.0 - 8.0 - ui.spacing().item_spacing.x);
let prev = egui::Image::new(crate::data::PREV_ICON)
.tint(crate::data::C_ACCENT)
.max_size(Vec2::new(16.0, 16.0));
let play = egui::Image::new(crate::data::PLAY_ICON)
.tint(crate::data::C_ACCENT)
.max_size(Vec2::new(16.0, 16.0));
let next = egui::Image::new(crate::data::NEXT_ICON)
.tint(crate::data::C_ACCENT)
.max_size(Vec2::new(16.0, 16.0));
if ui.add(prev).clicked() {}
if ui.add(play).clicked() {}
if ui.add(next).clicked() {}
});
ui.add_space(3.0);
});
Ok(())
}
}

View File

@@ -0,0 +1,34 @@
use egui::{Color32, Vec2};
use xmpd_manifest::store::BaseStore;
use super::CompUi;
#[derive(Debug, Default)]
pub struct SongList;
component_register!(SongList);
impl CompUi for SongList {
fn draw(ui: &mut egui::Ui, state: &mut crate::GuiState) -> crate::Result<()> {
egui::ScrollArea::vertical().id_source("song_list").show(ui, |ui| {
ui.vertical(|ui| {
let songs = state.manifest.store().get_songs();
for (sid, song) in songs.iter() {
ui.horizontal(|ui| {
ui.add(
egui::Image::new(crate::data::NOTE_ICON)
.tint(crate::data::C_ACCENT)
.fit_to_exact_size(Vec2::new(32.0, 32.0))
);
ui.label(song.author());
ui.strong(" - ");
ui.label(song.name());
});
ui.separator();
}
});
});
Ok(())
}
}

View File

@@ -0,0 +1,30 @@
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);
ui.horizontal(|ui| {
ui.add_space(3.0);
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
// TBD
});
ui.menu_button("Help", |ui| {
if ui.button("Source").clicked() {
ui.ctx().open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
}
})
});
});
Ok(())
}
}

18
xmpd-gui/src/data.rs Normal file
View File

@@ -0,0 +1,18 @@
// 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 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

View File

@@ -1,12 +1,17 @@
#![feature(async_closure)]
use std::{path::{Path, PathBuf}, sync::mpsc, thread::JoinHandle};
use std::{path::{Path, PathBuf}, time::Duration};
use egui::TextStyle;
use windows::WindowId;
use xmpd_manifest::{store::JsonStore, Manifest};
#[macro_use]
mod macros;
mod main_window;
mod windows;
mod components;
mod data;
mod utils;
const W_NAME: &str = "xmpd v2.0.0a";
@@ -15,12 +20,24 @@ type Result<T> = anyhow::Result<T>;
pub fn start(manifest_path: PathBuf) -> Result<()> {
let options = eframe::NativeOptions::default();
let mut state = GuiState::new(&manifest_path)?;
state.windows.toggle(&WindowId::Error, true);
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().show(ctx, |ui| main_window::draw(ui, &mut state));
ctx.request_repaint();
egui::CentralPanel::default()
.frame(
egui::Frame::none()
.fill(data::C_PRIM_BG)
.stroke(egui::Stroke::new(
1.0,
data::C_SEC_BG,
)),
)
.show(ctx, |ui| {
ui.style_mut().visuals.override_text_color = Some(crate::data::C_TEXT);
main_window::draw(ui, &mut state)
});
ctx.request_repaint_after(Duration::from_millis(500));
});
if let Err(e) = res { // dumb err value by eframe
anyhow::bail!(e.to_string());

29
xmpd-gui/src/macros.rs Normal file
View File

@@ -0,0 +1,29 @@
macro_rules! component_register {
($comp:ident) => {
lazy_static::lazy_static! {
static ref __COMPONENT: std::sync::Arc<std::sync::Mutex<$comp>> =
std::sync::Arc::new(std::sync::Mutex::new($comp::default()));
}
impl crate::components::CompGetter for $comp {
fn get() -> crate::Result<std::sync::MutexGuard<'static, Self>> {
match __COMPONENT.lock() {
Ok(l) => Ok(l),
Err(e) => Err(anyhow::anyhow!(format!("{e:?}"))),
}
}
}
};
}
macro_rules! handle_error_ui {
($val:expr) => {
match $val {
Ok(v) => v,
Err(e) => {
log::error!("Error in ui: {e:?}");
return;
}
}
};
}

View File

@@ -1,14 +1,71 @@
use egui::ViewportId;
use xmpd_manifest::store::JsonStore;
use crate::GuiState;
use crate::{components::{CompGetter, CompUi}, GuiState};
pub fn draw(ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()> {
ui.label("Hello! this is root of main window");
if ui.button("open iwndow").clicked() {
state.windows.toggle(&crate::windows::WindowId::Error, true);
}
// The central panel the region left after adding TopPanel's and SidePanel's
// ui.heading(format!("Songs ({})", self.manifest.get_song_count()));
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);
let avail = ui.available_size();
ui.horizontal(|ui| {
ui.set_height(avail.y);
ui.group(|ui| {
ui.set_height(avail.y);
ui.set_width(avail.x * 0.25);
handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
});
handle_error_ui!(crate::components::song_list::SongList::draw(ui, state));
});
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "player")
.frame(
egui::Frame::none()
.fill(crate::data::C_PRIM_BG)
.stroke(egui::Stroke::new(
1.0,
crate::data::C_SEC_BG,
)),
)
.show(ui.ctx(), |ui| {
ui.style_mut().visuals.override_text_color = Some(crate::data::C_TEXT);
handle_error_ui!(crate::components::player::Player::draw(ui, state));
});
});
//ui.vertical_centered_justified(|ui| {
// ui.with_layout(egui::Layout::top_down_justified(egui::Align::TOP), |ui| {
// let avail = ui.available_size();
// ui.vertical(|ui| {
// let avail_width = ui.available_width();
// ui.set_height(avail.y);
// ui.vertical(|ui| {
// ui.set_height(avail.y * 0.9);
// ui.horizontal(|ui| {
//
// ui.group(|ui| {
// ui.set_width(avail_width * 0.25);
// handle_error_ui!(crate::components::left_nav::LeftNav::draw(ui, state));
// });
// // crate::utils::super_separator(ui, crate::data::C_ACCENT, 3.0, avail_width);
// // handle_error_ui!(crate::components::song_list::SongList::draw(ui, state));
// });
// // handle_error_ui!(crate::components::player::Player::draw(ui, state));
// });
// });
//
// ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
// egui::warn_if_debug_build(ui);
// });
// });
//});
//
Ok(())
}

11
xmpd-gui/src/utils.rs Normal file
View File

@@ -0,0 +1,11 @@
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);
});
}

View File

@@ -64,7 +64,6 @@ impl Windows {
if status {
OPEN_WINDOWS.lock().unwrap().insert(id.clone());
} else {
log::debug!("tried to kill");
OPEN_WINDOWS.lock().unwrap().remove(id);
}
}