#![allow(unsafe_op_in_unsafe_fn)]
use core::ffi::{CStr, c_char};
use core::ptr::{self, NonNull};
use std::ffi::{CString, c_void};
use std::sync::Mutex;
use crate::drawing::DrawingContext;
use crate::ffi::{
ClassBase, LayoutVtable, PropType, noesis_base_component_release, noesis_class_create_instance,
noesis_class_register, noesis_class_register_enum_property, noesis_class_register_property_ex,
noesis_class_set_coerce, noesis_class_set_layout, noesis_class_set_render,
noesis_class_unregister, noesis_freezable_can_freeze, noesis_freezable_freeze,
noesis_freezable_is_frozen, noesis_image_source_get_size, noesis_instance_get_property,
noesis_instance_set_property, noesis_instance_set_readonly_property, noesis_uielement_arrange,
noesis_uielement_desired_size, noesis_uielement_measure, noesis_visual_child,
noesis_visual_children_count,
};
unsafe extern "C" fn class_handler_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
forget_prop_types(userdata);
unsafe {
drop(Box::from_raw(
userdata.cast::<Box<dyn PropertyChangeHandler>>(),
))
};
})
}
#[must_use]
pub unsafe fn image_source_size(image_source: NonNull<c_void>) -> Option<(f32, f32)> {
let mut w: f32 = 0.0;
let mut h: f32 = 0.0;
let ok = noesis_image_source_get_size(image_source.as_ptr(), &mut w, &mut h);
ok.then_some((w, h))
}
pub trait PropertyChangeHandler: Send + 'static {
fn on_changed(&self, instance: Instance, prop_index: u32, value: PropertyValue<'_>);
}
#[derive(Debug)]
pub enum PropertyValue<'a> {
Int32(i32),
UInt32(u32),
UInt64(u64),
Float(f32),
Double(f64),
Bool(bool),
String(Option<&'a str>),
Thickness {
left: f32,
top: f32,
right: f32,
bottom: f32,
},
Color {
r: f32,
g: f32,
b: f32,
a: f32,
},
Rect {
x: f32,
y: f32,
width: f32,
height: f32,
},
Point {
x: f32,
y: f32,
},
Size {
width: f32,
height: f32,
},
Vector {
x: f32,
y: f32,
},
Enum(i32),
ImageSource(Option<NonNull<c_void>>),
BaseComponent(Option<NonNull<c_void>>),
}
struct PropSpec {
name: CString,
kind: PropType,
default: OwnedDefault,
options: PropertyOptions,
enum_type: Option<CString>,
}
pub struct ClassBuilder<H: PropertyChangeHandler> {
name: CString,
base: ClassBase,
handler: H,
props: Vec<PropSpec>,
coerce: Option<Box<dyn CoerceHandler>>,
layout: Option<Box<dyn LayoutHandler>>,
render: Option<Box<dyn RenderHandler>>,
}
enum OwnedDefault {
None,
Int32(i32),
UInt32(u32),
UInt64(u64),
Float(f32),
Double(f64),
Bool(bool),
String(Option<StringDefault>),
Thickness([f32; 4]),
Color([f32; 4]),
Rect([f32; 4]),
Point([f32; 2]),
Size([f32; 2]),
Vector([f32; 2]),
Enum(i32),
}
struct StringDefault {
_bytes: CString,
ptr: *const c_char,
}
impl<H: PropertyChangeHandler> ClassBuilder<H> {
pub fn new(name: &str, base: ClassBase, handler: H) -> Self {
Self {
name: CString::new(name).expect("class name contained NUL"),
base,
handler,
props: Vec::new(),
coerce: None,
layout: None,
render: None,
}
}
pub fn add_property(&mut self, name: &str, kind: PropType) -> u32 {
self.add_property_with(name, kind, PropertyDefault::None)
}
pub fn add_property_with(
&mut self,
name: &str,
kind: PropType,
default: PropertyDefault<'_>,
) -> u32 {
self.add_property_ex(name, kind, default, PropertyOptions::default())
}
pub fn add_property_ex(
&mut self,
name: &str,
kind: PropType,
default: PropertyDefault<'_>,
options: PropertyOptions,
) -> u32 {
let cstr = CString::new(name).expect("property name contained NUL");
self.props.push(PropSpec {
name: cstr,
kind,
default: default.into_owned(),
options,
enum_type: None,
});
self.props.len() as u32 - 1
}
pub fn add_enum_property(
&mut self,
name: &str,
enum_type_name: &str,
default: i32,
options: PropertyOptions,
) -> u32 {
let cstr = CString::new(name).expect("property name contained NUL");
let etype = CString::new(enum_type_name).expect("enum type name contained NUL");
self.props.push(PropSpec {
name: cstr,
kind: PropType::Enum,
default: OwnedDefault::Enum(default),
options,
enum_type: Some(etype),
});
self.props.len() as u32 - 1
}
pub fn set_coerce(&mut self, handler: impl CoerceHandler) {
self.coerce = Some(Box::new(handler));
}
pub fn set_layout(&mut self, handler: impl LayoutHandler) {
self.layout = Some(Box::new(handler));
}
pub fn set_render(&mut self, handler: impl RenderHandler) {
self.render = Some(Box::new(handler));
}
pub fn register(self) -> Option<ClassRegistration> {
let ClassBuilder {
name,
base,
handler,
props,
coerce,
layout,
render,
} = self;
let prop_types: Vec<PropType> = props.iter().map(|p| p.kind).collect();
let boxed: Box<Box<dyn PropertyChangeHandler>> = Box::new(Box::new(handler));
let userdata = Box::into_raw(boxed);
record_prop_types(userdata.cast(), prop_types.clone());
let token = unsafe {
noesis_class_register(
name.as_ptr(),
base,
prop_changed_trampoline,
userdata.cast(),
class_handler_free_trampoline,
)
};
let Some(token) = NonNull::new(token) else {
forget_prop_types(userdata.cast());
unsafe { drop(Box::from_raw(userdata)) };
return None;
};
for spec in &props {
let idx = if let Some(enum_type) = &spec.enum_type {
let default = match spec.default {
OwnedDefault::Enum(v) => v,
_ => 0,
};
unsafe {
noesis_class_register_enum_property(
token.as_ptr(),
spec.name.as_ptr(),
enum_type.as_ptr(),
default,
spec.options.fpm_options,
spec.options.read_only,
)
}
} else {
let default_ptr = spec.default.as_ffi_ptr();
unsafe {
noesis_class_register_property_ex(
token.as_ptr(),
spec.name.as_ptr(),
spec.kind,
default_ptr,
spec.options.fpm_options,
spec.options.read_only,
spec.options.coerce,
)
}
};
if idx == u32::MAX {
unsafe { noesis_class_unregister(token.as_ptr()) };
drop(coerce);
drop(layout);
drop(render);
return None;
}
}
if let Some(handler) = coerce {
let boxed: Box<Box<dyn CoerceHandler>> = Box::new(handler);
let coerce_ud = Box::into_raw(boxed);
record_prop_types(coerce_ud.cast(), prop_types);
unsafe {
noesis_class_set_coerce(
token.as_ptr(),
coerce_trampoline,
coerce_ud.cast(),
coerce_handler_free_trampoline,
);
}
}
if let Some(handler) = layout {
let boxed: Box<Box<dyn LayoutHandler>> = Box::new(handler);
let layout_ud = Box::into_raw(boxed);
let vtable = LayoutVtable {
measure: Some(layout_measure_trampoline),
arrange: Some(layout_arrange_trampoline),
};
unsafe {
noesis_class_set_layout(
token.as_ptr(),
&vtable,
layout_ud.cast(),
layout_handler_free_trampoline,
);
}
}
if let Some(handler) = render {
let boxed: Box<Box<dyn RenderHandler>> = Box::new(handler);
let render_ud = Box::into_raw(boxed);
unsafe {
noesis_class_set_render(
token.as_ptr(),
render_trampoline,
render_ud.cast(),
render_handler_free_trampoline,
);
}
}
Some(ClassRegistration {
token,
_name: name,
num_props: props.len() as u32,
})
}
}
#[derive(Debug, Clone, Copy)]
pub enum PropertyDefault<'a> {
None,
Int32(i32),
UInt32(u32),
UInt64(u64),
Float(f32),
Double(f64),
Bool(bool),
String(&'a str),
Thickness {
left: f32,
top: f32,
right: f32,
bottom: f32,
},
Color {
r: f32,
g: f32,
b: f32,
a: f32,
},
Rect {
x: f32,
y: f32,
width: f32,
height: f32,
},
Point {
x: f32,
y: f32,
},
Size {
width: f32,
height: f32,
},
Vector {
x: f32,
y: f32,
},
Enum(i32),
}
impl PropertyDefault<'_> {
fn into_owned(self) -> OwnedDefault {
match self {
PropertyDefault::None => OwnedDefault::None,
PropertyDefault::Int32(v) => OwnedDefault::Int32(v),
PropertyDefault::UInt32(v) => OwnedDefault::UInt32(v),
PropertyDefault::UInt64(v) => OwnedDefault::UInt64(v),
PropertyDefault::Float(v) => OwnedDefault::Float(v),
PropertyDefault::Double(v) => OwnedDefault::Double(v),
PropertyDefault::Bool(v) => OwnedDefault::Bool(v),
PropertyDefault::String(s) => {
let slot = CString::new(s).ok().map(|bytes| {
let ptr = bytes.as_ptr();
StringDefault { _bytes: bytes, ptr }
});
OwnedDefault::String(slot)
}
PropertyDefault::Thickness {
left,
top,
right,
bottom,
} => OwnedDefault::Thickness([left, top, right, bottom]),
PropertyDefault::Color { r, g, b, a } => OwnedDefault::Color([r, g, b, a]),
PropertyDefault::Rect {
x,
y,
width,
height,
} => OwnedDefault::Rect([x, y, width, height]),
PropertyDefault::Point { x, y } => OwnedDefault::Point([x, y]),
PropertyDefault::Size { width, height } => OwnedDefault::Size([width, height]),
PropertyDefault::Vector { x, y } => OwnedDefault::Vector([x, y]),
PropertyDefault::Enum(v) => OwnedDefault::Enum(v),
}
}
}
impl OwnedDefault {
fn as_ffi_ptr(&self) -> *const c_void {
match self {
OwnedDefault::None => ptr::null(),
OwnedDefault::Int32(v) => (v as *const i32).cast(),
OwnedDefault::UInt32(v) => (v as *const u32).cast(),
OwnedDefault::UInt64(v) => (v as *const u64).cast(),
OwnedDefault::Float(v) => (v as *const f32).cast(),
OwnedDefault::Double(v) => (v as *const f64).cast(),
OwnedDefault::Bool(v) => (v as *const bool).cast(),
OwnedDefault::String(slot) => match slot {
Some(slot) => (&slot.ptr as *const *const c_char).cast(),
None => ptr::null(),
},
OwnedDefault::Thickness(arr) | OwnedDefault::Color(arr) | OwnedDefault::Rect(arr) => {
arr.as_ptr().cast()
}
OwnedDefault::Point(arr) | OwnedDefault::Size(arr) | OwnedDefault::Vector(arr) => {
arr.as_ptr().cast()
}
OwnedDefault::Enum(v) => (v as *const i32).cast(),
}
}
}
#[must_use = "dropping the guard immediately clears the registration"]
pub struct ClassRegistration {
token: NonNull<c_void>,
_name: CString,
num_props: u32,
}
unsafe impl Send for ClassRegistration {}
impl ClassRegistration {
#[must_use]
pub fn num_properties(&self) -> u32 {
self.num_props
}
pub fn token(&self) -> NonNull<c_void> {
self.token
}
#[must_use]
pub fn create_instance(&self) -> Option<ClassInstance> {
let ptr = unsafe { noesis_class_create_instance(self.token.as_ptr()) };
NonNull::new(ptr).map(|ptr| ClassInstance { ptr })
}
}
pub struct ClassInstance {
ptr: NonNull<c_void>,
}
unsafe impl Send for ClassInstance {}
impl ClassInstance {
#[must_use]
pub fn handle(&self) -> Instance {
unsafe { Instance::from_raw(self.ptr) }
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
pub fn freeze(&self) -> bool {
unsafe { noesis_freezable_freeze(self.ptr.as_ptr()) }
}
#[must_use]
pub fn is_frozen(&self) -> bool {
unsafe { noesis_freezable_is_frozen(self.ptr.as_ptr()) }
}
#[must_use]
pub fn can_freeze(&self) -> bool {
unsafe { noesis_freezable_can_freeze(self.ptr.as_ptr()) }
}
}
impl Drop for ClassInstance {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
impl Drop for ClassRegistration {
fn drop(&mut self) {
unsafe { noesis_class_unregister(self.token.as_ptr()) };
}
}
#[derive(Copy, Clone, Debug)]
pub struct Instance(NonNull<c_void>);
impl Instance {
pub unsafe fn from_raw(ptr: NonNull<c_void>) -> Self {
Self(ptr)
}
pub fn as_ptr(self) -> *mut c_void {
self.0.as_ptr()
}
pub fn set_int32(self, prop_index: u32, value: i32) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const i32).cast(),
);
}
}
pub fn set_u64(self, prop_index: u32, value: u64) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const u64).cast(),
);
}
}
pub fn set_float(self, prop_index: u32, value: f32) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const f32).cast(),
);
}
}
pub fn set_double(self, prop_index: u32, value: f64) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const f64).cast(),
);
}
}
pub fn set_bool(self, prop_index: u32, value: bool) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const bool).cast(),
);
}
}
pub fn set_string(self, prop_index: u32, value: &str) {
let cstr = CString::new(value).expect("string contained NUL");
let ptr: *const c_char = cstr.as_ptr();
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&ptr as *const *const c_char).cast(),
);
}
}
pub fn set_thickness(self, prop_index: u32, left: f32, top: f32, right: f32, bottom: f32) {
let arr = [left, top, right, bottom];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_color(self, prop_index: u32, r: f32, g: f32, b: f32, a: f32) {
let arr = [r, g, b, a];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_rect(self, prop_index: u32, x: f32, y: f32, width: f32, height: f32) {
let arr = [x, y, width, height];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_point(self, prop_index: u32, x: f32, y: f32) {
let arr = [x, y];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_size(self, prop_index: u32, width: f32, height: f32) {
let arr = [width, height];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_vector(self, prop_index: u32, x: f32, y: f32) {
let arr = [x, y];
unsafe {
noesis_instance_set_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast());
}
}
pub fn set_enum(self, prop_index: u32, value: i32) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&value as *const i32).cast(),
);
}
}
pub unsafe fn set_component(self, prop_index: u32, component: *mut c_void) {
unsafe {
noesis_instance_set_property(
self.0.as_ptr(),
prop_index,
(&component as *const *mut c_void).cast(),
);
}
}
pub fn set_command(self, prop_index: u32, command: &impl crate::commands::AsCommand) {
unsafe { self.set_component(prop_index, command.command_ptr()) }
}
pub fn get_int32(self, prop_index: u32) -> Option<i32> {
let mut out: i32 = 0;
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, (&mut out as *mut i32).cast())
};
ok.then_some(out)
}
pub fn get_u64(self, prop_index: u32) -> Option<u64> {
let mut out: u64 = 0;
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, (&mut out as *mut u64).cast())
};
ok.then_some(out)
}
pub fn get_float(self, prop_index: u32) -> Option<f32> {
let mut out: f32 = 0.0;
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, (&mut out as *mut f32).cast())
};
ok.then_some(out)
}
pub fn get_string(self, prop_index: u32) -> Option<String> {
let mut p: *const c_char = ptr::null();
let ok = unsafe {
noesis_instance_get_property(
self.0.as_ptr(),
prop_index,
(&mut p as *mut *const c_char).cast(),
)
};
if !ok || p.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(p) }.to_string_lossy().into_owned())
}
pub fn get_thickness(self, prop_index: u32) -> Option<(f32, f32, f32, f32)> {
let mut out = [0.0f32; 4];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1], out[2], out[3]))
}
pub fn get_rect(self, prop_index: u32) -> Option<(f32, f32, f32, f32)> {
let mut out = [0.0f32; 4];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1], out[2], out[3]))
}
pub fn get_point(self, prop_index: u32) -> Option<(f32, f32)> {
let mut out = [0.0f32; 2];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1]))
}
pub fn get_size(self, prop_index: u32) -> Option<(f32, f32)> {
let mut out = [0.0f32; 2];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1]))
}
pub fn get_vector(self, prop_index: u32) -> Option<(f32, f32)> {
let mut out = [0.0f32; 2];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1]))
}
pub fn get_enum(self, prop_index: u32) -> Option<i32> {
let mut out: i32 = 0;
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, (&mut out as *mut i32).cast())
};
ok.then_some(out)
}
pub fn get_color(self, prop_index: u32) -> Option<(f32, f32, f32, f32)> {
let mut out = [0.0f32; 4];
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, out.as_mut_ptr().cast())
};
ok.then_some((out[0], out[1], out[2], out[3]))
}
#[must_use]
pub fn get_image_source_size(self, prop_index: u32) -> Option<(f32, f32)> {
let mut raw_ptr: *mut c_void = ptr::null_mut();
let ok = unsafe {
noesis_instance_get_property(self.0.as_ptr(), prop_index, (&raw mut raw_ptr).cast())
};
if !ok {
return None;
}
let ptr = NonNull::new(raw_ptr)?;
unsafe { image_source_size(ptr) }
}
}
unsafe impl Send for Instance {}
unsafe extern "C" fn prop_changed_trampoline(
userdata: *mut c_void,
instance: *mut c_void,
prop_index: u32,
value_ptr: *const c_void,
) {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn PropertyChangeHandler>>();
let Some(instance) = NonNull::new(instance) else {
return;
};
let value = decode_value(userdata, prop_index, value_ptr);
handler.on_changed(Instance(instance), prop_index, value);
})
}
static CLASS_PROP_TYPES: Mutex<Vec<(usize, Vec<PropType>)>> = Mutex::new(Vec::new());
fn record_prop_types(userdata: *mut c_void, types: Vec<PropType>) {
let key = userdata as usize;
let mut table = CLASS_PROP_TYPES.lock().expect("CLASS_PROP_TYPES poisoned");
if let Some(slot) = table.iter_mut().find(|(k, _)| *k == key) {
slot.1 = types;
} else {
table.push((key, types));
}
}
fn forget_prop_types(userdata: *mut c_void) {
let key = userdata as usize;
let mut table = CLASS_PROP_TYPES.lock().expect("CLASS_PROP_TYPES poisoned");
table.retain(|(k, _)| *k != key);
}
fn lookup_prop_type(userdata: *mut c_void, prop_index: u32) -> Option<PropType> {
let key = userdata as usize;
let table = CLASS_PROP_TYPES.lock().expect("CLASS_PROP_TYPES poisoned");
table
.iter()
.find(|(k, _)| *k == key)
.and_then(|(_, types)| types.get(prop_index as usize).copied())
}
unsafe fn decode_value<'a>(
userdata: *mut c_void,
prop_index: u32,
value_ptr: *const c_void,
) -> PropertyValue<'a> {
let kind = match lookup_prop_type(userdata, prop_index) {
Some(k) => k,
None => return PropertyValue::Bool(false), };
if value_ptr.is_null() {
return match kind {
PropType::String => PropertyValue::String(None),
PropType::ImageSource => PropertyValue::ImageSource(None),
PropType::BaseComponent => PropertyValue::BaseComponent(None),
PropType::Int32 => PropertyValue::Int32(0),
PropType::UInt32 => PropertyValue::UInt32(0),
PropType::UInt64 => PropertyValue::UInt64(0),
PropType::Float => PropertyValue::Float(0.0),
PropType::Double => PropertyValue::Double(0.0),
PropType::Bool => PropertyValue::Bool(false),
PropType::Thickness => PropertyValue::Thickness {
left: 0.0,
top: 0.0,
right: 0.0,
bottom: 0.0,
},
PropType::Color => PropertyValue::Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
},
PropType::Rect => PropertyValue::Rect {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
},
PropType::Point => PropertyValue::Point { x: 0.0, y: 0.0 },
PropType::Size => PropertyValue::Size {
width: 0.0,
height: 0.0,
},
PropType::Vector => PropertyValue::Vector { x: 0.0, y: 0.0 },
PropType::Enum => PropertyValue::Enum(0),
};
}
match kind {
PropType::Int32 => PropertyValue::Int32(*value_ptr.cast::<i32>()),
PropType::UInt32 => PropertyValue::UInt32(*value_ptr.cast::<u32>()),
PropType::UInt64 => PropertyValue::UInt64(*value_ptr.cast::<u64>()),
PropType::Float => PropertyValue::Float(*value_ptr.cast::<f32>()),
PropType::Double => PropertyValue::Double(*value_ptr.cast::<f64>()),
PropType::Bool => PropertyValue::Bool(*value_ptr.cast::<bool>()),
PropType::String => {
let p = *value_ptr.cast::<*const c_char>();
let s = if p.is_null() {
None
} else {
CStr::from_ptr(p).to_str().ok()
};
PropertyValue::String(s)
}
PropType::Thickness => {
let f = value_ptr.cast::<f32>();
PropertyValue::Thickness {
left: *f,
top: *f.add(1),
right: *f.add(2),
bottom: *f.add(3),
}
}
PropType::Color => {
let f = value_ptr.cast::<f32>();
PropertyValue::Color {
r: *f,
g: *f.add(1),
b: *f.add(2),
a: *f.add(3),
}
}
PropType::Rect => {
let f = value_ptr.cast::<f32>();
PropertyValue::Rect {
x: *f,
y: *f.add(1),
width: *f.add(2),
height: *f.add(3),
}
}
PropType::Point => {
let f = value_ptr.cast::<f32>();
PropertyValue::Point {
x: *f,
y: *f.add(1),
}
}
PropType::Size => {
let f = value_ptr.cast::<f32>();
PropertyValue::Size {
width: *f,
height: *f.add(1),
}
}
PropType::Vector => {
let f = value_ptr.cast::<f32>();
PropertyValue::Vector {
x: *f,
y: *f.add(1),
}
}
PropType::Enum => PropertyValue::Enum(*value_ptr.cast::<i32>()),
PropType::ImageSource => {
let p = *value_ptr.cast::<*mut c_void>();
PropertyValue::ImageSource(NonNull::new(p))
}
PropType::BaseComponent => {
let p = *value_ptr.cast::<*mut c_void>();
PropertyValue::BaseComponent(NonNull::new(p))
}
}
}
pub mod fpm_options {
pub const NONE: u32 = 0x000;
pub const AFFECTS_MEASURE: u32 = 0x001;
pub const AFFECTS_ARRANGE: u32 = 0x002;
pub const AFFECTS_PARENT_MEASURE: u32 = 0x004;
pub const AFFECTS_PARENT_ARRANGE: u32 = 0x008;
pub const AFFECTS_RENDER: u32 = 0x010;
pub const INHERITS: u32 = 0x020;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct PropertyOptions {
pub fpm_options: u32,
pub read_only: bool,
pub coerce: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Coerced {
Unchanged,
Int32(i32),
UInt32(u32),
Float(f32),
Double(f64),
Bool(bool),
Thickness {
left: f32,
top: f32,
right: f32,
bottom: f32,
},
Color {
r: f32,
g: f32,
b: f32,
a: f32,
},
Rect {
x: f32,
y: f32,
width: f32,
height: f32,
},
Point {
x: f32,
y: f32,
},
Size {
width: f32,
height: f32,
},
Vector {
x: f32,
y: f32,
},
}
pub trait CoerceHandler: Send + 'static {
fn coerce(&self, instance: Instance, prop_index: u32, value: PropertyValue<'_>) -> Coerced;
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Size {
pub width: f32,
pub height: f32,
}
impl Size {
pub const ZERO: Size = Size {
width: 0.0,
height: 0.0,
};
#[must_use]
pub fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
}
pub trait LayoutHandler: Send + 'static {
fn measure(&self, instance: Instance, available: Size) -> Size {
let _ = (instance, available);
Size::ZERO
}
fn arrange(&self, instance: Instance, final_size: Size) -> Size {
let _ = instance;
final_size
}
}
pub struct LayoutChild {
ptr: NonNull<c_void>,
}
impl LayoutChild {
pub fn measure(&self, available: Size) -> bool {
unsafe { noesis_uielement_measure(self.ptr.as_ptr(), available.width, available.height) }
}
pub fn arrange(&self, x: f32, y: f32, w: f32, h: f32) -> bool {
unsafe { noesis_uielement_arrange(self.ptr.as_ptr(), x, y, w, h) }
}
#[must_use]
pub fn desired_size(&self) -> Option<Size> {
let mut w = 0.0f32;
let mut h = 0.0f32;
let ok = unsafe { noesis_uielement_desired_size(self.ptr.as_ptr(), &mut w, &mut h) };
ok.then_some(Size::new(w, h))
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Instance {
#[must_use]
pub fn layout_child_count(self) -> u32 {
unsafe { noesis_visual_children_count(self.0.as_ptr()) }
}
#[must_use]
pub fn layout_child(self, index: u32) -> Option<LayoutChild> {
let p = unsafe { noesis_visual_child(self.0.as_ptr(), index) };
NonNull::new(p).map(|ptr| LayoutChild { ptr })
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_int32(self, prop_index: u32, value: i32) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const i32).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_uint32(self, prop_index: u32, value: u32) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const u32).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_float(self, prop_index: u32, value: f32) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const f32).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_double(self, prop_index: u32, value: f64) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const f64).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_bool(self, prop_index: u32, value: bool) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const bool).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_string(self, prop_index: u32, value: &str) -> bool {
let cstr = CString::new(value).expect("string contained NUL");
let ptr: *const c_char = cstr.as_ptr();
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&ptr as *const *const c_char).cast(),
)
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_point(self, prop_index: u32, x: f32, y: f32) -> bool {
let arr = [x, y];
unsafe {
noesis_instance_set_readonly_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast())
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_size(self, prop_index: u32, width: f32, height: f32) -> bool {
let arr = [width, height];
unsafe {
noesis_instance_set_readonly_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast())
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_vector(self, prop_index: u32, x: f32, y: f32) -> bool {
let arr = [x, y];
unsafe {
noesis_instance_set_readonly_property(self.0.as_ptr(), prop_index, arr.as_ptr().cast())
}
}
#[must_use = "a false return means the property was not set (unknown name / type mismatch / read-only)"]
pub fn set_readonly_enum(self, prop_index: u32, value: i32) -> bool {
unsafe {
noesis_instance_set_readonly_property(
self.0.as_ptr(),
prop_index,
(&value as *const i32).cast(),
)
}
}
}
unsafe extern "C" fn coerce_trampoline(
userdata: *mut c_void,
instance: *mut c_void,
prop_index: u32,
in_value: *const c_void,
out_value: *mut c_void,
) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
let handler = &*userdata.cast::<Box<dyn CoerceHandler>>();
let Some(inst) = NonNull::new(instance) else {
return;
};
let value = decode_value(userdata, prop_index, in_value);
let coerced = handler.coerce(Instance(inst), prop_index, value);
encode_coerced(userdata, prop_index, coerced, out_value);
})
}
unsafe extern "C" fn coerce_handler_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
forget_prop_types(userdata);
drop(Box::from_raw(userdata.cast::<Box<dyn CoerceHandler>>()));
})
}
unsafe fn encode_coerced(
userdata: *mut c_void,
prop_index: u32,
coerced: Coerced,
out_value: *mut c_void,
) {
if out_value.is_null() {
return;
}
let kind = lookup_prop_type(userdata, prop_index);
match (kind, coerced) {
(_, Coerced::Unchanged) => {}
(Some(PropType::Int32), Coerced::Int32(v)) => *out_value.cast::<i32>() = v,
(Some(PropType::UInt32), Coerced::UInt32(v)) => *out_value.cast::<u32>() = v,
(Some(PropType::Float), Coerced::Float(v)) => *out_value.cast::<f32>() = v,
(Some(PropType::Double), Coerced::Double(v)) => *out_value.cast::<f64>() = v,
(Some(PropType::Bool), Coerced::Bool(v)) => *out_value.cast::<bool>() = v,
(
Some(PropType::Thickness),
Coerced::Thickness {
left,
top,
right,
bottom,
},
) => {
let f = out_value.cast::<f32>();
*f = left;
*f.add(1) = top;
*f.add(2) = right;
*f.add(3) = bottom;
}
(Some(PropType::Color), Coerced::Color { r, g, b, a }) => {
let f = out_value.cast::<f32>();
*f = r;
*f.add(1) = g;
*f.add(2) = b;
*f.add(3) = a;
}
(
Some(PropType::Rect),
Coerced::Rect {
x,
y,
width,
height,
},
) => {
let f = out_value.cast::<f32>();
*f = x;
*f.add(1) = y;
*f.add(2) = width;
*f.add(3) = height;
}
(Some(PropType::Point), Coerced::Point { x, y }) => {
let f = out_value.cast::<f32>();
*f = x;
*f.add(1) = y;
}
(Some(PropType::Size), Coerced::Size { width, height }) => {
let f = out_value.cast::<f32>();
*f = width;
*f.add(1) = height;
}
(Some(PropType::Vector), Coerced::Vector { x, y }) => {
let f = out_value.cast::<f32>();
*f = x;
*f.add(1) = y;
}
_ => {}
}
}
unsafe extern "C" fn layout_measure_trampoline(
userdata: *mut c_void,
instance: *mut c_void,
avail_w: f32,
avail_h: f32,
out_w: *mut f32,
out_h: *mut f32,
) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
let handler = &*userdata.cast::<Box<dyn LayoutHandler>>();
let size = match NonNull::new(instance) {
Some(inst) => handler.measure(Instance(inst), Size::new(avail_w, avail_h)),
None => Size::ZERO,
};
if !out_w.is_null() {
*out_w = size.width;
}
if !out_h.is_null() {
*out_h = size.height;
}
})
}
unsafe extern "C" fn layout_arrange_trampoline(
userdata: *mut c_void,
instance: *mut c_void,
final_w: f32,
final_h: f32,
out_w: *mut f32,
out_h: *mut f32,
) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
let handler = &*userdata.cast::<Box<dyn LayoutHandler>>();
let size = match NonNull::new(instance) {
Some(inst) => handler.arrange(Instance(inst), Size::new(final_w, final_h)),
None => Size::new(final_w, final_h),
};
if !out_w.is_null() {
*out_w = size.width;
}
if !out_h.is_null() {
*out_h = size.height;
}
})
}
unsafe extern "C" fn layout_handler_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(userdata.cast::<Box<dyn LayoutHandler>>()));
})
}
pub trait RenderHandler: Send + 'static {
fn render(&self, instance: Instance, ctx: DrawingContext<'_>);
}
unsafe extern "C" fn render_trampoline(
userdata: *mut c_void,
instance: *mut c_void,
context: *mut c_void,
) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
let handler = &*userdata.cast::<Box<dyn RenderHandler>>();
let (Some(inst), Some(ctx)) = (NonNull::new(instance), NonNull::new(context)) else {
return;
};
let ctx = DrawingContext::from_raw(ctx);
handler.render(Instance(inst), ctx);
})
}
unsafe extern "C" fn render_handler_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(userdata.cast::<Box<dyn RenderHandler>>()));
})
}