Popup system
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -1539,15 +1539,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "egui-phosphor"
|
||||
version = "0.5.0"
|
||||
@@ -2846,7 +2837,6 @@ dependencies = [
|
||||
"dirs",
|
||||
"eframe",
|
||||
"egui",
|
||||
"egui-modal",
|
||||
"egui-phosphor",
|
||||
"egui-wgpu",
|
||||
"egui_dock",
|
||||
|
@@ -16,7 +16,6 @@ eframe = { version = "0.27.2", features = ["wgpu", "serde"] }
|
||||
egui = "0.27.2"
|
||||
egui_dock = "0.12.0"
|
||||
egui_tracing = "0.2.2"
|
||||
egui-modal = "0.3.6"
|
||||
egui-phosphor = { git = "https://github.com/connorslade/egui-phosphor" }
|
||||
egui-wgpu = "0.27.2"
|
||||
encase = { version = "0.8.0", features = ["nalgebra"] }
|
||||
|
1
TODO.md
1
TODO.md
@@ -65,3 +65,4 @@
|
||||
- [ ] Allow changing broadcast address
|
||||
- [x] Actually init remote print at startup if requested
|
||||
- [ ] Preferred service ports
|
||||
- [ ] Button to disable remote print services
|
||||
|
@@ -13,7 +13,6 @@ dirs.workspace = true
|
||||
eframe.workspace = true
|
||||
egui_dock.workspace = true
|
||||
egui_tracing.workspace = true
|
||||
egui-modal.workspace = true
|
||||
egui-phosphor.workspace = true
|
||||
egui-wgpu.workspace = true
|
||||
egui.workspace = true
|
||||
|
@@ -4,7 +4,6 @@ use clone_macro::clone;
|
||||
use eframe::Theme;
|
||||
use egui::Visuals;
|
||||
use egui_dock::{DockState, NodeIndex};
|
||||
use egui_modal::{DialogBuilder, Icon, Modal};
|
||||
use egui_tracing::EventCollector;
|
||||
use image::imageops::FilterType;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
@@ -17,16 +16,20 @@ use crate::{
|
||||
remote_print::RemotePrint,
|
||||
render::{camera::Camera, rendered_mesh::RenderedMesh},
|
||||
slice_operation::{SliceOperation, SliceResult},
|
||||
ui_state::UiState,
|
||||
ui::{
|
||||
popup::{Popup, PopupIcon, PopupManager},
|
||||
state::UiState,
|
||||
},
|
||||
windows::{self, Tab},
|
||||
};
|
||||
use common::config::{ExposureConfig, SliceConfig};
|
||||
use goo_format::{File as GooFile, LayerEncoder, PreviewImage};
|
||||
|
||||
pub struct App {
|
||||
// todo: dock state in ui_state?
|
||||
pub dock_state: DockState<Tab>,
|
||||
pub fps: FpsTracker,
|
||||
modal: Option<Modal>,
|
||||
pub popup: PopupManager,
|
||||
|
||||
pub state: UiState,
|
||||
pub config: Config,
|
||||
@@ -63,7 +66,7 @@ impl App {
|
||||
|
||||
Self {
|
||||
dock_state,
|
||||
modal: None,
|
||||
popup: PopupManager::new(),
|
||||
state: UiState {
|
||||
event_collector,
|
||||
..Default::default()
|
||||
@@ -104,11 +107,11 @@ impl App {
|
||||
|
||||
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.";
|
||||
self.dialog_builder()
|
||||
.with_title("Slicing Error")
|
||||
.with_body(NO_MODELS_ERROR)
|
||||
.with_icon(Icon::Error)
|
||||
.open();
|
||||
self.popup.open(Popup::simple(
|
||||
"Slicing Error",
|
||||
PopupIcon::Error,
|
||||
NO_MODELS_ERROR,
|
||||
));
|
||||
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 {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
ctx.request_repaint();
|
||||
self.fps.update();
|
||||
self.popup.render(ctx);
|
||||
|
||||
// only update the visuals if the theme has changed
|
||||
match self.config.theme {
|
||||
Theme::Dark => ctx.set_visuals(Visuals::dark()),
|
||||
Theme::Light => ctx.set_visuals(Visuals::light()),
|
||||
}
|
||||
|
||||
match &mut self.modal {
|
||||
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);
|
||||
self.remote_print.tick(&mut self.state, &self.config);
|
||||
windows::ui(self, ctx);
|
||||
}
|
||||
}
|
||||
|
@@ -11,12 +11,11 @@ use wgpu::{DeviceDescriptor, Features, Limits, TextureFormat};
|
||||
const TEXTURE_FORMAT: TextureFormat = TextureFormat::Bgra8Unorm;
|
||||
|
||||
mod app;
|
||||
mod components;
|
||||
mod config;
|
||||
mod remote_print;
|
||||
mod render;
|
||||
mod slice_operation;
|
||||
mod ui_state;
|
||||
mod ui;
|
||||
mod windows;
|
||||
use app::App;
|
||||
|
||||
|
@@ -10,7 +10,6 @@ use std::{
|
||||
use anyhow::{Context, Result};
|
||||
use clone_macro::clone;
|
||||
use common::misc::random_string;
|
||||
use egui_modal::{Icon, Modal};
|
||||
use parking_lot::{Mutex, MutexGuard};
|
||||
use tracing::{info, warn};
|
||||
|
||||
@@ -25,7 +24,7 @@ use remote_send::{
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
ui_state::{RemotePrintConnectStatus, UiState},
|
||||
ui::state::{RemotePrintConnectStatus, UiState},
|
||||
};
|
||||
|
||||
pub struct RemotePrint {
|
||||
@@ -115,7 +114,7 @@ impl RemotePrint {
|
||||
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 {
|
||||
self.init().unwrap();
|
||||
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);
|
||||
}
|
||||
|
||||
let mut dialog_builder = || modal.as_mut().unwrap().dialog();
|
||||
// let mut dialog_builder = || modal.as_mut().unwrap().dialog();
|
||||
|
||||
let mut i = 0;
|
||||
while i < self.jobs.len() {
|
||||
@@ -138,11 +137,11 @@ impl RemotePrint {
|
||||
body.push(' ');
|
||||
}
|
||||
|
||||
dialog_builder()
|
||||
.with_title("Remote Print Error")
|
||||
.with_body(&body[..body.len() - 1])
|
||||
.with_icon(Icon::Error)
|
||||
.open();
|
||||
// dialog_builder()
|
||||
// .with_title("Remote Print Error")
|
||||
// .with_body(&body[..body.len() - 1])
|
||||
// .with_icon(Icon::Error)
|
||||
// .open();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
3
mslicer/src/ui/mod.rs
Normal file
3
mslicer/src/ui/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod components;
|
||||
pub mod popup;
|
||||
pub mod state;
|
114
mslicer/src/ui/popup.rs
Normal file
114
mslicer/src/ui/popup.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ use slicer::Pos;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
components::{vec3_dragger, vec3_dragger_proportional},
|
||||
ui::components::{vec3_dragger, vec3_dragger_proportional},
|
||||
};
|
||||
|
||||
enum Action {
|
||||
|
@@ -6,12 +6,17 @@ use const_format::concatcp;
|
||||
use egui::{
|
||||
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 notify_rust::Notification;
|
||||
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 {
|
||||
None,
|
||||
@@ -229,14 +234,14 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
|
||||
.add_printer(&app.state.working_address)
|
||||
.is_err()
|
||||
{
|
||||
app.dialog_builder()
|
||||
.with_title("Remote Print Error")
|
||||
.with_body(format!(
|
||||
app.popup.open(Popup::simple(
|
||||
"Remote Print Error",
|
||||
PopupIcon::Error,
|
||||
format!(
|
||||
"The entered address `{}` is invalid.",
|
||||
app.state.working_address,
|
||||
))
|
||||
.with_icon(Icon::Error)
|
||||
.open();
|
||||
),
|
||||
));
|
||||
app.state.working_address.clear();
|
||||
} else {
|
||||
app.state.remote_print_connecting =
|
||||
|
@@ -2,7 +2,7 @@ use egui::{Context, DragValue, Grid, Ui};
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
components::{vec2_dragger, vec3_dragger},
|
||||
ui::components::{vec2_dragger, vec3_dragger},
|
||||
};
|
||||
use common::config::ExposureConfig;
|
||||
|
||||
|
@@ -10,7 +10,7 @@ use nalgebra::Vector2;
|
||||
use rfd::FileDialog;
|
||||
|
||||
use crate::{
|
||||
app::App, components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback,
|
||||
app::App, ui::components::vec2_dragger, render::slice_preview::SlicePreviewRenderCallback,
|
||||
slice_operation::SliceResult,
|
||||
};
|
||||
use common::serde::DynamicSerializer;
|
||||
|
@@ -1,11 +1,14 @@
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
use egui::{Align, Context, Layout, TopBottomPanel};
|
||||
use egui_modal::Icon;
|
||||
use rfd::FileDialog;
|
||||
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) {
|
||||
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) {
|
||||
Ok(model) => model,
|
||||
Err(err) => {
|
||||
app.dialog_builder()
|
||||
.with_title("Import Error")
|
||||
.with_body(format!("Failed to import model.\n{err}"))
|
||||
.with_icon(Icon::Error)
|
||||
.open();
|
||||
app.popup.open(Popup::simple(
|
||||
"Import Error",
|
||||
PopupIcon::Error,
|
||||
format!("Failed to import model.\n{err}"),
|
||||
));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@ use tracing::error;
|
||||
|
||||
use crate::{
|
||||
app::App,
|
||||
components::{dragger, vec2_dragger, vec3_dragger},
|
||||
ui::components::{dragger, vec2_dragger, vec3_dragger},
|
||||
render::pipelines::model::RenderStyle,
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user