mirror of https://github.com/hannobraun/Fornjot
Merge pull request #1823 from hannobraun/window
Move `fj-window` to `fornjot-extra`
This commit is contained in:
commit
6ea77938d2
11
Cargo.toml
11
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"
|
||||
|
|
|
@ -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
|
|
@ -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<MouseButton>,
|
||||
|
||||
/// 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:
|
||||
/// <https://github.com/rust-windowing/winit/issues/2094>
|
||||
pub new_size: Option<ScreenSize>,
|
||||
pub stop_drawing: bool,
|
||||
}
|
||||
|
||||
impl EventLoopHandler {
|
||||
pub fn handle_event(
|
||||
&mut self,
|
||||
event: Event<ModelEvent>,
|
||||
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<T>(
|
||||
event: &Event<T>,
|
||||
window: &Window,
|
||||
held_mouse_button: &Option<MouseButton>,
|
||||
previous_cursor: &mut Option<NormalizedScreenPosition>,
|
||||
invert_zoom: bool,
|
||||
) -> Option<InputEvent> {
|
||||
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<fj_host::Error>),
|
||||
|
||||
#[error("Shape processing error")]
|
||||
ShapeProcessor(#[from] Box<shape_processor::Error>),
|
||||
}
|
||||
|
||||
/// 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.;
|
|
@ -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;
|
|
@ -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<Model>,
|
||||
shape_processor: ShapeProcessor,
|
||||
invert_zoom: bool,
|
||||
) -> Result<(), Error> {
|
||||
let event_loop = EventLoopBuilder::<ModelEvent>::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),
|
||||
}
|
|
@ -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<T>(event_loop: &EventLoop<T>) -> Result<Self, Error> {
|
||||
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);
|
Loading…
Reference in New Issue