Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/raw-window-metal/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 9 kB image not shown  

Quelle  observer.rs   Sprache: Rust

 

use core::ffi::c_void;
use objc2::rc::{Retained, Weak};
use objc2::runtime::{AnyClass, AnyObject};
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};

define_class!(
    /// A `CAMetalLayer` layer that will automatically update its bounds and scale factor to match
    /// its super layer.
    ///
    /// We do this by subclassing, to allow the user to just store the layer as
    /// `Retained<CAMetalLayer>`, and still have our observers work.
    ///
    /// 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>
    //
    // SAFETY:
    // - The superclass CAMetalLayer does not have any subclassing requirements.
    // - CustomLayer implements `Drop` and ensures that:
    //   - It does not call an overridden method.
    //   - It does not `retain` itself.
    #[unsafe(super(CAMetalLayer))]
    #[name = "RawWindowMetalLayer"]
    #[ivars = Weak<CALayer>]
    pub(crate) struct ObserverLayer;

    // `NSKeyValueObserving` category.
    //
    // SAFETY: The method is correctly defined.
    impl ObserverLayer {
        #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))]
        fn _observe_value(
            &self,
            key_path: Option<&NSString>,
            object: Option<&AnyObject>,
            change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
            context: *mut c_void,
        ) {
            self.observe_value(key_path, object, change, context);
        }
    }
);

impl Drop for ObserverLayer {
    fn drop(&mut self) {
        // It is possible for the root layer to be de-allocated before the custom layer.
        //
        // In that case, the observer is already de-registered, and so we don't need to do anything.
        //
        // We use a weak variable here to avoid issues if the layer was removed from the super
        // layer, and then later de-allocated, without de-registering these observers.
        if let Some(root_layer) = self.ivars().load() {
            // SAFETY: The observer is registered for these key paths in `new`.
            unsafe {
                root_layer.removeObserver_forKeyPath(self, ns_string!("contentsScale"));
                root_layer.removeObserver_forKeyPath(self, ns_string!("bounds"));
            }
        }
    }
}

impl ObserverLayer {
    /// The context pointer, to differentiate between key-value observing registered by this class,
    /// and the superclass.
    fn context() -> *mut c_void {
        ObserverLayer::class() as *const AnyClass as *mut c_void
    }

    /// Create a new custom layer that tracks parameters from the given super layer.
    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![super(this), init] };

        // Add the layer as a sublayer of the root layer.
        root_layer.addSublayer(&this);

        // Do not use auto-resizing mask.
        //
        // This is done to work around a bug in macOS 14 and above, where views using auto layout
        // may end up setting fractional values as the bounds, and that in turn doesn't propagate
        // properly through the auto-resizing mask and with contents gravity.
        //
        // Instead, we keep the bounds of the layer in sync with the root layer using an observer,
        // see below.
        //
        // this.setAutoresizingMask(kCALayerHeightSizable | kCALayerWidthSizable);

        // AppKit / UIKit automatically sets the correct scale factor and bounds for layers attached
        // to a view. Our layer, however, is not directly attached to a view, and so we need to
        // observe changes to the root layer's parameters, and apply them to our layer.
        //
        // Note the use of `NSKeyValueObservingOptionInitial` to also set the initial values here.
        //
        // Note that for AppKit, we _could_ make the layer match the window by adding a delegate on
        // the layer with the `layer:shouldInheritContentsScale:fromWindow:` method returning `true`
        // - this tells the system to automatically update the scale factor when it changes on a
        // window. But this wouldn't support headless rendering very well, and doesn't work on UIKit
        // anyhow, so we might as well just always use the observer technique.
        //
        // SAFETY: Observer deregistered in `Drop` before the observer object is deallocated.
        unsafe {
            root_layer.addObserver_forKeyPath_options_context(
                &this,
                ns_string!("contentsScale"),
                NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,
                ObserverLayer::context(),
            );
            root_layer.addObserver_forKeyPath_options_context(
                &this,
                ns_string!("bounds"),
                NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Initial,
                ObserverLayer::context(),
            );
        }

        // The default content gravity (`kCAGravityResize`) is a fine choice for most applications,
        // as it masks / alleviates issues with resizing and behaves better when moving the window
        // between monitors, so we won't modify that.
        //
        // Unfortunately, it may also make it harder to debug resize issues, swap this for
        // `kCAGravityTopLeft` instead when doing so.
        //
        // this.setContentsGravity(unsafe { kCAGravityResize });

        this
    }

    fn observe_value(
        &self,
        key_path: Option<&NSString>,
        object: Option<&AnyObject>,
        change: Option<&NSDictionary<NSKeyValueChangeKey, AnyObject>>,
        context: *mut c_void,
    ) {
        // An unrecognized context must belong to the super class.
        if context != ObserverLayer::context() {
            // SAFETY: The signature is correct, and it's safe to forward to the superclass' method
            // when we're overriding the method.
            return unsafe {
                msg_send![
                    super(self),
                    observeValueForKeyPath: key_path,
                    ofObject: object,
                    change: change,
                    context: context,
                ]
            };
        }

        let change =
            change.expect("requested a change dictionary in `addObserver`, but none was provided");
        // SAFETY: The static is declared with the correct type in `objc2`.
        let key = unsafe { NSKeyValueChangeNewKey };
        let new = change
            .objectForKey(key)
            .expect("requested change dictionary did not contain `NSKeyValueChangeNewKey`");

        // NOTE: Setting these values usually causes a quarter second animation to occur, which is
        // undesirable.
        //
        // However, since we're setting them inside an observer, there already is a transaction
        // ongoing, and as such we don't need to wrap this in a `CATransaction` ourselves.

        if key_path == Some(ns_string!("contentsScale")) {
            // `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")) {
            // `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.
            //
            // This differs from just setting the `bounds`, as it also takes into account any
            // translation that the superlayer may have that we'd want to preserve.
            self.setFrame(bounds);
        } else {
            panic!("unknown observed keypath {key_path:?}");
        }
    }
}

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

    use super::*;

    #[test]
    fn release_order_does_not_matter() {
        let root_layer = CALayer::new();
        let layer = ObserverLayer::new(&root_layer);
        drop(root_layer);
        drop(layer);

        let root_layer = CALayer::new();
        let layer = ObserverLayer::new(&root_layer);
        drop(layer);
        drop(root_layer);
    }

    #[test]
    fn scale_factor_propagates() {
        let root_layer = CALayer::new();
        let layer = ObserverLayer::new(&root_layer);

        root_layer.setContentsScale(3.0);
        assert_eq!(layer.contentsScale(), 3.0);
    }

    #[test]
    fn bounds_propagates() {
        let root_layer = CALayer::new();
        let layer = ObserverLayer::new(&root_layer);

        root_layer.setBounds(CGRect::new(
            CGPoint::new(10.0, 20.0),
            CGSize::new(30.0, 40.0),
        ));
        assert_eq!(layer.position(), CGPoint::new(25.0, 40.0));
        assert_eq!(
            layer.bounds(),
            CGRect::new(CGPoint::new(0.0, 0.0), CGSize::new(30.0, 40.0),)
        );
    }

    #[test]
    fn superlayer_can_remove_all_sublayers() {
        let root_layer = CALayer::new();
        let layer = ObserverLayer::new(&root_layer);
        layer.removeFromSuperlayer();
        drop(layer);
        root_layer.setContentsScale(3.0);
    }
}

Messung V0.5 in Prozent
C=69 H=84 G=76

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-09-30) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.