Added info, warn, and error toasts
This commit is contained in:
@@ -6,6 +6,7 @@ pub mod left_nav;
|
||||
pub mod song_list;
|
||||
pub mod top_nav;
|
||||
pub mod player;
|
||||
pub mod toast;
|
||||
|
||||
pub trait CompUi {
|
||||
fn draw(ui: &mut egui::Ui, state: &mut GuiState) -> crate::Result<()>;
|
||||
|
||||
107
xmpd-gui/src/components/toast.rs
Normal file
107
xmpd-gui/src/components/toast.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
|
||||
use std::{collections::VecDeque, time::SystemTime};
|
||||
|
||||
use egui::{epaint::Shadow, load::TexturePoll, Align2, Color32, Frame, Image, Margin, Pos2, Rect, RichText, Rounding, Stroke, Style, Vec2};
|
||||
|
||||
use super::{CompGetter, CompUi};
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone, Copy)]
|
||||
pub enum ToastType {
|
||||
#[default]
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Toast {
|
||||
queue: VecDeque<(String, String, ToastType, SystemTime)>
|
||||
}
|
||||
|
||||
component_register!(Toast);
|
||||
|
||||
impl CompUi for Toast {
|
||||
fn draw(ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
|
||||
let screen_size = ui.ctx().screen_rect().size();
|
||||
let (w, h) = (300.0, 100.0);
|
||||
let theme = &xmpd_settings::Settings::get()?.theme;
|
||||
let mut toastw = Toast::get()?;
|
||||
let mut height_iter = 6.0;
|
||||
let mut to_remove = Vec::new();
|
||||
|
||||
for (i, (title, description, toast_type, shown_since)) in toastw.queue.iter().enumerate() {
|
||||
let area = egui::Area::new(egui::Id::new(format!("toast_{i}")))
|
||||
.fixed_pos(Pos2::new(screen_size.x - w, height_iter))
|
||||
.pivot(Align2::LEFT_TOP)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.set_width(w);
|
||||
|
||||
let img;
|
||||
let color;
|
||||
match toast_type {
|
||||
ToastType::Info => {
|
||||
color = theme.accent_color;
|
||||
img = Image::new(crate::data::INFO_ICON)
|
||||
.max_size(Vec2::new(16.0, 16.0))
|
||||
.tint(color);
|
||||
}
|
||||
ToastType::Warn => {
|
||||
color = crate::data::C_WARN;
|
||||
img = Image::new(crate::data::WARN_ICON)
|
||||
.max_size(Vec2::new(16.0, 16.0))
|
||||
.tint(color);
|
||||
}
|
||||
ToastType::Error => {
|
||||
color = Color32::LIGHT_RED;
|
||||
img = Image::new(crate::data::ERROR_ICON)
|
||||
.max_size(Vec2::new(16.0, 16.0))
|
||||
.tint(color);
|
||||
}
|
||||
}
|
||||
Frame::none()
|
||||
.stroke(Stroke::new(1.0, color))
|
||||
.fill(theme.primary_bg_color)
|
||||
.rounding(Rounding::same(3.0))
|
||||
.inner_margin(Margin::same(3.0))
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(w-9.0);
|
||||
ui.style_mut().visuals.override_text_color = Some(theme.text_color);
|
||||
ui.horizontal(|ui| {
|
||||
|
||||
ui.add(img);
|
||||
ui.label(RichText::new(title));
|
||||
});
|
||||
ui.label(
|
||||
RichText::new(description)
|
||||
.size(10.0)
|
||||
);
|
||||
ui.shrink_height_to_current();
|
||||
// height_iter += ui.available_height();
|
||||
}
|
||||
)
|
||||
}
|
||||
);
|
||||
height_iter += area.response.rect.height() + 6.0;
|
||||
|
||||
// if shown for longer than 5 seconds remove it
|
||||
if SystemTime::now().duration_since(*shown_since)?.as_secs() > 5 {
|
||||
to_remove.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
for idx in to_remove {
|
||||
toastw.queue.remove(idx);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Toast {
|
||||
pub fn show_toast(&mut self, title: &str, description: &str, toast_type: ToastType) {
|
||||
self.queue.push_front((title.to_string(), description.to_string(), toast_type, SystemTime::now()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@ impl CompUi for TopNav {
|
||||
ui.ctx().open_url(egui::OpenUrl::new_tab("https://git.mcorangehq.xyz/XOR64/music"));
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if ui.button("Debug").clicked() {
|
||||
state.windows.toggle(&WindowId::Debug, true);
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -8,4 +8,10 @@ pub const PLAY_ICON: egui::ImageSource = egui::include_image!("../../assets/pla
|
||||
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 INFO_ICON: egui::ImageSource = egui::include_image!("../../assets/info.svg");
|
||||
pub const WARN_ICON: egui::ImageSource = egui::include_image!("../../assets/warning.svg");
|
||||
pub const ERROR_ICON: egui::ImageSource = egui::include_image!("../../assets/error.svg");
|
||||
|
||||
|
||||
pub const C_WARN: egui::Color32 = egui::Color32::from_rgb(255, 183, 0); // #ffb700
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const W_NAME: &str = "xmpd v2.0.0a";
|
||||
type Result<T> = anyhow::Result<T>;
|
||||
|
||||
pub fn start() -> Result<()> {
|
||||
xmpd_cache::Cache::get()?.init();
|
||||
xmpd_cache::Cache::get()?.init()?;
|
||||
let options = eframe::NativeOptions::default();
|
||||
let mut state = GuiState::new()?;
|
||||
let res = eframe::run_simple_native(W_NAME, options, move |ctx, _frame| {
|
||||
|
||||
@@ -21,7 +21,15 @@ macro_rules! handle_error_ui {
|
||||
match $val {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
log::error!("Error in ui: {e:?}");
|
||||
use crate::components::CompGetter;
|
||||
log::error!("Error in {}:{}: {e}", std::file!(), std::line!());
|
||||
if let Ok(mut toast) = crate::components::toast::Toast::get() {
|
||||
toast.show_toast(
|
||||
&format!("Error in {}:{}", std::file!(), std::line!()),
|
||||
&format!("{e}"),
|
||||
crate::components::toast::ToastType::Error,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use xmpd_settings::theme::Theme;
|
||||
|
||||
use crate::{components::{song_list, CompUi}, GuiState};
|
||||
use crate::{components::{self, 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
|
||||
// ui.heading(format!("Songs ({})", self.manifest.get_song_count()));
|
||||
let theme = xmpd_settings::Settings::get()?.theme.clone();
|
||||
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Top, "top_nav")
|
||||
.frame(get_themed_frame(&theme))
|
||||
@@ -16,6 +14,7 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
|
||||
egui::CentralPanel::default()
|
||||
.frame(get_themed_frame(&theme))
|
||||
.show(ctx, |ui| {
|
||||
handle_error_ui!(components::toast::Toast::draw(ui, state));
|
||||
let avail = ui.available_size();
|
||||
ui.vertical(|ui| {
|
||||
crate::utils::super_separator(ui, theme.accent_color, avail.x, 2.0);
|
||||
@@ -23,7 +22,7 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
|
||||
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);
|
||||
let song_list_width = avail.x - left_nav_width - 35.0;
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_height(main_height);
|
||||
ui.group(|ui| {
|
||||
@@ -54,6 +53,7 @@ pub fn draw(ctx: &egui::Context, state: &mut GuiState) -> crate::Result<()> {
|
||||
handle_error_ui!(crate::components::player::Player::draw(ui, state));
|
||||
}
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
74
xmpd-gui/src/windows/debug.rs
Normal file
74
xmpd-gui/src/windows/debug.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use egui::RichText;
|
||||
|
||||
use crate::components::{toast::{self, ToastType}, CompGetter};
|
||||
|
||||
use super::Window;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DebugW {
|
||||
toast_title: String,
|
||||
toast_descr: String,
|
||||
toast_type: ToastType,
|
||||
}
|
||||
|
||||
impl Window for DebugW {
|
||||
fn draw(&mut self, ui: &mut egui::Ui, _: &mut crate::GuiState) -> crate::Result<()> {
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(
|
||||
RichText::new("DEBUG")
|
||||
.heading()
|
||||
);
|
||||
ui.horizontal(|ui| {
|
||||
{
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.label(
|
||||
RichText::new("Toast")
|
||||
.heading()
|
||||
);
|
||||
Self::add_input_field(&mut self.toast_title, ui, "Title");
|
||||
Self::add_input_field(&mut self.toast_descr, ui, "Description");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Type:");
|
||||
egui::ComboBox::from_id_source("debug_combo")
|
||||
.selected_text(format!("{:?}", self.toast_type))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.toast_type, ToastType::Info, "Info");
|
||||
ui.selectable_value(&mut self.toast_type, ToastType::Warn, "Warn");
|
||||
ui.selectable_value(&mut self.toast_type, ToastType::Error, "Error");
|
||||
}
|
||||
);
|
||||
});
|
||||
if ui.button("Add").clicked() {
|
||||
toast::Toast::get().unwrap().show_toast(&self.toast_title, &self.toast_descr, self.toast_type);
|
||||
}
|
||||
if ui.button("Throw Error").clicked() {
|
||||
handle_error_ui!(Err(anyhow::anyhow!("{}: {}", self.toast_title, self.toast_descr)));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DebugW {
|
||||
fn add_input_field(inp: &mut String, ui: &mut egui::Ui, name: &str) {
|
||||
ui.horizontal(|ui|{
|
||||
ui.label(format!("{name}: "));
|
||||
ui.text_edit_singleline(inp);
|
||||
});
|
||||
}
|
||||
fn add_input_field_ml(inp: &mut String, ui: &mut egui::Ui, name: &str) {
|
||||
ui.horizontal(|ui|{
|
||||
ui.label(format!("{name}: "));
|
||||
ui.text_edit_multiline(inp);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ use std::{collections::{HashMap, HashSet}, sync::{Arc, Mutex}};
|
||||
use egui::{ViewportBuilder, ViewportId};
|
||||
use crate::GuiState;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod debug;
|
||||
mod error;
|
||||
mod settings;
|
||||
|
||||
@@ -18,7 +20,9 @@ pub trait Window: std::fmt::Debug + Send {
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
|
||||
pub enum WindowId {
|
||||
Settings,
|
||||
Error
|
||||
Error,
|
||||
#[cfg(debug_assertions)]
|
||||
Debug
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -38,6 +42,8 @@ impl Windows {
|
||||
pub fn add_all_windows(&mut self) {
|
||||
self.add_new_window(WindowId::Error, "Error!", Box::<error::ErrorW>::default());
|
||||
self.add_new_window(WindowId::Settings, "Settings", Box::<settings::SettingsW>::default());
|
||||
#[cfg(debug_assertions)]
|
||||
self.add_new_window(WindowId::Debug, "Debug", Box::<debug::DebugW>::default());
|
||||
}
|
||||
|
||||
pub fn add_new_window(&mut self, id: WindowId, title: &str, cb: Box<dyn Window>) {
|
||||
|
||||
Reference in New Issue
Block a user