ui-automation 0.1.1

Control the User Interface, cross-platform.
Documentation
// Copyright (C) 2024 Tristan Gerritsen <tristan@thewoosh.org>
// All Rights Reserved.

use std::{ffi::c_void, fmt::Debug, marker::PhantomData, ptr::null};

use super::{CFAllocatorRef, CFIndex, CFSerializable, CFType};

pub type CFArrayRef = *const c_void;

#[derive(Clone)]
pub(crate) struct CFArray {
    pub(crate) inner: CFType,
}

impl CFArray {
    pub fn from_slice<E: CFSerializable>(s: &[E]) -> Option<Self> {
        let data: Vec<*const c_void> = s.iter().map(|x| x.to_ptr()).collect();

        let alloc = null();
        let values = data.as_ptr();
        let num_values = data.len() as CFIndex;
        let callbacks = null();
        let reference = unsafe { CFArrayCreate(alloc, values, num_values, callbacks) };

        Some(Self { inner: CFType::from_create(reference)? })
    }

    pub fn from_ptr(reference: CFType) -> Self {
        Self { inner: reference }
    }

    #[must_use]
    pub fn len(&self) -> usize {
        let count = unsafe { CFArrayGetCount(self.inner.ptr()) };
        count as usize
    }

    #[must_use]
    pub fn get_ptr(&self, index: usize) -> Option<*const c_void> {
        if index >= self.len() {
            return None;
        }

        let index = index as CFIndex;
        let value = unsafe { CFArrayGetValueAtIndex(self.inner.ptr(), index) };
        if value.is_null() {
            return None;
        }
        Some(value)
    }

    #[must_use]
    pub fn get<V: CFSerializable>(&self, index: usize) -> Option<V> {
        let value = self.get_ptr(index)?;
        // if !value.is_null() {
        //     println!("Value is of type: {}", CFType::from_get(value).unwrap().description());
        // }
        Some(V::from_ptr(value))
    }

    pub fn iter<V: CFSerializable>(&self) -> CFArrayIterator<V> {
        CFArrayIterator {
            array: self.clone(),
            index: 0,
            _value: PhantomData,
        }
    }
}

impl Debug for CFArray {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("CFArray")
            .field("inner", &self.inner)
            .field("length", &self.len())
            .finish()
    }
}

pub(crate) struct CFArrayIterator<V> {
    array: CFArray,
    index: usize,
    _value: PhantomData<V>,
}

impl<V: CFSerializable> Iterator for CFArrayIterator<V> {
    type Item = V;

    fn next(&mut self) -> Option<Self::Item> {
        let value = self.array.get(self.index)?;
        self.index += 1;
        Some(value)
    }
}

extern "C-unwind" {
    pub(super) fn CFArrayGetCount(array: CFArrayRef) -> CFIndex;
    pub(super) fn CFArrayGetValueAtIndex(array: CFArrayRef, index: CFIndex) -> *const c_void;
    pub(super) fn CFArrayCreate(alloc: CFAllocatorRef, values: *const *const c_void, num_values: CFIndex, callbacks: *const c_void) -> CFArrayRef;
}

#[cfg(test)]
mod tests {
    use super::CFArray;

    #[test]
    fn test_cf_array() {
        let array = CFArray::from_slice(&[123u32, 456u32]).unwrap();
        assert_eq!(array.len(), 2);
        assert_eq!(array.get::<u32>(0), Some(123));
        assert_eq!(array.get::<u32>(1), Some(456));
    }
}