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