generated from PlexSheep/rs-base
feat: first ui with almost nothing
This commit is contained in:
parent
5da9a360d4
commit
c4a7b68c74
11
Cargo.toml
11
Cargo.toml
|
@ -12,13 +12,20 @@ repository = "https://git.cscherr.de/PlexSheep/beatbear"
|
||||||
keywords = ["media", "sound", "music", "player", "jellyfin", "downloads"]
|
keywords = ["media", "sound", "music", "player", "jellyfin", "downloads"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["backend-fs", "backend-jellyfin"]
|
||||||
backend-fs = []
|
backend-fs = []
|
||||||
backend-jellyfin = []
|
backend-jellyfin = []
|
||||||
gui = []
|
|
||||||
tui = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
clap = { version = "4.5.16", features = ["derive"] }
|
||||||
|
eframe = { version = "0.28.1", optional = false }
|
||||||
|
egui = { version = "0.28.1", optional = false }
|
||||||
|
human-panic = "2.0.1"
|
||||||
|
image = "0.25.2"
|
||||||
libpt = { version = "0.6.0", features = ["cli", "full"] }
|
libpt = { version = "0.6.0", features = ["cli", "full"] }
|
||||||
|
thiserror = "1.0.63"
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "beatbaer"
|
name = "beatbaer"
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -0,0 +1 @@
|
||||||
|
pub trait Backend {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Error in the player UI")]
|
||||||
|
UiError(#[from] eframe::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Other(#[from] anyhow::Error),
|
||||||
|
}
|
|
@ -1,2 +1,4 @@
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
|
pub mod error;
|
||||||
pub mod music;
|
pub mod music;
|
||||||
|
pub mod player;
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -1,3 +1,31 @@
|
||||||
fn main() {
|
use beatbaer::error::Error;
|
||||||
println!("Hello, world!");
|
use beatbaer::player::Player;
|
||||||
|
use human_panic::{setup_panic, Metadata};
|
||||||
|
use libpt::log::info;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Error> {
|
||||||
|
setup_panic!(
|
||||||
|
Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"))
|
||||||
|
.authors(env!("CARGO_PKG_AUTHORS"))
|
||||||
|
.homepage(env!("CARGO_PKG_HOMEPAGE"))
|
||||||
|
.support(format!(
|
||||||
|
"Public issue tracker at: {}\nor alternatively email: {}",
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
"software@cscherr.de"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
let mut player = Player::build()?;
|
||||||
|
info!("starting ui");
|
||||||
|
|
||||||
|
eframe::run_native(
|
||||||
|
beatbaer::player::TITLE,
|
||||||
|
player.gui_options.clone(),
|
||||||
|
Box::new(|cc| {
|
||||||
|
player.init(cc);
|
||||||
|
Ok(Box::new(player))
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
info!("leaving ui");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use clap::Parser;
|
||||||
|
use eframe::CreationContext;
|
||||||
|
use libpt::cli::args::VerbosityLevel;
|
||||||
|
use libpt::log::{debug, info, trace};
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
|
pub const TITLE: &str = "Beatbär";
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
pub struct Player {
|
||||||
|
#[command(flatten)]
|
||||||
|
verbosity: VerbosityLevel,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
pub gui_options: eframe::NativeOptions,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
show_info_window: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
pub fn build() -> Result<Self, Error> {
|
||||||
|
let mut app = Player::parse();
|
||||||
|
|
||||||
|
let filter = tracing_subscriber::EnvFilter::builder()
|
||||||
|
.with_default_directive(app.verbosity.level().into())
|
||||||
|
.from_env()
|
||||||
|
.expect("could not init logger")
|
||||||
|
.add_directive(
|
||||||
|
format!("{}={}", env!("CARGO_PKG_NAME"), app.verbosity.level())
|
||||||
|
.parse()
|
||||||
|
.expect("could not init logger"),
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing_subscriber::fmt::Subscriber::builder()
|
||||||
|
.with_env_filter(filter)
|
||||||
|
.with_max_level(app.verbosity.level())
|
||||||
|
.init();
|
||||||
|
debug!("logging initialized!");
|
||||||
|
debug!("level: {}", app.verbosity.level());
|
||||||
|
|
||||||
|
app.gui_options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default()
|
||||||
|
.with_inner_size([400.0, 300.0])
|
||||||
|
.with_min_inner_size([300.0, 220.0])
|
||||||
|
.with_icon(Player::load_icon()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Player ready to start UI");
|
||||||
|
Ok(app)
|
||||||
|
}
|
||||||
|
pub fn init(&mut self, _cc: &CreationContext) {
|
||||||
|
// we can use the creation context to do some customizing, but idc right now
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
use egui::IconData;
|
||||||
|
use libpt::log::trace;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const ICON_RAW: &[u8; 36525] = include_bytes!("../../assets/img/icon-512.jpg");
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
fn top_panel(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
egui::menu::bar(ui, |ui| {
|
||||||
|
// NOTE: no File->Quit on web pages!
|
||||||
|
let is_web = cfg!(target_arch = "wasm32");
|
||||||
|
if !is_web {
|
||||||
|
ui.menu_button("File", |ui| {
|
||||||
|
if ui.button("Info").clicked() {
|
||||||
|
self.show_info_window = true;
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("Quit").clicked() {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(16.0);
|
||||||
|
}
|
||||||
|
if self.show_info_window {
|
||||||
|
self.info_diag(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn info_diag(&mut self, ctx: &egui::Context) {
|
||||||
|
trace!("rendering info dialogue");
|
||||||
|
ctx.show_viewport_immediate(
|
||||||
|
egui::ViewportId::from_hash_of(format!("{TITLE}: Information")),
|
||||||
|
egui::ViewportBuilder::default()
|
||||||
|
.with_title(format!("{TITLE}: Information"))
|
||||||
|
.with_inner_size([500.0, 200.0]),
|
||||||
|
|ctx, class| {
|
||||||
|
assert!(
|
||||||
|
class == egui::ViewportClass::Immediate,
|
||||||
|
"This egui backend doesn't support multiple viewports"
|
||||||
|
);
|
||||||
|
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.label(format!("{TITLE} v{}", env!("CARGO_PKG_VERSION")));
|
||||||
|
ui.hyperlink_to("Source Code\n", env!("CARGO_PKG_REPOSITORY"));
|
||||||
|
ui.label(format!("Author: {}", env!("CARGO_PKG_AUTHORS")));
|
||||||
|
ui.label(format!("License: {}", env!("CARGO_PKG_LICENSE")));
|
||||||
|
ui.label(format!(
|
||||||
|
"\n{TITLE} is free software. If you paid for this you were scammed.\n"
|
||||||
|
));
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label("Powered by ");
|
||||||
|
ui.hyperlink_to("egui", "https://github.com/emilk/egui");
|
||||||
|
ui.label(" and ");
|
||||||
|
ui.hyperlink_to(
|
||||||
|
"eframe",
|
||||||
|
"https://github.com/emilk/egui/tree/master/crates/eframe",
|
||||||
|
);
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
});
|
||||||
|
if ctx.input(|i| i.viewport().close_requested()) {
|
||||||
|
// Tell parent viewport that we should not show next frame:
|
||||||
|
self.show_info_window = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bottom_label(ui: &mut egui::Ui) {
|
||||||
|
ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.spacing_mut().item_spacing.x = 0.0;
|
||||||
|
ui.label(format!("{TITLE} v{}", env!("CARGO_PKG_VERSION")));
|
||||||
|
ui.label(" | Powered by ");
|
||||||
|
ui.hyperlink_to("egui", "https://github.com/emilk/egui");
|
||||||
|
ui.label(" and ");
|
||||||
|
ui.hyperlink_to(
|
||||||
|
"eframe",
|
||||||
|
"https://github.com/emilk/egui/tree/master/crates/eframe",
|
||||||
|
);
|
||||||
|
ui.label(".");
|
||||||
|
});
|
||||||
|
egui::warn_if_debug_build(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_panel(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
for i in 0..100 {
|
||||||
|
ui.horizontal_wrapped(|ui| {
|
||||||
|
for j in 0..10 {
|
||||||
|
ui.label(format!("foo-{i}-{j}"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn meta_panel(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
|
||||||
|
egui::menu::bar(ui, |ui| {
|
||||||
|
// NOTE: no File->Quit on web pages!
|
||||||
|
ui.menu_button("File", |ui| {
|
||||||
|
if ui.button("Info").clicked() {
|
||||||
|
self.show_info_window = true;
|
||||||
|
}
|
||||||
|
ui.separator();
|
||||||
|
if ui.button("Quit").clicked() {
|
||||||
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.add_space(16.0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_icon() -> IconData {
|
||||||
|
let (icon_rgba, icon_width, icon_height) = {
|
||||||
|
let image = image::load_from_memory(ICON_RAW)
|
||||||
|
.expect("Failed to open icon path")
|
||||||
|
.into_rgba8();
|
||||||
|
let (width, height) = image.dimensions();
|
||||||
|
let rgba = image.into_raw();
|
||||||
|
(rgba, width, height)
|
||||||
|
};
|
||||||
|
|
||||||
|
IconData {
|
||||||
|
rgba: icon_rgba,
|
||||||
|
width: icon_width,
|
||||||
|
height: icon_height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl eframe::App for Player {
|
||||||
|
/// Called each time the UI needs repainting, which may be many times per second.
|
||||||
|
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||||
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||||
|
self.top_panel(ui, ctx);
|
||||||
|
});
|
||||||
|
egui::TopBottomPanel::top("top_panel2").show(ctx, |ui| {
|
||||||
|
self.meta_panel(ui, ctx);
|
||||||
|
});
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
self.main_panel(ui, ctx);
|
||||||
|
});
|
||||||
|
egui::TopBottomPanel::bottom("bot_panel").show(ctx, Self::bottom_label);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue