#![allow(unsafe_op_in_unsafe_fn)]
use core::ffi::CStr;
use core::ptr::{self, NonNull};
use std::ffi::{CString, c_void};
use crate::ffi::{
PlainSetFn, noesis_base_component_release, noesis_box_bool, noesis_box_double,
noesis_box_int32, noesis_box_string, noesis_box_u64, noesis_plain_vm_create_instance,
noesis_plain_vm_get_value, noesis_plain_vm_notify, noesis_plain_vm_register,
noesis_plain_vm_register_property, noesis_plain_vm_set_value, noesis_plain_vm_unregister,
noesis_unbox_bool, noesis_unbox_double, noesis_unbox_int32, noesis_unbox_string,
noesis_unbox_u64,
};
use crate::view::FrameworkElement;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum PlainType {
Int32 = 0,
Double = 1,
Bool = 2,
String = 3,
BaseComponent = 4,
U64 = 5,
}
#[derive(Debug, Clone)]
pub enum PlainValue {
Int32(i32),
Double(f64),
Bool(bool),
String(String),
U64(u64),
Null,
}
impl PlainValue {
fn into_boxed(self) -> *mut c_void {
match self {
PlainValue::Int32(i) => unsafe { noesis_box_int32(i) },
PlainValue::Double(d) => unsafe { noesis_box_double(d) },
PlainValue::Bool(b) => unsafe { noesis_box_bool(b) },
PlainValue::U64(v) => unsafe { noesis_box_u64(v) },
PlainValue::String(s) => {
let cs = CString::new(s).expect("plain value string contained NUL");
unsafe { noesis_box_string(cs.as_ptr()) }
}
PlainValue::Null => ptr::null_mut(),
}
}
}
pub struct PlainValueRef(Option<NonNull<c_void>>);
impl PlainValueRef {
fn new(raw: *mut c_void) -> Self {
Self(NonNull::new(raw))
}
#[must_use]
pub fn is_none(&self) -> bool {
self.0.is_none()
}
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
let p = self.0?;
let mut out = 0i32;
let ok = unsafe { noesis_unbox_int32(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_f64(&self) -> Option<f64> {
let p = self.0?;
let mut out = 0.0f64;
let ok = unsafe { noesis_unbox_double(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_u64(&self) -> Option<u64> {
let p = self.0?;
let mut out = 0u64;
let ok = unsafe { noesis_unbox_u64(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
let p = self.0?;
let mut out = false;
let ok = unsafe { noesis_unbox_bool(p.as_ptr(), &mut out) };
ok.then_some(out)
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
let p = self.0?;
let s = unsafe { noesis_unbox_string(p.as_ptr()) };
if s.is_null() {
return None;
}
unsafe { CStr::from_ptr(s) }.to_str().ok()
}
}
pub trait PlainSetHandler: Send + 'static {
fn on_set(&self, prop_index: u32, value: &PlainValueRef);
}
impl<F> PlainSetHandler for F
where
F: Fn(u32, &PlainValueRef) + Send + 'static,
{
fn on_set(&self, prop_index: u32, value: &PlainValueRef) {
self(prop_index, value);
}
}
unsafe extern "C" fn plain_set_trampoline(
userdata: *mut c_void,
_instance: *mut c_void,
prop_index: u32,
boxed_value: *mut c_void,
) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
let handler = &*userdata.cast::<Box<dyn PlainSetHandler>>();
let value = PlainValueRef::new(boxed_value);
handler.on_set(prop_index, &value);
})
}
unsafe extern "C" fn plain_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(userdata.cast::<Box<dyn PlainSetHandler>>()));
})
}
pub struct PlainVmBuilder {
name: CString,
props: Vec<(CString, PlainType)>,
handler: Option<Box<dyn PlainSetHandler>>,
}
impl PlainVmBuilder {
#[must_use]
pub fn new(name: &str) -> Self {
Self {
name: CString::new(name).expect("plain-VM type name contained NUL"),
props: Vec::new(),
handler: None,
}
}
pub fn add_property(&mut self, name: &str, kind: PlainType) -> u32 {
let idx = self.props.len() as u32;
self.props.push((
CString::new(name).expect("property name contained NUL"),
kind,
));
idx
}
#[must_use]
pub fn on_set<H: PlainSetHandler>(mut self, handler: H) -> Self {
self.handler = Some(Box::new(handler));
self
}
#[must_use]
pub fn register(self) -> Option<PlainVmClass> {
let (userdata, on_set): (*mut c_void, Option<PlainSetFn>) = match self.handler {
Some(h) => {
let boxed: Box<Box<dyn PlainSetHandler>> = Box::new(h);
(Box::into_raw(boxed).cast(), Some(plain_set_trampoline))
}
None => (ptr::null_mut(), None),
};
let free = if userdata.is_null() {
None
} else {
Some(plain_free_trampoline as crate::ffi::PlainFreeFn)
};
let token = unsafe { noesis_plain_vm_register(self.name.as_ptr(), on_set, userdata, free) };
let Some(token) = NonNull::new(token) else {
if !userdata.is_null() {
unsafe { drop(Box::from_raw(userdata.cast::<Box<dyn PlainSetHandler>>())) };
}
return None;
};
let mut count = 0u32;
for (pname, kind) in &self.props {
let idx = unsafe {
noesis_plain_vm_register_property(token.as_ptr(), pname.as_ptr(), *kind as u32)
};
if idx == u32::MAX {
unsafe { noesis_plain_vm_unregister(token.as_ptr()) };
return None;
}
count += 1;
}
Some(PlainVmClass {
token,
prop_count: count,
})
}
}
pub struct PlainVmClass {
token: NonNull<c_void>,
prop_count: u32,
}
unsafe impl Send for PlainVmClass {}
impl PlainVmClass {
#[must_use]
pub fn create_instance(&self) -> Option<PlainInstance> {
let ptr = unsafe { noesis_plain_vm_create_instance(self.token.as_ptr()) };
NonNull::new(ptr).map(|ptr| PlainInstance {
ptr,
prop_count: self.prop_count,
})
}
#[must_use]
pub fn property_count(&self) -> u32 {
self.prop_count
}
}
impl Drop for PlainVmClass {
fn drop(&mut self) {
unsafe { noesis_plain_vm_unregister(self.token.as_ptr()) }
}
}
pub struct PlainInstance {
ptr: NonNull<c_void>,
prop_count: u32,
}
unsafe impl Send for PlainInstance {}
impl PlainInstance {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
pub fn set(&self, prop_index: u32, value: PlainValue) -> bool {
if prop_index >= self.prop_count {
return false;
}
let boxed = value.into_boxed();
let ok = unsafe { noesis_plain_vm_set_value(self.ptr.as_ptr(), prop_index, boxed) };
if !boxed.is_null() {
unsafe { noesis_base_component_release(boxed) };
}
ok
}
pub fn notify(&self, prop_name: &str) -> bool {
let c = CString::new(prop_name).expect("property name contained NUL");
unsafe { noesis_plain_vm_notify(self.ptr.as_ptr(), c.as_ptr()) }
}
#[must_use = "a false return means the property was not set (prop_index out of range)"]
pub fn set_and_notify(&self, prop_index: u32, prop_name: &str, value: PlainValue) -> bool {
self.set(prop_index, value) && self.notify(prop_name)
}
#[must_use]
pub fn get_string(&self, prop_index: u32) -> Option<String> {
self.get(prop_index)
.and_then(|v| v.as_str().map(str::to_owned))
}
#[must_use]
pub fn get_i32(&self, prop_index: u32) -> Option<i32> {
self.get(prop_index).and_then(|v| v.as_i32())
}
#[must_use]
pub fn get_f64(&self, prop_index: u32) -> Option<f64> {
self.get(prop_index).and_then(|v| v.as_f64())
}
#[must_use]
pub fn get_u64(&self, prop_index: u32) -> Option<u64> {
self.get(prop_index).and_then(|v| v.as_u64())
}
#[must_use]
pub fn get_bool(&self, prop_index: u32) -> Option<bool> {
self.get(prop_index).and_then(|v| v.as_bool())
}
fn get(&self, prop_index: u32) -> Option<OwnedBoxed> {
let raw = unsafe { noesis_plain_vm_get_value(self.ptr.as_ptr(), prop_index) };
NonNull::new(raw).map(OwnedBoxed)
}
#[must_use = "a false return means the data context was not set (element is not a FrameworkElement)"]
pub fn set_data_context(&self, element: &mut FrameworkElement) -> bool {
unsafe { element.set_data_context_raw(self.raw()) }
}
}
impl Drop for PlainInstance {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
struct OwnedBoxed(NonNull<c_void>);
impl OwnedBoxed {
fn as_str(&self) -> Option<&str> {
let s = unsafe { noesis_unbox_string(self.0.as_ptr()) };
if s.is_null() {
return None;
}
unsafe { CStr::from_ptr(s) }.to_str().ok()
}
fn as_i32(&self) -> Option<i32> {
let mut out = 0i32;
let ok = unsafe { noesis_unbox_int32(self.0.as_ptr(), &mut out) };
ok.then_some(out)
}
fn as_f64(&self) -> Option<f64> {
let mut out = 0.0f64;
let ok = unsafe { noesis_unbox_double(self.0.as_ptr(), &mut out) };
ok.then_some(out)
}
fn as_bool(&self) -> Option<bool> {
let mut out = false;
let ok = unsafe { noesis_unbox_bool(self.0.as_ptr(), &mut out) };
ok.then_some(out)
}
fn as_u64(&self) -> Option<u64> {
let mut out = 0u64;
let ok = unsafe { noesis_unbox_u64(self.0.as_ptr(), &mut out) };
ok.then_some(out)
}
}
impl Drop for OwnedBoxed {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.0.as_ptr()) }
}
}