use std::collections::HashMap;
use noesis_runtime::name_scope::NameScope;
use noesis_runtime::transforms::ScaleTransform;
use noesis_runtime::view::{FrameworkElement, HitTestFilterBehavior, HitTestResultBehavior, View};
use noesis_runtime::xaml_provider::XamlProvider;
const SCENE_XAML: &str = r##"<?xml version="1.0" encoding="utf-8"?>
<Grid x:Name="Root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="200" Height="200">
<Border x:Name="Outer" Background="#FF202020" Width="200" Height="200">
<Border x:Name="Inner" Background="#FF00FF00" Width="100" Height="100"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</Grid>"##;
struct InMem {
bytes: 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.bytes.get(uri).map(Vec::as_slice)
}
}
fn names(hits: &[FrameworkElement]) -> Vec<String> {
hits.iter().filter_map(FrameworkElement::name).collect()
}
#[test]
fn filtered_hit_test_namescope_and_render_transform() {
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 mut bytes = HashMap::new();
bytes.insert("scene.xaml".to_string(), SCENE_XAML.as_bytes().to_vec());
let _registered = 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(200, 200);
view.activate();
assert!(view.update(0.0), "layout must run before hit testing");
let root = view.content().expect("View::content returned None");
let all = root.hit_test_all(100.0, 100.0);
let all_names = names(&all);
assert!(
all.len() >= 2,
"centre is covered by both Borders; expected >= 2 hits, got {} ({all_names:?})",
all.len()
);
assert!(
all_names.iter().any(|n| n == "Inner") && all_names.iter().any(|n| n == "Outer"),
"hit_test_all should include Inner and Outer, got {all_names:?}"
);
let inner_pos = all_names.iter().position(|n| n == "Inner");
let outer_pos = all_names.iter().position(|n| n == "Outer");
assert!(
inner_pos < outer_pos,
"topmost (Inner) should be reported before Outer, got {all_names:?}"
);
let mut stop_hits = 0usize;
root.hit_test_filtered(
100.0,
100.0,
|_| HitTestFilterBehavior::Stop,
|_| {
stop_hits += 1;
HitTestResultBehavior::Continue
},
);
assert_eq!(stop_hits, 0, "filter=Stop must prevent any result callback");
let mut first: Vec<String> = Vec::new();
root.hit_test_filtered(
100.0,
100.0,
|_| HitTestFilterBehavior::Continue,
|hit| {
if let Some(n) = hit.name() {
first.push(n);
}
HitTestResultBehavior::Stop
},
);
assert_eq!(
first,
vec!["Inner".to_string()],
"result=Stop after first hit"
);
let mut filtered: Vec<String> = Vec::new();
root.hit_test_filtered(
100.0,
100.0,
|v| {
if v.name().as_deref() == Some("Inner") {
HitTestFilterBehavior::ContinueSkipSelfAndChildren
} else {
HitTestFilterBehavior::Continue
}
},
|hit| {
if let Some(n) = hit.name() {
filtered.push(n);
}
HitTestResultBehavior::Continue
},
);
assert!(
!filtered.iter().any(|n| n == "Inner"),
"filtered-out Inner must not appear, got {filtered:?}"
);
assert!(
filtered.iter().any(|n| n == "Outer"),
"Outer should still be hit, got {filtered:?}"
);
let alpha = FrameworkElement::parse(
"<Border xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>",
)
.expect("parse alpha");
let beta = FrameworkElement::parse(
"<Border xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>",
)
.expect("parse beta");
let mut scope = NameScope::new();
scope.register_name("alpha", &alpha);
scope.register_name("beta", &beta);
let found = scope.find_name("alpha").expect("find_name(alpha)");
assert_eq!(
found.raw(),
alpha.raw(),
"find_name should return the alpha object"
);
assert!(scope.find_name("missing").is_none(), "absent name -> None");
assert_eq!(
scope.find_object(&beta).as_deref(),
Some("beta"),
"find_object(beta) should be \"beta\""
);
let mut enumerated: Vec<String> = Vec::new();
scope.for_each(|name, _obj| enumerated.push(name.to_string()));
enumerated.sort();
assert_eq!(enumerated, vec!["alpha".to_string(), "beta".to_string()]);
scope.unregister_name("alpha");
assert!(scope.find_name("alpha").is_none(), "unregistered name gone");
assert!(scope.find_name("beta").is_some(), "beta still registered");
let mut host = FrameworkElement::parse(
"<Border xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>",
)
.expect("parse host");
let pre = NameScope::of(&host).map(|s| s.raw());
assert!(
pre.is_some(),
"a parsed XAML root should already carry a namescope"
);
assert!(
NameScope::set_on(&mut host, Some(&scope)),
"set_on should succeed on a FrameworkElement"
);
let read_back = NameScope::of(&host).expect("of() after set_on");
assert_eq!(
read_back.raw(),
scope.raw(),
"GetNameScope should return the same scope object we set"
);
assert_ne!(
Some(read_back.raw()),
pre,
"set_on should have replaced the pre-existing namescope"
);
let mut t1 = FrameworkElement::parse(
"<Border xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>",
)
.expect("parse t1");
let identity = t1.render_transform().map(|t| t.raw());
assert!(
identity.is_some(),
"default RenderTransform should be the non-null Identity"
);
let scale = ScaleTransform::new(2.0, 3.0, 0.0, 0.0);
let scale_raw = scale.raw();
assert!(
t1.set_render_transform(&scale),
"set_render_transform should succeed on a UIElement"
);
let read = t1.render_transform().expect("render_transform after set");
assert_eq!(
read.raw(),
scale_raw,
"render_transform should hand back exactly the transform we set"
);
assert_ne!(
Some(read.raw()),
identity,
"the set transform must differ from the default Identity"
);
let mut t2 = FrameworkElement::parse(
"<Border xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"/>",
)
.expect("parse t2");
assert!(
t2.set_render_transform(&read),
"the read-back AnyTransform should be re-applicable"
);
assert_eq!(
t2.render_transform().map(|t| t.raw()),
Some(scale_raw),
"t2 should report the same re-applied transform object"
);
assert_eq!(
t1.render_transform_origin(),
(0.0, 0.0),
"default origin is (0,0)"
);
assert!(t1.set_render_transform_origin(0.25, 0.75));
let (ox, oy) = t1.render_transform_origin();
assert!(
(ox - 0.25).abs() < 1e-6 && (oy - 0.75).abs() < 1e-6,
"origin should round-trip, got ({ox}, {oy})"
);
assert!(t1.set_render_transform_origin(0.5, 0.1));
let (ox2, oy2) = t1.render_transform_origin();
assert!(
(ox2 - 0.5).abs() < 1e-6 && (oy2 - 0.1).abs() < 1e-6,
"a second distinct origin should also round-trip, got ({ox2}, {oy2})"
);
view.deactivate();
drop(view);
}
noesis_runtime::shutdown();
}