winio-ui-app-kit 0.6.0

AppKit backend for winio.
Documentation
use inherit_methods_macro::inherit_methods;
use objc2::{
    DeclaredClass, MainThreadOnly, define_class, msg_send,
    rc::{Allocated, Retained},
    runtime::ProtocolObject,
};
use objc2_app_kit::{
    NSControlTextEditingDelegate, NSSecureTextField, NSTextAlignment, NSTextField,
    NSTextFieldDelegate,
};
use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
use winio_callback::Callback;
use winio_handle::{AsContainer, BorrowedContainer};
use winio_primitive::{HAlign, Point, Size};

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

#[derive(Debug)]
struct EditImpl {
    handle: Widget,
    view: Retained<NSTextField>,
    delegate: Retained<EditDelegate>,
}

#[inherit_methods(from = "self.handle")]
impl EditImpl {
    pub fn new(
        parent: impl AsContainer,
        view: Retained<NSTextField>,
        delegate: Retained<EditDelegate>,
    ) -> Result<Self> {
        catch(|| unsafe {
            view.setBezeled(true);
            view.setDrawsBackground(true);
            view.setEditable(true);
            view.setSelectable(true);
            let del_obj = ProtocolObject::from_ref(&*delegate);
            view.setDelegate(Some(del_obj));

            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;

            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 preferred_size(&self) -> Result<Size>;

    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 tooltip(&self) -> Result<String>;

    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;

    pub fn text(&self) -> Result<String>;

    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;

    pub fn halign(&self) -> Result<HAlign> {
        let align = catch(|| self.view.alignment())?;
        let align = match align {
            NSTextAlignment::Right => HAlign::Right,
            NSTextAlignment::Center => HAlign::Center,
            NSTextAlignment::Justified => HAlign::Stretch,
            _ => HAlign::Left,
        };
        Ok(align)
    }

    pub fn set_halign(&mut self, align: HAlign) -> Result<()> {
        let align = match align {
            HAlign::Left => NSTextAlignment::Left,
            HAlign::Center => NSTextAlignment::Center,
            HAlign::Right => NSTextAlignment::Right,
            HAlign::Stretch => NSTextAlignment::Justified,
        };
        catch(|| self.view.setAlignment(align))
    }

    pub fn is_readonly(&self) -> Result<bool> {
        catch(|| !self.view.isEditable())
    }

    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
        catch(|| self.view.setEditable(!v))
    }

    pub async fn wait_change(&self) {
        self.delegate.ivars().changed.wait().await
    }
}

winio_handle::impl_as_widget!(EditImpl, handle);

#[derive(Debug)]
pub struct Edit {
    handle: EditImpl,
    password: bool,
}

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

        catch(|| {
            let view = NSTextField::new(mtm);
            let delegate = EditDelegate::new(mtm);
            let handle = EditImpl::new(parent, view, delegate)?;
            Ok(Self {
                handle,
                password: false,
            })
        })
        .flatten()
    }

    fn recreate(&mut self, password: bool, mtm: MainThreadMarker) -> Result<()> {
        let view = catch(|| unsafe {
            if password {
                Retained::cast_unchecked(NSSecureTextField::new(mtm))
            } else {
                NSTextField::new(mtm)
            }
        })?;
        let parent = self.handle.handle.parent()?;
        let mut new_handle = EditImpl::new(
            BorrowedContainer::app_kit(&parent),
            view,
            self.handle.delegate.clone(),
        )?;
        new_handle.set_visible(self.handle.is_visible()?)?;
        new_handle.set_enabled(self.handle.is_enabled()?)?;
        new_handle.set_loc(self.handle.loc()?)?;
        new_handle.set_size(self.handle.size()?)?;
        new_handle.set_text(self.handle.text()?)?;
        new_handle.set_halign(self.handle.halign()?)?;
        self.handle = new_handle;
        Ok(())
    }

    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 preferred_size(&self) -> Result<Size>;

    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 tooltip(&self) -> Result<String>;

    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;

    pub fn text(&self) -> Result<String>;

    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;

    pub fn is_password(&self) -> Result<bool> {
        Ok(self.password)
    }

    pub fn set_password(&mut self, v: bool) -> Result<()> {
        if self.password != v {
            self.recreate(v, self.handle.view.mtm())?;
            self.password = v;
        }
        Ok(())
    }

    pub fn halign(&self) -> Result<HAlign>;

    pub fn set_halign(&mut self, align: HAlign) -> Result<()>;

    pub fn is_readonly(&self) -> Result<bool> {
        if self.is_password()? {
            Ok(false)
        } else {
            self.handle.is_readonly()
        }
    }

    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
        if !self.is_password()? {
            self.handle.set_readonly(v)?;
        }
        Ok(())
    }

    pub async fn wait_change(&self) {
        self.handle.wait_change().await
    }
}

winio_handle::impl_as_widget!(Edit, handle);

#[derive(Debug, Default)]
struct EditDelegateIvars {
    changed: Callback,
}

define_class! {
    #[unsafe(super(NSObject))]
    #[name = "WinioEditDelegate"]
    #[ivars = EditDelegateIvars]
    #[thread_kind = MainThreadOnly]
    #[derive(Debug)]
    struct EditDelegate;

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

    unsafe impl NSObjectProtocol for EditDelegate {}

    #[allow(non_snake_case)]
    unsafe impl NSControlTextEditingDelegate for EditDelegate {
        #[unsafe(method(controlTextDidChange:))]
        fn controlTextDidChange(&self, _notification: &NSNotification) {
            self.ivars().changed.signal::<GlobalRuntime>(());
        }
    }

    unsafe impl NSTextFieldDelegate for EditDelegate {}
}

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