1///! Macos screen have a y axis that goings up from the bottom of the screen and
2///! an origin at the bottom left of the main display.
3mod dispatcher;
4mod events;
5mod platform;
6mod screen;
7mod window;
8mod window_appearence;
9
10use std::{
11 ffi::{c_char, CStr, OsStr},
12 ops::Range,
13 os::unix::prelude::OsStrExt,
14 path::PathBuf,
15};
16
17use crate::{px, size, Pixels, Size};
18use anyhow::anyhow;
19use cocoa::{
20 base::{id, nil},
21 foundation::{NSAutoreleasePool, NSNotFound, NSRect, NSSize, NSString, NSUInteger, NSURL},
22};
23use objc::{
24 msg_send,
25 runtime::{BOOL, NO, YES},
26 sel, sel_impl,
27};
28pub use platform::*;
29pub use screen::*;
30pub use window::*;
31use window_appearence::*;
32
33trait BoolExt {
34 fn to_objc(self) -> BOOL;
35}
36
37impl BoolExt for bool {
38 fn to_objc(self) -> BOOL {
39 if self {
40 YES
41 } else {
42 NO
43 }
44 }
45}
46
47#[repr(C)]
48#[derive(Copy, Clone, Debug)]
49struct NSRange {
50 pub location: NSUInteger,
51 pub length: NSUInteger,
52}
53
54impl NSRange {
55 fn invalid() -> Self {
56 Self {
57 location: NSNotFound as NSUInteger,
58 length: 0,
59 }
60 }
61
62 fn is_valid(&self) -> bool {
63 self.location != NSNotFound as NSUInteger
64 }
65
66 fn to_range(self) -> Option<Range<usize>> {
67 if self.is_valid() {
68 let start = self.location as usize;
69 let end = start + self.length as usize;
70 Some(start..end)
71 } else {
72 None
73 }
74 }
75}
76
77impl From<Range<usize>> for NSRange {
78 fn from(range: Range<usize>) -> Self {
79 NSRange {
80 location: range.start as NSUInteger,
81 length: range.len() as NSUInteger,
82 }
83 }
84}
85
86unsafe impl objc::Encode for NSRange {
87 fn encode() -> objc::Encoding {
88 let encoding = format!(
89 "{{NSRange={}{}}}",
90 NSUInteger::encode().as_str(),
91 NSUInteger::encode().as_str()
92 );
93 unsafe { objc::Encoding::from_str(&encoding) }
94 }
95}
96
97unsafe fn ns_string(string: &str) -> id {
98 NSString::alloc(nil).init_str(string).autorelease()
99}
100
101impl From<NSSize> for Size<Pixels> {
102 fn from(value: NSSize) -> Self {
103 Size {
104 width: px(value.width as f32),
105 height: px(value.height as f32),
106 }
107 }
108}
109
110pub trait NSRectExt {
111 fn size(&self) -> Size<Pixels>;
112 fn intersects(&self, other: Self) -> bool;
113}
114
115impl NSRectExt for NSRect {
116 fn size(&self) -> Size<Pixels> {
117 size(px(self.size.width as f32), px(self.size.height as f32))
118 }
119
120 fn intersects(&self, other: Self) -> bool {
121 self.size.width > 0.
122 && self.size.height > 0.
123 && other.size.width > 0.
124 && other.size.height > 0.
125 && self.origin.x <= other.origin.x + other.size.width
126 && self.origin.x + self.size.width >= other.origin.x
127 && self.origin.y <= other.origin.y + other.size.height
128 && self.origin.y + self.size.height >= other.origin.y
129 }
130}
131
132unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {
133 let path: *mut c_char = msg_send![url, fileSystemRepresentation];
134 if path.is_null() {
135 Err(anyhow!(
136 "url is not a file path: {}",
137 CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
138 ))
139 } else {
140 Ok(PathBuf::from(OsStr::from_bytes(
141 CStr::from_ptr(path).to_bytes(),
142 )))
143 }
144}