stipple 0.0.4

Stipple: a cross-platform, self-drawn UI toolkit in Rust.
Documentation
//! The small part of the Objective-C runtime the native renderers use (also
//! shared by the clipboard and file-dialog shims).

use std::ffi::{CString, c_void};

pub type Id = *mut c_void;
pub type Sel = *mut c_void;

#[link(name = "objc", kind = "dylib")]
unsafe extern "C" {
    pub fn objc_msgSend();
    fn objc_getClass(name: *const std::ffi::c_char) -> Id;
    fn sel_registerName(name: *const std::ffi::c_char) -> Sel;
    fn objc_autoreleasePoolPush() -> *mut c_void;
    fn objc_autoreleasePoolPop(pool: *mut c_void);
}

#[derive(Clone, Copy)]
#[repr(C)]
pub struct CgSize {
    pub width: f64,
    pub height: f64,
}

#[derive(Clone, Copy)]
#[repr(C)]
pub struct CgRect {
    pub x: f64,
    pub y: f64,
    pub width: f64,
    pub height: f64,
}

pub fn class(name: &str) -> Id {
    let c = CString::new(name).expect("class name");
    unsafe { objc_getClass(c.as_ptr()) }
}

pub fn sel(name: &str) -> Sel {
    let c = CString::new(name).expect("selector");
    unsafe { sel_registerName(c.as_ptr()) }
}

/// Send a message with a fixed signature: `msg!(obj, "setX:", (x: f64) -> ())`.
macro_rules! msg {
    ($obj:expr, $sel:expr, ($($arg:ident : $ty:ty),*) -> $ret:ty) => {{
        let f: unsafe extern "C" fn($crate::platform::native::objc::Id,
            $crate::platform::native::objc::Sel $(, $ty)*) -> $ret =
            std::mem::transmute($crate::platform::native::objc::objc_msgSend as *const std::ffi::c_void);
        f($obj, $crate::platform::native::objc::sel($sel) $(, $arg)*)
    }};
}
pub(crate) use msg;

/// `[NSString stringWithUTF8String:s]`. Interior NULs end the string.
pub fn nsstring(s: &str) -> Id {
    let s = s.split('\0').next().unwrap_or("");
    let c = CString::new(s).expect("string");
    let p = c.as_ptr();
    unsafe { msg!(class("NSString"), "stringWithUTF8String:", (p: *const std::ffi::c_char) -> Id) }
}

/// Copy an `NSString` into a Rust `String` (via `UTF8String`); `None` for nil.
/// The bytes are copied out before returning, so the result outlives any
/// enclosing autorelease pool.
pub fn rust_string(ns: Id) -> Option<String> {
    if ns.is_null() {
        return None;
    }
    unsafe {
        let p = msg!(ns, "UTF8String", () -> *const std::ffi::c_char);
        if p.is_null() {
            return None;
        }
        Some(std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned())
    }
}

/// Run `f` inside an autorelease pool, for calls made outside the event loop.
pub fn with_pool<R>(f: impl FnOnce() -> R) -> R {
    unsafe {
        let pool = objc_autoreleasePoolPush();
        let r = f();
        objc_autoreleasePoolPop(pool);
        r
    }
}