Skip to content

Commit

Permalink
Update objc2 to v0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jan 24, 2025
1 parent c38c759 commit 3d20575
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 61 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased

- Update `objc2` dep to `0.6`.

# 1.0.0 (2024-09-09)

- Bump Rust Edition from 2018 to 2021.
Expand Down
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ readme = "README.md"
keywords = ["window", "metal", "graphics"]
categories = ["game-development", "graphics", "os::macos-apis"]
exclude = [".github/*"]
# Current, though not specified by policy, see:
# https://github.com/rust-windowing/raw-window-metal/issues/26
rust-version = "1.71"

[features]
default = ["std"]
std = ["alloc"]
alloc = []

[target.'cfg(target_vendor = "apple")'.dependencies]
objc2 = "0.5.2"
objc2-foundation = { version = "0.2.2", features = [
objc2 = "0.6.0"
objc2-core-foundation = { version = "0.3.0", features = ["CFCGTypes"] }
objc2-foundation = { version = "0.3.0", features = [
"NSDictionary",
"NSGeometry",
"NSKeyValueObserving",
Expand All @@ -28,7 +32,7 @@ objc2-foundation = { version = "0.2.2", features = [
"NSThread",
"NSValue",
] }
objc2-quartz-core = { version = "0.2.2", features = [
objc2-quartz-core = { version = "0.3.0", features = [
"CALayer",
"CAMetalLayer",
"objc2-metal",
Expand All @@ -38,10 +42,10 @@ objc2-quartz-core = { version = "0.2.2", features = [
raw-window-handle = "0.6.0"

[target.'cfg(target_os = "macos")'.dev-dependencies]
objc2-app-kit = { version = "0.2.2", features = ["NSResponder", "NSView"] }
objc2-app-kit = { version = "0.3.0", features = ["NSResponder", "NSView"] }

[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dev-dependencies]
objc2-ui-kit = { version = "0.2.2", features = ["UIResponder", "UIView"] }
objc2-ui-kit = { version = "0.3.0", features = ["UIResponder", "UIView"] }

[package.metadata.docs.rs]
targets = [
Expand Down
54 changes: 25 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
//! use raw_window_handle::{RawWindowHandle, HasWindowHandle};
//! use raw_window_metal::Layer;
//! #
//! # let mtm = objc2_foundation::MainThreadMarker::new().expect("doc tests to run on main thread");
//! # let mtm = objc2::MainThreadMarker::new().expect("doc tests to run on main thread");
//! #
//! # #[cfg(target_os = "macos")]
//! # let view = unsafe { objc2_app_kit::NSView::new(mtm) };
Expand Down Expand Up @@ -136,17 +136,19 @@

mod observer;

use crate::observer::ObserverLayer;
use core::ffi::c_void;
use core::ffi::{c_void, CStr};
use core::hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::NonNull;

use objc2::rc::Retained;
use objc2::runtime::AnyClass;
use objc2::{msg_send, rc::Retained};
use objc2::{msg_send_id, ClassType};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol};
use objc2::{msg_send, ClassType, MainThreadMarker, Message};
use objc2_foundation::{NSObject, NSObjectProtocol};
use objc2_quartz_core::{CALayer, CAMetalLayer};

use crate::observer::ObserverLayer;

#[cfg(not(feature = "alloc"))]
compile_error!("The `alloc` feature must currently be enabled.");

Expand Down Expand Up @@ -325,38 +327,32 @@ impl Layer {
);
}

// Check if the view's layer is already a `CAMetalLayer`.
if root_layer.is_kind_of::<CAMetalLayer>() {
let layer = root_layer.retain();
// SAFETY: Just checked that the layer is a `CAMetalLayer`.
let layer: Retained<CAMetalLayer> = unsafe { Retained::cast(layer) };
if let Some(layer) = root_layer.downcast_ref::<CAMetalLayer>() {
Layer {
layer,
layer: layer.retain(),
pre_existing: true,
}
} else {
let layer = ObserverLayer::new(root_layer);
Layer {
layer: Retained::into_super(layer),
layer: layer.into_super(),
pre_existing: false,
}
}
}

fn from_retained_layer(root_layer: Retained<CALayer>) -> Self {
// Check if the view's layer is already a `CAMetalLayer`.
if root_layer.is_kind_of::<CAMetalLayer>() {
// SAFETY: Just checked that the layer is a `CAMetalLayer`.
let layer: Retained<CAMetalLayer> = unsafe { Retained::cast(root_layer) };
Layer {
match root_layer.downcast::<CAMetalLayer>() {
Ok(layer) => Layer {
layer,
pre_existing: true,
}
} else {
let layer = ObserverLayer::new(&root_layer);
Layer {
layer: Retained::into_super(layer),
pre_existing: false,
},
Err(root_layer) => {
let layer = ObserverLayer::new(&root_layer);
Layer {
layer: layer.into_super(),
pre_existing: false,
}
}
}
}
Expand Down Expand Up @@ -391,7 +387,7 @@ impl Layer {
/// use raw_window_metal::Layer;
///
/// let handle: AppKitWindowHandle;
/// # let mtm = objc2_foundation::MainThreadMarker::new().expect("doc tests to run on main thread");
/// # let mtm = objc2::MainThreadMarker::new().expect("doc tests to run on main thread");
/// # #[cfg(target_os = "macos")]
/// # let view = unsafe { objc2_app_kit::NSView::new(mtm) };
/// # #[cfg(target_os = "macos")]
Expand All @@ -415,7 +411,7 @@ impl Layer {
if cfg!(debug_assertions) {
// Load the class at runtime (instead of using `class!`)
// to ensure that this still works if AppKit isn't linked.
let cls = AnyClass::get("NSView").unwrap();
let cls = AnyClass::get(CStr::from_bytes_with_nul(b"NSView\0").unwrap()).unwrap();
assert!(ns_view.isKindOfClass(cls), "view was not a valid NSView");
}

Expand All @@ -424,7 +420,7 @@ impl Layer {
let _: () = unsafe { msg_send![ns_view, setWantsLayer: true] };

// SAFETY: `-[NSView layer]` returns an optional `CALayer`
let root_layer: Option<Retained<CALayer>> = unsafe { msg_send_id![ns_view, layer] };
let root_layer: Option<Retained<CALayer>> = unsafe { msg_send![ns_view, layer] };
let root_layer = root_layer.expect("failed making the view layer-backed");

Self::from_retained_layer(root_layer)
Expand Down Expand Up @@ -475,12 +471,12 @@ impl Layer {
if cfg!(debug_assertions) {
// Load the class at runtime (instead of using `class!`)
// to ensure that this still works if UIKit isn't linked.
let cls = AnyClass::get("UIView").unwrap();
let cls = AnyClass::get(CStr::from_bytes_with_nul(b"UIView\0").unwrap()).unwrap();
assert!(ui_view.isKindOfClass(cls), "view was not a valid UIView");
}

// SAFETY: `-[UIView layer]` returns a non-optional `CALayer`
let root_layer: Retained<CALayer> = unsafe { msg_send_id![ui_view, layer] };
let root_layer: Retained<CALayer> = unsafe { msg_send![ui_view, layer] };

// Unlike on macOS, we cannot replace the main view as `UIView` does
// not allow it (when `NSView` does).
Expand Down
45 changes: 18 additions & 27 deletions src/observer.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use core::ffi::c_void;
use objc2::rc::{Retained, Weak};
use objc2::runtime::{AnyClass, AnyObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2::{define_class, msg_send, AllocAnyThread, ClassType, DefinedClass};
use objc2_foundation::{
ns_string, NSDictionary, NSKeyValueChangeKey, NSKeyValueChangeNewKey,
NSKeyValueObservingOptions, NSNumber, NSObjectNSKeyValueObserverRegistration, NSString,
NSValue,
};
use objc2_quartz_core::{CALayer, CAMetalLayer};

declare_class!(
define_class!(
/// A `CAMetalLayer` layer that will automatically update its bounds and scale factor to match
/// its super layer.
///
Expand All @@ -18,29 +18,22 @@ declare_class!(
///
/// See the documentation on Key-Value Observing for details on how this works in general:
/// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html>
pub(crate) struct ObserverLayer;

//
// SAFETY:
// - The superclass CAMetalLayer does not have any subclassing requirements.
// - Interior mutability is a safe default.
// - CustomLayer implements `Drop` and ensures that:
// - It does not call an overridden method.
// - It does not `retain` itself.
unsafe impl ClassType for ObserverLayer {
type Super = CAMetalLayer;
type Mutability = mutability::InteriorMutable;
const NAME: &'static str = "RawWindowMetalLayer";
}

impl DeclaredClass for ObserverLayer {
type Ivars = Weak<CALayer>;
}
#[unsafe(super(CAMetalLayer))]
#[name = "RawWindowMetalLayer"]
#[ivars = Weak<CALayer>]
pub(crate) struct ObserverLayer;

// `NSKeyValueObserving` category.
//
// SAFETY: The method is correctly defined.
unsafe impl ObserverLayer {
#[method(observeValueForKeyPath:ofObject:change:context:)]
impl ObserverLayer {
#[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
fn _observe_value(
&self,
key_path: Option<&NSString>,
Expand Down Expand Up @@ -82,7 +75,7 @@ impl ObserverLayer {
pub fn new(root_layer: &CALayer) -> Retained<Self> {
let this = Self::alloc().set_ivars(Weak::new(root_layer));
// SAFETY: Initializing `CAMetalLayer` is safe.
let this: Retained<Self> = unsafe { msg_send_id![super(this), init] };
let this: Retained<Self> = unsafe { msg_send![super(this), init] };

// Add the layer as a sublayer of the root layer.
root_layer.addSublayer(&this);
Expand Down Expand Up @@ -115,15 +108,13 @@ impl ObserverLayer {
root_layer.addObserver_forKeyPath_options_context(
&this,
ns_string!("contentsScale"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial,
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,
ObserverLayer::context(),
);
root_layer.addObserver_forKeyPath_options_context(
&this,
ns_string!("bounds"),
NSKeyValueObservingOptions::NSKeyValueObservingOptionNew
| NSKeyValueObservingOptions::NSKeyValueObservingOptionInitial,
NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,
ObserverLayer::context(),
);
}
Expand Down Expand Up @@ -167,7 +158,7 @@ impl ObserverLayer {
// SAFETY: The static is declared with the correct type in `objc2`.
let key = unsafe { NSKeyValueChangeNewKey };
let new = change
.get(key)
.objectForKey(key)
.expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");

// NOTE: Setting these values usually causes a quarter second animation to occur, which is
Expand All @@ -177,16 +168,16 @@ impl ObserverLayer {
// ongoing, and as such we don't need to wrap this in a `CATransaction` ourselves.

if key_path == Some(ns_string!("contentsScale")) {
// SAFETY: `contentsScale` is a CGFloat, and so the observed value is always a NSNumber.
let new = unsafe { &*(new as *const AnyObject as *const NSNumber) };
// `contentsScale` is a CGFloat, and so the observed value is always a NSNumber.
let new = new.downcast::<NSNumber>().unwrap();
let scale_factor = new.as_cgfloat();

// Set the scale factor of the layer to match the root layer when it changes (e.g. if
// moved to a different monitor, or monitor settings changed).
self.setContentsScale(scale_factor);
} else if key_path == Some(ns_string!("bounds")) {
// SAFETY: `bounds` is a CGRect, and so the observed value is always a NSValue.
let new = unsafe { &*(new as *const AnyObject as *const NSValue) };
// `bounds` is a CGRect, and so the observed value is always a NSNumber.
let new = new.downcast::<NSValue>().unwrap();
let bounds = new.get_rect().expect("new bounds value was not CGRect");

// Set `bounds` and `position` so that the new layer is inside the superlayer.
Expand All @@ -202,7 +193,7 @@ impl ObserverLayer {

#[cfg(test)]
mod tests {
use objc2_foundation::{CGPoint, CGRect, CGSize};
use objc2_core_foundation::{CGPoint, CGRect, CGSize};

use super::*;

Expand Down

0 comments on commit 3d20575

Please sign in to comment.