diff --git a/Cargo.toml b/Cargo.toml index 25bc1b8f1..f376828fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "crates/fj-kernel", "crates/fj-math", "crates/fj-viewer", - # "crates/fj-window", "tools/autolib", "tools/automator", @@ -20,7 +19,6 @@ default-members = [ "crates/fj-kernel", "crates/fj-math", "crates/fj-viewer", - # "crates/fj-window", ] @@ -28,10 +26,7 @@ default-members = [ version = "0.46.0" edition = "2021" -description = """\ -Early-stage, next-generation, code-first CAD application. Because the world \ -needs another CAD program.\ -""" +description = "Early-stage b-rep CAD kernel." readme = "README.md" homepage = "https://www.fornjot.app/" repository = "https://github.com/hannobraun/fornjot" @@ -75,7 +70,3 @@ path = "crates/fj-proc" [workspace.dependencies.fj-viewer] version = "0.46.0" path = "crates/fj-viewer" - -[workspace.dependencies.fj-window] -version = "0.46.0" -path = "crates/fj-window" diff --git a/crates/fj-window/Cargo.toml b/crates/fj-window/Cargo.toml deleted file mode 100644 index f7dc36e24..000000000 --- a/crates/fj-window/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "fj-window" -version.workspace = true -edition.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[dependencies] -fj-host.workspace = true -fj-operations.workspace = true -fj-viewer.workspace = true -fj-interop.workspace = true -crossbeam-channel = "0.5.8" -futures = "0.3.28" -thiserror = "1.0.40" -tracing = "0.1.37" -winit = "0.28.5" - -[dependencies.egui-winit] -version = "0.21.1" -default-features = false diff --git a/crates/fj-window/src/event_loop_handler.rs b/crates/fj-window/src/event_loop_handler.rs deleted file mode 100644 index 4ff98cbd3..000000000 --- a/crates/fj-window/src/event_loop_handler.rs +++ /dev/null @@ -1,290 +0,0 @@ -use fj_host::{Host, Model, ModelEvent, Parameters}; -use fj_operations::shape_processor; -use fj_viewer::{ - GuiState, InputEvent, NormalizedScreenPosition, Screen, ScreenSize, - StatusReport, Viewer, -}; -use winit::{ - dpi::PhysicalPosition, - event::{ - ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, - VirtualKeyCode, WindowEvent, - }, - event_loop::ControlFlow, -}; - -use crate::window::Window; - -pub struct EventLoopHandler { - pub invert_zoom: bool, - pub window: Window, - pub viewer: Viewer, - pub egui_winit_state: egui_winit::State, - pub host: Host, - pub status: StatusReport, - pub held_mouse_button: Option, - - /// Only handle resize events once every frame. This filters out spurious - /// resize events that can lead to wgpu warnings. See this issue for some - /// context: - /// - pub new_size: Option, - pub stop_drawing: bool, -} - -impl EventLoopHandler { - pub fn handle_event( - &mut self, - event: Event, - control_flow: &mut ControlFlow, - ) -> Result<(), Error> { - // Trigger a panic if the host thread has panicked. - self.host.propagate_panic(); - - if let Event::WindowEvent { event, .. } = &event { - let egui_winit::EventResponse { - consumed, - // This flag was introduced in egui-winit 0.20. I don't think we - // need to handle this, as we already do a full update of the - // GUI every frame. It might be possible to do less repainting - // though, if we only did it here, if the flag was set. - repaint: _, - } = self - .egui_winit_state - .on_event(self.viewer.gui.context(), event); - - if consumed { - return Ok(()); - } - } - - let input_event = input_event( - &event, - &self.window, - &self.held_mouse_button, - &mut self.viewer.cursor, - self.invert_zoom, - ); - if let Some(input_event) = input_event { - self.viewer.handle_input_event(input_event); - } - - // fj-window events - match event { - Event::UserEvent(event) => match event { - ModelEvent::StartWatching => { - self.status - .update_status("New model loaded. Evaluating model..."); - } - ModelEvent::ChangeDetected => { - self.status.update_status( - "Change in model detected. Evaluating model...", - ); - } - ModelEvent::Evaluated => { - self.status - .update_status("Model evaluated. Processing model..."); - } - ModelEvent::ProcessedShape(shape) => { - self.viewer.handle_shape_update(shape); - self.status.update_status("Model processed."); - } - - ModelEvent::Error(err) => { - return Err(Box::new(err).into()); - } - ModelEvent::Warning(warning) => { - self.status.update_status(&warning); - } - }, - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - } - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(virtual_key_code), - .. - }, - .. - }, - .. - } => match virtual_key_code { - VirtualKeyCode::Escape => *control_flow = ControlFlow::Exit, - VirtualKeyCode::Key1 => { - self.viewer.toggle_draw_model(); - } - VirtualKeyCode::Key2 => { - self.viewer.toggle_draw_mesh(); - } - VirtualKeyCode::Key3 => { - self.viewer.toggle_draw_debug(); - } - _ => {} - }, - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - self.new_size = Some(ScreenSize { - width: size.width, - height: size.height, - }); - } - Event::WindowEvent { - event: WindowEvent::MouseInput { state, button, .. }, - .. - } => match state { - ElementState::Pressed => { - self.held_mouse_button = Some(button); - self.viewer.add_focus_point(); - } - ElementState::Released => { - self.held_mouse_button = None; - self.viewer.remove_focus_point(); - } - }, - Event::WindowEvent { - event: WindowEvent::MouseWheel { .. }, - .. - } => self.viewer.add_focus_point(), - Event::MainEventsCleared => { - self.window.window().request_redraw(); - } - Event::RedrawRequested(_) => { - // Only do a screen resize once per frame. This protects against - // spurious resize events that cause issues with the renderer. - if let Some(size) = self.new_size.take() { - self.stop_drawing = size.width == 0 || size.height == 0; - if !self.stop_drawing { - self.viewer.handle_screen_resize(size); - } - } - - if !self.stop_drawing { - let pixels_per_point = - self.window.window().scale_factor() as f32; - - self.egui_winit_state - .set_pixels_per_point(pixels_per_point); - let egui_input = self - .egui_winit_state - .take_egui_input(self.window.window()); - - let gui_state = GuiState { - status: &self.status, - model_available: self.host.is_model_loaded(), - }; - let new_model_path = self.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()) - .map_err(Box::new)?; - self.host.load_model(model); - } - } - } - _ => {} - } - - Ok(()) - } -} - -fn input_event( - event: &Event, - window: &Window, - held_mouse_button: &Option, - previous_cursor: &mut Option, - invert_zoom: bool, -) -> Option { - match event { - Event::WindowEvent { - event: WindowEvent::CursorMoved { position, .. }, - .. - } => { - let [width, height] = window.size().as_f64(); - let aspect_ratio = width / height; - - // Cursor position in normalized coordinates (-1 to +1) with - // aspect ratio taken into account. - let current = NormalizedScreenPosition { - x: position.x / width * 2. - 1., - y: -(position.y / height * 2. - 1.) / aspect_ratio, - }; - let event = match (*previous_cursor, held_mouse_button) { - (Some(previous), Some(button)) => match button { - MouseButton::Left => { - let diff_x = current.x - previous.x; - let diff_y = current.y - previous.y; - let angle_x = -diff_y * ROTATION_SENSITIVITY; - let angle_y = diff_x * ROTATION_SENSITIVITY; - - Some(InputEvent::Rotation { angle_x, angle_y }) - } - MouseButton::Right => { - Some(InputEvent::Translation { previous, current }) - } - _ => None, - }, - _ => None, - }; - *previous_cursor = Some(current); - event - } - Event::WindowEvent { - event: WindowEvent::MouseWheel { delta, .. }, - .. - } => { - let delta = match delta { - MouseScrollDelta::LineDelta(_, y) => { - f64::from(*y) * ZOOM_FACTOR_LINE - } - MouseScrollDelta::PixelDelta(PhysicalPosition { - y, .. - }) => y * ZOOM_FACTOR_PIXEL, - }; - - let delta = if invert_zoom { -delta } else { delta }; - - Some(InputEvent::Zoom(delta)) - } - _ => None, - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Host error")] - Host(#[from] Box), - - #[error("Shape processing error")] - ShapeProcessor(#[from] Box), -} - -/// Affects the speed of zoom movement given a scroll wheel input in lines. -/// -/// Smaller values will move the camera less with the same input. -/// Larger values will move the camera more with the same input. -const ZOOM_FACTOR_LINE: f64 = 0.075; - -/// Affects the speed of zoom movement given a scroll wheel input in pixels. -/// -/// Smaller values will move the camera less with the same input. -/// Larger values will move the camera more with the same input. -const ZOOM_FACTOR_PIXEL: f64 = 0.005; - -/// Affects the speed of rotation given a change in normalized screen position [-1, 1] -/// -/// Smaller values will move the camera less with the same input. -/// Larger values will move the camera more with the same input. -const ROTATION_SENSITIVITY: f64 = 5.; diff --git a/crates/fj-window/src/lib.rs b/crates/fj-window/src/lib.rs deleted file mode 100644 index ccf7450ba..000000000 --- a/crates/fj-window/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! # Fornjot Window Abstraction -//! -//! This library is part of the [Fornjot] ecosystem. Fornjot is an open-source, -//! code-first CAD application; and collection of libraries that make up the CAD -//! application, but can be used independently. -//! -//! This library is an internal component of Fornjot. It is not relevant to end -//! users that just want to create CAD models. -//! -//! This library provides a window abstraction based on Winit. -//! -//! [Fornjot]: https://www.fornjot.app/ - -#![warn(missing_docs)] - -pub mod run; -pub mod window; - -mod event_loop_handler; diff --git a/crates/fj-window/src/run.rs b/crates/fj-window/src/run.rs deleted file mode 100644 index 94df90d71..000000000 --- a/crates/fj-window/src/run.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Model viewer initialization and event processing -//! -//! Provides the functionality to create a window and perform basic viewing -//! with programmed models. - -use std::{ - error, - fmt::{self, Write}, - thread, -}; - -use fj_host::{Host, Model, ModelEvent}; -use fj_operations::shape_processor::ShapeProcessor; -use fj_viewer::{RendererInitError, StatusReport, Viewer}; -use futures::executor::block_on; -use tracing::trace; -use winit::event_loop::EventLoopBuilder; - -use crate::{ - event_loop_handler::{self, EventLoopHandler}, - window::{self, Window}, -}; - -/// Initializes a model viewer for a given model and enters its process loop. -pub fn run( - model: Option, - shape_processor: ShapeProcessor, - invert_zoom: bool, -) -> Result<(), Error> { - let event_loop = EventLoopBuilder::::with_user_event().build(); - let window = Window::new(&event_loop)?; - let viewer = block_on(Viewer::new(&window))?; - - let egui_winit_state = egui_winit::State::new(&event_loop); - - let (model_event_tx, model_event_rx) = crossbeam_channel::unbounded(); - let event_proxy = event_loop.create_proxy(); - - let _event_relay_join_handle = thread::Builder::new() - .name("event_relay".to_string()) - .spawn(move || { - for event in model_event_rx { - if event_proxy.send_event(event).is_err() { - // Looks like the main window closed. - break; - } - } - }); - - let mut host = Host::new(shape_processor, model_event_tx); - - if let Some(model) = model { - host.load_model(model); - } - - let mut handler = EventLoopHandler { - invert_zoom, - window, - viewer, - egui_winit_state, - host, - status: StatusReport::new(), - held_mouse_button: None, - new_size: None, - stop_drawing: false, - }; - - event_loop.run(move |event, _, control_flow| { - trace!("Handling event: {:?}", event); - - if let Err(err) = handler.handle_event(event, control_flow) { - handle_error(err, &mut handler.status) - .expect("Expected error handling not to fail"); - } - }); -} - -fn handle_error( - err: event_loop_handler::Error, - status: &mut StatusReport, -) -> Result<(), fmt::Error> { - // Can be cleaned up, once `Report` is stable: - // https://doc.rust-lang.org/std/error/struct.Report.html - - let mut msg = String::new(); - - writeln!(msg, "Shape processing error: {err}")?; - - let mut current_err = &err as &dyn error::Error; - while let Some(err) = current_err.source() { - writeln!(msg)?; - writeln!(msg, "Caused by:")?; - writeln!(msg, " {err}")?; - - current_err = err; - } - - status.update_status(&msg); - - Ok(()) -} - -/// Error in main loop -#[derive(Debug, thiserror::Error)] -pub enum Error { - /// Error loading model - #[error("Error loading model")] - Model(#[from] fj_host::Error), - - /// Error initializing window - #[error("Error initializing window")] - WindowInit(#[from] window::Error), - - /// Error initializing graphics - #[error("Error initializing graphics")] - GraphicsInit(#[from] RendererInitError), -} diff --git a/crates/fj-window/src/window.rs b/crates/fj-window/src/window.rs deleted file mode 100644 index 25d709d39..000000000 --- a/crates/fj-window/src/window.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! CAD viewer utility windowing abstraction - -use fj_viewer::{Screen, ScreenSize}; -use winit::{event_loop::EventLoop, window::WindowBuilder}; - -/// Window abstraction providing details such as the width or height and easing initialization. -pub struct Window(winit::window::Window); - -impl Window { - /// Returns a new window with the given `EventLoop`. - pub fn new(event_loop: &EventLoop) -> Result { - let window = WindowBuilder::new() - .with_title("Fornjot") - .with_maximized(true) - .with_decorations(true) - .with_transparent(false) - .build(event_loop)?; - - Ok(Self(window)) - } -} - -impl Screen for Window { - type Window = winit::window::Window; - - fn size(&self) -> ScreenSize { - let size = self.0.inner_size(); - - ScreenSize { - width: size.width, - height: size.height, - } - } - - fn window(&self) -> &winit::window::Window { - &self.0 - } -} - -/// Error initializing window -#[derive(Debug, thiserror::Error)] -#[error("Error initializing window")] -pub struct Error(#[from] pub winit::error::OsError);