#![allow(unsafe_op_in_unsafe_fn)]
use core::ptr::NonNull;
use std::ffi::{CStr, CString, c_void};
use std::os::raw::c_char;
use crate::ffi::{
CommandVTable, noesis_application_command, noesis_base_component_release,
noesis_command_binding_attach, noesis_command_binding_create, noesis_command_binding_destroy,
noesis_command_create, noesis_command_destroy, noesis_command_raise_can_execute_changed,
noesis_component_command, noesis_routed_command_can_execute, noesis_routed_command_create,
noesis_routed_command_execute, noesis_routed_command_get_name, noesis_routed_ui_command_create,
noesis_routed_ui_command_get_text, noesis_routed_ui_command_set_text, noesis_unbox_bool,
noesis_unbox_double, noesis_unbox_int32, noesis_unbox_string,
};
use crate::view::FrameworkElement;
unsafe fn cstr_opt(p: *const c_char) -> Option<String> {
if p.is_null() {
None
} else {
Some(std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned())
}
}
pub struct CommandParameterValue(Option<NonNull<c_void>>);
impl CommandParameterValue {
#[must_use]
pub 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 raw(&self) -> Option<NonNull<c_void>> {
self.0
}
#[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_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_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 CommandHandler: Send + 'static {
fn can_execute(&self, _param: CommandParameterValue) -> bool {
true
}
fn execute(&self, param: CommandParameterValue);
}
impl<F: Fn(CommandParameterValue) + Send + 'static> CommandHandler for F {
fn execute(&self, param: CommandParameterValue) {
self(param);
}
}
static COMMAND_VTABLE: CommandVTable = CommandVTable {
can_execute: command_can_execute_trampoline,
execute: command_execute_trampoline,
};
unsafe extern "C" fn command_can_execute_trampoline(
userdata: *mut c_void,
param: *mut c_void,
) -> bool {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn CommandHandler>>();
handler.can_execute(CommandParameterValue::new(param))
})
}
unsafe extern "C" fn command_execute_trampoline(userdata: *mut c_void, param: *mut c_void) {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn CommandHandler>>();
handler.execute(CommandParameterValue::new(param));
})
}
unsafe extern "C" fn command_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(userdata.cast::<Box<dyn CommandHandler>>()));
})
}
pub struct Command {
ptr: NonNull<c_void>,
}
unsafe impl Send for Command {}
impl Command {
#[must_use]
pub fn new<H: CommandHandler>(handler: H) -> Self {
let boxed: Box<Box<dyn CommandHandler>> = Box::new(Box::new(handler));
let userdata = Box::into_raw(boxed);
let ptr = unsafe {
noesis_command_create(&COMMAND_VTABLE, userdata.cast(), command_free_trampoline)
};
match NonNull::new(ptr) {
Some(ptr) => Command { ptr },
None => {
unsafe { drop(Box::from_raw(userdata)) };
unreachable!("noesis_command_create returned null for a non-null vtable");
}
}
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
pub fn raise_can_execute_changed(&self) {
unsafe { noesis_command_raise_can_execute_changed(self.ptr.as_ptr()) }
}
}
impl Drop for Command {
fn drop(&mut self) {
unsafe { noesis_command_destroy(self.ptr.as_ptr()) }
}
}
pub trait AsCommand {
fn command_ptr(&self) -> *mut c_void;
}
impl AsCommand for Command {
fn command_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
pub struct RoutedCommand {
ptr: NonNull<c_void>,
}
unsafe impl Send for RoutedCommand {}
impl RoutedCommand {
#[must_use]
pub fn new(name: &str, owner_type: &str) -> Option<Self> {
let cn = CString::new(name).expect("name contained interior NUL");
let co = CString::new(owner_type).expect("owner_type contained interior NUL");
let ptr = unsafe { noesis_routed_command_create(cn.as_ptr(), co.as_ptr()) };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
pub fn execute(&self, param: CommandParameterValue, target: &FrameworkElement) {
unsafe {
noesis_routed_command_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw());
}
}
#[must_use]
pub fn can_execute(&self, param: CommandParameterValue, target: &FrameworkElement) -> bool {
unsafe {
noesis_routed_command_can_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw())
}
}
#[must_use]
pub fn name(&self) -> Option<String> {
unsafe { cstr_opt(noesis_routed_command_get_name(self.ptr.as_ptr())) }
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl AsCommand for RoutedCommand {
fn command_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for RoutedCommand {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
pub struct RoutedUICommand {
ptr: NonNull<c_void>,
}
unsafe impl Send for RoutedUICommand {}
impl RoutedUICommand {
#[must_use]
pub fn new(name: &str, text: &str, owner_type: &str) -> Option<Self> {
let cn = CString::new(name).expect("name contained interior NUL");
let ct = CString::new(text).expect("text contained interior NUL");
let co = CString::new(owner_type).expect("owner_type contained interior NUL");
let ptr = unsafe { noesis_routed_ui_command_create(cn.as_ptr(), ct.as_ptr(), co.as_ptr()) };
NonNull::new(ptr).map(|ptr| Self { ptr })
}
pub fn execute(&self, param: CommandParameterValue, target: &FrameworkElement) {
unsafe {
noesis_routed_command_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw());
}
}
#[must_use]
pub fn can_execute(&self, param: CommandParameterValue, target: &FrameworkElement) -> bool {
unsafe {
noesis_routed_command_can_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw())
}
}
#[must_use]
pub fn text(&self) -> Option<String> {
unsafe { cstr_opt(noesis_routed_ui_command_get_text(self.ptr.as_ptr())) }
}
pub fn set_text(&mut self, text: &str) {
let c = CString::new(text).expect("text contained interior NUL");
unsafe { noesis_routed_ui_command_set_text(self.ptr.as_ptr(), c.as_ptr()) };
}
#[must_use]
pub fn name(&self) -> Option<String> {
unsafe { cstr_opt(noesis_routed_command_get_name(self.ptr.as_ptr())) }
}
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl AsCommand for RoutedUICommand {
fn command_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
impl Drop for RoutedUICommand {
fn drop(&mut self) {
unsafe { noesis_base_component_release(self.ptr.as_ptr()) }
}
}
fn param_ptr(param: &CommandParameterValue) -> *mut c_void {
param.raw().map_or(core::ptr::null_mut(), NonNull::as_ptr)
}
#[derive(Copy, Clone)]
pub struct BorrowedCommand {
ptr: NonNull<c_void>,
}
unsafe impl Send for BorrowedCommand {}
impl BorrowedCommand {
#[must_use]
pub fn raw(&self) -> *mut c_void {
self.ptr.as_ptr()
}
#[must_use]
pub fn text(&self) -> Option<String> {
unsafe { cstr_opt(noesis_routed_ui_command_get_text(self.ptr.as_ptr())) }
}
#[must_use]
pub fn name(&self) -> Option<String> {
unsafe { cstr_opt(noesis_routed_command_get_name(self.ptr.as_ptr())) }
}
pub fn execute(&self, param: CommandParameterValue, target: &FrameworkElement) {
unsafe {
noesis_routed_command_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw());
}
}
#[must_use]
pub fn can_execute(&self, param: CommandParameterValue, target: &FrameworkElement) -> bool {
unsafe {
noesis_routed_command_can_execute(self.ptr.as_ptr(), param_ptr(¶m), target.raw())
}
}
}
impl AsCommand for BorrowedCommand {
fn command_ptr(&self) -> *mut c_void {
self.ptr.as_ptr()
}
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ApplicationCommand {
CancelPrint = 0,
Close = 1,
ContextMenu = 2,
Copy = 3,
CorrectionList = 4,
Cut = 5,
Delete = 6,
Find = 7,
Help = 8,
New = 9,
Open = 10,
Paste = 11,
Print = 12,
PrintPreview = 13,
Properties = 14,
Redo = 15,
Replace = 16,
Save = 17,
SaveAs = 18,
SelectAll = 19,
Stop = 20,
Undo = 21,
}
impl ApplicationCommand {
#[must_use]
pub fn command(self) -> BorrowedCommand {
let ptr = unsafe { noesis_application_command(self as u32) };
BorrowedCommand {
ptr: NonNull::new(ptr.cast_mut())
.expect("ApplicationCommands singleton was null (runtime not initialized?)"),
}
}
}
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ComponentCommand {
ExtendSelectionDown = 0,
ExtendSelectionLeft = 1,
ExtendSelectionRight = 2,
ExtendSelectionUp = 3,
MoveDown = 4,
MoveFocusBack = 5,
MoveFocusDown = 6,
MoveFocusForward = 7,
MoveFocusPageDown = 8,
MoveFocusPageUp = 9,
MoveFocusUp = 10,
MoveLeft = 11,
MoveRight = 12,
MoveToEnd = 13,
MoveToHome = 14,
MoveToPageDown = 15,
MoveToPageUp = 16,
MoveUp = 17,
ScrollByLine = 18,
ScrollPageDown = 19,
ScrollPageLeft = 20,
ScrollPageRight = 21,
ScrollPageUp = 22,
SelectToEnd = 23,
SelectToHome = 24,
SelectToPageDown = 25,
SelectToPageUp = 26,
}
impl ComponentCommand {
#[must_use]
pub fn command(self) -> BorrowedCommand {
let ptr = unsafe { noesis_component_command(self as u32) };
BorrowedCommand {
ptr: NonNull::new(ptr.cast_mut())
.expect("ComponentCommands singleton was null (runtime not initialized?)"),
}
}
}
pub trait CommandBindingHandler: Send + 'static {
fn can_execute(&self, _param: CommandParameterValue) -> bool {
true
}
fn execute(&self, param: CommandParameterValue);
}
impl<F: Fn(CommandParameterValue) + Send + 'static> CommandBindingHandler for F {
fn execute(&self, param: CommandParameterValue) {
self(param);
}
}
unsafe extern "C" fn cb_executed_trampoline(userdata: *mut c_void, param: *mut c_void) {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn CommandBindingHandler>>();
handler.execute(CommandParameterValue::new(param));
})
}
unsafe extern "C" fn cb_can_execute_trampoline(userdata: *mut c_void, param: *mut c_void) -> bool {
crate::panic_guard::guard(|| {
let handler = &*userdata.cast::<Box<dyn CommandBindingHandler>>();
handler.can_execute(CommandParameterValue::new(param))
})
}
unsafe extern "C" fn cb_free_trampoline(userdata: *mut c_void) {
crate::panic_guard::guard(|| {
if userdata.is_null() {
return;
}
drop(Box::from_raw(
userdata.cast::<Box<dyn CommandBindingHandler>>(),
));
})
}
pub struct CommandBinding {
token: NonNull<c_void>,
}
unsafe impl Send for CommandBinding {}
impl CommandBinding {
#[must_use]
pub fn new<C: AsCommand, H: CommandBindingHandler>(command: &C, handler: H) -> Option<Self> {
let boxed: Box<Box<dyn CommandBindingHandler>> = Box::new(Box::new(handler));
let userdata = Box::into_raw(boxed);
let token = unsafe {
noesis_command_binding_create(
command.command_ptr(),
cb_executed_trampoline,
Some(cb_can_execute_trampoline),
userdata.cast(),
cb_free_trampoline,
)
};
match NonNull::new(token) {
Some(token) => Some(Self { token }),
None => {
unsafe { drop(Box::from_raw(userdata)) };
None
}
}
}
pub fn attach(&self, element: &FrameworkElement) -> bool {
unsafe { noesis_command_binding_attach(self.token.as_ptr(), element.raw()) }
}
}
impl Drop for CommandBinding {
fn drop(&mut self) {
unsafe { noesis_command_binding_destroy(self.token.as_ptr()) }
}
}