diff --git a/examples/open_parented/src/main.rs b/examples/open_parented/src/main.rs index dac0c1da..8ac9bda3 100644 --- a/examples/open_parented/src/main.rs +++ b/examples/open_parented/src/main.rs @@ -26,6 +26,7 @@ impl ParentWindowHandler { .with_title("baseview child"); let child_window = Window::create(window_open_options, ChildWindowHandler::new)?; + child_window.show()?; Ok(Self { surface: surface.into(), damaged: true.into(), child_window }) } diff --git a/examples/plugin_clack/Cargo.toml b/examples/plugin_clack/Cargo.toml index 71546862..03a3164a 100644 --- a/examples/plugin_clack/Cargo.toml +++ b/examples/plugin_clack/Cargo.toml @@ -8,7 +8,7 @@ crate-type = ["cdylib"] [dependencies] clack-plugin = "0.1.0" -clack-extensions = { version = "0.1.0", features = ["gui", "clack-plugin", "raw-window-handle_06"] } +clack-extensions = { version = "0.1.0", features = ["gui", "state", "clack-plugin", "raw-window-handle_06"] } baseview = { path = "../..", features = ["opengl"] } softbuffer = "0.4.8" raw-window-handle = "0.6.2" diff --git a/examples/plugin_clack/src/gui.rs b/examples/plugin_clack/src/gui.rs index 4fcfac6a..19accba8 100644 --- a/examples/plugin_clack/src/gui.rs +++ b/examples/plugin_clack/src/gui.rs @@ -32,6 +32,7 @@ impl PluginGuiImpl for ExamplePluginMainThread<'_> { fn create(&mut self, _configuration: GuiConfiguration) -> Result<(), PluginError> { let options = WindowSettings::new() + .parented() .with_size(PhysicalSize::new(400, 200)) .with_gl_config(GlConfig::default()); diff --git a/examples/plugin_clack/src/lib.rs b/examples/plugin_clack/src/lib.rs index b300b33c..316f258f 100644 --- a/examples/plugin_clack/src/lib.rs +++ b/examples/plugin_clack/src/lib.rs @@ -1,7 +1,9 @@ use crate::audio::ExamplePluginAudioProcessor; use crate::gui::ExamplePluginGui; use clack_extensions::gui::{HostGui, PluginGui}; +use clack_extensions::state::{PluginState, PluginStateImpl}; use clack_plugin::prelude::*; +use clack_plugin::stream::{InputStream, OutputStream}; mod audio; mod gui; @@ -18,7 +20,7 @@ impl Plugin for ExamplePlugin { type MainThread<'a> = ExamplePluginMainThread<'a>; fn declare_extensions(builder: &mut PluginExtensions, _shared: Option<&()>) { - builder.register::(); + builder.register::().register::(); } } @@ -59,4 +61,14 @@ impl<'a> PluginMainThread<'a, ()> for ExamplePluginMainThread<'a> { } } +impl PluginStateImpl for ExamplePluginMainThread<'_> { + fn save(&mut self, _output: &mut OutputStream) -> Result<(), PluginError> { + Ok(()) + } + + fn load(&mut self, _input: &mut InputStream) -> Result<(), PluginError> { + Ok(()) + } +} + clack_export_entry!(SinglePluginEntry); diff --git a/src/handler.rs b/src/handler.rs index f6dfe515..4d6ee69d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -9,8 +9,16 @@ pub trait WindowHandler: 'static { fn on_frame(&self) -> core::result::Result<(), HandlerError>; /// Informs the handler that the window has been resized. /// - /// If this returns an error, the resize operation will be reverted in order to keep the current - /// size. + /// # Errors + /// + /// This operation can fail, in which case an [`HandlerError`] can be returned. + /// This can happen if e.g. an underlying buffer could not be resized, or some kind of driver error. + /// + /// In case this `resized` operation fails, `baseview` will assume that it did not meaningfully + /// change anything, and that the window is still able to render and operate at the previous size. + /// + /// It will also attempt to resize the underlying platform window and parent window back to the + /// previous size, but this is only a best-effort attempt since those operations can also fail. fn resized(&self, new_size: WindowSize) -> core::result::Result<(), HandlerError>; fn on_event(&self, event: Event) -> EventStatus; } diff --git a/src/lib.rs b/src/lib.rs index c97c11cb..a7f6a6e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,9 +6,9 @@ mod handler; pub mod host; mod keyboard; mod mouse_cursor; +mod settings; mod tracing; mod window; -mod window_open_options; pub(crate) mod platform; @@ -22,8 +22,8 @@ pub use error::*; pub use event::*; pub use handler::WindowHandler; pub use mouse_cursor::MouseCursor; +pub use settings::*; pub use window::*; -pub use window_open_options::*; #[allow(unused)] pub(crate) use tracing::*; diff --git a/src/platform/macos/view.rs b/src/platform/macos/view.rs index 4c66d122..3d4fd51b 100644 --- a/src/platform/macos/view.rs +++ b/src/platform/macos/view.rs @@ -2,15 +2,15 @@ use super::keyboard::{make_modifiers, KeyboardState}; use super::window::WindowSharedState; -use crate::handler::WindowHandlerBuilder; use crate::host::Host; use crate::platform::*; use crate::tracing::warn; +use crate::window::WindowInitializer; use crate::wrappers::appkit::*; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; use crate::{ DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, ScrollDelta, WindowEvent, - WindowHandler, WindowSettings, WindowSize, + WindowHandler, WindowSize, }; use dpi::{LogicalPosition, LogicalSize, Size}; use objc2::__framework_prelude::Retained; @@ -79,8 +79,8 @@ pub(crate) struct BaseviewView { impl BaseviewView { pub fn new( - _options: WindowSettings, builder: WindowHandlerBuilder, parenting: ViewParentingType, - host: Host, final_size: LogicalSize, mtm: MainThreadMarker, + init: WindowInitializer, parenting: ViewParentingType, final_size: LogicalSize, + mtm: MainThreadMarker, ) -> Result<(Retained>, Rc)> { let view_rect = NSRect::new(NSPoint::ZERO, NSSize::new(final_size.width, final_size.height)); @@ -96,7 +96,7 @@ impl BaseviewView { window_handler: WindowHandlerContainer::new(), notification_center_observer: None.into(), parenting: ViewParentingType::Uninitialized.into(), - host, + host: init.host, lifetime_tied_to_app: None.into(), #[cfg(feature = "opengl")] @@ -112,13 +112,13 @@ impl BaseviewView { view.state.size.set(view.view.size()); #[cfg(feature = "opengl")] - if let Some(gl_config) = _options.gl_config { + if let Some(gl_config) = init.settings.gl_config { let gl_context = super::gl::GlContext::create(view.view, gl_config, view.mtm)?; let Ok(()) = view.gl_context.set(gl_context) else { unreachable!() }; } let context = WindowContext::new(view); - let handler = builder.build(crate::WindowContext::new(context))?; + let handler = init.builder.build(crate::WindowContext::new(context))?; // Initialize handler view.window_handler.set(handler); diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index b309c007..f6ae43ac 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -6,8 +6,6 @@ use objc2_foundation::{NSSize, NSString}; use std::cell::Cell; use std::rc::Rc; -use crate::handler::WindowHandlerBuilder; -use crate::host::Host; use crate::platform::macos::view::{BaseviewView, ViewParentingType}; use crate::platform::ParentWindowHandle; use crate::platform::Result; @@ -31,9 +29,7 @@ impl Drop for WindowHandle { } impl WindowHandle { - pub fn create_window( - mut options: WindowSettings, handler: WindowHandlerBuilder, host: Host, - ) -> Result { + pub fn create_window(mut init: WindowInitializer) -> Result { autoreleasepool(|_| { let Some(mtm) = MainThreadMarker::new() else { panic!("macOS: Windows can only be created on the main thread!") @@ -42,48 +38,40 @@ impl WindowHandle { // Creates the global NSApplication instance, if it doesn't exist yet let _ = NSApplication::sharedApplication(mtm); - if let Some(parent) = options.parent.take() { - return Self::create_window_parented( - options, - handler, - host, - parent.inner.view, - mtm, - ); + if let Some(parent) = init.settings.parent.take() { + return Self::create_window_parented(init, parent.inner.view, mtm); } - Self::create_window_standalone(options, handler, host, mtm) + Self::create_window_standalone(init, mtm) }) } pub fn create_window_parented( - builder: WindowSettings, handler: WindowHandlerBuilder, host: Host, - parent_view: Retained, mtm: MainThreadMarker, + init: WindowInitializer, parent_view: Retained, mtm: MainThreadMarker, ) -> Result { let parenting = ViewParentingType::Parented { parent_view: Weak::from_retained(&parent_view) }; let backing_scale_factor = parent_view.window().map(|w| w.backingScaleFactor()).unwrap_or(1.0); - let final_size = builder.size.to_logical(backing_scale_factor); + let final_size = init.settings.size.to_logical(backing_scale_factor); - let (ns_view, state) = - BaseviewView::new(builder, handler, parenting, host, final_size, mtm)?; + let (ns_view, state) = BaseviewView::new(init, parenting, final_size, mtm)?; Ok(Self { mtm, state, _window: None, view: Weak::from_retained(&ns_view) }) } pub fn create_window_standalone( - builder: WindowSettings, handler: WindowHandlerBuilder, host: Host, mtm: MainThreadMarker, + init: WindowInitializer, mtm: MainThreadMarker, ) -> Result { - let window = create_window_with_options(&builder, mtm); + let window = create_window_with_options(&init.settings, mtm); let final_size = window.contentRectForFrameRect(window.frame()).size; let final_size = LogicalSize::new(final_size.width, final_size.height); let parenting = ViewParentingType::Windowed { owned_window: Weak::from_retained(&window) }; - let (view, state) = BaseviewView::new(builder, handler, parenting, host, final_size, mtm)?; + let (view, state) = BaseviewView::new(init, parenting, final_size, mtm)?; Ok(Self { mtm, state, view: Weak::from_retained(&view), _window: Some(window) }) } diff --git a/src/platform/win/window.rs b/src/platform/win/window.rs index fa8fe08b..7ccfa334 100644 --- a/src/platform/win/window.rs +++ b/src/platform/win/window.rs @@ -26,13 +26,14 @@ use crate::handler::WindowHandlerBuilder; use crate::host::Host; use crate::platform::win::window_state::{WindowSharedState, WindowState}; use crate::platform::Error; +use crate::window::WindowInitializer; use crate::wrappers::win32::cursor::SystemCursor; use crate::wrappers::win32::window::*; use crate::wrappers::win32::{ ole_initialize, run_thread_message_loop_until, Dpi, DpiAwarenessContext, ExtendedUser32, Rect, WindowStyle, }; -use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSettings, WindowSize}; +use crate::{Event, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowSize}; #[allow(non_snake_case)] fn HIWORD(wparam: WPARAM) -> u16 { @@ -50,6 +51,7 @@ const WIN_FRAME_TIMER: NonZeroUsize = match NonZeroUsize::new(4242) { }; pub struct WindowHandle { + init: Cell>, hwnd: Cell>, state: Rc, } @@ -71,8 +73,15 @@ impl WindowHandle { } pub fn resize(&self, new_size: Size) -> Result<()> { - let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; let new_size = new_size.to_physical(self.state.scale_factor()); + let hwnd = match self.hwnd.get() { + Some(hwnd) => hwnd, + None => { + self.state.current_size.set(new_size); + return Ok(()); + } + }; + let _guard = self.state.originate_host_resize(); hwnd.resize_and_activate(new_size, self.state.current_dpi.get(), &self.state.user32)?; @@ -84,8 +93,6 @@ impl WindowHandle { } pub fn suggest_scale_factor(&self, scale_factor: f64) -> Result<()> { - let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; - let current_scale_factor = self.state.scale_factor(); self.state.fallback_scale_factor.set(Some(scale_factor)); @@ -93,6 +100,8 @@ impl WindowHandle { return Ok(()); } + let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + let current_size = self.state.current_size.get(); let new_size = self .state @@ -118,7 +127,18 @@ impl WindowHandle { } pub fn set_parent(&self, new_parent: ParentWindowHandle) -> Result<()> { - let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + let hwnd = match self.hwnd.get() { + Some(hwnd) => hwnd, + None => { + let Some(mut init) = self.init.take() else { return Ok(()) }; + init.settings.parent = Some(new_parent.into()); + + let window = BaseviewWindow::create(self.state.clone(), init)?; + self.hwnd.set(Some(window)); + + return Ok(()); + } + }; hwnd.set_parent(&new_parent.handle)?; @@ -140,7 +160,18 @@ impl WindowHandle { } pub fn show(&self) -> Result<()> { - let Some(hwnd) = self.hwnd.get() else { return Ok(()) }; + let hwnd = match self.hwnd.get() { + Some(hwnd) => hwnd, + None => { + let Some(init) = self.init.take() else { return Ok(()) }; + + let window = BaseviewWindow::create(self.state.clone(), init)?; + self.hwnd.set(Some(window)); + + return Ok(()); + } + }; + hwnd.show_and_activate(); Ok(()) @@ -182,6 +213,58 @@ pub struct BaseviewWindow { } impl BaseviewWindow { + pub fn create(shared_state: Rc, init: WindowInitializer) -> Result { + let dpi_ctx = DpiAwarenessContext::new(&shared_state.user32)?; + + let style = if init.settings.parent.is_some() { + WindowStyle::parented() + } else { + WindowStyle::embedded() + }; + + let window_size = shared_state.current_size.get(); + + let initializer = { + let shared_state = shared_state.clone(); + + move |hwnd: HWnd| { + let window_state = Rc::new(WindowState::new( + hwnd, + shared_state.user32.clone(), + shared_state.clone(), + )); + + BaseviewWindow { + window_state, + initial_size: init.settings.size, + handler_builder: Cell::new(Some(init.builder)), + shared_state, + host: init.host, + + _drop_target: None.into(), + _keyboard_hook: None.into(), + + #[cfg(feature = "opengl")] + gl_config: init.settings.gl_config, + } + } + }; + + let parent = init.settings.parent.map(|p| p.inner.handle); + let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, None)?; + let title = HSTRING::from(init.settings.title); + let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; + + // FIXME: this SetTimer call could be in after_create, but for some reason it changes the ordering + // for a parent+child window situation, which results in the parent drawing over the child. + // This timer should be replaced by proper window redrawing/damage/vsync handling, but this + // would be a breaking change, so we'll do that later. + // TODO: create a new timer instead of hard-coding a specific ID + window.set_timer(WIN_FRAME_TIMER, 15)?; + + Ok(window) + } + fn notify_destroyed_to_host(&self) { if self.shared_state.destroy_host_originated.get() { return; @@ -560,62 +643,25 @@ unsafe fn wnd_proc_inner( } impl WindowHandle { - pub fn create_window( - options: WindowSettings, build: WindowHandlerBuilder, host: Host, - ) -> Result { + pub fn create_window(init: WindowInitializer) -> Result { let extended_user_32 = ExtendedUser32::load()?; - let title = HSTRING::from(options.title); - let scaling_factor = 1.0; + let window_size = init.settings.size.to_physical(1.0); - let window_size = options.size.to_physical(scaling_factor); - - let style = if options.parent.is_some() { - WindowStyle::parented() - } else { - WindowStyle::embedded() - }; - let dpi_ctx = DpiAwarenessContext::new(&extended_user_32)?; let shared_state = - WindowSharedState::new(window_size, extended_user_32.clone(), options.parent.is_some()); - - let initializer = { - let extended_user_32 = extended_user_32.clone(); - let shared_state = shared_state.clone(); - - move |hwnd: HWnd| { - let window_state = - Rc::new(WindowState::new(hwnd, extended_user_32, shared_state.clone())); - - BaseviewWindow { - window_state, - initial_size: options.size, - handler_builder: Cell::new(Some(build)), - shared_state, - host, - - _drop_target: None.into(), - _keyboard_hook: None.into(), - - #[cfg(feature = "opengl")] - gl_config: options.gl_config, - } - } - }; + WindowSharedState::new(window_size, extended_user_32, init.settings.parent.is_some()); - let parent = options.parent.map(|p| p.inner.handle); - let rect = dpi_ctx.client_area_to_nc_area(window_size.into(), style, None)?; - - let window = create_window(&title, style, rect.size(), parent, &dpi_ctx, initializer)?; + if init.settings.parented && init.settings.parent.is_none() { + return Ok(WindowHandle { + hwnd: None.into(), + state: shared_state, + init: Some(init).into(), + }); + } - // FIXME: this SetTimer call could be in after_create, but for some reason it changes the ordering - // for a parent+child window situation, which results in the parent drawing over the child. - // This timer should be replaced by proper window redrawing/damage/vsync handling, but this - // would be a breaking change, so we'll do that later. - // TODO: create a new timer instead of hard-coding a specific ID - window.set_timer(WIN_FRAME_TIMER, 15)?; + let window = BaseviewWindow::create(shared_state.clone(), init)?; - Ok(WindowHandle { hwnd: Some(window).into(), state: shared_state }) + Ok(WindowHandle { hwnd: Some(window).into(), state: shared_state, init: None.into() }) } } diff --git a/src/platform/x11/window_thread.rs b/src/platform/x11/window_thread.rs index 1135cd99..e32ec04f 100644 --- a/src/platform/x11/window_thread.rs +++ b/src/platform/x11/window_thread.rs @@ -1,9 +1,10 @@ use super::*; use crate::handler::WindowHandlerBuilder; -use crate::host::{Host, HostCallbacks}; +use crate::host::HostCallbacks; use crate::platform::x11::event_loop::{EventLoop, MainThreadCaller}; use crate::platform::x11::window_shared::WindowInner; use crate::warn; +use crate::window::WindowInitializer; use crate::{WindowContext, WindowSettings, WindowSize}; use calloop::LoopSignal; use dpi::{PhysicalSize, Size}; @@ -101,14 +102,13 @@ pub struct WindowThreadHandle { } impl WindowThreadHandle { - pub fn create_window( - options: WindowSettings, handler: WindowHandlerBuilder, host: Host, - ) -> Result { + pub fn create_window(init: WindowInitializer) -> Result { let (tx, rx) = result_channel(); let shared = Arc::new(WindowThreadShared::new()); let (request_sender, request_receiver) = calloop::channel::sync_channel(1); let (response_sender, response_receiver) = mpsc::channel(); - let (main_thread_caller, main_thread_receiver) = MainThreadCaller::new(host.main_thread); + let (main_thread_caller, main_thread_receiver) = + MainThreadCaller::new(init.host.main_thread); let join_handle = { let shared = shared.clone(); @@ -116,8 +116,8 @@ impl WindowThreadHandle { thread::spawn(move || { let thread = match WindowThread::create( - options, - handler, + init.settings, + init.builder, shared, request_receiver, response_sender, @@ -144,7 +144,7 @@ impl WindowThreadHandle { loop_signal, request_sender, response_receiver, - host_callbacks: host.callbacks.map(|c| c.into_inner()), + host_callbacks: init.host.callbacks.map(|c| c.into_inner()), callback_receiver: main_thread_receiver, }) } diff --git a/src/window_open_options.rs b/src/settings.rs similarity index 84% rename from src/window_open_options.rs rename to src/settings.rs index 7cca66b6..8c6edf36 100644 --- a/src/window_open_options.rs +++ b/src/settings.rs @@ -19,6 +19,12 @@ pub struct WindowSettings { /// If `None`, the window will be standalone. pub parent: Option, + /// If the window expects to have a parent when first displayed. + /// + /// Setting this will delay the actual creation of the window until the parent is set (unless + /// the window is shown first). + pub parented: bool, + /// If provided, then an OpenGL context will be created for this window. You'll be able to /// access this context through [crate::WindowContext::gl_context]. /// @@ -53,6 +59,12 @@ impl WindowSettings { self } + #[inline] + pub fn parented(mut self) -> Self { + self.parented = true; + self + } + #[cfg(feature = "opengl")] #[inline] pub fn with_gl_config(mut self, gl_config: impl Into>) -> Self { @@ -67,6 +79,7 @@ impl Default for WindowSettings { title: String::from("baseview window"), size: LogicalSize { width: 500.0, height: 400.0 }.into(), parent: None, + parented: false, #[cfg(feature = "opengl")] gl_config: None, } @@ -102,3 +115,9 @@ impl From<&W> for ParentWindowHandle { Self::from_window(window) } } + +impl From for ParentWindowHandle { + fn from(inner: platform::ParentWindowHandle) -> Self { + Self { inner } + } +} diff --git a/src/window.rs b/src/window.rs index e635ab3b..44638b91 100644 --- a/src/window.rs +++ b/src/window.rs @@ -69,16 +69,18 @@ impl Window { /// Calling this function with [`None`] for the `host` value is equivalent to calling /// [`create`](Self::create). pub fn create_with_host( - options: WindowSettings, + settings: WindowSettings, handler: impl FnOnce(WindowContext) -> Result + Send + 'static, host: impl Into>, ) -> Result { + let initializer = WindowInitializer { + settings, + builder: WindowHandlerBuilder::new(handler), + host: host.into().unwrap_or_else(Host::default), + }; + Ok(Self { - inner: platform::WindowHandle::create_window( - options, - WindowHandlerBuilder::new(handler), - host.into().unwrap_or_else(Host::default), - )?, + inner: platform::WindowHandle::create_window(initializer)?, phantom: PhantomData, }) } @@ -190,6 +192,12 @@ impl Window { } } +pub(crate) struct WindowInitializer { + pub settings: WindowSettings, + pub builder: WindowHandlerBuilder, + pub host: Host, +} + /// A window's size, which can be read in either logical or physical pixels. /// /// Methods that produce this type in baseview guarantee that either the physical or the logical