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