use std::{cell::Cell, rc::Rc};
use arrayvec::ArrayVec;
use block2::StackBlock;
use futures_util::{FutureExt, future::Either};
use objc2::{MainThreadOnly, rc::Retained};
use objc2_app_kit::{
NSAlert, NSAlertFirstButtonReturn, NSAlertStyle, NSImage, NSImageNameCaution, NSImageNameInfo,
};
use objc2_foundation::{MainThreadMarker, NSString, ns_string};
use winio_handle::AsWindow;
use winio_primitive::{MessageBoxButton, MessageBoxResponse, MessageBoxStyle};
use crate::{Error, Result, catch};
fn msgbox_custom(
parent: Option<impl AsWindow>,
msg: Retained<NSString>,
title: Retained<NSString>,
instr: Retained<NSString>,
style: MessageBoxStyle,
btns: MessageBoxButton,
cbtns: Vec<CustomButton>,
) -> Result<impl Future<Output = Result<MessageBoxResponse>> + 'static> {
let parent = parent.as_ref().map(|p| p.as_window().as_app_kit());
let mtm = parent
.as_ref()
.map(|w| w.mtm())
.or_else(MainThreadMarker::new)
.ok_or(Error::NotMainThread)?;
let (alert, responses) = catch(|| {
let alert = NSAlert::new(mtm);
if let Some(parent) = &parent {
unsafe {
alert.window().setParentWindow(Some(parent));
}
}
alert.setAlertStyle(match style {
MessageBoxStyle::Info => NSAlertStyle::Informational,
MessageBoxStyle::Warning | MessageBoxStyle::Error => NSAlertStyle::Critical,
_ => NSAlertStyle::Warning,
});
let image = match style {
MessageBoxStyle::Info => NSImage::imageNamed(unsafe { NSImageNameInfo }),
MessageBoxStyle::Warning | MessageBoxStyle::Error => {
NSImage::imageNamed(unsafe { NSImageNameCaution })
}
_ => None,
};
unsafe {
alert.setIcon(image.as_deref());
}
alert.window().setTitle(&title);
if instr.is_empty() {
alert.setMessageText(&msg);
} else {
alert.setMessageText(&instr);
alert.setInformativeText(&msg);
}
let mut responses = ArrayVec::<MessageBoxResponse, 6>::new();
if btns.contains(MessageBoxButton::Ok) {
alert.addButtonWithTitle(ns_string!("Ok"));
responses.push(MessageBoxResponse::Ok);
}
if btns.contains(MessageBoxButton::Yes) {
alert.addButtonWithTitle(ns_string!("Yes"));
responses.push(MessageBoxResponse::Yes);
}
if btns.contains(MessageBoxButton::No) {
alert.addButtonWithTitle(ns_string!("No"));
responses.push(MessageBoxResponse::No);
}
if btns.contains(MessageBoxButton::Cancel) {
alert.addButtonWithTitle(ns_string!("Cancel"));
responses.push(MessageBoxResponse::Cancel);
}
if btns.contains(MessageBoxButton::Retry) {
alert.addButtonWithTitle(ns_string!("Try again"));
responses.push(MessageBoxResponse::Retry);
}
if btns.contains(MessageBoxButton::Close) {
alert.addButtonWithTitle(ns_string!("Close"));
responses.push(MessageBoxResponse::Close);
}
for b in cbtns {
alert.addButtonWithTitle(&b.text);
responses.push(MessageBoxResponse::Custom(b.result));
}
Ok((alert, responses))
})
.flatten()?;
if let Some(parent) = &parent {
let (tx, rx) = local_sync::oneshot::channel();
let tx = Rc::new(Cell::new(Some(tx)));
let block = StackBlock::new(move |res| {
tx.take()
.expect("the handler should only be called once")
.send(responses[(res - NSAlertFirstButtonReturn) as usize])
.ok();
});
catch(|| alert.beginSheetModalForWindow_completionHandler(parent, Some(&block)))?;
let parent = (**parent).clone();
Ok(Either::Left(rx.map(move |res| {
let res = res?;
catch(|| parent.makeKeyWindow())?;
Ok(res)
})))
} else {
let res = catch(|| alert.runModal())?;
Ok(Either::Right(std::future::ready(Ok(
responses[res as usize - NSAlertFirstButtonReturn as usize]
))))
}
}
#[derive(Debug, Default, Clone)]
pub struct MessageBox {
msg: Retained<NSString>,
title: Retained<NSString>,
instr: Retained<NSString>,
style: MessageBoxStyle,
btns: MessageBoxButton,
cbtns: Vec<CustomButton>,
}
unsafe impl Send for MessageBox {}
unsafe impl Sync for MessageBox {}
impl MessageBox {
pub fn new() -> Self {
Self::default()
}
pub fn show(
self,
parent: Option<impl AsWindow>,
) -> Result<impl Future<Output = Result<MessageBoxResponse>> + 'static> {
msgbox_custom(
parent, self.msg, self.title, self.instr, self.style, self.btns, self.cbtns,
)
}
pub fn message(&mut self, msg: &str) {
self.msg = NSString::from_str(msg);
}
pub fn title(&mut self, title: &str) {
self.title = NSString::from_str(title);
}
pub fn instruction(&mut self, instr: &str) {
self.instr = NSString::from_str(instr);
}
pub fn style(&mut self, style: MessageBoxStyle) {
self.style = style;
}
pub fn buttons(&mut self, btns: MessageBoxButton) {
self.btns = btns;
}
pub fn custom_button(&mut self, btn: CustomButton) {
self.cbtns.push(btn);
}
pub fn custom_buttons(&mut self, btn: impl IntoIterator<Item = CustomButton>) {
self.cbtns.extend(btn);
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CustomButton {
pub result: u16,
pub text: Retained<NSString>,
}
impl CustomButton {
pub fn new(result: u16, text: &str) -> Self {
Self {
result,
text: NSString::from_str(text),
}
}
}