Popup system
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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",
|
||||||
|
@@ -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"] }
|
||||||
|
1
TODO.md
1
TODO.md
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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
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::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
components::{vec3_dragger, vec3_dragger_proportional},
|
ui::components::{vec3_dragger, vec3_dragger_proportional},
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
|
@@ -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 =
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user