use core::ptr::NonNull;
use std::ffi::{CStr, CString, c_void};
use crate::animation::BeginStoryboard;
use crate::binding::{Binding, Boxed};
use crate::ffi::{
TemplateSelectorVTable, noesis_base_component_release, noesis_control_template_parse,
noesis_data_template_parse, noesis_framework_template_find_name, noesis_style_add_setter,
noesis_style_create, noesis_style_destroy, noesis_style_set_based_on,
noesis_style_set_target_type, noesis_templates_data_trigger_add_setter,
noesis_templates_data_trigger_create, noesis_templates_data_trigger_get_binding,
noesis_templates_data_trigger_get_value, noesis_templates_data_trigger_set_binding,
noesis_templates_data_trigger_set_value, noesis_templates_data_trigger_setter_count,
noesis_templates_event_trigger_action_count, noesis_templates_event_trigger_add_action,
noesis_templates_event_trigger_create, noesis_templates_event_trigger_get_routed_event_name,
noesis_templates_event_trigger_get_source_name,
noesis_templates_event_trigger_set_routed_event,
noesis_templates_event_trigger_set_source_name,
noesis_templates_multi_data_trigger_add_condition,
noesis_templates_multi_data_trigger_add_setter,
noesis_templates_multi_data_trigger_condition_count,
noesis_templates_multi_data_trigger_condition_has_binding,
noesis_templates_multi_data_trigger_create,
noesis_templates_multi_data_trigger_get_condition_value,
noesis_templates_multi_data_trigger_setter_count, noesis_templates_multi_trigger_add_condition,
noesis_templates_multi_trigger_add_setter, noesis_templates_multi_trigger_condition_count,
noesis_templates_multi_trigger_create,
noesis_templates_multi_trigger_get_condition_property_name,
noesis_templates_multi_trigger_get_condition_value,
noesis_templates_multi_trigger_setter_count, noesis_templates_selector_create,
noesis_templates_selector_destroy, noesis_templates_selector_select,
noesis_templates_style_add_trigger, noesis_templates_style_get_trigger,
noesis_templates_style_trigger_count, noesis_templates_trigger_add_setter,
noesis_templates_trigger_create, noesis_templates_trigger_get_property_name,
noesis_templates_trigger_get_value, noesis_templates_trigger_set_property,
noesis_templates_trigger_set_value, noesis_templates_trigger_setter_count, noesis_unbox_bool,
noesis_unbox_int32, noesis_unbox_string,
};
use crate::view::FrameworkElement;
pub struct Style {
ptr: NonNull<c_void>,
}
unsafe impl Send for Style {}
impl Default for Style {
fn default() -> Self {
Self::new()
}
}
impl Style {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { noesis_style_create() };
Self {
ptr: NonNull::new(ptr).expect("noesis_style_create returned null"),
}
}
#[must_use]
pub(crate) unsafe fn from_owned(ptr: NonNull<c_void>) -> Self {
Self { ptr }
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_target_type(&mut self, type_name: &str) -> bool {
let Ok(c) = CString::new(type_name) else {
return false;
};
unsafe { noesis_style_set_target_type(self.ptr.as_ptr(), c.as_ptr()) }
}
pub fn add_setter(&mut self, dp_name: &str, value: &Boxed) -> bool {
unsafe { self.add_setter_raw(dp_name, value.raw()) }
}
pub unsafe fn add_setter_raw(&mut self, dp_name: &str, value: *mut c_void) -> bool {
let Ok(c) = CString::new(dp_name) else {
return false;
};
unsafe { noesis_style_add_setter(self.ptr.as_ptr(), c.as_ptr(), value) }
}
pub fn set_based_on(&mut self, base: &Style) {
unsafe { noesis_style_set_based_on(self.ptr.as_ptr(), base.raw()) }
}
pub fn add_trigger<T: TriggerHandle>(&mut self, trigger: &T) -> bool {
unsafe { noesis_templates_style_add_trigger(self.ptr.as_ptr(), trigger.trigger_ptr()) }
}
#[must_use]
pub fn trigger_count(&self) -> u32 {
let n = unsafe { noesis_templates_style_trigger_count(self.ptr.as_ptr()) };
u32::try_from(n.max(0)).unwrap_or(0)
}
#[must_use]
pub fn get_trigger(&self, index: u32) -> Option<TriggerReadback> {
let p = unsafe { noesis_templates_style_get_trigger(self.ptr.as_ptr(), index) };
NonNull::new(p).map(|ptr| TriggerReadback { ptr })
}
pub fn builder(target_type: &str) -> StyleBuilder {
let mut style = Style::new();
assert!(
style.set_target_type(target_type),
"Style::builder: unknown target type {target_type:?}"
);
StyleBuilder { style }
}
}
#[must_use]
pub struct StyleBuilder {
style: Style,
}
impl StyleBuilder {
pub fn setter(mut self, dp_name: &str, value: &Boxed) -> Self {
let _ = self.style.add_setter(dp_name, value);
self
}
pub fn based_on(mut self, base: &Style) -> Self {
self.style.set_based_on(base);
self
}
pub fn trigger<T: TriggerHandle>(mut self, trigger: &T) -> Self {
let _ = self.style.add_trigger(trigger);
self
}
#[must_use]
pub fn build(self) -> Style {
self.style
}
}
impl Drop for Style {
fn drop(&mut self) {
unsafe { noesis_style_destroy(self.ptr.as_ptr()) }
}
}
pub struct ControlTemplate {
ptr: NonNull<c_void>,
}
unsafe impl Send for ControlTemplate {}
impl ControlTemplate {
#[must_use]
pub fn parse(xaml: &str) -> Option<Self> {
let c = CString::new(xaml).expect("xaml contained interior NUL");
let ptr = unsafe { noesis_control_template_parse(c.as_ptr()) };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[must_use]
pub(crate) unsafe fn from_owned(ptr: NonNull<c_void>) -> Self {
Self { ptr }
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn find_name(
&self,
name: &str,
templated_parent: &FrameworkElement,
) -> Option<NonNull<c_void>> {
let c = CString::new(name).expect("name contained interior NUL");
let p = unsafe {
noesis_framework_template_find_name(
self.ptr.as_ptr(),
c.as_ptr(),
templated_parent.raw(),
)
};
NonNull::new(p)
}
}
impl Drop for ControlTemplate {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
pub struct DataTemplate {
ptr: NonNull<c_void>,
}
unsafe impl Send for DataTemplate {}
impl DataTemplate {
#[must_use]
pub fn parse(xaml: &str) -> Option<Self> {
let c = CString::new(xaml).expect("xaml contained interior NUL");
let ptr = unsafe { noesis_data_template_parse(c.as_ptr()) };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn find_name(
&self,
name: &str,
templated_parent: &FrameworkElement,
) -> Option<NonNull<c_void>> {
let c = CString::new(name).expect("name contained interior NUL");
let p = unsafe {
noesis_framework_template_find_name(
self.ptr.as_ptr(),
c.as_ptr(),
templated_parent.raw(),
)
};
NonNull::new(p)
}
}
impl Drop for DataTemplate {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
pub struct OwnedValue {
ptr: NonNull<c_void>,
}
unsafe impl Send for OwnedValue {}
impl OwnedValue {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
let mut out = false;
let ok = unsafe { noesis_unbox_bool(self.ptr.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
let mut out = 0;
let ok = unsafe { noesis_unbox_int32(self.ptr.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_string(&self) -> Option<String> {
let p = unsafe { noesis_unbox_string(self.ptr.as_ptr()) };
if p.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
}
impl Drop for OwnedValue {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
pub trait TriggerHandle: private::Sealed {
fn trigger_ptr(&self) -> *mut c_void;
}
mod private {
pub trait Sealed {}
}
macro_rules! owned_trigger {
($name:ident, $create:ident, $doc:literal) => {
#[doc = $doc]
pub struct $name {
ptr: NonNull<c_void>,
}
unsafe impl Send for $name {}
impl Default for $name {
fn default() -> Self {
Self::new()
}
}
impl $name {
#[must_use]
pub fn new() -> Self {
let ptr = unsafe { $create() };
Self {
ptr: NonNull::new(ptr).expect(concat!(stringify!($create), " returned null")),
}
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for $name {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
impl private::Sealed for $name {}
impl TriggerHandle for $name {
fn trigger_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
};
}
owned_trigger!(
Trigger,
noesis_templates_trigger_create,
"A property `Trigger`: applies its setters while a dependency property on the\ntargeted element equals the trigger `Value` (`Noesis::Trigger`)."
);
owned_trigger!(
DataTrigger,
noesis_templates_data_trigger_create,
"A `DataTrigger`: applies its setters while a bound value equals the trigger\n`Value` (`Noesis::DataTrigger`)."
);
owned_trigger!(
MultiTrigger,
noesis_templates_multi_trigger_create,
"A `MultiTrigger`: applies its setters while **all** of its property\nconditions are met (`Noesis::MultiTrigger`)."
);
owned_trigger!(
MultiDataTrigger,
noesis_templates_multi_data_trigger_create,
"A `MultiDataTrigger`: applies its setters while **all** of its\nbinding-value conditions are met (`Noesis::MultiDataTrigger`). The\nbinding-condition sibling of [`MultiTrigger`]."
);
owned_trigger!(
EventTrigger,
noesis_templates_event_trigger_create,
"An `EventTrigger`: runs its actions in response to a routed event\n(`Noesis::EventTrigger`)."
);
impl Trigger {
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_property(&mut self, type_name: &str, dp_name: &str) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe { noesis_templates_trigger_set_property(self.ptr.as_ptr(), t.as_ptr(), d.as_ptr()) }
}
#[must_use]
pub fn property_name(&self) -> Option<String> {
read_name(unsafe { noesis_templates_trigger_get_property_name(self.ptr.as_ptr()) })
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_value(&mut self, value: &Boxed) -> bool {
unsafe { noesis_templates_trigger_set_value(self.ptr.as_ptr(), value.raw()) }
}
#[must_use]
pub fn value(&self) -> Option<OwnedValue> {
owned_value(unsafe { noesis_templates_trigger_get_value(self.ptr.as_ptr()) })
}
pub fn add_setter(&mut self, type_name: &str, dp_name: &str, value: &Boxed) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe {
noesis_templates_trigger_add_setter(
self.ptr.as_ptr(),
t.as_ptr(),
d.as_ptr(),
value.raw(),
)
}
}
#[must_use]
pub fn setter_count(&self) -> u32 {
count(unsafe { noesis_templates_trigger_setter_count(self.ptr.as_ptr()) })
}
}
impl DataTrigger {
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_binding(&mut self, binding: &Binding) -> bool {
unsafe { noesis_templates_data_trigger_set_binding(self.ptr.as_ptr(), binding.raw()) }
}
#[must_use]
pub fn has_binding(&self) -> bool {
let p = unsafe { noesis_templates_data_trigger_get_binding(self.ptr.as_ptr()) };
if p.is_null() {
false
} else {
unsafe { noesis_base_component_release(p) };
true
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_value(&mut self, value: &Boxed) -> bool {
unsafe { noesis_templates_data_trigger_set_value(self.ptr.as_ptr(), value.raw()) }
}
#[must_use]
pub fn value(&self) -> Option<OwnedValue> {
owned_value(unsafe { noesis_templates_data_trigger_get_value(self.ptr.as_ptr()) })
}
pub fn add_setter(&mut self, type_name: &str, dp_name: &str, value: &Boxed) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe {
noesis_templates_data_trigger_add_setter(
self.ptr.as_ptr(),
t.as_ptr(),
d.as_ptr(),
value.raw(),
)
}
}
#[must_use]
pub fn setter_count(&self) -> u32 {
count(unsafe { noesis_templates_data_trigger_setter_count(self.ptr.as_ptr()) })
}
}
impl MultiTrigger {
pub fn add_condition(&mut self, type_name: &str, dp_name: &str, value: &Boxed) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe {
noesis_templates_multi_trigger_add_condition(
self.ptr.as_ptr(),
t.as_ptr(),
d.as_ptr(),
value.raw(),
)
}
}
#[must_use]
pub fn condition_count(&self) -> u32 {
count(unsafe { noesis_templates_multi_trigger_condition_count(self.ptr.as_ptr()) })
}
#[must_use]
pub fn condition_property_name(&self, index: u32) -> Option<String> {
read_name(unsafe {
noesis_templates_multi_trigger_get_condition_property_name(self.ptr.as_ptr(), index)
})
}
#[must_use]
pub fn condition_value(&self, index: u32) -> Option<OwnedValue> {
owned_value(unsafe {
noesis_templates_multi_trigger_get_condition_value(self.ptr.as_ptr(), index)
})
}
pub fn add_setter(&mut self, type_name: &str, dp_name: &str, value: &Boxed) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe {
noesis_templates_multi_trigger_add_setter(
self.ptr.as_ptr(),
t.as_ptr(),
d.as_ptr(),
value.raw(),
)
}
}
#[must_use]
pub fn setter_count(&self) -> u32 {
count(unsafe { noesis_templates_multi_trigger_setter_count(self.ptr.as_ptr()) })
}
}
impl MultiDataTrigger {
pub fn add_condition(&mut self, binding: &Binding, value: &Boxed) -> bool {
unsafe {
noesis_templates_multi_data_trigger_add_condition(
self.ptr.as_ptr(),
binding.raw(),
value.raw(),
)
}
}
#[must_use]
pub fn condition_count(&self) -> u32 {
count(unsafe { noesis_templates_multi_data_trigger_condition_count(self.ptr.as_ptr()) })
}
#[must_use]
pub fn condition_has_binding(&self, index: u32) -> bool {
let has = unsafe {
noesis_templates_multi_data_trigger_condition_has_binding(self.ptr.as_ptr(), index)
};
has == 1
}
#[must_use]
pub fn condition_value(&self, index: u32) -> Option<OwnedValue> {
owned_value(unsafe {
noesis_templates_multi_data_trigger_get_condition_value(self.ptr.as_ptr(), index)
})
}
pub fn add_setter(&mut self, type_name: &str, dp_name: &str, value: &Boxed) -> bool {
let (Ok(t), Ok(d)) = (CString::new(type_name), CString::new(dp_name)) else {
return false;
};
unsafe {
noesis_templates_multi_data_trigger_add_setter(
self.ptr.as_ptr(),
t.as_ptr(),
d.as_ptr(),
value.raw(),
)
}
}
#[must_use]
pub fn setter_count(&self) -> u32 {
count(unsafe { noesis_templates_multi_data_trigger_setter_count(self.ptr.as_ptr()) })
}
}
impl EventTrigger {
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_routed_event(&mut self, owner_type: &str, event_name: &str) -> bool {
let (Ok(o), Ok(e)) = (CString::new(owner_type), CString::new(event_name)) else {
return false;
};
unsafe {
noesis_templates_event_trigger_set_routed_event(
self.ptr.as_ptr(),
o.as_ptr(),
e.as_ptr(),
)
}
}
#[must_use]
pub fn routed_event_name(&self) -> Option<String> {
read_name(unsafe {
noesis_templates_event_trigger_get_routed_event_name(self.ptr.as_ptr())
})
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_source_name(&mut self, name: &str) -> bool {
let Ok(n) = CString::new(name) else {
return false;
};
unsafe { noesis_templates_event_trigger_set_source_name(self.ptr.as_ptr(), n.as_ptr()) }
}
#[must_use]
pub fn source_name(&self) -> Option<String> {
read_name(unsafe { noesis_templates_event_trigger_get_source_name(self.ptr.as_ptr()) })
.filter(|s| !s.is_empty())
}
#[must_use]
pub fn action_count(&self) -> u32 {
count(unsafe { noesis_templates_event_trigger_action_count(self.ptr.as_ptr()) })
}
pub fn add_action(&mut self, action: &BeginStoryboard) -> bool {
unsafe { noesis_templates_event_trigger_add_action(self.ptr.as_ptr(), action.raw()) }
}
}
pub struct TriggerReadback {
ptr: NonNull<c_void>,
}
unsafe impl Send for TriggerReadback {}
impl TriggerReadback {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn property_name(&self) -> Option<String> {
read_name(unsafe { noesis_templates_trigger_get_property_name(self.ptr.as_ptr()) })
}
#[must_use]
pub fn value(&self) -> Option<OwnedValue> {
owned_value(unsafe { noesis_templates_trigger_get_value(self.ptr.as_ptr()) })
}
#[must_use]
pub fn setter_count(&self) -> u32 {
count(unsafe { noesis_templates_trigger_setter_count(self.ptr.as_ptr()) })
}
#[must_use]
pub fn routed_event_name(&self) -> Option<String> {
read_name(unsafe {
noesis_templates_event_trigger_get_routed_event_name(self.ptr.as_ptr())
})
}
#[must_use]
pub fn condition_count(&self) -> u32 {
count(unsafe { noesis_templates_multi_trigger_condition_count(self.ptr.as_ptr()) })
}
}
impl Drop for TriggerReadback {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
fn read_name(p: *const std::os::raw::c_char) -> Option<String> {
if p.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
fn owned_value(p: *mut c_void) -> Option<OwnedValue> {
NonNull::new(p).map(|ptr| OwnedValue { ptr })
}
fn count(n: i32) -> u32 {
u32::try_from(n.max(0)).unwrap_or(0)
}
pub trait SelectTemplate: Send + 'static {
fn select(&self, item: *mut c_void, container: *mut c_void) -> Option<*mut c_void>;
}
impl<F: Fn(*mut c_void, *mut c_void) -> Option<*mut c_void> + Send + 'static> SelectTemplate for F {
fn select(&self, item: *mut c_void, container: *mut c_void) -> Option<*mut c_void> {
self(item, container)
}
}
pub struct TemplateSelector {
ptr: NonNull<c_void>,
}
unsafe impl Send for TemplateSelector {}
impl TemplateSelector {
#[must_use]
pub fn new<H: SelectTemplate>(handler: H) -> Self {
let boxed: Box<Box<dyn SelectTemplate>> = Box::new(Box::new(handler));
let userdata = Box::into_raw(boxed).cast::<c_void>();
const VTABLE: TemplateSelectorVTable = TemplateSelectorVTable {
select: selector_select_trampoline,
};
let ptr = unsafe {
noesis_templates_selector_create(&VTABLE, userdata, selector_free_trampoline)
};
match NonNull::new(ptr) {
Some(ptr) => Self { ptr },
None => {
drop(unsafe { Box::from_raw(userdata.cast::<Box<dyn SelectTemplate>>()) });
panic!("noesis_templates_selector_create returned null");
}
}
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub unsafe fn select(
&self,
item: *mut c_void,
container: *mut c_void,
) -> Option<NonNull<c_void>> {
let p = unsafe { noesis_templates_selector_select(self.ptr.as_ptr(), item, container) };
NonNull::new(p)
}
}
impl Drop for TemplateSelector {
fn drop(&mut self) {
unsafe { noesis_templates_selector_destroy(self.ptr.as_ptr()) }
}
}
unsafe extern "C" fn selector_select_trampoline(
userdata: *mut c_void,
item: *mut c_void,
container: *mut c_void,
) -> *mut c_void {
crate::panic_guard::guard_or(core::ptr::null_mut(), || {
if userdata.is_null() {
return core::ptr::null_mut();
}
let handler = unsafe { &*userdata.cast::<Box<dyn SelectTemplate>>() };
handler
.select(item, container)
.unwrap_or(core::ptr::null_mut())
})
}
unsafe extern "C" fn selector_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(unsafe { Box::from_raw(userdata.cast::<Box<dyn SelectTemplate>>()) });
})
}