From 35a9841b296a83c327f6187d884d82b3cc7c57d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Sun, 29 Nov 2020 14:37:59 +0100 Subject: [PATCH] Add WindowHandle::try_send_message, implement it on macOS --- examples/open_window.rs | 32 +++++++++++++++++-- src/macos/view.rs | 29 ++++++++++++++++- src/macos/window.rs | 71 +++++++++++++++++++++++++++++++++++++---- src/window.rs | 21 ++++++++++-- 4 files changed, 140 insertions(+), 13 deletions(-) diff --git a/examples/open_window.rs b/examples/open_window.rs index c428b33..51e51fd 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(), @@ -27,5 +40,20 @@ fn main() { }; let handle = Window::open(window_open_options, |_| OpenWindowExample); + + { + let handle = handle.clone(); + + ::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"); + } + } + }); + } + handle.app_run_blocking(); } diff --git a/src/macos/view.rs b/src/macos/view.rs index f9f6f3b..e5278cc 100644 --- a/src/macos/view.rs +++ b/src/macos/view.rs @@ -20,7 +20,10 @@ 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, + RUNTIME_TIMER_IVAR_NAME +}; pub(super) unsafe fn create_view( @@ -67,6 +70,10 @@ unsafe fn create_view_class() -> &'static Class { accepts_first_mouse:: as extern "C" fn(&Object, Sel, id) -> BOOL ); + class.add_method( + sel!(runtimeTick:), + runtime_tick:: as extern "C" fn(&Object, Sel, id) + ); class.add_method( sel!(triggerOnFrame:), trigger_on_frame:: as extern "C" fn(&Object, Sel, id) @@ -150,6 +157,7 @@ unsafe fn create_view_class() -> &'static Class { class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME); class.add_ivar::<*mut c_void>(FRAME_TIMER_IVAR_NAME); + class.add_ivar::<*mut c_void>(RUNTIME_TIMER_IVAR_NAME); class.register() } @@ -193,6 +201,19 @@ extern "C" fn trigger_on_frame( } +extern "C" fn runtime_tick( + this: &Object, + _sel: Sel, + _event: id +){ + let state: &mut WindowState = unsafe { + WindowState::from_field(this) + }; + + state.handle_messages(); +} + + extern "C" fn release(this: &Object, _sel: Sel) { unsafe { let superclass = msg_send![this, superclass]; @@ -210,6 +231,12 @@ extern "C" fn release(this: &Object, _sel: Sel) { ); let _: () = msg_send![frame_timer_ptr as id, invalidate]; + // Invalidate runtime timer + let frame_timer_ptr: *mut c_void = *this.get_ivar( + RUNTIME_TIMER_IVAR_NAME + ); + let _: () = msg_send![frame_timer_ptr as id, invalidate]; + // Drop WindowState let state_ptr: *mut c_void = *this.get_ivar( WINDOW_STATE_IVAR_NAME diff --git a/src/macos/window.rs b/src/macos/window.rs index 78295fd..11c42fc 100644 --- a/src/macos/window.rs +++ b/src/macos/window.rs @@ -3,7 +3,7 @@ /// Inspired by implementation in https://github.com/antonok-edm/vst_window use std::ffi::c_void; -use std::sync::Arc; +use std::sync::{Arc, mpsc::{self, Sender, Receiver}}; use cocoa::appkit::{ NSApp, NSApplication, NSApplicationActivationPolicyRegular, @@ -31,6 +31,7 @@ use super::keyboard::KeyboardState; pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME"; pub(super) const FRAME_TIMER_IVAR_NAME: &str = "FRAME_TIMER"; +pub(super) const RUNTIME_TIMER_IVAR_NAME: &str = "RUNTIME_TIMER"; pub struct Window { @@ -42,10 +43,22 @@ pub struct Window { } -pub struct WindowHandle; +pub struct WindowHandle { + message_tx: Sender, +} -impl WindowHandle { +// Implement Clone manually to avoid H: Clone bound +impl Clone for WindowHandle { + fn clone(&self) -> Self { + Self { + message_tx: self.message_tx.clone() + } + } +} + + +impl WindowHandle { pub fn app_run_blocking(self) { unsafe { // Get reference to already created shared NSApplication object @@ -53,13 +66,21 @@ impl WindowHandle { NSApp().run(); } } + + pub fn try_send_message( + &self, + message: H::Message + ) -> Result<(), H::Message> { + self.message_tx.send(message) + .map_err(|err| err.0) + } } impl Window { pub fn open( options: WindowOpenOptions, build: B - ) -> crate::WindowHandle + ) -> crate::WindowHandle where H: WindowHandler, B: FnOnce(&mut crate::Window) -> H, B: Send + 'static @@ -160,10 +181,13 @@ impl Window { let window_handler = build(&mut crate::Window(&mut window)); + let (message_tx, message_rx) = mpsc::channel(); + 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,37 @@ 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) + // Setup runtime timer once window state is stored + unsafe { + let timer_interval = 0.01; + let selector = sel!(runtimeTick:); + + let timer: id = msg_send![ + ::objc::class!(NSTimer), + scheduledTimerWithTimeInterval:timer_interval + target:window_state_arc.window.ns_view + selector:selector + userInfo:nil + repeats:YES + ]; + + // Store pointer to timer for invalidation when view is released + (*window_state_arc.window.ns_view).set_ivar( + RUNTIME_TIMER_IVAR_NAME, + timer as *mut c_void, + ) + } + + crate::WindowHandle(WindowHandle { + message_tx + }) } } @@ -207,6 +254,7 @@ pub(super) struct WindowState { window: Window, window_handler: H, keyboard_state: KeyboardState, + message_rx: Receiver, } @@ -222,6 +270,15 @@ impl WindowState { &mut *(state_ptr as *mut Self) } + pub(super) fn handle_messages(&mut self){ + for message in self.message_rx.try_iter(){ + 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/window.rs b/src/window.rs index 93f3aa2..4444dc6 100644 --- a/src/window.rs +++ b/src/window.rs @@ -11,13 +11,28 @@ use crate::x11 as platform; use crate::macos as platform; -pub struct WindowHandle(pub(crate) platform::WindowHandle); +pub struct WindowHandle(pub(crate) platform::WindowHandle); -impl WindowHandle { +// Implement Clone manually to avoid H: Clone bound +impl Clone for WindowHandle { + fn clone(&self) -> Self { + WindowHandle(self.0.clone()) + } +} + + +impl WindowHandle { pub fn app_run_blocking(self){ self.0.app_run_blocking(); } + + pub fn try_send_message( + &self, + message: H::Message + ) -> Result<(), H::Message> { + self.0.try_send_message(message) + } } @@ -28,7 +43,7 @@ impl <'a>Window<'a> { pub fn open( options: WindowOpenOptions, build: B - ) -> WindowHandle + ) -> WindowHandle where H: WindowHandler, B: FnOnce(&mut Window) -> H, B: Send + 'static