winio-ui-app-kit 0.6.0

AppKit backend for winio.
Documentation
use inherit_methods_macro::inherit_methods;
use objc2::{
    DefinedClass, MainThreadOnly, define_class, msg_send,
    rc::{Allocated, Retained},
    runtime::ProtocolObject,
};
use objc2_app_kit::{NSTabView, NSTabViewDelegate, NSTabViewItem, NSView};
use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSString};
use winio_callback::Callback;
use winio_handle::{AsContainer, BorrowedContainer};
use winio_primitive::{Point, Size};

use crate::{Error, GlobalRuntime, Result, catch, from_cgsize, from_nsstring, widgets::Widget};

#[derive(Debug)]
pub struct TabView {
    handle: Widget,
    view: Retained<NSTabView>,
    delegate: Retained<TabViewDelegate>,
}

#[inherit_methods(from = "self.handle")]
impl TabView {
    pub fn new(parent: impl AsContainer) -> Result<Self> {
        let parent = parent.as_container();
        let mtm = parent.as_app_kit().mtm();

        catch(|| unsafe {
            let view = NSTabView::new(mtm);
            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;

            let delegate = TabViewDelegate::new(mtm);
            let del_obj = ProtocolObject::from_ref(&*delegate);
            view.setDelegate(Some(del_obj));

            Ok(Self {
                handle,
                view,
                delegate,
            })
        })
        .flatten()
    }

    pub fn is_visible(&self) -> Result<bool>;

    pub fn set_visible(&mut self, v: bool) -> Result<()>;

    pub fn is_enabled(&self) -> Result<bool>;

    pub fn set_enabled(&mut self, v: bool) -> Result<()>;

    pub fn loc(&self) -> Result<Point>;

    pub fn set_loc(&mut self, p: Point) -> Result<()>;

    pub fn size(&self) -> Result<Size>;

    pub fn set_size(&mut self, v: Size) -> Result<()>;

    pub fn selection(&self) -> Result<Option<usize>> {
        catch(|| {
            self.view
                .selectedTabViewItem()
                .map(|item| self.view.indexOfTabViewItem(&item) as _)
        })
    }

    pub fn set_selection(&mut self, i: usize) -> Result<()> {
        catch(|| self.view.selectTabViewItemAtIndex(i as _))
    }

    pub async fn wait_select(&self) {
        self.delegate.ivars().did_select.wait().await
    }

    pub fn insert(&mut self, i: usize, item: &TabViewItem) -> Result<()> {
        catch(|| self.view.insertTabViewItem_atIndex(&item.item, i as _))
    }

    pub fn remove(&mut self, i: usize) -> Result<()> {
        catch(|| {
            self.view
                .removeTabViewItem(&self.view.tabViewItemAtIndex(i as _))
        })
    }

    pub fn len(&self) -> Result<usize> {
        catch(|| self.view.numberOfTabViewItems() as _)
    }

    pub fn is_empty(&self) -> Result<bool> {
        Ok(self.len()? == 0)
    }

    pub fn clear(&mut self) -> Result<()> {
        catch(|| {
            while self.view.numberOfTabViewItems() > 0 {
                self.view
                    .removeTabViewItem(&self.view.tabViewItemAtIndex(0));
            }
        })
    }
}

winio_handle::impl_as_widget!(TabView, handle);

#[derive(Debug, Default)]
struct TabViewDelegateIvars {
    did_select: Callback,
}

define_class! {
    #[unsafe(super(NSObject))]
    #[name = "WinioTabViewDelegate"]
    #[ivars = TabViewDelegateIvars]
    #[thread_kind = MainThreadOnly]
    #[derive(Debug)]
    struct TabViewDelegate;

    impl TabViewDelegate {
        #[unsafe(method_id(init))]
        fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
            let this = this.set_ivars(TabViewDelegateIvars::default());
            unsafe { msg_send![super(this), init] }
        }
    }

    unsafe impl NSObjectProtocol for TabViewDelegate {}

    #[allow(non_snake_case)]
    unsafe impl NSTabViewDelegate for TabViewDelegate {
        #[unsafe(method(tabView:didSelectTabViewItem:))]
        unsafe fn tabView_didSelectTabViewItem(
            &self,
            _tab_view: &NSTabView,
            _tab_view_item: Option<&NSTabViewItem>,
        ) {
            self.ivars().did_select.signal::<GlobalRuntime>(());
        }
    }
}

impl TabViewDelegate {
    pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
        unsafe { msg_send![mtm.alloc::<Self>(), init] }
    }
}

#[derive(Debug)]
pub struct TabViewItem {
    item: Retained<NSTabViewItem>,
    view: Retained<NSView>,
}

impl TabViewItem {
    pub fn new() -> Result<Self> {
        let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread)?;
        catch(|| {
            let item = NSTabViewItem::new();
            let view = NSView::new(mtm);
            item.setView(Some(&view));
            Self { item, view }
        })
    }

    pub fn text(&self) -> Result<String> {
        catch(|| from_nsstring(&self.item.label()))
    }

    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
        catch(|| self.item.setLabel(&NSString::from_str(s.as_ref())))
    }

    pub fn size(&self) -> Result<Size> {
        catch(|| {
            let frame = self.view.frame().size;
            Ok(from_cgsize(frame))
        })
        .flatten()
    }
}

impl AsContainer for TabViewItem {
    fn as_container(&self) -> BorrowedContainer<'_> {
        BorrowedContainer::app_kit(&self.view)
    }
}