diff --git a/Cargo.lock b/Cargo.lock index 97dab07e7..ded72f909 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1177,7 +1177,6 @@ dependencies = [ name = "fj-interop" version = "0.22.0" dependencies = [ - "chrono", "fj-math", ] @@ -1236,6 +1235,7 @@ name = "fj-viewer" version = "0.22.0" dependencies = [ "bytemuck", + "chrono", "crossbeam-channel", "egui", "egui-wgpu", diff --git a/crates/fj-host/src/host.rs b/crates/fj-host/src/host.rs index 191423c5c..8a556e10b 100644 --- a/crates/fj-host/src/host.rs +++ b/crates/fj-host/src/host.rs @@ -12,7 +12,7 @@ impl Host { /// Create a new instance of `Host` /// /// This is only useful, if you want to continuously watch the model for - /// changes. If you don't just keep using `Model`. + /// changes. If you don't, just keep using `Model`. pub fn from_model(model: Model) -> Result { let watch_path = model.watch_path(); let evaluator = Evaluator::from_model(model); diff --git a/crates/fj-interop/Cargo.toml b/crates/fj-interop/Cargo.toml index cf6bf44e5..e40226a36 100644 --- a/crates/fj-interop/Cargo.toml +++ b/crates/fj-interop/Cargo.toml @@ -11,5 +11,4 @@ keywords.workspace = true categories.workspace = true [dependencies] -chrono = "0.4.22" fj-math.workspace = true diff --git a/crates/fj-interop/src/lib.rs b/crates/fj-interop/src/lib.rs index 450190fe1..cea11ceb3 100644 --- a/crates/fj-interop/src/lib.rs +++ b/crates/fj-interop/src/lib.rs @@ -18,4 +18,3 @@ pub mod debug; pub mod ext; pub mod mesh; pub mod processed_shape; -pub mod status_report; diff --git a/crates/fj-viewer/Cargo.toml b/crates/fj-viewer/Cargo.toml index 3ec99213f..8fca4af35 100644 --- a/crates/fj-viewer/Cargo.toml +++ b/crates/fj-viewer/Cargo.toml @@ -12,6 +12,7 @@ categories.workspace = true [dependencies] bytemuck = "1.12.2" +chrono = "0.4.22" crossbeam-channel = "0.5.6" egui = "0.19.0" egui-wgpu = "0.19.0" diff --git a/crates/fj-viewer/src/graphics/renderer.rs b/crates/fj-viewer/src/graphics/renderer.rs index 4912ab30d..cea4a85ce 100644 --- a/crates/fj-viewer/src/graphics/renderer.rs +++ b/crates/fj-viewer/src/graphics/renderer.rs @@ -1,6 +1,5 @@ -use std::{io, mem::size_of, path::PathBuf}; +use std::{io, mem::size_of}; -use crossbeam_channel::{Receiver, Sender}; use thiserror::Error; use tracing::debug; use wgpu::util::DeviceExt as _; @@ -172,12 +171,8 @@ impl Renderer { }) } - pub(crate) fn init_gui( - &self, - event_rx: Receiver<()>, - event_tx: Sender, - ) -> Gui { - Gui::new(&self.device, self.surface_config.format, event_rx, event_tx) + pub(crate) fn init_gui(&self) -> Gui { + Gui::new(&self.device, self.surface_config.format) } /// Updates the geometry of the model being rendered. diff --git a/crates/fj-viewer/src/gui.rs b/crates/fj-viewer/src/gui.rs index 6c1fad6d3..3e1216776 100644 --- a/crates/fj-viewer/src/gui.rs +++ b/crates/fj-viewer/src/gui.rs @@ -19,42 +19,24 @@ use std::path::PathBuf; #[cfg(not(target_arch = "wasm32"))] use std::env::current_dir; -use crossbeam_channel::{Receiver, Sender}; - #[cfg(not(target_arch = "wasm32"))] use rfd::FileDialog; -use fj_interop::status_report::StatusReport; use fj_math::{Aabb, Scalar}; -use crate::graphics::DrawConfig; - -struct GuiState { - has_model: bool, -} - -impl Default for GuiState { - fn default() -> Self { - Self { has_model: true } - } -} +use crate::{graphics::DrawConfig, StatusReport}; /// The GUI pub struct Gui { context: egui::Context, render_pass: egui_wgpu::renderer::RenderPass, options: Options, - event_rx: Receiver<()>, - event_tx: Sender, - state: GuiState, } impl Gui { pub(crate) fn new( device: &wgpu::Device, texture_format: wgpu::TextureFormat, - event_rx: Receiver<()>, - event_tx: Sender, ) -> Self { // The implementation of the integration with `egui` is likely to need // to change "significantly" depending on what architecture approach is @@ -94,9 +76,6 @@ impl Gui { context, render_pass, options: Default::default(), - event_rx, - event_tx, - state: Default::default(), } } @@ -111,26 +90,9 @@ impl Gui { egui_input: egui::RawInput, config: &mut DrawConfig, aabb: &Aabb<3>, - status: &StatusReport, line_drawing_available: bool, - ) { - loop { - let gui_event = self - .event_rx - .try_recv() - .map_err(|err| { - if err.is_disconnected() { - panic!("Expected channel to never disconnect"); - } - }) - .ok(); - - match gui_event { - Some(_) => self.state.has_model = false, - None => break, - }; - } - + state: GuiState, + ) -> Option { self.context.set_pixels_per_point(pixels_per_point); self.context.begin_frame(egui_input); @@ -281,15 +243,20 @@ impl Gui { egui::Area::new("fj-status-message").show(&self.context, |ui| { ui.group(|ui| { ui.add(egui::Label::new( - egui::RichText::new(format!("Status:{}", status.status())) - .monospace() - .color(egui::Color32::BLACK) - .background_color(egui::Color32::WHITE), + egui::RichText::new(format!( + "Status:{}", + state.status.status() + )) + .monospace() + .color(egui::Color32::BLACK) + .background_color(egui::Color32::WHITE), )) }) }); - if !self.state.has_model { + let mut new_model_path = None; + + if !state.model_available { egui::Area::new("ask-model") .anchor(egui::Align2::CENTER_CENTER, [0_f32, -5_f32]) .show(&self.context, |ui| { @@ -302,18 +269,13 @@ impl Gui { .button(egui::RichText::new("Pick a model")) .clicked() { - let model_dir = show_file_dialog(); - if let Some(model_dir) = model_dir { - self.event_tx - .send(model_dir) - .expect("Channel is disconnected"); - - self.state.has_model = true; - } + new_model_path = show_file_dialog(); } }) }); } + + new_model_path } pub(crate) fn draw( @@ -376,3 +338,12 @@ pub struct Options { pub show_settings_ui: bool, pub show_inspection_ui: bool, } + +/// The current status of the GUI +pub struct GuiState<'a> { + /// Reference to the status messages + pub status: &'a StatusReport, + + /// Indicates whether a model is currently available + pub model_available: bool, +} diff --git a/crates/fj-viewer/src/lib.rs b/crates/fj-viewer/src/lib.rs index eea0499fb..bab957c54 100644 --- a/crates/fj-viewer/src/lib.rs +++ b/crates/fj-viewer/src/lib.rs @@ -19,13 +19,15 @@ mod graphics; mod gui; mod input; mod screen; +mod status_report; mod viewer; pub use self::{ camera::Camera, graphics::{DrawConfig, Renderer, RendererInitError}, - gui::Gui, + gui::{Gui, GuiState}, input::{InputEvent, InputHandler}, screen::{NormalizedScreenPosition, Screen, ScreenSize}, + status_report::StatusReport, viewer::Viewer, }; diff --git a/crates/fj-interop/src/status_report.rs b/crates/fj-viewer/src/status_report.rs similarity index 95% rename from crates/fj-interop/src/status_report.rs rename to crates/fj-viewer/src/status_report.rs index 9405ff862..b78cf9238 100644 --- a/crates/fj-interop/src/status_report.rs +++ b/crates/fj-viewer/src/status_report.rs @@ -11,7 +11,7 @@ pub struct StatusReport { } impl StatusReport { - /// Create a new ``StatusReport`` instance with a blank status + /// Create a new `StatusReport` instance with a blank status pub fn new() -> Self { Self::default() } diff --git a/crates/fj-viewer/src/viewer.rs b/crates/fj-viewer/src/viewer.rs index c0520084f..db03c42f4 100644 --- a/crates/fj-viewer/src/viewer.rs +++ b/crates/fj-viewer/src/viewer.rs @@ -1,16 +1,13 @@ use std::path::PathBuf; -use fj_interop::{ - processed_shape::ProcessedShape, status_report::StatusReport, -}; +use fj_interop::processed_shape::ProcessedShape; use fj_math::Aabb; use tracing::warn; -use crossbeam_channel::{Receiver, Sender}; - use crate::{ - camera::FocusPoint, gui::Gui, Camera, DrawConfig, InputEvent, InputHandler, - NormalizedScreenPosition, Renderer, RendererInitError, Screen, ScreenSize, + camera::FocusPoint, gui::Gui, Camera, DrawConfig, GuiState, InputEvent, + InputHandler, NormalizedScreenPosition, Renderer, RendererInitError, + Screen, ScreenSize, }; /// The Fornjot model viewer @@ -42,13 +39,9 @@ pub struct Viewer { impl Viewer { /// Construct a new instance of `Viewer` - pub async fn new( - screen: &impl Screen, - event_rx: Receiver<()>, - event_tx: Sender, - ) -> Result { + pub async fn new(screen: &impl Screen) -> Result { let renderer = Renderer::new(screen).await?; - let gui = renderer.init_gui(event_rx, event_tx); + let gui = renderer.init_gui(); Ok(Self { camera: Camera::default(), @@ -128,9 +121,9 @@ impl Viewer { pub fn draw( &mut self, pixels_per_point: f32, - status: &mut StatusReport, egui_input: egui::RawInput, - ) { + gui_state: GuiState, + ) -> Option { let aabb = self .shape .as_ref() @@ -139,13 +132,13 @@ impl Viewer { self.camera.update_planes(&aabb); - self.gui.update( + let new_model_path = self.gui.update( pixels_per_point, egui_input, &mut self.draw_config, &aabb, - status, self.renderer.is_line_drawing_available(), + gui_state, ); if let Err(err) = self.renderer.draw( @@ -156,5 +149,7 @@ impl Viewer { ) { warn!("Draw error: {}", err); } + + new_model_path } } diff --git a/crates/fj-window/src/run.rs b/crates/fj-window/src/run.rs index 4bc91f38b..759a58f22 100644 --- a/crates/fj-window/src/run.rs +++ b/crates/fj-window/src/run.rs @@ -3,14 +3,13 @@ //! Provides the functionality to create a window and perform basic viewing //! with programmed models. -use std::{error, path::PathBuf}; +use std::error; use fj_host::{Host, Model, ModelEvent, Parameters}; -use fj_interop::status_report::StatusReport; use fj_operations::shape_processor::ShapeProcessor; use fj_viewer::{ - InputEvent, NormalizedScreenPosition, RendererInitError, Screen, - ScreenSize, Viewer, + GuiState, InputEvent, NormalizedScreenPosition, RendererInitError, Screen, + ScreenSize, StatusReport, Viewer, }; use futures::executor::block_on; use tracing::trace; @@ -31,26 +30,17 @@ pub fn run( shape_processor: ShapeProcessor, invert_zoom: bool, ) -> Result<(), Error> { - let (send_gui, gui_event_rx) = crossbeam_channel::bounded::<()>(1); - let (gui_event_tx, recv_gui) = crossbeam_channel::bounded::(1); - let mut status = StatusReport::new(); let event_loop = EventLoop::new(); let window = Window::new(&event_loop)?; - let mut viewer = - block_on(Viewer::new(&window, gui_event_rx, gui_event_tx))?; + let mut viewer = block_on(Viewer::new(&window))?; let mut held_mouse_button = None; let mut egui_winit_state = egui_winit::State::new(&event_loop); - let mut host = None; - if let Some(model) = model { - host = Some(Host::from_model(model)?); - } else { - send_gui.send(()).expect("Channel is disconnected"); - } + let mut host = model.map(Host::from_model).transpose()?; // Only handle resize events once every frame. This filters out spurious // resize events that can lead to wgpu warnings. See this issue for some @@ -61,28 +51,6 @@ pub fn run( event_loop.run(move |event, _, control_flow| { trace!("Handling event: {:?}", event); - let gui_event = recv_gui - .try_recv() - .map_err(|err| { - if err.is_disconnected() { - panic!("Expected channel to never disconnect"); - } - }) - .ok(); - - if let Some(model_path) = gui_event { - let model = Model::new(model_path, Parameters::empty()).unwrap(); - match Host::from_model(model) { - Ok(new_host) => { - host = Some(new_host); - } - Err(_) => { - status.update_status("Error creating host."); - send_gui.send(()).expect("Channel is disconnected"); - } - } - } - if let Some(host) = &host { loop { let events = host.events(); @@ -235,7 +203,25 @@ pub fn run( let egui_input = egui_winit_state.take_egui_input(window.window()); - viewer.draw(pixels_per_point, &mut status, egui_input); + let gui_state = GuiState { + status: &status, + model_available: host.is_some(), + }; + let new_model_path = + viewer.draw(pixels_per_point, egui_input, gui_state); + + if let Some(model_path) = new_model_path { + let model = + Model::new(model_path, Parameters::empty()).unwrap(); + match Host::from_model(model) { + Ok(new_host) => { + host = Some(new_host); + } + Err(_) => { + status.update_status("Error creating host."); + } + } + } } _ => {} }