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 `MarkupExtension` integration: a Rust-backed `{sample:Loc}` extension resolves
//! a positional key and the resulting string appears on the live element.

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use noesis_runtime::markup::MarkupExtensionRegistration;
use noesis_runtime::view::{FrameworkElement, View};
use noesis_runtime::xaml_provider::XamlProvider;

const LOC_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:sample="clr-namespace:Sample"
      Background="#FF202020" Width="400" Height="100">
  <TextBlock x:Name="Greeting" Text="{sample:Loc menu.greeting}"
             HorizontalAlignment="Center" VerticalAlignment="Center"
             Foreground="White" FontSize="24"/>
</Grid>"##;

struct InMem(HashMap<String, Vec<u8>>);
impl XamlProvider for InMem {
    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }
    fn load_xaml(&mut self, uri: &str) -> Option<&[u8]> {
        self.0.get(uri).map(Vec::as_slice)
    }
}

#[test]
fn markup_extension_resolves_positional_key() {
    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 observed_keys: Arc<Mutex<Vec<String>>> = Arc::default();

    {
        let observed_for_cb = Arc::clone(&observed_keys);
        let registration = MarkupExtensionRegistration::from_closure("Sample.Loc", move |key| {
            observed_for_cb.lock().unwrap().push(key.to_string());
            match key {
                "menu.greeting" => Some("Hello, world!".to_string()),
                _ => None,
            }
        })
        .expect("MarkupExtensionRegistration returned None");

        let mut bytes = HashMap::new();
        bytes.insert("scene.xaml".to_string(), LOC_XAML.as_bytes().to_vec());
        let _provider_guard = noesis_runtime::xaml_provider::set_xaml_provider(InMem(bytes));

        let element =
            FrameworkElement::load("scene.xaml").expect("load_xaml returned None for scene.xaml");

        let mut view = View::create(element);
        view.set_size(400, 100);
        view.activate();
        assert!(view.update(0.0));

        let content = view.content().expect("View::content returned None");
        let greeting = content
            .find_name("Greeting")
            .expect("find_name returned None for Greeting");
        let observed = observed_keys.lock().unwrap().clone();
        assert!(
            observed.iter().any(|k| k == "menu.greeting"),
            "expected callback to fire with key='menu.greeting'; saw {observed:?}"
        );

        // Duplicate registration must fail: C++ rejects a second claim on the same Reflection slot.
        // Tested here because Noesis allows only one init/shutdown per process.
        let dup = MarkupExtensionRegistration::from_closure("Sample.Loc", |_| Some(String::new()));
        assert!(
            dup.is_none(),
            "duplicate-name registration unexpectedly succeeded"
        );

        // Drop wrappers before the registration so extension instances are freed first.
        drop(greeting);
        drop(content);
        view.deactivate();
        drop(view);
        drop(registration);
    }

    noesis_runtime::shutdown();
}