Popup system

This commit is contained in:
Connor Slade
2024-07-28 18:45:37 -04:00
parent c14828437f
commit d45da4973a
17 changed files with 169 additions and 62 deletions

10
Cargo.lock generated
View File

@@ -1539,15 +1539,6 @@ dependencies = [
"nohash-hasher", "nohash-hasher",
] ]
[[package]]
name = "egui-modal"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "738cdffefd15dbb6a5fff75d118eee82a9e894bbfa45e41c24d7de42519fa673"
dependencies = [
"egui",
]
[[package]] [[package]]
name = "egui-phosphor" name = "egui-phosphor"
version = "0.5.0" version = "0.5.0"
@@ -2846,7 +2837,6 @@ dependencies = [
"dirs", "dirs",
"eframe", "eframe",
"egui", "egui",
"egui-modal",
"egui-phosphor", "egui-phosphor",
"egui-wgpu", "egui-wgpu",
"egui_dock", "egui_dock",

View File

@@ -16,7 +16,6 @@ eframe = { version = "0.27.2", features = ["wgpu", "serde"] }
egui = "0.27.2" egui = "0.27.2"
egui_dock = "0.12.0" egui_dock = "0.12.0"
egui_tracing = "0.2.2" egui_tracing = "0.2.2"
egui-modal = "0.3.6"
egui-phosphor = { git = "https://github.com/connorslade/egui-phosphor" } egui-phosphor = { git = "https://github.com/connorslade/egui-phosphor" }
egui-wgpu = "0.27.2" egui-wgpu = "0.27.2"
encase = { version = "0.8.0", features = ["nalgebra"] } encase = { version = "0.8.0", features = ["nalgebra"] }

View File

@@ -65,3 +65,4 @@
- [ ] Allow changing broadcast address - [ ] Allow changing broadcast address
- [x] Actually init remote print at startup if requested - [x] Actually init remote print at startup if requested
- [ ] Preferred service ports - [ ] Preferred service ports
- [ ] Button to disable remote print services

View File

@@ -13,7 +13,6 @@ dirs.workspace = true
eframe.workspace = true eframe.workspace = true
egui_dock.workspace = true egui_dock.workspace = true
egui_tracing.workspace = true egui_tracing.workspace = true
egui-modal.workspace = true
egui-phosphor.workspace = true egui-phosphor.workspace = true
egui-wgpu.workspace = true egui-wgpu.workspace = true
egui.workspace = true egui.workspace = true

View File

@@ -4,7 +4,6 @@ use clone_macro::clone;
use eframe::Theme; use eframe::Theme;
use egui::Visuals; use egui::Visuals;
use egui_dock::{DockState, NodeIndex}; use egui_dock::{DockState, NodeIndex};
use egui_modal::{DialogBuilder, Icon, Modal};
use egui_tracing::EventCollector; use egui_tracing::EventCollector;
use image::imageops::FilterType; use image::imageops::FilterType;
use nalgebra::{Vector2, Vector3}; use nalgebra::{Vector2, Vector3};
@@ -17,16 +16,20 @@ use crate::{
remote_print::RemotePrint, remote_print::RemotePrint,
render::{camera::Camera, rendered_mesh::RenderedMesh}, render::{camera::Camera, rendered_mesh::RenderedMesh},
slice_operation::{SliceOperation, SliceResult}, slice_operation::{SliceOperation, SliceResult},
ui_state::UiState, ui::{
popup::{Popup, PopupIcon, PopupManager},
state::UiState,
},
windows::{self, Tab}, windows::{self, Tab},
}; };
use common::config::{ExposureConfig, SliceConfig}; use common::config::{ExposureConfig, SliceConfig};
use goo_format::{File as GooFile, LayerEncoder, PreviewImage}; use goo_format::{File as GooFile, LayerEncoder, PreviewImage};
pub struct App { pub struct App {
// todo: dock state in ui_state?
pub dock_state: DockState<Tab>, pub dock_state: DockState<Tab>,
pub fps: FpsTracker, pub fps: FpsTracker,
modal: Option<Modal>, pub popup: PopupManager,
pub state: UiState, pub state: UiState,
pub config: Config, pub config: Config,
@@ -63,7 +66,7 @@ impl App {
Self { Self {
dock_state, dock_state,
modal: None, popup: PopupManager::new(),
state: UiState { state: UiState {
event_collector, event_collector,
..Default::default() ..Default::default()
@@ -104,11 +107,11 @@ impl App {
if meshes.is_empty() { if meshes.is_empty() {
const NO_MODELS_ERROR: &str = "There are no models to slice. Add one by going to File → Open Model or drag and drop a model file into the workspace."; const NO_MODELS_ERROR: &str = "There are no models to slice. Add one by going to File → Open Model or drag and drop a model file into the workspace.";
self.dialog_builder() self.popup.open(Popup::simple(
.with_title("Slicing Error") "Slicing Error",
.with_body(NO_MODELS_ERROR) PopupIcon::Error,
.with_icon(Icon::Error) NO_MODELS_ERROR,
.open(); ));
return; return;
} }
@@ -177,29 +180,21 @@ impl App {
} }
)); ));
} }
pub fn dialog_builder(&mut self) -> DialogBuilder {
self.modal.as_mut().unwrap().dialog()
}
} }
impl eframe::App for App { impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.request_repaint(); ctx.request_repaint();
self.fps.update(); self.fps.update();
self.popup.render(ctx);
// only update the visuals if the theme has changed
match self.config.theme { match self.config.theme {
Theme::Dark => ctx.set_visuals(Visuals::dark()), Theme::Dark => ctx.set_visuals(Visuals::dark()),
Theme::Light => ctx.set_visuals(Visuals::light()), Theme::Light => ctx.set_visuals(Visuals::light()),
} }
match &mut self.modal { self.remote_print.tick(&mut self.state, &self.config);
Some(modal) => modal.show_dialog(),
None => self.modal = Some(Modal::new(ctx, "modal")),
}
self.remote_print
.tick(&mut self.modal, &mut self.state, &self.config);
windows::ui(self, ctx); windows::ui(self, ctx);
} }
} }

