#![cfg(target_os = "ios")]
#![cfg(feature = "ios-uikit-ffi")]
use crate::compat::String;
use crate::platform::clipboard::{ClipboardContent, RichClipboardBackend};
use objc2::MainThreadMarker;
use objc2_core_foundation::{CGPoint, CGRect, CGSize};
use objc2_foundation::{NSData, NSString};
use objc2_ui_kit::{NSObjectUIAccessibility, UIColor, UIPasteboard, UIViewController, UIWindow};
const HTML_TYPE: &str = "public.html";
fn make_rect(x: i32, y: i32, width: u32, height: u32) -> CGRect {
CGRect::new(
CGPoint::new(x as f64, y as f64),
CGSize::new(width.max(1) as f64, height.max(1) as f64),
)
}
pub(crate) fn create_ui_window(
mtm: MainThreadMarker,
title: &str,
_x: i32,
_y: i32,
width: u32,
height: u32,
) -> objc2::rc::Retained<UIWindow> {
let frame = make_rect(0, 0, width, height);
#[allow(deprecated)]
let window = UIWindow::initWithFrame(mtm.alloc(), frame);
let bg = UIColor::whiteColor();
window.setBackgroundColor(Some(&bg));
let vc = UIViewController::initWithNibName_bundle(mtm.alloc(), None, None);
window.setRootViewController(Some(&vc));
window.makeKeyAndVisible();
window.setAccessibilityLabel(Some(&NSString::from_str(title)), mtm);
window
}
fn pasteboard() -> objc2::rc::Retained<UIPasteboard> {
UIPasteboard::generalPasteboard()
}
pub(crate) fn pasteboard_set_text(text: &str) -> bool {
let board = pasteboard();
unsafe {
board.setString(Some(&NSString::from_str(text)));
board.string().is_some_and(|stored| stored.to_string() == text)
}
}
pub(crate) fn pasteboard_text() -> Option<String> {
let board = pasteboard();
unsafe { board.string() }.map(|text| text.to_string())
}
pub(crate) fn pasteboard_set_html(html: &str, plain: &str) -> bool {
let board = pasteboard();
let plain_ok = pasteboard_set_text(plain);
board.setData_forPasteboardType(
&NSData::with_bytes(html.as_bytes()),
&NSString::from_str(HTML_TYPE),
);
let html_ok = board
.dataForPasteboardType(&NSString::from_str(HTML_TYPE))
.is_some_and(|stored| stored.to_vec() == html.as_bytes());
html_ok || plain_ok
}
pub(crate) fn pasteboard_html() -> Option<String> {
let board = pasteboard();
let data = board.dataForPasteboardType(&NSString::from_str(HTML_TYPE))?;
String::from_utf8(data.to_vec()).ok()
}
pub(crate) fn pasteboard_has_format(content_type: &str) -> bool {
match content_type {
"text/html" => pasteboard_html().is_some(),
"text/plain" => pasteboard_text().is_some(),
_ => false,
}
}
pub(crate) struct IosClipboard;
impl IosClipboard {
pub(crate) fn new() -> Self {
Self
}
}
impl RichClipboardBackend for IosClipboard {
fn set_contents(&self, content: ClipboardContent) -> bool {
match content {
ClipboardContent::Text(text) => pasteboard_set_text(&text),
ClipboardContent::Html { html, plain } => pasteboard_set_html(&html, &plain),
other => {
log::warn!(
"[ios clipboard] {} is not carried by this backend; the write is reported \
as refused rather than pretended",
other.content_type()
);
false
}
}
}
fn get_contents(&self) -> Option<ClipboardContent> {
if let Some(html) = pasteboard_html() {
return Some(ClipboardContent::Html {
html,
plain: pasteboard_text().unwrap_or_default(),
});
}
pasteboard_text().map(ClipboardContent::Text)
}
fn has_format(&self, content_type: &str) -> bool {
pasteboard_has_format(content_type)
}
}