diff --git a/Cargo.toml b/Cargo.toml index 1f3ba27..4e825f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ license = "MIT OR Apache-2.0" log = "0.4.11" keyboard-types = { version = "0.5.0", default-features = false } raw-window-handle = "0.3.3" +rtrb = "0.1.1" +static_assertions = "1.1.0" [target.'cfg(target_os="linux")'.dependencies] xcb = { version = "0.9", features = ["thread", "xlib_xcb", "dri2"] } diff --git a/README.md b/README.md index c05be59..925e85e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Below is a proposed list of milestones (roughly in-order) and their status. Subj | Can find DPI scale factor | | | :heavy_check_mark: | | Basic event handling (mouse, keyboard) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Parent window support | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Send messages | | :heavy_check_mark: | | | *(Converge on a common API for all platforms?)* | | | | ## Prerequisites diff --git a/examples/open_window.rs b/examples/open_window.rs index c428b33..d4a5b38 100644 --- a/examples/open_window.rs +++ b/examples/open_window.rs @@ -1,9 +1,19 @@ +use std::time::Duration; + use baseview::{Event, Window, WindowHandler, WindowScalePolicy}; + +#[derive(Debug, Clone)] +enum Message { + Hello +} + + struct OpenWindowExample; + impl WindowHandler for OpenWindowExample { - type Message = (); + type Message = Message; fn on_frame(&mut self) {} @@ -15,9 +25,12 @@ impl WindowHandler for OpenWindowExample { } } - fn on_message(&mut self, _window: &mut Window, _message: Self::Message) {} + fn on_message(&mut self, _window: &mut Window, message: Self::Message) { + println!("Message: {:?}", message); + } } + fn main() { let window_open_options = baseview::WindowOpenOptions { title: "baseview".into(), @@ -26,6 +39,20 @@ fn main() { parent: baseview::Parent::None, }; - let handle = Window::open(window_open_options, |_| OpenWindowExample); - handle.app_run_blocking(); + let (mut handle, opt_app_runner) = Window::open( + window_open_options, + |_| OpenWindowExample + ); + + ::std::thread::spawn(move || { + loop { + ::std::thread::sleep(Duration::from_secs(5)); + + if let Err(_) = handle.try_send_message(Message::Hello){ + println!("Failed sending message"); + } + } + }); + + opt_app_runner.unwrap().app_run_blocking(); } diff --git a/src/lib.rs b/src/lib.rs index 47a95a8..4a0d9e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ pub use window::*; pub use window_info::*; pub use window_open_options::*; +const MESSAGE_QUEUE_LEN: usize = 128; + #[derive(Debug)] pub enum Parent { None, @@ -30,7 +32,7 @@ pub enum Parent { unsafe impl Send for Parent {} pub trait WindowHandler { - type Message; + type Message: Send; fn on_frame(&mut self); fn on_event(&mut self, window: &mut Window, event: Event); diff --git a/src/macos/view.rs b/src/macos/view.rs index f9f6f3b..293fc04 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -20,7 +20,9 @@ use crate::{ }; use crate::MouseEvent::{ButtonPressed, ButtonReleased}; -use super::window::{WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME}; +use super::window::{ + WindowState, WINDOW_STATE_IVAR_NAME, FRAME_TIMER_IVAR_NAME +}; pub(super) unsafe fn create_view( @@ -189,6 +191,7 @@ extern "C" fn trigger_on_frame( WindowState::from_field(this) }; + state.handle_messages(); state.trigger_frame(); } diff --git a/src/macos/window.rs b/src/macos/window.rs index 78295fd..80b511d 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -1,7 +1,3 @@ -/// macOS window handling -/// -/// Inspired by implementation in https://github.com/antonok-edm/vst_window - use std::ffi::c_void; use std::sync::Arc; @@ -16,10 +12,11 @@ use keyboard_types::KeyboardEvent; use objc::{msg_send, runtime::Object, sel, sel_impl}; use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle}; +use rtrb::{RingBuffer, Producer, Consumer, PushError}; use crate::{ Event, Parent, WindowHandler, WindowOpenOptions, - WindowScalePolicy, WindowInfo + WindowScalePolicy, WindowInfo, MESSAGE_QUEUE_LEN }; use super::view::create_view; @@ -42,10 +39,10 @@ pub struct Window { } -pub struct WindowHandle; +pub struct AppRunner; -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { unsafe { // Get reference to already created shared NSApplication object @@ -55,18 +52,35 @@ impl WindowHandle { } } + +pub struct WindowHandle { + message_tx: Producer, +} + + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + self.message_tx.push(message) + .map_err(|PushError::Full(message)| message) + } +} + + impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static { let _pool = unsafe { NSAutoreleasePool::new(nil) }; - let mut window = match options.parent { + let (mut window, opt_app_runner) = match options.parent { Parent::WithParent(parent) => { if let RawWindowHandle::MacOS(handle) = parent { let ns_view = handle.ns_view as *mut objc::runtime::Object; @@ -76,10 +90,12 @@ impl Window { let _: id = msg_send![ns_view, addSubview: subview]; - Window { + let window = Window { ns_window: None, ns_view: subview, - } + }; + + (window, None) } } else { panic!("Not a macOS window"); @@ -90,10 +106,12 @@ impl Window { create_view::(&options) }; - Window { + let window = Window { ns_window: None, ns_view, - } + }; + + (window, None) }, Parent::None => { // It seems prudent to run NSApp() here before doing other @@ -150,20 +168,26 @@ impl Window { ns_window.setContentView_(subview); - Window { + let window = Window { ns_window: Some(ns_window), ns_view: subview, - } + }; + + (window, Some(crate::AppRunner(AppRunner))) } }, }; let window_handler = build(&mut crate::Window(&mut window)); + let (message_tx, message_rx) = RingBuffer::new(MESSAGE_QUEUE_LEN) + .split(); + let window_state_arc = Arc::new(WindowState { window, window_handler, keyboard_state: KeyboardState::new(), + message_rx }); let window_state_pointer = Arc::into_raw( @@ -177,8 +201,7 @@ impl Window { ); } - // Activate timer after window handler is setup and save a pointer to - // it, so that it can be invalidated when view is released. + // Setup frame timer once window state is stored unsafe { let timer_interval = 0.015; let selector = sel!(triggerOnFrame:); @@ -192,13 +215,18 @@ impl Window { repeats:YES ]; + // Store pointer to timer for invalidation when view is released (*window_state_arc.window.ns_view).set_ivar( FRAME_TIMER_IVAR_NAME, timer as *mut c_void, ) } - crate::WindowHandle(WindowHandle) + let window_handle = crate::WindowHandle(WindowHandle { + message_tx + }); + + (window_handle, opt_app_runner) } } @@ -207,6 +235,7 @@ pub(super) struct WindowState { window: Window, window_handler: H, keyboard_state: KeyboardState, + message_rx: Consumer, } @@ -222,6 +251,15 @@ impl WindowState { &mut *(state_ptr as *mut Self) } + pub(super) fn handle_messages(&mut self){ + while let Ok(message) = self.message_rx.pop(){ + self.window_handler.on_message( + &mut crate::Window(&mut self.window), + message + ) + } + } + pub(super) fn trigger_event(&mut self, event: Event){ self.window_handler.on_event( &mut crate::Window(&mut self.window), diff --git a/src/win/window.rs b/src/win/window.rs index 8fd2bc2..06ff2a2 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -207,11 +207,25 @@ pub struct Window { hwnd: HWND, } -pub struct WindowHandle { +pub struct WindowHandle { + // FIXME: replace this with channel sender + phantom_data: std::marker::PhantomData, +} + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + Err(message) + } +} + +pub struct AppRunner { hwnd: HWND, } -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { unsafe { let mut msg: MSG = std::mem::zeroed(); @@ -234,7 +248,7 @@ impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static @@ -311,7 +325,17 @@ impl Window { SetWindowLongPtrA(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _); SetTimer(hwnd, 4242, 13, None); - crate::WindowHandle(WindowHandle { hwnd }) + let window_handle = crate::WindowHandle(WindowHandle { + phantom_data: std::marker::PhantomData, + }); + + let opt_app_runner = if let crate::Parent::None = options.parent { + Some(crate::AppRunner(AppRunner { hwnd })) + } else { + None + }; + + (window_handle, opt_app_runner) } } } diff --git a/src/window.rs b/src/window.rs index 93f3aa2..22ccb7b 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,16 +11,31 @@ use crate::x11 as platform; use crate::macos as platform; -pub struct WindowHandle(pub(crate) platform::WindowHandle); +pub struct AppRunner(pub(crate) platform::AppRunner); -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self){ self.0.app_run_blocking(); } } +pub struct WindowHandle( + pub(crate) platform::WindowHandle +); + + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + self.0.try_send_message(message) + } +} + + pub struct Window<'a>(pub(crate) &'a mut platform::Window); @@ -28,7 +43,7 @@ impl <'a>Window<'a> { pub fn open( options: WindowOpenOptions, build: B - ) -> WindowHandle + ) -> (WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut Window) -> H, B: Send + 'static @@ -42,4 +57,28 @@ unsafe impl <'a>HasRawWindowHandle for Window<'a> { fn raw_window_handle(&self) -> RawWindowHandle { self.0.raw_window_handle() } -} \ No newline at end of file +} + + +// Compile-time API assertions +#[doc(hidden)] +mod assertions { + use crate::{WindowHandle, WindowHandler, Event, Window}; + + struct TestWindowHandler { + #[allow(dead_code)] + ptr: *mut ::std::ffi::c_void, + } + + impl WindowHandler for TestWindowHandler { + type Message = (); + + fn on_event(&mut self, _: &mut Window, _: Event) {} + fn on_message(&mut self, _: &mut Window, _: Self::Message) {} + fn on_frame(&mut self) {} + } + + // Assert that WindowHandle is Send even if WindowHandler isn't + static_assertions::assert_not_impl_any!(TestWindowHandler: Send); + static_assertions::assert_impl_all!(WindowHandle: Send); +} diff --git a/src/x11/window.rs b/src/x11/window.rs index caceaa4..049ffc1 100644 --- a/src/x11/window.rs +++ b/src/x11/window.rs @@ -30,12 +30,25 @@ pub struct Window { new_physical_size: Option } -// FIXME: move to outer crate context -pub struct WindowHandle { +pub struct WindowHandle { + // FIXME: replace this with channel sender + phantom_data: std::marker::PhantomData, +} + +impl WindowHandle { + pub fn try_send_message( + &mut self, + message: H::Message + ) -> Result<(), H::Message> { + Err(message) + } +} + +pub struct AppRunner { thread: std::thread::JoinHandle<()>, } -impl WindowHandle { +impl AppRunner { pub fn app_run_blocking(self) { let _ = self.thread.join(); } @@ -47,11 +60,13 @@ impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> (crate::WindowHandle, Option) where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static { + let is_not_parented = matches!(options.parent, Parent::None); + let (tx, rx) = mpsc::sync_channel::(1); let thread = thread::spawn(move || { @@ -63,7 +78,17 @@ impl Window { // FIXME: placeholder types for returning errors in the future let _ = rx.recv(); - crate::WindowHandle(WindowHandle { thread }) + let window_handle = crate::WindowHandle(WindowHandle { + phantom_data: std::marker::PhantomData + }); + + let opt_app_runner = if is_not_parented { + Some(crate::AppRunner(AppRunner { thread })) + } else { + None + }; + + (window_handle, opt_app_runner) } fn window_thread(options: WindowOpenOptions, build: B,