1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Standalone `NameScope`: the freestanding XAML namescope object,
//! distinct from the per-`FrameworkElement` `RegisterName`/`UnregisterName`
//! path (which routes through whatever scope already hosts the element).
//!
//! A [`NameScope`] owns a `+1` reference on the underlying `Noesis::NameScope`.
//! Attach one to an element with [`NameScope::set_on`], read it back with
//! [`NameScope::of`], and register / look up names against it directly.
use core::ptr::NonNull;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_void};
use crate::ffi::{
noesis_base_component_release, noesis_name_scope_create, noesis_name_scope_enum,
noesis_name_scope_find_name, noesis_name_scope_find_object, noesis_name_scope_get,
noesis_name_scope_register_name, noesis_name_scope_set, noesis_name_scope_unregister_name,
noesis_name_scope_update_name,
};
use crate::view::FrameworkElement;
/// An owning handle to a `Noesis::NameScope`. Holds a `+1` reference, released
/// on drop.
pub struct NameScope {
ptr: NonNull<c_void>,
}
// SAFETY: Send-only (NOT Sync); see the crate-level "Thread affinity" docs.
unsafe impl Send for NameScope {}
impl NameScope {
/// Create a new, empty namescope.
///
/// # Panics
///
/// Panics if Noesis fails to allocate the namescope.
#[must_use]
pub fn new() -> Self {
// SAFETY: the C side returns a freshly-created NameScope* at +1.
let ptr = unsafe { noesis_name_scope_create() };
Self {
ptr: NonNull::new(ptr).expect("noesis_name_scope_create returned null"),
}
}
/// The namescope attached to `element` (`NameScope::GetNameScope`), or
/// `None` if it carries none / is not a `DependencyObject`. The returned
/// handle owns its own `+1` reference.
#[must_use]
pub fn of(element: &FrameworkElement) -> Option<Self> {
// SAFETY: element.raw() is a live BaseComponent*; the C side AddRef's
// any attached scope before handing it back.
let ptr = unsafe { noesis_name_scope_get(element.raw()) };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
/// Attach `scope` to `element` as its namescope (`NameScope::SetNameScope`),
/// or pass `None` to clear it. Returns `false` if `element` is not a
/// `DependencyObject`.
#[must_use = "a false return means the scope was not set (element is not a DependencyObject)"]
pub fn set_on(element: &mut FrameworkElement, scope: Option<&NameScope>) -> bool {
let scope_ptr = scope.map_or(core::ptr::null_mut(), NameScope::raw);
// SAFETY: both pointers are live (or null to clear); Noesis stores its
// own reference to the scope.
unsafe { noesis_name_scope_set(element.raw(), scope_ptr) }
}
/// Look up the object registered under `name` (`INameScope::FindName`), or
/// `None`. The returned element owns its own `+1` reference.
///
/// A name scope can hold any registered `BaseComponent` (a parsed scope may
/// contain `Storyboard`s or other `Freezable`s), so the returned handle is
/// not guaranteed to be a real `FrameworkElement`. It's typed as one for
/// convenience; the shims `DynamicCast` on each call, so calling element
/// methods on a non-element handle degrades to a no-op / `None` rather than
/// undefined behavior.
///
/// # Panics
///
/// Panics if `name` contains an interior NUL byte.
#[must_use]
pub fn find_name(&self, name: &str) -> Option<FrameworkElement> {
let c = CString::new(name).expect("name contained interior NUL");
// SAFETY: self.ptr is a live NameScope*; c lives for the call; the C
// side AddRef's the found object (or returns NULL).
let ptr = unsafe { noesis_name_scope_find_name(self.ptr.as_ptr(), c.as_ptr()) };
// SAFETY: ptr is a +1 BaseComponent* we take ownership of.
NonNull::new(ptr).map(|ptr| unsafe { FrameworkElement::from_owned(ptr) })
}
/// Register `obj` under `name` (`INameScope::RegisterName`). The scope takes
/// its own reference. Registering an existing name is undefined per Noesis;
/// use [`Self::update_name`] to replace.
///
/// # Panics
///
/// Panics if `name` contains an interior NUL byte.
pub fn register_name(&mut self, name: &str, obj: &FrameworkElement) {
let c = CString::new(name).expect("name contained interior NUL");
// SAFETY: all pointers are live; c lives for the call.
unsafe { noesis_name_scope_register_name(self.ptr.as_ptr(), c.as_ptr(), obj.raw()) };
}
/// Remove `name` from this scope (`INameScope::UnregisterName`).
///
/// # Panics
///
/// Panics if `name` contains an interior NUL byte.
pub fn unregister_name(&mut self, name: &str) {
let c = CString::new(name).expect("name contained interior NUL");
// SAFETY: self.ptr is a live NameScope*; c lives for the call.
unsafe { noesis_name_scope_unregister_name(self.ptr.as_ptr(), c.as_ptr()) };
}
/// Replace the object registered under `name` with `obj`
/// (`INameScope::UpdateName`). Used to refresh bindings when freezables are
/// cloned during animations.
///
/// # Panics
///
/// Panics if `name` contains an interior NUL byte.
pub fn update_name(&mut self, name: &str, obj: &FrameworkElement) {
let c = CString::new(name).expect("name contained interior NUL");
// SAFETY: all pointers are live; c lives for the call.
unsafe { noesis_name_scope_update_name(self.ptr.as_ptr(), c.as_ptr(), obj.raw()) };
}
/// Reverse lookup: the name `obj` is registered under in this scope
/// (`NameScope::FindObject`), or `None`.
#[must_use]
pub fn find_object(&self, obj: &FrameworkElement) -> Option<String> {
// SAFETY: both pointers are live; the returned C string is owned by the
// scope and valid until the scope is mutated. We copy it immediately.
let p = unsafe { noesis_name_scope_find_object(self.ptr.as_ptr(), obj.raw()) };
if p.is_null() {
return None;
}
// SAFETY: p is a live, NUL-terminated C string from Noesis.
let s = unsafe { CStr::from_ptr(p) };
Some(s.to_string_lossy().into_owned())
}
/// Call `f` for each `(name, object)` pair registered in this scope
/// (`NameScope::EnumNamedObjects`). The element passed to `f` is **borrowed**
/// and valid only for that call. Use
/// [`clone_ref`](FrameworkElement::clone_ref) to keep it.
pub fn for_each<F: FnMut(&str, &FrameworkElement)>(&self, mut f: F) {
let mut callback: &mut dyn FnMut(&str, &FrameworkElement) = &mut f;
unsafe extern "C" fn tramp(ud: *mut c_void, name: *const c_char, obj: *mut c_void) {
crate::panic_guard::guard(|| {
// SAFETY: ud is the &mut &mut dyn FnMut passed below.
let f = unsafe { &mut *ud.cast::<&mut dyn FnMut(&str, &FrameworkElement)>() };
let name = if name.is_null() {
""
} else {
// SAFETY: name is a live, NUL-terminated C string for this call.
match unsafe { CStr::from_ptr(name) }.to_str() {
Ok(s) => s,
Err(_) => return,
}
};
if let Some(p) = NonNull::new(obj) {
// SAFETY: from_owned just stores the ptr; ManuallyDrop keeps the
// borrowed object from being Released here.
let elem =
core::mem::ManuallyDrop::new(unsafe { FrameworkElement::from_owned(p) });
f(name, &elem);
}
})
}
// SAFETY: tramp matches the C ABI; `callback` outlives the synchronous
// enumeration; self.ptr is a live NameScope*.
unsafe {
noesis_name_scope_enum(self.ptr.as_ptr(), tramp, (&raw mut callback).cast());
}
}
/// Raw `Noesis::NameScope*` (a `BaseComponent*`). Borrowed for the lifetime
/// of `self`.
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Default for NameScope {
fn default() -> Self {
Self::new()
}
}
impl Drop for NameScope {
fn drop(&mut self) {
// SAFETY: self.ptr carries a +1 we own; released exactly once here.
unsafe { noesis_base_component_release(self.ptr.as_ptr()) };
}
}