#![cfg(target_os = "macos")]
#![cfg(feature = "macos")]
#![allow(dead_code)]
#![allow(unused_unsafe)]
#![allow(unused_imports)]
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::MainThreadMarker;
use objc2::{class, msg_send, sel};
use objc2_app_kit::{
NSAlert, NSApplication, NSBackingStoreType, NSBorderType, NSButton, NSButtonType, NSColorPanel,
NSFontPanel, NSMenu, NSMenuItem, NSOpenPanel, NSPopUpButton, NSProgressIndicator, NSScrollView,
NSSlider, NSStepper, NSTableColumn, NSTableView, NSTextField, NSView, NSWindow,
NSWindowStyleMask,
};
use objc2_foundation::{NSPoint, NSRect, NSSize, NSString};
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::Mutex;
#[derive(Clone, Copy)]
struct NativePtr(*mut std::ffi::c_void);
unsafe impl Send for NativePtr {}
static NATIVE_VIEWS: LazyLock<Mutex<HashMap<u64, NativePtr>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static PARENT_MAP: LazyLock<Mutex<HashMap<u64, u64>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
pub(crate) fn store_native_view(widget_id: u64, view: *mut std::ffi::c_void) {
if view.is_null() {
return;
}
remove_native_view(widget_id);
unsafe {
let object = view as *mut AnyObject;
let _: *mut AnyObject = msg_send![object, retain];
}
NATIVE_VIEWS.lock().unwrap().insert(widget_id, NativePtr(view));
}
pub(crate) fn get_native_view(widget_id: u64) -> Option<*mut std::ffi::c_void> {
NATIVE_VIEWS.lock().unwrap().get(&widget_id).map(|p| p.0)
}
pub(crate) fn native_view_count() -> usize {
NATIVE_VIEWS.lock().unwrap().len()
}
pub(crate) fn remove_native_view(widget_id: u64) {
let removed = NATIVE_VIEWS.lock().unwrap().remove(&widget_id);
if let Some(ptr) = removed {
unsafe {
let object = ptr.0 as *mut AnyObject;
let has_superview: bool =
msg_send![object, respondsToSelector: sel!(removeFromSuperview)];
if has_superview {
let _: () = msg_send![object, removeFromSuperview];
}
let _: () = msg_send![object, release];
}
}
PARENT_MAP.lock().unwrap().remove(&widget_id);
}
pub(crate) fn set_parent(widget_id: u64, parent_id: u64) {
PARENT_MAP.lock().unwrap().insert(widget_id, parent_id);
}
pub(crate) fn get_parent(widget_id: u64) -> Option<u64> {
PARENT_MAP.lock().unwrap().get(&widget_id).copied()
}
pub(crate) fn add_as_subview(widget_id: u64, parent_id: u64) {
let views = NATIVE_VIEWS.lock().unwrap();
let Some(parent_ptr) = views.get(&parent_id).map(|p| p.0) else {
return;
};
let Some(child_ptr) = views.get(&widget_id).map(|p| p.0) else {
return;
};
drop(views);
unsafe {
let parent = parent_ptr as *mut AnyObject;
let child = child_ptr as *mut AnyObject;
let content_view_selector = sel!(contentView);
let responds: bool = msg_send![parent, respondsToSelector: content_view_selector];
let container: *mut AnyObject =
if responds { msg_send![parent, contentView] } else { parent };
if !container.is_null() {
let _: () = msg_send![container, addSubview: child];
set_parent(widget_id, parent_id);
}
}
}
pub(crate) fn set_native_frame(widget_id: u64, x: i32, y: i32, width: u32, height: u32) {
let Some(ptr) = get_native_view(widget_id) else {
return;
};
if MainThreadMarker::new().is_none() {
return;
}
unsafe {
let object = ptr as *mut AnyObject;
let rect = make_rect(x, y, width, height);
let is_window: bool = msg_send![object, isKindOfClass: class!(NSWindow)];
if is_window {
let _: () = msg_send![object, setFrame: rect, display: true];
} else {
let responds: bool = msg_send![object, respondsToSelector: sel!(setFrame:)];
if responds {
let _: () = msg_send![object, setFrame: rect];
}
}
}
}
pub(crate) fn set_native_hidden(widget_id: u64, hidden: bool) {
let Some(ptr) = get_native_view(widget_id) else {
return;
};
if MainThreadMarker::new().is_none() {
return;
}
unsafe {
let object = ptr as *mut AnyObject;
let is_window: bool = msg_send![object, isKindOfClass: class!(NSWindow)];
if is_window {
if hidden {
let _: () = msg_send![object, orderOut: std::ptr::null_mut::<AnyObject>()];
} else {
let _: () =
msg_send![object, makeKeyAndOrderFront: std::ptr::null_mut::<AnyObject>()];
}
} else {
let responds: bool = msg_send![object, respondsToSelector: sel!(setHidden:)];
if responds {
let _: () = msg_send![object, setHidden: hidden];
}
}
}
}
pub(crate) fn set_native_enabled(widget_id: u64, enabled: bool) {
let Some(ptr) = get_native_view(widget_id) else {
return;
};
if MainThreadMarker::new().is_none() {
return;
}
unsafe {
let object = ptr as *mut AnyObject;
let selector = sel!(setEnabled:);
let responds: bool = msg_send![object, respondsToSelector: selector];
if responds {
let _: () = msg_send![object, setEnabled: enabled];
}
}
}
pub(crate) fn set_native_text(widget_id: u64, text: &str) {
let Some(ptr) = get_native_view(widget_id) else {
return;
};
if MainThreadMarker::new().is_none() {
return;
}
unsafe {
let object = ptr as *mut AnyObject;
let value = NSString::from_str(text);
let is_window: bool = msg_send![object, isKindOfClass: class!(NSWindow)];
if is_window {
let _: () = msg_send![object, setTitle: &*value];
return;
}
for selector in [sel!(setStringValue:), sel!(setTitle:), sel!(setAccessibilityLabel:)] {
let responds: bool = msg_send![object, respondsToSelector: selector];
if !responds {
continue;
}
if selector == sel!(setStringValue:) {
let _: () = msg_send![object, setStringValue: &*value];
} else if selector == sel!(setTitle:) {
let _: () = msg_send![object, setTitle: &*value];
} else {
let _: () = msg_send![object, setAccessibilityLabel: &*value];
}
return;
}
}
}
pub(crate) fn set_native_menu_shortcut(widget_id: u64, key: &str, modifier_mask: u64) {
let Some(ptr) = get_native_view(widget_id) else {
return;
};
if MainThreadMarker::new().is_none() {
return;
}
unsafe {
let item = ptr as *mut AnyObject;
let key = NSString::from_str(key);
if msg_send![item, respondsToSelector: sel!(setKeyEquivalent:)] {
let _: () = msg_send![item, setKeyEquivalent: &*key];
}
if msg_send![item, respondsToSelector: sel!(setKeyEquivalentModifierMask:)] {
let _: () = msg_send![item, setKeyEquivalentModifierMask: modifier_mask];
}
}
}
fn make_rect(x: i32, y: i32, width: u32, height: u32) -> NSRect {
NSRect::new(
objc2_foundation::NSPoint::new(x as f64, y as f64),
objc2_foundation::NSSize::new(width.max(1) as f64, height.max(1) as f64),
)
}
pub(crate) fn create_ns_window(
mtm: MainThreadMarker,
title: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSWindow> {
let style_mask = NSWindowStyleMask::Titled
| NSWindowStyleMask::Closable
| NSWindowStyleMask::Miniaturizable
| NSWindowStyleMask::Resizable;
let rect = make_rect(x, y, width, height);
let window = unsafe {
NSWindow::initWithContentRect_styleMask_backing_defer(
mtm.alloc(),
rect,
style_mask,
NSBackingStoreType::Buffered,
false,
)
};
window.setTitle(&NSString::from_str(title));
window.makeKeyAndOrderFront(None);
window
}
pub(crate) fn create_ns_button(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSButton> {
let rect = make_rect(x, y, width, height);
let button = unsafe { NSButton::initWithFrame(mtm.alloc(), rect) };
button.setTitle(&NSString::from_str(text));
button.setButtonType(NSButtonType::MomentaryLight);
button
}
pub(crate) fn create_ns_checkbox(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSButton> {
let rect = make_rect(x, y, width, height);
let button = unsafe { NSButton::initWithFrame(mtm.alloc(), rect) };
button.setTitle(&NSString::from_str(text));
button.setButtonType(NSButtonType::Switch);
button
}
pub(crate) fn create_ns_radio(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSButton> {
let rect = make_rect(x, y, width, height);
let button = unsafe { NSButton::initWithFrame(mtm.alloc(), rect) };
button.setTitle(&NSString::from_str(text));
button.setButtonType(NSButtonType::Radio);
button
}
pub(crate) fn create_ns_label(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSTextField> {
let rect = make_rect(x, y, width, height);
let label = unsafe { NSTextField::initWithFrame(mtm.alloc(), rect) };
label.setStringValue(&NSString::from_str(text));
label.setEditable(false);
label.setBezeled(false);
label.setDrawsBackground(false);
label
}
pub(crate) fn create_ns_slider(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSSlider> {
let rect = make_rect(x, y, width, height);
unsafe { NSSlider::initWithFrame(mtm.alloc(), rect) }
}
pub(crate) fn create_ns_textfield(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSTextField> {
let rect = make_rect(x, y, width, height);
let field = unsafe { NSTextField::initWithFrame(mtm.alloc(), rect) };
field.setStringValue(&NSString::from_str(text));
field.setEditable(true);
field.setBezeled(true);
field.setDrawsBackground(true);
field
}
pub(crate) fn create_ns_progress(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSProgressIndicator> {
let rect = make_rect(x, y, width, height);
let progress = unsafe { NSProgressIndicator::initWithFrame(mtm.alloc(), rect) };
progress.setIndeterminate(false);
progress.setMinValue(0.0);
progress.setMaxValue(100.0);
progress.setDoubleValue(0.0);
progress
}
pub(crate) fn create_ns_combo_box(
mtm: MainThreadMarker,
text: &str,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSPopUpButton> {
let rect = make_rect(x, y, width, height);
let combo = unsafe { NSPopUpButton::initWithFrame_pullsDown(mtm.alloc(), rect, false) };
combo.setTitle(&NSString::from_str(text));
combo
}
pub(crate) fn create_ns_list_box(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSScrollView> {
let rect = make_rect(x, y, width, height);
let scroll = unsafe { NSScrollView::initWithFrame(mtm.alloc(), rect) };
scroll.setHasVerticalScroller(true);
scroll.setHasHorizontalScroller(false);
scroll.setBorderType(NSBorderType::BezelBorder);
scroll.setAutohidesScrollers(true);
let table_rect = NSRect::new(NSPoint::new(0.0, 0.0), scroll.contentSize());
let table = unsafe { NSTableView::initWithFrame(mtm.alloc(), table_rect) };
let column_id = NSString::from_str("list-column");
let column = unsafe { NSTableColumn::initWithIdentifier(mtm.alloc(), &column_id) };
column.setTitle(&NSString::from_str("Items"));
table.addTableColumn(&column);
table.setHeaderView(None);
scroll.setDocumentView(Some(&table));
scroll
}
pub(crate) fn create_ns_panel(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSView> {
let rect = make_rect(x, y, width, height);
unsafe { NSView::initWithFrame(mtm.alloc(), rect) }
}
pub(crate) fn create_ns_scroll_view(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSScrollView> {
let rect = make_rect(x, y, width, height);
let scroll = unsafe { NSScrollView::initWithFrame(mtm.alloc(), rect) };
scroll.setHasVerticalScroller(true);
scroll.setHasHorizontalScroller(true);
scroll.setBorderType(NSBorderType::BezelBorder);
scroll.setAutohidesScrollers(true);
scroll
}
pub(crate) fn create_ns_stepper(
mtm: MainThreadMarker,
x: i32,
y: i32,
width: u32,
height: u32,
) -> Retained<NSStepper> {
let rect = make_rect(x, y, width, height);
let stepper = unsafe { NSStepper::initWithFrame(mtm.alloc(), rect) };
stepper.setMinValue(0.0);
stepper.setMaxValue(100.0);
stepper.setIncrement(1.0);
stepper.setValueWraps(false);
stepper.setAutorepeat(true);
stepper
}
pub(crate) fn create_ns_menu(mtm: MainThreadMarker, title: &str) -> Retained<NSMenu> {
let menu = unsafe { NSMenu::initWithTitle(mtm.alloc(), &NSString::from_str(title)) };
menu.setAutoenablesItems(false);
menu
}
pub(crate) fn create_ns_menu_item(
mtm: MainThreadMarker,
title: &str,
key_equivalent: &str,
) -> Retained<NSMenuItem> {
unsafe {
NSMenuItem::initWithTitle_action_keyEquivalent(
mtm.alloc(),
&NSString::from_str(title),
None,
&NSString::from_str(key_equivalent),
)
}
}
pub(crate) fn create_ns_alert(mtm: MainThreadMarker, title: &str, text: &str) -> Retained<NSAlert> {
let alert = NSAlert::new(mtm);
alert.setMessageText(&NSString::from_str(title));
alert.setInformativeText(&NSString::from_str(text));
alert.addButtonWithTitle(&NSString::from_str("OK"));
alert
}
pub(crate) fn create_ns_open_panel(mtm: MainThreadMarker) -> Retained<NSOpenPanel> {
let panel = NSOpenPanel::new(mtm);
panel.setAllowsMultipleSelection(false);
panel
}
pub(crate) fn menu_add_child_to_menu(
parent_menu: *mut std::ffi::c_void,
child: *mut std::ffi::c_void,
) {
if parent_menu.is_null() || child.is_null() {
return;
}
unsafe {
let menu = parent_menu as *mut NSMenu;
let item = child as *mut NSMenuItem;
(*menu).addItem(&*item);
}
}
pub(crate) fn menu_set_submenu_on_item(
item: *mut std::ffi::c_void,
submenu: *mut std::ffi::c_void,
) {
if item.is_null() || submenu.is_null() {
return;
}
unsafe {
let item = item as *mut NSMenuItem;
let submenu = submenu as *mut NSMenu;
(*item).setSubmenu(Some(&*submenu));
}
}
pub(crate) fn install_main_menu(menu_bar: *mut std::ffi::c_void) -> bool {
if menu_bar.is_null() {
return false;
}
let Some(mtm) = MainThreadMarker::new() else {
return false;
};
let app = NSApplication::sharedApplication(mtm);
let menu = menu_bar as *mut NSMenu;
app.setMainMenu(Some(unsafe { &*menu }));
true
}
pub(crate) fn bootstrap_ns_application() -> bool {
let Some(mtm) = MainThreadMarker::new() else {
return false;
};
let app = NSApplication::sharedApplication(mtm);
app.finishLaunching();
true
}
pub(crate) fn create_ns_color_panel(mtm: MainThreadMarker) -> Retained<NSColorPanel> {
NSColorPanel::new(mtm)
}
pub(crate) fn create_ns_font_panel(mtm: MainThreadMarker) -> Retained<NSFontPanel> {
NSFontPanel::new(mtm)
}