Base gui, player, side, top nav, song list
Super happy with this
This commit is contained in:
@@ -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"
|
||||
|
||||
40
xmpd-gui/src/components/left_nav.rs
Normal file
40
xmpd-gui/src/components/left_nav.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>;
|
||||
}
|
||||
|
||||
55
xmpd-gui/src/components/player.rs
Normal file
55
xmpd-gui/src/components/player.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
34
xmpd-gui/src/components/song_list.rs
Normal file
34
xmpd-gui/src/components/song_list.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
30
xmpd-gui/src/components/top_nav.rs
Normal file
30
xmpd-gui/src/components/top_nav.rs
Normal 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
18
xmpd-gui/src/data.rs
Normal 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
|
||||
|
||||
|
||||
@@ -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
29
xmpd-gui/src/macros.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
11
xmpd-gui/src/utils.rs
Normal 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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user