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