noesis_runtime 0.12.1

Rust bindings for the Noesis GUI Native SDK: load XAML UI, drive the view and renderer, and write custom controls in Rust. Renderer-agnostic; Bevy integration lives in noesis_bevy.
Documentation
//! Custom routed event registration, subscription, raising, and unsubscribe on a Rust-backed type.

use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};

use noesis_runtime::classes::{ClassBuilder, Instance, PropertyChangeHandler, PropertyValue};
use noesis_runtime::events::subscribe_event_by_name;
use noesis_runtime::ffi::ClassBase;
use noesis_runtime::reflection::{RoutingStrategy, raise_event, register_routed_event};
use noesis_runtime::view::FrameworkElement;

const XAML: &str = r##"<?xml version="1.0" encoding="utf-8"?>
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:nz="clr-namespace:NzTest">
  <nz:Eventful x:Name="Target"/>
</Grid>"##;

struct NoopHandler;
impl PropertyChangeHandler for NoopHandler {
    fn on_changed(&self, _i: Instance, _idx: u32, _v: PropertyValue<'_>) {}
}

#[test]
fn custom_routed_event_fires() {
    if let (Ok(name), Ok(key)) = (
        std::env::var("NOESIS_LICENSE_NAME"),
        std::env::var("NOESIS_LICENSE_KEY"),
    ) {
        noesis_runtime::set_license(&name, &key);
    }
    noesis_runtime::init();

    let counter = Arc::new(AtomicU32::new(0));

    {
        // Type and event must both be registered before the XAML that references them is parsed.
        let _reg = ClassBuilder::new("NzTest.Eventful", ClassBase::ContentControl, NoopHandler)
            .register()
            .expect("class registration failed");

        assert!(
            register_routed_event("NzTest.Eventful", "MyEvent", RoutingStrategy::Bubble),
            "register_routed_event returned false"
        );
        assert!(
            !register_routed_event("NzTest.Eventful", "MyEvent", RoutingStrategy::Bubble),
            "duplicate routed-event registration should be rejected"
        );
        assert!(
            !register_routed_event("NzTest.NoSuchType", "MyEvent", RoutingStrategy::Bubble),
            "routed event on unknown type should be rejected"
        );

        let root = FrameworkElement::parse(XAML).expect("parse returned None");
        let target = root
            .find_name("Target")
            .expect("find_name(Target) returned None");

        assert!(
            subscribe_event_by_name(&target, "NotAnEvent", false, |_: &_| false).is_none(),
            "subscribing to an unregistered event should return None"
        );

        let c = Arc::clone(&counter);
        let sub = subscribe_event_by_name(&target, "MyEvent", false, move |_args: &_| {
            c.fetch_add(1, Ordering::SeqCst);
            false
        })
        .expect("subscribe_event(MyEvent) returned None");

        assert!(
            !raise_event(&target, "NotAnEvent"),
            "raising an unregistered event should return false"
        );
        assert_eq!(counter.load(Ordering::SeqCst), 0);

        assert!(
            raise_event(&target, "MyEvent"),
            "raise_event returned false"
        );
        assert_eq!(
            counter.load(Ordering::SeqCst),
            1,
            "handler should have fired exactly once"
        );

        drop(sub);
        assert!(
            raise_event(&target, "MyEvent"),
            "raise_event returned false"
        );
        assert_eq!(
            counter.load(Ordering::SeqCst),
            1,
            "no handler should fire after unsubscribe"
        );
    }

    noesis_runtime::shutdown();
}