use objc2::__framework_prelude::{Allocated, AnyClass, ProtocolObject, Retained};
use objc2::runtime::AnyObject;
use objc2::{msg_send, Encoding, Message, RefEncode};
use objc2_app_kit::{NSDragOperation, NSDraggingInfo, NSEvent, NSView, NSWindow};
use objc2_core_foundation::CGRect;
use objc2_foundation::{NSNotification, NSPoint};
use raw_window_handle::{AppKitWindowHandle, HasRawWindowHandle, RawWindowHandle};
use std::ffi::{c_void, CStr};
use std::marker::PhantomData;
use std::ops::Deref;
mod implementation;
const BASEVIEW_STATE_IVAR: &CStr = c"baseview_state";
#[repr(C)]
pub struct View<V> {
parent: NSView,
_inner: PhantomData<ViewInner<V>>,
}
unsafe impl<V> RefEncode for View<V> {
const ENCODING_REF: Encoding = NSView::ENCODING_REF;
}
unsafe impl<V> Message for View<V> {}
impl<V> Deref for View<V> {
type Target = NSView;
fn deref(&self) -> &Self::Target {
&self.parent
}
}
impl<V: ViewImpl> View<V> {
pub fn new(frame: CGRect, inner: V, init: impl FnOnce(ViewRef<V>)) -> Retained<View<V>> {
let class = unsafe { implementation::create_view_class::<V>() };
let view: Allocated<View<V>> = unsafe { msg_send![class, alloc] };
Self::set_inner(&view, class, ViewInner { inner });
let view: Retained<View<V>> = unsafe { msg_send![view, initWithFrame: frame] };
init(view.inner_ref());
view
}
fn set_inner(view: &Allocated<View<V>>, class: &AnyClass, inner: ViewInner<V>) {
let inner = Box::new(inner);
let ivar = class.instance_variable(BASEVIEW_STATE_IVAR).unwrap();
let ivar_target = unsafe { &*Allocated::as_ptr(view).cast() };
let ivar = unsafe { ivar.load_ptr::<*mut c_void>(ivar_target) };
unsafe { ivar.write(Box::into_raw(inner).cast()) };
}
fn free_inner(this: &AnyObject, class: &AnyClass) {
let ivar = class.instance_variable(BASEVIEW_STATE_IVAR).unwrap();
let ivar = unsafe { ivar.load_ptr::<*mut c_void>(this) };
let raw = unsafe { ivar.read() };
let inner = unsafe { Box::<ViewInner<V>>::from_raw(raw.cast()) };
unsafe { ivar.write(core::ptr::null_mut()) };
drop(inner);
}
fn get_inner(&self) -> &ViewInner<V> {
let ivar = self.class().instance_variable(BASEVIEW_STATE_IVAR).unwrap();
let ivar = unsafe { ivar.load::<*mut c_void>(self) };
unsafe { ivar.cast::<ViewInner<V>>().as_ref() }.unwrap()
}
pub fn inner(&self) -> &V {
&self.get_inner().inner
}
pub fn inner_ref(&self) -> ViewRef<'_, V> {
ViewRef { view: self, inner: self.inner() }
}
}
pub struct ViewInner<V> {
inner: V,
}
pub struct ViewRef<'a, V> {
pub view: &'a View<V>,
pub inner: &'a V,
}
impl<'a, V> Clone for ViewRef<'a, V> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, V> Copy for ViewRef<'a, V> {}
impl<V> Deref for ViewRef<'_, V> {
type Target = V;
fn deref(&self) -> &Self::Target {
self.inner
}
}
pub trait ViewImpl: Sized {
fn become_first_responder(this: ViewRef<Self>) -> bool;
fn resign_first_responder(this: ViewRef<Self>) -> bool;
fn window_should_close(this: ViewRef<Self>) -> bool;
fn view_did_change_backing_properties(this: ViewRef<Self>);
fn hit_test(this: ViewRef<'_, Self>, point: NSPoint) -> Option<&NSView>;
fn view_will_move_to_window(this: ViewRef<Self>, new_window: Option<&NSWindow>);
fn update_tracking_areas(this: ViewRef<Self>);
fn mouse_moved(this: ViewRef<Self>, event: &NSEvent);
fn scroll_wheel(this: ViewRef<Self>, event: &NSEvent);
fn dragging_entered(
this: ViewRef<Self>, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>,
) -> NSDragOperation;
fn dragging_updated(
this: ViewRef<Self>, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>,
) -> NSDragOperation;
fn prepare_for_drag_operation(
this: ViewRef<Self>, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>,
) -> bool;
fn perform_drag_operation(
this: ViewRef<Self>, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>,
) -> bool;
fn dragging_exited(this: ViewRef<Self>, sender: Option<&ProtocolObject<dyn NSDraggingInfo>>);
fn handle_notification(this: ViewRef<Self>, notification: &NSNotification);
fn mouse_down(this: ViewRef<Self>, event: &NSEvent);
fn mouse_up(this: ViewRef<Self>, event: &NSEvent);
fn right_mouse_down(this: ViewRef<Self>, event: &NSEvent);
fn right_mouse_up(this: ViewRef<Self>, event: &NSEvent);
fn other_mouse_down(this: ViewRef<Self>, event: &NSEvent);
fn other_mouse_up(this: ViewRef<Self>, event: &NSEvent);
fn mouse_entered(this: ViewRef<Self>);
fn mouse_exited(this: ViewRef<Self>);
fn key_down(this: ViewRef<Self>, event: &NSEvent);
fn key_up(this: ViewRef<Self>, event: &NSEvent);
fn flags_changed(this: ViewRef<Self>, event: &NSEvent);
}
unsafe impl<V: ViewImpl> HasRawWindowHandle for View<V> {
fn raw_window_handle(&self) -> RawWindowHandle {
let mut handle = AppKitWindowHandle::empty();
handle.ns_view = (&self.parent as *const NSView).cast_mut().cast();
if let Some(window) = self.window() {
handle.ns_window = Retained::as_ptr(&window).cast_mut().cast()
}
handle.into()
}
}