Merge pull request #1823 from hannobraun/window

Move `fj-window` to `fornjot-extra`
This commit is contained in:
Hanno Braun 2023-05-15 13:20:12 +02:00 committed by GitHub
commit 6ea77938d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1 additions and 505 deletions

View File

@ -6,7 +6,6 @@ members = [
"crates/fj-kernel", "crates/fj-kernel",
"crates/fj-math", "crates/fj-math",
"crates/fj-viewer", "crates/fj-viewer",
# "crates/fj-window",
"tools/autolib", "tools/autolib",
"tools/automator", "tools/automator",
@ -20,7 +19,6 @@ default-members = [
"crates/fj-kernel", "crates/fj-kernel",
"crates/fj-math", "crates/fj-math",
"crates/fj-viewer", "crates/fj-viewer",
# "crates/fj-window",
] ]
@ -28,10 +26,7 @@ default-members = [
version = "0.46.0" version = "0.46.0"
edition = "2021" edition = "2021"
description = """\ description = "Early-stage b-rep CAD kernel."
Early-stage, next-generation, code-first CAD application. Because the world \
needs another CAD program.\
"""
readme = "README.md" readme = "README.md"
homepage = "https://www.fornjot.app/" homepage = "https://www.fornjot.app/"
repository = "https://github.com/hannobraun/fornjot" repository = "https://github.com/hannobraun/fornjot"
@ -75,7 +70,3 @@ path = "crates/fj-proc"
[workspace.dependencies.fj-viewer] [workspace.dependencies.fj-viewer]
version = "0.46.0" version = "0.46.0"
path = "crates/fj-viewer" path = "crates/fj-viewer"
[workspace.dependencies.fj-window]
version = "0.46.0"
path = "crates/fj-window"

View File

@ -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

View File

@ -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.;

View File

@ -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;

View File

@ -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),
}

View File

@ -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);