View File

@@ -11,12 +11,11 @@ use wgpu::{DeviceDescriptor, Features, Limits, TextureFormat};
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm; const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
mod app; mod app;
mod components;
mod config; mod config;
mod remote_print; mod remote_print;
mod render; mod render;
mod slice_operation; mod slice_operation;
mod ui_state; mod ui;
mod windows; mod windows;
use app::App; use app::App;

View File

@@ -10,7 +10,6 @@ use std::{
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use clone_macro::clone; use clone_macro::clone;
use common::misc::random_string; use common::misc::random_string;
use egui_modal::{Icon, Modal};
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
use tracing::{info, warn}; use tracing::{info, warn};
@@ -25,7 +24,7 @@ use remote_send::{
use crate::{ use crate::{
config::Config, config::Config,
ui_state::{RemotePrintConnectStatus, UiState}, ui::state::{RemotePrintConnectStatus, UiState},
}; };
pub struct RemotePrint { pub struct RemotePrint {
@@ -115,7 +114,7 @@ impl RemotePrint {
Ok(()) Ok(())
} }
pub fn tick(&mut self, modal: &mut Option<Modal>, ui_state: &mut UiState, config: &Config) { pub fn tick(&mut self, ui_state: &mut UiState, config: &Config) {
if !self.is_initialized() && config.init_remote_print_at_startup { if !self.is_initialized() && config.init_remote_print_at_startup {
self.init().unwrap(); self.init().unwrap();
self.set_network_timeout(Duration::from_secs_f32(config.network_timeout)); self.set_network_timeout(Duration::from_secs_f32(config.network_timeout));
@@ -124,7 +123,7 @@ impl RemotePrint {
services.http.set_proxy_enabled(config.http_status_proxy); services.http.set_proxy_enabled(config.http_status_proxy);
} }
let mut dialog_builder = || modal.as_mut().unwrap().dialog(); // let mut dialog_builder = || modal.as_mut().unwrap().dialog();
let mut i = 0; let mut i = 0;
while i < self.jobs.len() { while i < self.jobs.len() {
@@ -138,11 +137,11 @@ impl RemotePrint {
body.push(' '); body.push(' ');
} }
dialog_builder() // dialog_builder()
.with_title("Remote Print Error") // .with_title("Remote Print Error")
.with_body(&body[..body.len() - 1]) // .with_body(&body[..body.len() - 1])
.with_icon(Icon::Error) // .with_icon(Icon::Error)
.open(); // .open();
} }
continue; continue;
} }

3
mslicer/src/ui/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod components;
pub mod popup;
pub mod state;

114
mslicer/src/ui/popup.rs Normal file
View File

@@ -0,0 +1,114 @@
use egui::{pos2, vec2, Color32, Context, Grid, Id, RichText, Ui, WidgetText, Window};
pub struct PopupManager {
popups: Vec<Popup>,
}
pub struct Popup {
title: String,
id: Id,
ui: Box<dyn FnMut(&mut Ui) -> bool>,
}
pub enum PopupIcon {
Info,
Warning,
Error,
Success,
}
impl PopupManager {
pub fn new() -> Self {
Self { popups: Vec::new() }
}
pub fn open(&mut self, popup: Popup) {
self.popups.push(popup);
}
pub fn render(&mut self, ctx: &Context) {
let window_size = ctx.screen_rect().size();
let mut i = 0;
let mut close = false;
while i < self.popups.len() {
let popup = &mut self.popups[i];
Window::new("")
.id(popup.id)
.title_bar(false)
.resizable(false)
.default_size(vec2(400.0, 0.0))
.default_pos(pos2(
window_size.x * 0.5 - 200.0,
window_size.y * 0.5 - 100.0,
))
.show(ctx, |ui| {
ui.vertical_centered(|ui| {
ui.heading(popup.title.clone());
});
ui.separator();
close = (popup.ui)(ui);
});
if close {
self.popups.remove(i);
} else {
i += 1;
}
}
}
}
impl Popup {
pub fn new(title: String, ui: impl FnMut(&mut Ui) -> bool + 'static) -> Self {
Self::new_with_id(Id::new(rand::random::<u64>()), title, ui)
}
fn new_with_id(id: Id, title: String, ui: impl FnMut(&mut Ui) -> bool + 'static) -> Self {
Self {
title,
id,
ui: Box::new(ui),
}
}
pub fn simple(title: impl AsRef<str>, icon: PopupIcon, body: impl Into<WidgetText>) -> Self {
let id = Id::new(rand::random::<u64>());
let title = title.as_ref().to_owned();
let body = body.into();
Self::new_with_id(id, title, move |ui| {
let mut close = false;
ui.centered_and_justified(|ui| {
Grid::new(id.with("grid")).num_columns(2).show(ui, |ui| {
ui.label(RichText::new(icon.as_char()).size(30.0).color(icon.color()));
ui.label(body.clone());
});
ui.add_space(5.0);
close = ui.button("Close").clicked();
});
close
})
}
}
impl PopupIcon {
pub fn as_char(&self) -> char {
match self {
Self::Info => '',
Self::Warning => '⚠',
Self::Error => '❌',
Self::Success => '✔',
}
}
pub fn color(&self) -> egui::Color32 {
match self {
Self::Info => Color32::from_rgb(150, 200, 210),
Self::Warning => Color32::from_rgb(230, 220, 140),
Self::Error => Color32::from_rgb(200, 90, 90),
Self::Success => Color32::from_rgb(140, 230, 140),
}
}
}

View File

@@ -5,7 +5,7 @@ use slicer::Pos;
use crate::{ use crate::{
app::App, app::App,
components::{vec3_dragger, vec3_dragger_proportional}, ui::components::{vec3_dragger, vec3_dragger_proportional},
}; };
enum Action { enum Action {

View File

@@ -6,12 +6,17 @@ use const_format::concatcp;
use egui::{ use egui::{
vec2, Align, Context, DragValue, Grid, Layout, ProgressBar, Separator, Spinner, TextEdit, Ui, vec2, Align, Context, DragValue, Grid, Layout, ProgressBar, Separator, Spinner, TextEdit, Ui,
}; };
use egui_modal::Icon;
use egui_phosphor::regular::{NETWORK, PLUGS, TRASH_SIMPLE, UPLOAD_SIMPLE}; use egui_phosphor::regular::{NETWORK, PLUGS, TRASH_SIMPLE, UPLOAD_SIMPLE};
use notify_rust::Notification; use notify_rust::Notification;
use remote_send::status::{FileTransferStatus, PrintInfoStatus}; use remote_send::status::{FileTransferStatus, PrintInfoStatus};
use crate::{app::App, ui_state::RemotePrintConnectStatus}; use crate::{
app::App,
ui::{
popup::{Popup, PopupIcon},
state::RemotePrintConnectStatus,
},
};
enum Action { enum Action {
None, None,
@@ -229,14 +234,14 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
.add_printer(&app.state.working_address) .add_printer(&app.state.working_address)
.is_err() .is_err()
{ {
app.dialog_builder() app.popup.open(Popup::simple(
.with_title("Remote Print Error") "Remote Print Error",
.with_body(format!( PopupIcon::Error,
format!(
"The entered address `{}` is invalid.", "The entered address `{}` is invalid.",
app.state.working_address, app.state.working_address,
)) ),
.with_icon(Icon::Error) ));
.open();
app.state.working_address.clear(); app.state.working_address.clear();
} else { } else {
app.state.remote_print_connecting = app.state.remote_print_connecting =

View File

@@ -2,7 +2,7 @@ use egui::{Context, DragValue, Grid, Ui};
use crate::{ use crate::{
app::App, app::App,
components::{vec2_dragger, vec3_dragger}, ui::components::{vec2_dragger, vec3_dragger},
}; };
use common::config::ExposureConfig; use common::config::ExposureConfig;

View File

@@ -10,7 +10,7 @@ use nalgebra::Vector2;
use rfd::FileDialog; use rfd::FileDialog;
use crate::{ use crate::{
app::App, components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback, app::App, ui::components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback,
slice_operation::SliceResult, slice_operation::SliceResult,
}; };
use common::serde::DynamicSerializer; use common::serde::DynamicSerializer;

View File

@@ -1,11 +1,14 @@
use std::{fs::File, io::BufReader}; use std::{fs::File, io::BufReader};
use egui::{Align, Context, Layout, TopBottomPanel}; use egui::{Align, Context, Layout, TopBottomPanel};
use egui_modal::Icon;
use rfd::FileDialog; use rfd::FileDialog;
use tracing::info; use tracing::info;
use crate::{app::App, render::rendered_mesh::RenderedMesh}; use crate::{
app::App,
render::rendered_mesh::RenderedMesh,
ui::popup::{Popup, PopupIcon},
};
pub fn ui(app: &mut App, ctx: &Context) { pub fn ui(app: &mut App, ctx: &Context) {
TopBottomPanel::top("top_panel").show(ctx, |ui| { TopBottomPanel::top("top_panel").show(ctx, |ui| {
@@ -30,11 +33,11 @@ pub fn ui(app: &mut App, ctx: &Context) {
let model = match slicer::mesh::load_mesh(&mut buf, &format) { let model = match slicer::mesh::load_mesh(&mut buf, &format) {
Ok(model) => model, Ok(model) => model,
Err(err) => { Err(err) => {
app.dialog_builder() app.popup.open(Popup::simple(
.with_title("Import Error") "Import Error",
.with_body(format!("Failed to import model.\n{err}")) PopupIcon::Error,
.with_icon(Icon::Error) format!("Failed to import model.\n{err}"),
.open(); ));
return; return;
} }
}; };

View File

@@ -6,7 +6,7 @@ use tracing::error;
use crate::{ use crate::{
app::App, app::App,
components::{dragger, vec2_dragger, vec3_dragger}, ui::components::{dragger, vec2_dragger, vec3_dragger},
render::pipelines::model::RenderStyle, render::pipelines::model::RenderStyle,
}; };