diff --git a/examples/nannou_basics/all_functions.rs b/examples/nannou_basics/all_functions.rs index 6d6fd7f32..2e1cc4474 100644 --- a/examples/nannou_basics/all_functions.rs +++ b/examples/nannou_basics/all_functions.rs @@ -33,6 +33,7 @@ fn model(app: &App) -> Model { .focused(window_focused) .unfocused(window_unfocused) .closed(window_closed) + .occluded(occluded) .build() .unwrap(); Model {} @@ -79,6 +80,7 @@ fn window_event(_app: &App, _model: &mut Model, event: WindowEvent) { Focused => {} Unfocused => {} Closed => {} + Occluded(_val) => {} } } @@ -119,3 +121,5 @@ fn hovered_file(_app: &App, _model: &mut Model, _path: std::path::PathBuf) {} fn hovered_file_cancelled(_app: &App, _model: &mut Model) {} fn dropped_file(_app: &App, _model: &mut Model, _path: std::path::PathBuf) {} + +fn occluded(_app: &App, _model: &mut Model, _val: bool) {} diff --git a/examples/offline/tree.rs b/examples/offline/tree.rs index 10260ca6d..ada578fd1 100644 --- a/examples/offline/tree.rs +++ b/examples/offline/tree.rs @@ -122,6 +122,7 @@ fn window_event(_app: &App, _model: &mut Model, event: WindowEvent) { Focused => {} Unfocused => {} Closed => {} + Occluded(_val) => {} } } diff --git a/examples/rust_basics/1_nannou_events.rs b/examples/rust_basics/1_nannou_events.rs index 8be3921bf..4581d70d7 100644 --- a/examples/rust_basics/1_nannou_events.rs +++ b/examples/rust_basics/1_nannou_events.rs @@ -54,6 +54,7 @@ fn event(_app: &App, _model: &mut Model, event: WindowEvent) { Focused => {} Unfocused => {} Closed => {} + Occluded(_val) => {} } } diff --git a/guide/src/changelog.md b/guide/src/changelog.md index 79f4b6fb8..42ac80487 100644 --- a/guide/src/changelog.md +++ b/guide/src/changelog.md @@ -7,6 +7,8 @@ back to the origins. # Unreleased +- Handle `winit::window::WindowEvent::Occluded` events. +- Update winit to `0.27`. - Add GL backend to default backends for better WASM support. - Add CI for testing the `wasm32-unknown-unknown` target. - Enable `wgpu/webgl` when `wasm` feature is enabled. diff --git a/nannou/Cargo.toml b/nannou/Cargo.toml index 3d50152bf..2ebcd60df 100644 --- a/nannou/Cargo.toml +++ b/nannou/Cargo.toml @@ -32,7 +32,7 @@ toml = "0.5" walkdir = "2" web-sys = { version = "0.3.55", optional = true } wgpu_upstream = { version = "0.11.1", package = "wgpu" } -winit = "0.26" +winit = "0.27" [features] default = ["notosans"] diff --git a/nannou/src/app.rs b/nannou/src/app.rs index 6fd899182..2a450e027 100644 --- a/nannou/src/app.rs +++ b/nannou/src/app.rs @@ -1708,6 +1708,7 @@ where event::WindowEvent::Focused => call_user_function!(focused), event::WindowEvent::Unfocused => call_user_function!(unfocused), event::WindowEvent::Closed => call_user_function!(closed), + event::WindowEvent::Occluded(val) => call_user_function!(occluded, val), } } } diff --git a/nannou/src/event.rs b/nannou/src/event.rs index 286230a48..b369697ef 100644 --- a/nannou/src/event.rs +++ b/nannou/src/event.rs @@ -172,6 +172,13 @@ pub enum WindowEvent { /// The window was closed and is no longer stored in the `App`. Closed, + + /// The window has been hidden from view. This event could be used to + /// optimize rendering. + /// + /// Currently supported on macOS, unsupported on iOS / Android / Web / + /// Wayland / Windows. + Occluded(bool), } impl WindowEvent { @@ -307,6 +314,14 @@ impl WindowEvent { | winit::event::WindowEvent::ScaleFactorChanged { .. } => { return None; } + + // These events will never happen as long as + // `Window::set_ime_allowed` is not called. + winit::event::WindowEvent::Ime(_) => { + return None; + } + + winit::event::WindowEvent::Occluded(occluded) => Occluded(*occluded), }; Some(event) diff --git a/nannou/src/window.rs b/nannou/src/window.rs index 81bab37d5..6fa64765a 100644 --- a/nannou/src/window.rs +++ b/nannou/src/window.rs @@ -40,6 +40,10 @@ pub struct Builder<'app> { app: &'app App, window: winit::window::WindowBuilder, title_was_set: bool, + fullscreen: Option, + size: Option, + min_size: Option, + max_size: Option, surface_conf_builder: SurfaceConfigurationBuilder, power_preference: wgpu::PowerPreference, force_fallback_adapter: bool, @@ -76,6 +80,7 @@ pub(crate) struct UserFunctions { pub(crate) focused: Option, pub(crate) unfocused: Option, pub(crate) closed: Option, + pub(crate) occluded: Option, } /// The user function type for drawing their model to the surface of a single window. @@ -163,6 +168,9 @@ pub type UnfocusedFn = fn(&App, &mut Model); /// A function for processing window closed events. pub type ClosedFn = fn(&App, &mut Model); +/// A function for processing window occluded events. +pub type OccludedFn = fn(&App, &mut Model, bool); + /// Errors that might occur while building the window. #[derive(Debug)] pub enum BuildError { @@ -230,6 +238,7 @@ fn_any!(DroppedFileFn, DroppedFileFnAny); fn_any!(FocusedFn, FocusedFnAny); fn_any!(UnfocusedFn, UnfocusedFnAny); fn_any!(ClosedFn, ClosedFnAny); +fn_any!(OccludedFn, OccludedFnAny); /// A nannou window. /// @@ -355,6 +364,10 @@ impl<'app> Builder<'app> { app, window: winit::window::WindowBuilder::new(), title_was_set: false, + fullscreen: None, + size: None, + min_size: None, + max_size: None, surface_conf_builder: Default::default(), power_preference: Self::DEFAULT_POWER_PREFERENCE, force_fallback_adapter: Self::DEFAULT_FORCE_FALLBACK_ADAPTER, @@ -691,6 +704,16 @@ impl<'app> Builder<'app> { self } + /// A function for processing the occluded event associated with this + /// window. + pub fn occluded(mut self, f: OccludedFn) -> Self + where + M: 'static, + { + self.user_functions.occluded = Some(OccludedFnAny::from_fn_ptr(f)); + self + } + /// The maximum number of simultaneous capture frame jobs that can be run for this window /// before we block and wait for the existing jobs to complete. /// @@ -737,6 +760,10 @@ impl<'app> Builder<'app> { app, mut window, title_was_set, + fullscreen, + size, + min_size, + max_size, surface_conf_builder, power_preference, force_fallback_adapter, @@ -771,39 +798,33 @@ impl<'app> Builder<'app> { ))] { use winit::platform::unix::WindowBuilderExtUnix; - window = window.with_class("nannou".to_string(), "nannou".to_string()); + window = window.with_name("nannou".to_string(), "nannou".to_string()); } // Set default dimensions in the case that none were given. - let initial_window_size = window - .window - .inner_size + let initial_window_size = size .or_else(|| { - window - .window - .fullscreen - .as_ref() - .and_then(|fullscreen| match fullscreen { - Fullscreen::Exclusive(video_mode) => { - let monitor = video_mode.monitor(); - Some( - video_mode - .size() - .to_logical::(monitor.scale_factor()) - .into(), - ) - } - Fullscreen::Borderless(monitor) => monitor.as_ref().map(|monitor| { - monitor + fullscreen.as_ref().and_then(|fullscreen| match fullscreen { + Fullscreen::Exclusive(video_mode) => { + let monitor = video_mode.monitor(); + Some( + video_mode .size() .to_logical::(monitor.scale_factor()) - .into() - }), - }) + .into(), + ) + } + Fullscreen::Borderless(monitor) => monitor.as_ref().map(|monitor| { + monitor + .size() + .to_logical::(monitor.scale_factor()) + .into() + }), + }) }) .unwrap_or_else(|| { let mut dim = DEFAULT_DIMENSIONS; - if let Some(min) = window.window.min_inner_size { + if let Some(min) = min_size { match min { winit::dpi::Size::Logical(min) => { dim.width = dim.width.max(min.width as _); @@ -816,7 +837,7 @@ impl<'app> Builder<'app> { } } } - if let Some(max) = window.window.max_inner_size { + if let Some(max) = max_size { match max { winit::dpi::Size::Logical(max) => { dim.width = dim.width.min(max.width as _); @@ -832,24 +853,23 @@ impl<'app> Builder<'app> { dim.into() }); - // Use the `initial_window_size` as the default dimensions for the window if none - // were specified. - if window.window.inner_size.is_none() && window.window.fullscreen.is_none() { - window.window.inner_size = Some(initial_window_size); + // Defaults dimensions are used if no size was set and not in fullscreen + // mode. + if size.is_none() && fullscreen.is_none() { + window = window.with_inner_size(initial_window_size); } // Set a default minimum window size for configuring the surface. - if window.window.min_inner_size.is_none() && window.window.fullscreen.is_none() { - window.window.min_inner_size = Some(winit::dpi::Size::Physical(MIN_SC_PIXELS)); + if min_size.is_none() && fullscreen.is_none() { + window = window.with_min_inner_size(winit::dpi::Size::Physical(MIN_SC_PIXELS)); } // Background must be initially cleared let is_invalidated = true; - let clear_color = clear_color.unwrap_or_else(|| { - let mut color: wgpu::Color = Default::default(); - color.a = if window.window.transparent { 0.0 } else { 1.0 }; - color + let clear_color = clear_color.unwrap_or_else(|| nannou_wgpu::Color { + a: if window.transparent() { 0.0 } else { 1.0 }, + ..Default::default() }); // Build the window. @@ -963,6 +983,10 @@ impl<'app> Builder<'app> { app, window, title_was_set, + fullscreen, + size, + min_size, + max_size, device_desc, power_preference, force_fallback_adapter, @@ -978,6 +1002,10 @@ impl<'app> Builder<'app> { app, window, title_was_set, + fullscreen, + size, + min_size, + max_size, device_desc, power_preference, force_fallback_adapter, @@ -999,13 +1027,17 @@ impl<'app> Builder<'app> { /// /// This describes to the "inner" part of the window, not including desktop decorations like the /// title bar. - pub fn size(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_inner_size(winit::dpi::LogicalSize { width, height })) + pub fn size(mut self, width: u32, height: u32) -> Self { + let size = winit::dpi::LogicalSize { width, height }; + self.size = Some(size.into()); + self.map_window(|w| w.with_inner_size(size)) } /// Set the minimum size in points for the window. - pub fn min_size(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_min_inner_size(winit::dpi::LogicalSize { width, height })) + pub fn min_size(mut self, width: u32, height: u32) -> Self { + let size = winit::dpi::LogicalSize { width, height }; + self.min_size = Some(size.into()); + self.map_window(|w| w.with_min_inner_size(size)) } /// Set the maximum size in points for the window. @@ -1017,8 +1049,10 @@ impl<'app> Builder<'app> { /// /// This describes to the "inner" part of the window, not including desktop decorations like the /// title bar. - pub fn size_pixels(self, width: u32, height: u32) -> Self { - self.map_window(|w| w.with_inner_size(winit::dpi::PhysicalSize { width, height })) + pub fn size_pixels(mut self, width: u32, height: u32) -> Self { + let size = winit::dpi::PhysicalSize { width, height }; + self.size = Some(size.into()); + self.map_window(|w| w.with_inner_size(size)) } /// Whether or not the window should be resizable after creation. @@ -1036,8 +1070,9 @@ impl<'app> Builder<'app> { } /// Create the window fullscreened on the current monitor. - pub fn fullscreen(self) -> Self { + pub fn fullscreen(mut self) -> Self { let fullscreen = Fullscreen::Borderless(self.app.primary_monitor()); + self.fullscreen = Some(fullscreen.clone()); self.fullscreen_with(Some(fullscreen)) } @@ -1380,7 +1415,17 @@ impl Window { /// - **iOS:** Always returns an Err. /// - **Web:** Has no effect. pub fn set_cursor_grab(&self, grab: bool) -> Result<(), winit::error::ExternalError> { - self.window.set_cursor_grab(grab) + if grab { + self.window + .set_cursor_grab(winit::window::CursorGrabMode::Confined) + .or_else(|_e| { + self.window + .set_cursor_grab(winit::window::CursorGrabMode::Locked) + }) + } else { + self.window + .set_cursor_grab(winit::window::CursorGrabMode::None) + } } /// Set the cursor's visibility. diff --git a/nannou_egui/Cargo.toml b/nannou_egui/Cargo.toml index b2776bff2..61657a4e4 100644 --- a/nannou_egui/Cargo.toml +++ b/nannou_egui/Cargo.toml @@ -14,5 +14,5 @@ readme = "../README.md" [dependencies] egui_wgpu_backend = "0.14" egui = "0.15.0" -winit = "0.26" +winit = "0.27" nannou = { version = "0.18.1", path = "../nannou" }